Skip to content

shi3z式ゲームプログラミング入門 #8 enchant.jsでプチ物理シミュレースゲームを作ってみた

やあみんなプログラミングしてるかな? shi3zだよ。
ついにハッカー・オブ・ハッカー、トロチチこと南治さんの連載が始まった!
トロチチさんは株式会社ビサイドの社長で、あの「どこでもいっしょ」の開発者だ。


どこでもいっしょ

僕もこの「どこいつ」には思い出があって、大学生だったとき、ラストシーンでわんわん大泣きして記憶がある。心底あそこまで泣いたゲームはこのゲームが最初で最後だった。

命の大切さ、愛情の大切さ、そんなものをこのゲームから学んだ気がする。

ところで9leap 9Days Challengeだけど、ぽつぽつと投稿が来始めている。
けっこう盛り上がって来たみたいだぞ!

僕もひとつゲームを作ってみた。
みんなの大好きな物理シミュレーションっぽい要素を少し入れたカーレースゲームだ。

名付けてバナナレーシング。
これはタイムトライアル型のバナナ獲得レースゲームだ。
なぜバナナなのかって?
僕がバナナが好きだからだ。

まずは遊んでみてほしい。

9leap: Bananaレーシング
http://9leap.net/games/26
ソースコードもこのページからダウンロードできる

操作は簡単。カーソルキーの上でアクセル、下でブレーキ、左右でハンドルだ。
なぜこうなっているかというと、今の9leapのレギュレーションではシングルタッチしか対応していないからだ。

これはAndoidとiPhoneのクロスプラットフォームを標榜する9leap独自の制約で、Androidのブラウザはたとえ最新の端末であってもシングルタッチしかサポートしていないことが多いんだ(マルチタッチの拡大縮小をサポートしているブラウザはあるが、JavaScriptのイベントとしては飛んでこない)。

そこで最初はシングルタッチのみでどこまで面白いゲームを作れるか競ってみようぜってワケ。

さて、このゲームの構造を解説すると、まず背景はいわずとしれたMapクラスを使っている。

ただし、これだけ複雑なマップを書くのは面倒だったので、α版のenchantMapEditorを使って書いた。

これを使うとenchant.js用のマップデータをサクサクと作ることができて大変便利だ。

しかしこれ、現状のバージョンには恐るべきバグがあって、なんと書いたマップをコード生成するところまではできるのだが、この生成されたコードを読み取る方法がない(ダメじゃん!!)

これに気づいた時は愕然としたけど、まあα版だって言ってるんだししょうがない(?)部分もある。

そこで苦肉の作として、コード生成したあと、ブラウザのURLの部分に無理矢理Javascriptを打ち込んでalertで吐き出させることにした。

javascript:a=document.getElementsByTagName('textarea');b=a[0];alert(b);

これがJavascriptのいいところ(?)だ。
もとのプログラムがバグっていてもURLのところにコードを打ち込むことでデータを救済することができる。

Safariだと、alertで出て来たウィンドウの中身はコピペできるので、それを使う。

ただし、今回は速度を優先するため、前景は一切使わず、背景のみでコースを構成した。

マップエディタ上ではあくまでコースのみを描き、あまった部分にはプログラムで草のタイルを置くことにした。というのも、現状のenchantMapEditorでは塗り潰し機能がないのだ。もっとも、開発者の高橋君によると、塗りつぶし機能そのものは作ってあるそうで、UIでいくつか迷っているだけらしいので、近日中には塗りつぶし昨日と正しいコード生成が入ったバージョンが出てくるのではないだろうか

        map.loadData(baseMap);
        var colMap = new Array();
        for(var i=0;i<baseMap.length;i++){
            var row =new Array();
            for(var j=0;j<baseMap[i].length;j++){
                row[j] = 0;
                if(baseMap[i][j]>0)row[j]=1;
                else baseMap[i][j]=322;
            }
            colMap.push(row);
        }
        map.collisionData =colMap;

ついでに、道路以外のタイルは全部当たり判定をONにした。
これも、enchantMapEditorでは当たり判定の設定がかなり細かくできるんだけど、こまかくやる根性がないので、プログラムでラクをしているというわけだね。

マップ作成はツールを使ってラクをして、しかし当たり判定の設定は塗りつぶしはプログラム側でラクをする。これが効率的な開発ってもんだね(?)

さて、肝心のクルマの動きの方はどうだろうか。
これも基本を抑えればとても簡単だ。

図のように、このクルマには加速度(a)と速度(v)のパラメータを用意している。
カーソルキーの上を押すと、加速度(a)が上がる。ただし、これはいきなり固定した数値に上がる。加速度が次第に上がっていくようにすると、実は直感に反するゲームになってしまうんだ。そして下を押した場合も、同じように固定値を入れる。

加速度はあくまでも速度を加速する要素なので、それまでの速度は保存されている(運動量保存の法則)、だから、クルマは急に止まれない、という感じで横滑りするわけだ。この横滑りする感覚がリアルな感覚につながることなる。

ハンドルを左右にまわす場合は、player.rotateという変数を変化させる。

        player.addEventListener('enterframe', function() {
            this.frame = 2;
                if (game.input.left) {
                    this.rotate-=6;
        }
        if (game.input.right) {
                    this.rotate+=6;
        }
        if (game.input.up) {
                    this.accel = 2.5;
        }
        if (game.input.down) {
                    this.accel = -0.5;
        }
        this.rotation = this.rotate+90;

これだと、6度ずつ変化させているわけだ。
これが角速度になる。角速度をフレームごとに(enterframeで処理しているので)足し続けるまたは減らし続ける、これを積分(せきぶん)する、と言う。積み重なっていく訳だね。

加速度を表現しているのはplayer.accel。ここではアクセルを踏むと加速度2.5、ブレーキを踏むと加速度を-0.5にするようにした。

最後にthis.rotation = this.rotate+90としているのは、ちょっと説明が必要で、enchant.jsの場合、this.rotationに角度を入れると画像を勝手に回転してくれる。
しかしその回転角は後述する三角関数を使った場合と比べてちょうど90度ズレてる。

そこで内部的なthis.rotateという角度と、表示のために使うthis.rotationという角度を分けている訳だね。

さて、実際に加速していく場合の計算式がどうなるかというと

                var x=this.x,y=this.y;
                var ax =Math.cos(this.rotate*3.14159/180)*this.accel;
                var ay =Math.sin(this.rotate*3.14159/180)*this.accel;
                this.accel*=0.95;
                this.vx +=ax;
                this.vy +=ay;
                var vx =this.vx,vy=this.vy;
                this.vx*=0.8;
                this.vy*=0.8;

という感じになる。

基本的には、加速しつつ、加速度を緩める(0.8を掛けてる)、速度も加速がなければ緩める(vx,vyに0.8を掛けている)という感じだ。

まずx,yにthis.xとthis.yを代入しているのはいいだろう。
次のaxとayのところに三角関数がある。これは慣れないと少々厄介だ。

さて、三角関数については学校で習ったと思うが、図のような性質を持っている。
三角関数はゲームを作る上で必須とは言わないが知っていれば限りなく役立つ魔法の道具だ。ぜひ使いこなしてほしい。

さて、この図をみてもらうとわかるけど、角度θ(シータ、ギリシャ文字だ。なぜか三角関数の話題ではθを使うのが礼儀のようだ)の時の三角関数は、そのまま加速度ベクトルの方向を示している。

x要素はcosθ、y要素はsinθだ。ここでこの図を見て「おやっ?」と思ったのは数学の授業を多少は真面目に聞いていた人だろう。

そう、普通、こういう図ではsinθはマイナスになるハズなのだ。ところでここではプラスになっている。なぜだろうか?

答えは簡単。数学では、yのプラス方向は上だが、コンピュータのディスプレイではyのプラス方向は下向きだ。

したがって、sinθの符号も反転するのだ。オーケイ?

cosとsinがそもそもなにを意味する記号だったか、覚えている人はいるかな?

これは、それぞれ角度θのときの直角二等辺三角形で、斜辺の長さが1のときの底辺の長さ(cos)と、三角形の高さ(sin)を意味するのだ。

ということは、あとは斜辺の長さを掛けてやればいい。この場合、加速度の長さというのはthis.accelの値だ。

だから

                var ax =Math.cos(this.rotate*3.14159/180)*this.accel;
                var ay =Math.sin(this.rotate*3.14159/180)*this.accel;

のようになる。
しかし、Math.cosとMath.sinに渡している謎のパラメータに呪術めいた計算を施してあるのがさらに混乱を招くかもしれにない。

実は、Math.cos、Math.sinが受け付けるのは伝統的にラジアン角だけだ。
ラジアン角というのは、一周が2π(パイ)になるという謎の角度で、まあ考えた人にはそれなりの理由と意義があったのだけど、小学校からコツコツ勉強してきた脳みそにとっては、一周は360度以外のなにものでもないから(というのもなんだかへんな話だと思うけどね)、ちょっと混乱する。

まあ簡単に言うと、単位が違うからここで単位を変換しているというわけだ。
一周が2πの角度系に一周が360度の角度系から変換するのだ。それで円周率が入っているというワケ。

これはもはや呪文の一種だから意味を深く考える必要は、ない。

とまあ、こんな感じで加速度のある動きを再現できるわけだ。

また、このゲームではマップデータもゲームの構築に使っていて、特定のマップチップの上に来たときにタイムがカウントされるようになっている。

                if(map.checkTile(x+vx+16,y+vy+16)==player.countTile){
                    player.countTile=(320+321)-player.countTile;
                    timeAttack.timeLimit+=10;

ただし、このmap.checkTileという関数はenchant.jsには用意されていない。
そこでenchant.jsを勝手に改造した。

    checkTile: function(x, y) {
        if (x < 0 || this.width <= x || y < 0 || this.height <= y) {
            return false;
        }
        var width = this._image.width;
        var height = this._image.height;
        var tileWidth = this._tileWidth || width;
        var tileHeight = this._tileHeight || height;
        x = x / tileWidth | 0;
        y = y / tileHeight | 0;
        //      return this._data[y][x];
        var data = this._data[0];
        return data[y][x];
    },

この関数をMapの定義のところに追加したというワケ。
checkTileは、あるx,y座標の真下にあるタイルの番号を調べる命令だ。
こういうのがあるとゲームがグっと作りやすくなる。
enchant.js本体にも反映されるといいな。

そうそう。そして最後に当たり判定だ。

                if( !map.hitTest(x+vx+16, y+vy+16) ) {
                    //new Explode(this.x,this.y);
                    this.accel=0;
                    this.vx=0;
                    this.vy=0;
                }else{
                    this.x += this.vx;
                    this.y += this.vy;
                }

当たり判定は、enchant.jsのまさに本領発揮といったところだ。

ただし、注意が必要なのは、enchant.jsでスプライトのx,y座標はあくまでも左上だということ。

だから、当たり判定をとるときには、スプライトの中心を指すようにしなければならない。この場合、クルマの絵は32×32ドットだから、xとyにそれぞれ16を足している。

また、この当たり判定処理の大事なところは、xとyにvxとvyを足したあとの位置で当たり判定をとっているところ。

ブロックにめり込まないようにするためにはこの判定は必ず必要だ。
当たっている場合はペナルティとして速度と加速度をゼロにしている。
本当は爆発させようかとも思ったが、とりあえず今回は爆発はしないことにした。

もし当たっていなければ、x,yにそれぞれvx,vyを積分して、実際にクルマが移動する、というわけ。

今回はちょっと難しかったかな?
けれども三角関数は友達。怖くないぜ。
ではまた次回!

文・shi3z

このエントリーをはてなブックマークに追加
はてなブックマーク - shi3z式ゲームプログラミング入門 #8 enchant.jsでプチ物理シミュレースゲームを作ってみた
Post to Google Buzz
Share on GREE

Related posts:

  1. [enchant.js]shi3z式ゲームプログラミング #7 タイムアタックは神!
  2. shi3z式ゲームプログラミング入門 #3 “ゲームらしく”する
  3. shi3z式ゲームプログラミング入門 #4 バナナと配列
  4. shi3z式ゲームプログラミング入門 #2 あと48時間でゲームプログラマになる方法
  5. shi3z式ゲームプログラミング入門 #1 72時間でゲームプログラマになる方法

Facebook comments:

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*