惑星破壊ゲーム

このゲームは、matter.jsでつくりました。
ゲーム説明
スターデストロイヤー級宇宙船で惑星系を破壊するゲームです。昔懐かしい小惑星撃退ゲームのリメイク版です。
スマホ:画面右側タップで右、左側タップで左に回転。左右タップで前進です。
PC:カーソル「→」「←」で左右回転「↑」で前進、スペースで魚雷発射です。

スマホとPCでプレイできます。

HTML5で使えるjavascriptの2D物理エンジンは何種類かありますが、使い方についての情報はあまりありません。そこで2D物理エンジンの使い方として、このゲームを例に記述しようと思います。
まずゲームで使いやすい2D物理エンジンを探したところ、planck.jsとmatter.jsが候補に挙がりました。取りあえずGitHubからplanck.js-masterとmatter-js-masterを取って来ました。
まずplanck.jsからです。planck.js-masterにはexampleというフォルダがあります。そこに、サンプルのソースがあるのでそれをローカルで動かしてみます。無料で使えるMicrosoft Expression Web 4(日本語版)などで一度ソースを開いて、すべてをコピーしてメモ帳にペーストして、文字コードUTF-8で同名で保存します。すると読みやすくフォーマットしたものになります。
適当な名前でフォルダを作り、そこにplanck.js-masterからplanck.jsとplanck-with-testbed.jsそして保存したソースをおきます。もう一つ、メモ帳で下記の内容のindex.htmlを文字コードUTF-8でつくり保存します。
index.html

<!doctype html>
<html>
<body>
  <script src="planck-with-testbed.js"></script>
  <script src="8-Ball.js"></script>
</body>
</html>

これはソースの名前が”8-Ball.js”の例です。
index.htmlを実行すればサンプルがローカルで実行できます。ソースをメモ帳で開き少し変更すれば、サンプルの動きが変わるのが確認できます。
planckはマウスだけでなくキーボード入力にも対応しています。座標系は特殊で扱いにくい印象です。画像を使うのは少し面倒です。GitHubにはデフォルトでレンダラーを使用しないのでwindow.requestAnimationFrame(function())を使えと書いてあります。
次にmatter.jsを調べます。上記と同じに適当なフォルダを作りmatter-js-masterからmatter.jsとexamplesフォルダにあるサンプルのソースを見やすく整形して保存します。そして下記index.htmlを文字コードUTF-8でつくり保存します。
index.html

<!doctype html>
<html>
<body>
<script src="matter.js"></script>
<script src="car.js"></script>
</body>
</html>

これはソースの名前がcar.jsの例です。
しかしindex.htmlを実行しても動きません。ネットで調べるとサンプルcar.jsの最初と最後の所を書き換える必要があるようです。
元のcar.js

var Example = Example || {};

Example.car = function() {
    var Engine = Matter.Engine,
        Render = Matter.Render,
        Runner = Matter.Runner,
             ~
        stop: function() {
            Matter.Render.stop(render);
            Matter.Runner.stop(runner);
        }
    };
};

修正したcar.js

/*var Example = Example || {};

Example.car = function() {*/
(function(){//上のコードをコメントアウトしてこの行を追加
    var Engine = Matter.Engine,
        Render = Matter.Render,
        Runner = Matter.Runner,
              ~
        stop: function() {
            Matter.Render.stop(render);
            Matter.Runner.stop(runner);
        }
    };
//};この行をコメントアウトして下の行を追加
})();

これでmatter-js-masterのサンプルが動くようになりました。
matterはキーボード入力に対応してないのでjavascriptで処理する事にします。座標系はjavascriptのままです。レンダラーに対応してます。いくつかのイベント処理もできます。
フレーム毎実行はMatter.Events.on(engine, ‘beforeUpdate’, function())でできます。ゲームを作れることが分かりました。
まず、簡単なソースでレンダラーを確かめます。同じフォルダに何か画像を用意してください。
index.html

<!doctype html>
<html>
<head>
<script src="matter.js"></script>
</head>
<body>
<div id="canvas"></div>
<script src="boxball.js"></script>
</body>
</html>

boxball.js

(function() {
    var Engine = Matter.Engine,
        Render = Matter.Render,
        World = Matter.World,
        Bodies = Matter.Bodies;
    // create engine
    var container = document.getElementById('canvas');
    var engine = Engine.create(container, {
        render: { //レンダリングの設定
            options: {
                wireframes: false,
                //ワイヤーフレームをoffで画像表示
                width: 800,
                height: 600,
                //background: 'background.png' //画像があればコメントアウト解除
            }
        }
    });
    // create box and ball and ground
    var boxA = Bodies.rectangle(400, 70, 60, 60, {
        render: {
            sprite: {
                //texture: 'box.png' //画像があればコメントアウト解除
            }
        }
    });
    var ballA = Bodies.circle(400, 300, 50, {
        render: {
            sprite: {
                //texture: 'ball.png' //画像があればコメントアウト解除
            }
        }
    });
    var ground = Bodies.rectangle(400, 500, 810, 30, {
        isStatic: true
    });
    // add all of the bodies to the world
    World.add(engine.world, [boxA, ballA, ground]);
    // run the engine
    Engine.run(engine);
})();

問題なく動きます。
次に入力についてテストします。
boxball.js

(function() {
    var Engine = Matter.Engine,
        Render = Matter.Render,
        World = Matter.World,
        Bodies = Matter.Bodies,
        MouseConstraint = Matter.MouseConstraint,//マウス操作
        Mouse = Matter.Mouse;
    // create engine
    var container = document.getElementById('canvas');
    var engine = Engine.create(container, {
        render: { //レンダリングの設定
            options: {
                wireframes: false,
                //ワイヤーフレームをoffで画像表示
                width: 800,
                height: 600,
                //background: 'background.png' //画像があればコメントアウト解除
            }
        }
    });
    // create box and ball and ground
    var boxA = Bodies.rectangle(400, 70, 60, 60, {
        render: {
            sprite: {
                //texture: 'box.png' //画像があればコメントアウト解除
            }
        }
    });
    var ballA = Bodies.circle(400, 300, 50, {
        render: {
            sprite: {
                //texture: 'ball.png' //画像があればコメントアウト解除
            }
        }
    });
    var ground = Bodies.rectangle(400, 500, 810, 30, {
        isStatic: true
    });
    // add all of the bodies to the world
    World.add(engine.world, [boxA, ballA, ground]);

    // マウスドラッグ追加
    var mousedrag = MouseConstraint.create(engine);
    World.add(engine.world, mousedrag);

    var RIGHT = 0;
    var LEFT = 0;
    //キー入力
    document.onkeydown = function(event){
         if (event.keyCode == 39) {RIGHT = 1};
         if (event.keyCode == 37) {LEFT = 1};
    };
    document.onkeyup = function(event){
         if (event.keyCode == 39) {RIGHT = 0};
         if (event.keyCode == 37) {LEFT = 0};
    };

    //フレーム毎実行
    Matter.Events.on(engine, 'beforeUpdate', function() {
         if (RIGHT == 1) {Matter.Body.setAngularVelocity(boxA, Math.PI/6)};
         if (LEFT == 1) {Matter.Body.setAngularVelocity(boxA, -Math.PI/6)};
    });

    // run the engine
    Engine.run(engine);
})();

問題なく動きます。
次は衝突判定です。

(function() {
    var Engine = Matter.Engine,
        Render = Matter.Render,
        World = Matter.World,
        Bodies = Matter.Bodies,
        MouseConstraint = Matter.MouseConstraint,//マウス操作
        Mouse = Matter.Mouse;
    // create engine
    var container = document.getElementById('canvas');
    var engine = Engine.create(container, {
        render: { //レンダリングの設定
            options: {
                wireframes: false,
                //ワイヤーフレームをoffで画像表示
                width: 800,
                height: 600,
                //background: 'background.png' //画像があればコメントアウト解除
            }
        }
    });
    // create box and ball and ground
    var boxA = Bodies.rectangle(400, 70, 60, 60, {
        label: 'box', //本体の名前
        render: {
            sprite: {
                //texture: 'box.png' //画像があればコメントアウト解除
            }
        }
    });
    var ballA = Bodies.circle(400, 300, 50, {
        label: 'ball', //本体の名前
        render: {
            sprite: {
                //texture: 'ball.png' //画像があればコメントアウト解除
            }
        }
    });
    var ground = Bodies.rectangle(400, 500, 810, 30, {
        isStatic: true
    });
    // add all of the bodies to the world
    World.add(engine.world, [boxA, ballA, ground]);

//衝突判定
Matter.Events.on(engine, 'collisionEnd', function(event) {
    pairs = event.pairs;
    for (i = 0;i < pairs.length;i++) {
        var pair = pairs[i];
        if (pair.bodyA.label !== 'ball' && pair.bodyB.label == 'box' || pair.bodyA.label == 'box' && pair.bodyB.label !== 'ball') {
                 Matter.Body.translate( ballA, {x: Math.floor( Math.random() * 101) - 50, y: -200});
        }
    };
});

    // マウスドラッグ追加
    var mousedrag = MouseConstraint.create(engine);
    World.add(engine.world, mousedrag);

    var RIGHT = 0;
    var LEFT = 0;
    //キー入力
    document.onkeydown = function(event){
         if (event.keyCode == 39) {RIGHT = 1};
         if (event.keyCode == 37) {LEFT = 1};
    };
    document.onkeyup = function(event){
         if (event.keyCode == 39) {RIGHT = 0};
         if (event.keyCode == 37) {LEFT = 0};
    };

    //フレーム毎実行
    Matter.Events.on(engine, 'beforeUpdate', function() {
         if (RIGHT == 1) {Matter.Body.setAngularVelocity(boxA, Math.PI/6)};
         if (LEFT == 1) {Matter.Body.setAngularVelocity(boxA, -Math.PI/6)};
    });

    // run the engine
    Engine.run(engine);
})();

問題なく動きます。楽しいゲームを作る手助けになれば幸いです。

コメント