enchant.js を使って 0 から 30分ぐらいでシューティングゲームを作ってみました! んで, その様子をビデオキャプチャーしてみました!!

phiary に引っ越しました. 毎日プログラミングやWebに関する情報を発信しています! RSS 登録してたまに覗いたり, tweet やハテブして拡散してもらえると幸いです.

Pocket

先日『enchant.js を使って 0 から 48 分以内にゲームを作ってみました!んで, その様子をビデオキャプチャーしてみました!!』 というエントリーを書かせていただきました.

結構沢山の方に見ていただいたみたいでありがとうございます.

また, UEI の shi3z さんが『shi3zが自ら10分でシューティングゲームを作ってみたぞ!』なんてことをしちゃったりとかして, なんかもう面白くなってきました.

そういうことで, またまたやってみた次第です. 今回は, 作るゲームを決めて望んだので enchant.js のセットアップからプレイヤーの移動, 敵,弾の生成, 衝突判定, スコアの計算まで ほぼ 30 分でできました.

enchant.js の特性を活かし, css の class を活用して プログラムとデザインを分離する小技なんかも使っているので ぜひ参考にしてみてください. 詳しくはこのエントリーの下の方で解説しています.

途中集中しすぎちゃって解説を忘れてしまっているので, 間違いや質問, アドバイスなどありましたら ほんの些細なことでも良いので直接Tweet してくれると嬉しいです.

キャプチャーした動画は youbute にアップして, 下に貼りつけています. HD 画質でアップしているので, HD モードでフルスクリーンにしてもらえればコードもクッキリ見えるかと思います.

作ったゲームは少し修正して 9leap にアップしています. 下記の “Play Game” ボタンを押してもらえればプレイできます.

Play Game Download

About

上の “Play Game” ボタンを押すと 9leap さんのサイトでプレイできます.

タッチでプレイヤー移動, タッチエンドでショットを打ちます.

ゲームオーバー条件

  • 敵にぶつかる
  • 敵を見逃す
  • ショットを外す

Code

今回作ったゲームのスクリプト部分です.

/**
 * phi
 */

// --------------------------------
// 定数
// --------------------------------
var SCREEN_SIZE = 320;
var PLAYER_SIZE = 20;
var BULLET_SIZE = 5;
var ENEMY_SIZE  = 20;

// --------------------------------
// グローバル
// --------------------------------
var game    = null;
var player  = null;
var enemyList = null;
var bulletList= null;
var scoreLabel= null;

// --------------------------------
// おまじない
// --------------------------------
enchant();


// --------------------------------
// util
// --------------------------------
Array.prototype.erase = function(elm)
{
    var index = this.indexOf(elm);
    this.splice(index, 1);
    return this;
};

window.onload = function()
{
    game = new Game(SCREEN_SIZE, SCREEN_SIZE);
    game.fps = 30;
    
    game.onload = function() {
        var scene = game.rootScene;
        scene.backgroundColor = "black";
        
        scene.ontouchmove = function(e) {
            player.x = e.x - PLAYER_SIZE/2;
            player.y = e.y - PLAYER_SIZE/2;
        };
        
        // タッチ終了時に弾を飛ばす
        scene.ontouchend = function() {
            var bullet = new Bullet();
            bullet.x = player.x + PLAYER_SIZE/2 - BULLET_SIZE/2;
            bullet.y = player.y - 5;
            bulletList.push(bullet);
            scene.addChild(bullet);
        };
        
        scene.onenter = function() {
            game.frame = 0;
            game.score = 0;
            
            // プレイヤー生成
            player = new Player();
            player.x = SCREEN_SIZE/2 - PLAYER_SIZE/2;
            player.y = SCREEN_SIZE - 40;
            scene.addChild(player);
            
            // エネミーリスト生成
            enemyList = [];
            
            // 弾リスト生成
            bulletList = [];
            
            // スコア
            scoreLabel = new Label();
            scoreLabel.color = "white";
            scoreLabel.text = "";
            scene.addChild(scoreLabel);
        };
        
        scene.onenterframe = function() {
            // 敵生成
            if (game.frame%50 === 0) {
                var enemy = new Enemy();
                enemy.x = Math.random()*(SCREEN_SIZE-ENEMY_SIZE);
                enemy.y = -20;
                enemyList.push(enemy);
                scene.addChild(enemy);
            }
            
            // 衝突判定
            for (var i=0,len=enemyList.length; i<len; ++i) {
                var enemy = enemyList&#91;i&#93;;
                if (player.intersect(enemy) == true) {
                    gameOver("敵にぶつかりました");
                }
            }
            
            // 弾と敵の衝突判定
            for (var i=0,len=enemyList.length; i<len; ++i) {
                var enemy = enemyList&#91;i&#93;;
                for (var j=0,len2=bulletList.length; j<len2; ++j) {
                    var bullet = bulletList&#91;j&#93;;
                    // 敵と弾の衝突判定
                    if (bullet.intersect(enemy) == true) {
                        enemy.destroy = true;
                        bullet.destroy = true;
                        
                        game.score += 100;
                        break;
                    }
                }
            }
            
            // スコア表示更新
            scoreLabel.text = "SCORE : " + game.score;
        };
    };
    game.start();
};


var gameOver = function(msg)
{
    console.log(game.score, msg);
    game.end(game.score, msg);
};

// --------------------------------
// プレイヤークラス
// --------------------------------
var Player = Class.create(Sprite, {
    initialize: function() {
        Sprite.call(this, PLAYER_SIZE, PLAYER_SIZE);
        
        this.className = "player";
    }
});



// --------------------------------
// 敵クラスを作成
// --------------------------------
var Enemy = Class.create(Sprite, {
    initialize: function() {
        Sprite.call(this, ENEMY_SIZE, ENEMY_SIZE);
        
        this.className = "enemy";
        this.timer = Math.random()*360;
    },
    
    onenterframe: function() {
        this.x += Math.cos(this.timer*2 * Math.PI/180)*2;
        this.y += 1;
        
        if (this.x < 0) this.x = 0;
        if (this.x > SCREEN_SIZE-ENEMY_SIZE) this.x = SCREEN_SIZE-ENEMY_SIZE;
        
        if (this.destroy == true) {
            this.parentNode.removeChild(this);
            enemyList.erase(this);
        }
        
        if (this.y >= SCREEN_SIZE + 20) {
            gameOver("敵を見逃しました.");
        }
        
        ++this.timer;
    }
});


// --------------------------------
// 弾クラスを作成
// --------------------------------
var Bullet = Class.create(Sprite, {
    initialize: function() {
        Sprite.call(this, BULLET_SIZE, BULLET_SIZE);
        
        this.className = "bullet";
    },
    
    onenterframe: function() {
        this.y -= 8;
        
        if (this.destroy == true) {
            this.parentNode.removeChild(this);
            bulletList.erase(this);
        }
        
        if (this.y < -20) {
            gameOver("弾を外しました.");
        }
    }
});
&#91;/code&#93;</pre>
</section>

<section>
    <h2>Tips</h2>
    <section>
        <h3>enchant.js の特性を活かし, プログラムとデザインを分離</h3>
        <p>
            今回制作したサンプルでは, プログラムとデザインを分離した作りになっています.
        </p>
        <p>
            ゲーム制作の現場では, プログラム(処理の実装), デザイン(見た目の調整), データ(ゲームバランスの調整)をうまく分離して
            プログラマ, デザイナ, プランナーでそれぞれ効率良く作業分担できるようにするのが基本です.
        </p>
        <p>
            そして, これができるかどうかは根本部分を作るプログラマの重要な役割になります.
            優秀なプログラマほどこの仕組みを作るのが上手だったりします.
        </p>
        <p>
            今回制作したゲームでは script.js のコードがプログラム部, index.html の style タグの中身がデザイン部,
            script.js の一番上の定数の部分がデータ部にあたります.
        </p>
        <p>
            enchant.js の Sprite クラスや Label クラスといった Entity クラスを継承しているクラスは
            内部で DOM Element を抱えているのでプログラムとデザインの分離は簡単に行うことができます.
        </p>
        <p>
            やりかたはこんな感じ.
        </p>
        <h4>プログラム部分</h4>
        <pre class="prettyprint">
var player = new Sprite(10, 10);
player.className = "player";

デザイン部分

.player {
    background-color: red;
    /* こんな感じで画像も使えます */
    /* background: bear.png; */
    border-radius: 50%;
}

このようにゲームで使うクラス名をあらかじめ洗い出しておき, それらを定義したスタイルシートをデザイナさんにを投げておけば, プログラマはプログラムに専念し, デザイナさんはプログラムの作業を気にせず見た目の調整を行うことができます.

Array(配列)に特定の要素を削除する erase メソッドを追加

Array には特定の要素を削除する機能が標準では存在しません.

なので, 今回のゲームでは自分で実装しています.

Array.prototype.erase = function(elm)
{
    // elm と一致する要素の index を取得
    var index = this.indexOf(elm);
    // index 番目の要素を取り除く
    this.splice(index, 1);
    return this;
};

もうこのメソッド実装したの 100 回目ぐらいかもw ちなみに自作JavaScriptライブラリ tmlib.jsでは標準で実装されています.

次回は何をしようかな.

間違いや質問, アドバイスなどありましたらコメントもしくは Tweet していただけると幸いです.

TRACK BACK URL

POST COMMENT

メールアドレスが公開されることはありません。

COMMENT