galapagosit’s blog

本家 > http://galapagosit.com/

Cocos2d-JSでflappy bird

某有名フラッピーバードのロジック部分をCocos2d-JSで再現してみました。

コードはあんま綺麗じゃないかもしれないですが、 C++で書くよりだいぶシンプルに書けるというのが伝わればいいと思います。

  • 画面サイズは720×1280
  • underscorejsを使用しています。

実際の動き

シーンのコード

var createPlayer = (function(){
    var player = {
        // 重力
        GRAVITY_Y_FORCE: -3,
        // ジャンプ力
        JUMP_Y_FORCE: 25,

        // y方向に向かう力
        y_force: 0,

        update: function(dt) {
            // y方向の移動
            this.y += this.y_force;
            // 重力を加算
            this.y_force += this.GRAVITY_Y_FORCE;
        },
        jump: function() {
            // ジャンプの分 Y方向を加算
            this.y_force = this.JUMP_Y_FORCE;
        }
    };
    return function () {
        // playerにあたるスプライトを生成
        var sprite = new cc.Sprite();
        sprite.setTextureRect(cc.rect(0, 0, 100, 100));
        sprite.color = cc.color(255, 0, 0, 128);

        // 初期位置
        sprite.x = 300;
        sprite.y = 640;

        // spriteに属性を追加
        _.extend(sprite, player);

        return sprite;
    }
})();

var createEnemy = (function(){
    var enemy = {};
    return function (y_under, y_top) {
        // 敵(土管)にあたるスプライトを生成
        var sprite = new cc.Sprite();
        sprite.color = cc.color(0, 255, 0, 128);

        sprite.x = 900; // 画面外にセット
        sprite.y = (y_top + y_under) / 2;
        sprite.setTextureRect(cc.rect(0, 0, 100, (y_top - y_under)));

        // spriteに属性を追加
        _.extend(sprite, enemy);
        return sprite;
    }
})();

var createScorePoint = (function(){
    var scorePoint = {};
    return function (y_under, y_top) {
        // スコア獲得地点にあたるスプライトを生成
        var sprite = new cc.Sprite();
        sprite.color = cc.color(0, 0, 255, 30);

        sprite.x = 900; // 画面外にセット
        sprite.y = (y_top + y_under) / 2;
        sprite.setTextureRect(cc.rect(0, 0, 20, (y_top - y_under)));

        // spriteに属性を追加
        _.extend(sprite, scorePoint);
        return sprite;
    }
})();


READY = 1;
START = 2;
OVER = 3;

var ActionLayer = cc.Layer.extend({
    // ゲームの状態
    gameMode: READY,
    // ゲームの状態を表すテキスト
    labelGameMode: null,
    // スコア
    score: 0,
    // スコアを表すテキスト
    labelScore: null,

    // 敵(土管)スピード
    ENEMY_X_SPEED: -10,
    // 敵(土管)の隙間
    ENEMY_Y_SPACE: 400,

    // プレイヤー
    player: null,
    // 敵(土管)
    enemies: [],
    // スコア獲得ポイント
    scorePoints: [],
    // 敵(土管)が生まれてからの時間
    enemyBornDt: 0,

    ctor:function () {
        this._super();

        // プレイヤー作成
        this.player = createPlayer();
        this.addChild(this.player);

        // タッチイベント登録
        this.initTouchEvent();

        // ゲームスタート
        this.changeGameMode(READY);

        this.enemies = [];
        this.scorePoints = [];

        return true;
    },
    update:function (dt) {
        // 敵(土管)の作成
        this.manageEnemy(dt);
        // プレイヤーの死亡判定
        this.chackDeath();
        // スコア獲得判定
        this.chackEarnScore();
    },
    initTouchEvent: function() {
        var listener = cc.EventListener.create({
            event: cc.EventListener.TOUCH_ONE_BY_ONE,
            swallowTouches: false,
            onTouchBegan: function (touch, event) {
                var target = event.getCurrentTarget();
                target.touched();
                return true;
            }
        });
        cc.eventManager.addListener(listener, this);
    },
    touched: function() {
        if (this.gameMode == READY) {
            // ゲームスタート
            this.changeGameMode(START);
        }
        if (this.gameMode == START) {
            // ジャンプ
            this.player.jump();
        }
        if (this.gameMode == OVER) {
            // シーンを読み込み直す
            cc.director.runScene(new ActionScene());
        }
    },
    changeGameMode: function(gameMode) {
        this.gameMode = gameMode;

        if(!this.labelGameMode){
            this.labelGameMode = new cc.LabelTTF();
            this.labelGameMode.setFontSize(30);
            this.labelGameMode.setPosition(100, 1180);
            this.addChild(this.labelGameMode);
        }
        this.labelGameMode.setString("gameMode:" + gameMode);

        if (this.gameMode == START) {
            // それぞれのupdateメソッドを毎フレーム呼び出すようにする
            this.scheduleUpdate();
            this.player.scheduleUpdate();
        }
        if (this.gameMode == OVER) {
            // それぞれのupdateメソッド呼び出しを停止
            this.unscheduleUpdate();
            this.player.unscheduleUpdate();
        }
    },
    manageEnemy:function (dt) {
        // 敵(土管)が生まれてからの時間
        this.enemyBornDt += dt;

        // 存在する敵(土管)を移動
        _.each(this.enemies, function(enemy) {
            // 移動
            enemy.x += this.ENEMY_X_SPEED;
        }, this);
        // スコアポイントを移動
        _.each(this.scorePoints, function(scorePoint) {
            // 移動
            scorePoint.x += this.ENEMY_X_SPEED;
        }, this);

        // 存在する敵(土管)が画面外に出たら消す
        this.enemies = _.filter(this.enemies, function(enemy) {
            if(enemy.x < -100){
                enemy.removeFromParent();
                return false;
            }
            return true;
        }, this);

        // 一定時間たったら敵(土管)を生成
        if(this.enemyBornDt > 5){
            this.enemyBornDt = 0;

            // 上下の土管の中心Y座標をランダムで取得
            var centerOfEnemy = _.random(200, 1080);

            // 上の土管の下部の座標
            var _top = centerOfEnemy + this.ENEMY_Y_SPACE / 2;
            // 下の土管の上部の座標
            var _bottom = centerOfEnemy - this.ENEMY_Y_SPACE / 2;

            // 上側
            var enemyUpper = createEnemy(_top, 1280);
            this.addChild(enemyUpper);
            this.enemies.push(enemyUpper);

            // 下側
            var enemyUnder = createEnemy(0, _bottom);
            this.addChild(enemyUnder);
            this.enemies.push(enemyUnder);

            // スコア獲得ポイント
            var scorePoint = createScorePoint(_top, _bottom);
            this.addChild(scorePoint);
            this.scorePoints.push(scorePoint);
        }
    },
    chackDeath:function () {
        // 敵(土管)と接触したら死ぬ
        _.each(this.enemies, function(enemy, index, array) {
            if(cc.rectIntersectsRect(
                this.player.getBoundingBox(),
                enemy.getBoundingBox()
            )){
                this.die();
            }
        }, this);

        // プレイヤーが画面外に出たら死ぬ
        if(this.player.y < 0 || 1280 < this.player.y){
            this.die();
        }
    },
    chackEarnScore:function () {
        // スコア獲得ポイントと接触したらスコア加算
        _.each(this.scorePoints, function(scorePoint, index, array) {
            if(cc.rectIntersectsRect(
                this.player.getBoundingBox(),
                scorePoint.getBoundingBox()
            )){
                this.addScore();
                scorePoint.removeFromParent();
                array.splice(index, 1);
            }
        }, this);
    },
    addScore:function () {
        this.score += 1;
        if(!this.labelScore){
            this.labelScore = new cc.LabelTTF();
            this.labelScore.setFontSize(30);
            this.labelScore.setPosition(100, 1080);
            this.addChild(this.labelScore);
        }
        this.labelScore.setString("score:" + this.score);
    },
    die:function () {
        // 死んだ
        this.changeGameMode(OVER);
    }
});

var ActionScene = cc.Scene.extend({
    onEnter:function () {
        this._super();
        var layer = new ActionLayer();
        this.addChild(layer);
    }
});