contentHomeWise9とは?投稿募集!enchant.jsで3Dゲームを作ってみる!こんにちは。先日、3Dプログラミングの簡単なやり方をここで紹介した。そこで今日は、先日のサンプルをもとにゲームを作ってみた。遊んでみてほしいさて、ではどうやってゲームにしているのか?それを解説していこう。このゲームは非常にシンプルなものになっている。画面の奥から手前に向かってやってくる立方体をひたすらタップやクリックで消していくだけだ。時間が経つと立方体が増えたり、スピードが上がったりして非常に難しくなる。前回のサンプルとの違いは、Shapeクラスが追加されたことと、変換行列の掛けかたを変えたこと。iPhoneやAndroidでもちゃんとスピードが出るようにプログラム的な高速化(最適化)を行っている。実際のコードは以下のjsdo.itにアップしてある。VertexTable(this.vt);this.x=0;this.y=0;this.z=0;this.color="#0f0";},Shapeクラスは、その名の通り形状を保存するためのクラスだ。メンバーとしてVertexTableとmovMat(移動行列)を持ち、draw関数で実際の描画を行う。color=white>SHIELD:"+shield+"</font>";if(shield<=0)game.end(score);}}冒頭でmovMatの[0][3]と[1][3]、[2][3]にそれぞれx,y,zを代入している(Aの行)けど、これで平行移動を実現している。その後、rotMatXとrotMatYの二つの回転行列を乗じている(Bの行)。この二つの行列は、今回は高速化のために総ての立方体が同じように回転するようになっているのでグローバル変数になっている。今回は高速化のため、Matrixクラスにcompsitionというメソッドとは別に、自分自身の演算結果を変えるcompositionMeというメソッドを用意した。通常のcompositionメソッドは、合成する度に新しいMatrixクラスのインスタンスを返していたが、compositionMeはインスタンス生成を行わないため、そのぶんのオーバーヘッド(これがバカにならない)が短縮される。C行で示すように、rotMatにmovMatを乗じると、rotMatで回転してからmovMatで移動する、という動きになる。行列は乗じる順番によって動きが変わる(これが普通の乗算と違う)ので、順番には注意が必要だ。次のD行ではconvMatというグローバル変数を乗じたあと、E行でVertexTableに変換行列を適用してF行以降で描画している。D行で乗じられているconvMatは、このdrawメソッドの外で作られていて、内容は透視変換とスクリーン変換を予め乗じたものだ。行列の乗算も計算コストが高いので、できるだけ外でまとめてやっておくのがいい。=screenMat.composition(projMat);一度合成しておけば、この行列はずっと使えるわけだ。プログラミングにおいては、しばしばこの”最適化(Optimize)”という考え方が重要になる。最近のトレンドは関数型言語を多用した「トリッキーだけど確実に動作する」か、「あとから誰が読んでも間違わないように書く可読性と保守性を重視する」という考え方だけれども、ゲームプログラミングの世界や、最先端のユーザーインターフェースを実現するためには、しばしばその確実性や可読性は犠牲になる。書いた本人以外誰も理解できない。誰のコードよりも最も高速かつ”最適”に動作する。このコードの場合、最適化とは、1フレーム毎に発生する計算量をトータルでなるべく減らすことだ。4×4行列の乗算には4の3重ループが必要だから64回の乗算が必要になる。コンピュータが苦手な計算の筆頭は除算、とりわけ剰余算(余りを求める計算)だと言われているが、最近のコンピュータは浮動小数点の計算に特化してコンピュータ自身が最適化されているため、どの計算が一概に遅いとは言いにくくなっている。いえ、計算の絶対量が減れば確実に全体を高速化できるし、”最近のコンピュータ”と言っても、電力を無尽蔵に使えるPentum世代以降の話なので、未だにモバイル用途においてはこうした細かい最適化が有効なのだ。インスタンス生成のオーバーヘッド(余分にかかる時間)が掛かる場合は多い。少なくともインスタンスを生成するために、メモリ確保とコンストラクタの実行という処理は走るし、下手をするとこの時点でメモリが一杯になってがベージコレクションが走るわけだから、これが省けるにこしたことはない。ガベージコレクションのあるコンピュータ言語でガベージコレクションの時間を最も少なくするには?当然、ガベージを作らない、ということが重要になる。ガベージが出なければガベージコレクションは不要になり、最小限で済む。このガベージコレクションがある環境でもガベージを作らないようにプログラミングする、という考え方は、OSやシェルのように常時動作することが前提となっている場合は非常に重要だ。OSやシェルが気持ちよくガベージコレクションを連発していたらコンピュータ全体の動作が恐ろしく重くなってしまう。ガベージコレクションというのはあくまでも「メモリ管理についてそれほど難しく考えなくてもコンピュータが破壊されない」ために存在するのだ。ガベージを出さないようにするにはどうすればいいか?基本的にはインスタンスを使い回す、ということになる。静的メソッドでインスタンスを生成して返すようなものがあったら、インスタンスは生成せず自分自身のデータを書き換えるような破壊的メソッドを用意するということだ。今回作ったMatrixクラスのcompsotinMeやVertexTableクラスのapplyMeといったメソッドは、そういう破壊的なメソッドである。メインループの中からnewを無くしていくとガベージコレクションの回数は極端に減る。総ての立方体を描画するbox[i].draw();});って、いきなりメインループの中でnew使ってるじゃん!(A行)と思うかもしれないが、これはt%100==0のとき、つまり100フレームに一回だけしか呼ばれない部分だからいいのだ。その上の行のif分でbox.lengthが20未満の時しかnewを呼ばないのでShapeのnewが呼ばれる数はゲーム内トータルで20回だけだ。B行で総ての立方体を描画している。ここでさっきのdrawメソッドを順番に呼んでいるわけだ。これだけだとゲームにならない。少なくともタッチに対する反応くらいは欲しいところだ。そこでtouchendイベントに対するイベントハンドラを書こう。color=white>SCORE:"+score+"</font>";box[i].setPosition(Math.random()*400-200,Math.random()*400-200,Math.random()*100+1000);}}}});touchendイベントは、タッチが終了したとき、またはマウスボタンが離れたときに呼び出される。タッチする度に総ての立方体について、intersectというメソッドを呼び出し(A行)て、当たったかどうか判定するのだ。このintersectというメソッドはShapeクラスの中で定義されている。false;},これ、前半部分はdrawメソッドとほとんど同じだということに気づいただろうか?また、Shapeクラスでは、メインループ内でのnewを減らすため、rvtという変換結果を格納するインスタンスを予め確保してそれを再利用するようにしている。これで実際の画面上の座標を計算して、A行以降で画面上のx,yの最小値と最大値をそれぞれ求めていてる。これを行うと、下図のように、この立方体を囲む矩形(くけい)が求まるわけだ。この矩形にタッチ座標が含まれているかどうか判定して、当たっていたらtrueを返す(B行)というわけなのである。この計算だとちょっとズレても当たったと判定されてしまうが、それでもかなり難しいゲームになっているので良しとしよう。次はもっと本格的なものを作ってみよう。