Categories
JavaScript Node.js WEBサービス

オンラインゲーム(MMORPG)の作り方基礎 – リソースや帯域を節約し、不正ユーザを防ぐには

ローグライク風アクションオンラインゲームを制作しながら気づいたことやタメになった記事のまとめです。運用時の規模が10人から100人ほどの規模を想定しています。小規模オンラインゲームにおいて考慮しなければならない点をひと通りまとめてみました。

 

 

Categories
game JavaScript tmlib.js Tutorial

05:「ゲームの処理を作ろう」 – tmlib.js入門用チュートリアル!避けゲーを作ってみよう

モンスターを表示しよう

サンプル

コードの解説

モンスターを表示して動かしてみます。プレイヤークラスと同じような要領でクラスを作成します。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};
var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;
var ENEMY_WIDTH  = 38;
var ENEMY_HEIGHT = 30;

/**
 * リソースの読み込み
 */
var ASSETS = {
    "player":   "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Animal]Chicken.png",
    "playerSS": "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/playerSS.tmss",
    "enemy":    "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Monster]Dragon_B_pochi.png",
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // Player
        this.player = Player().addChildTo(this);
        this.player.position.set(150, 600);

        // enemy
        this.enemyGroup = tm.app.CanvasElement().addChildTo(this);

        // スコア用カウントアップ
        this.timer = 0;

        // ラベル表示
        this.fromJSON(UI_DATA.main);

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });
    },

    update: function (app) {
        // カウントアップを行う
        ++this.timer;

        // 敵の生成(難易度をどんどん上げる)
        if (this.timer % 30 === 0) {
        	for (var i = 0, n = (this.timer / 300); i < n; ++i) {
                var enemy = Enemy().addChildTo(this.enemyGroup);
                enemy.x = Math.rand(0, SCREEN_WIDTH);
                enemy.y = 0 - enemy.height;
        	}
        }
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});


/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        this.gotoAndPlay("left");
    },
});


/*
 * enemy
 */
tm.define("Enemy", {
    superClass: "tm.app.Sprite",

    init: function() {
        this.superInit("enemy", ENEMY_WIDTH*4, ENEMY_HEIGHT*4);
        this.speed = Math.rand(6, 12);
    },

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }
});

まずはじめに、プレイヤークラスを追加したやり方と同じように画像を読み込みます。

    "enemy":    "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Monster]Dragon_B_pochi.png",

MainSceneクラスでは、モンスターのクラスであるEnemyクラスを複数入れるグループを追加します。

        // enemy
        this.enemyGroup = tm.app.CanvasElement().addChildTo(this);

モンスターは複数作るので、グループ用の変数enemyGroupを作っておきます。モンスターは時間経過にともなって生成していくので、スコアの計算と併用するtimer変数も用意しておきましょう。

        // スコア用カウントアップ
        this.timer = 0;

そして、ゲームの処理が行われるたびに処理されるupdate関数を作ります。

    update: function (app) {
        // カウントアップを行う
        ++this.timer;

        // 敵の生成(難易度をどんどん上げる)
        if (this.timer % 30 === 0) {
        	for (var i = 0, n = (this.timer / 300); i < n; ++i) {
                var enemy = Enemy().addChildTo(this.enemyGroup);
                enemy.x = Math.rand(0, SCREEN_WIDTH);
                enemy.y = 0 - enemy.height;
        	}
        }
    },

時間を計るtimerのカウントアップと、モンスターの生成処理を行なっています。モンスターを作るタイミングになればEnemyクラスを使ってモンスターを生成します。生成したモンスターは、モンスターのグループに入れることで画面に描画されるようになります。複数のオブジェクトを管理したいときは、このようにグループを使うと便利です。
 
次はEnemyクラスです。

/*
 * enemy
 */
tm.define("Enemy", {
    superClass: "tm.app.Sprite",

    init: function() {
        this.superInit("enemy", ENEMY_WIDTH*4, ENEMY_HEIGHT*4);
        this.speed = Math.rand(6, 12);
    },

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }
});

init関数は、クラスを使うときに最初に処理される関数でした。なので、init関数内でモンスターが落ちてくるスピードをランダムに決定しています。

this.speed = Math.rand(6, 12);

このランダムに決定したスピードを使って下へ移動する処理を作ります。移動処理もゲーム処理が実行されるたびに処理されるupdate関数に書きます。

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }

スピードの分だけY座標を足していけば、モンスターが落ちてくるように動かすことができますね。なお、画面から見えなくなったらモンスターを消す処理も入れています。
 

これだけでも、何となくゲームの形になってきました。次はプレイヤーを動かせるようにしていきます。

プレイヤーを動かせるようにしよう

サンプル

コードの解説

タッチするたびに、プレイヤーの移動方向が逆になるようにします。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};
var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;
var PLAYER_GROUND_LIMIT_LEFT  = PLAYER_WIDTH/2;
var PLAYER_GROUND_LIMIT_RIGHT = SCREEN_WIDTH - PLAYER_WIDTH/2;
var ENEMY_WIDTH  = 38;
var ENEMY_HEIGHT = 30;

/**
 * リソースの読み込み
 */
var ASSETS = {
    "player":   "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Animal]Chicken.png",
    "playerSS": "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/playerSS.tmss",
    "enemy":    "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Monster]Dragon_B_pochi.png",
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // Player
        this.player = Player().addChildTo(this);
        this.player.position.set(150, 600);

        // enemy
        this.enemyGroup = tm.app.CanvasElement().addChildTo(this);

        // スコア用カウントアップ
        this.timer = 0;

        // ラベル表示
        this.fromJSON(UI_DATA.main);

        // // 画面(シーンの描画箇所)をタッチした時の動作
        // this.addEventListener("pointingend", function(e) {
        //     // シーンの遷移
        //     e.app.replaceScene(EndScene());
        // });
    },

    update: function (app) {
        // カウントアップを行う
        ++this.timer;

        // 敵の生成(難易度をどんどん上げる)
        if (this.timer % 30 === 0) {
        	for (var i = 0, n = (this.timer / 300); i < n; ++i) {
                var enemy = Enemy().addChildTo(this.enemyGroup);
                enemy.x = Math.rand(0, SCREEN_WIDTH);
                enemy.y = 0 - enemy.height;
        	}
        }
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});


/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        // this.gotoAndPlay("left");
        // 移動の方向を保持
        this.direct = "left";
    },

    moveLimit: function () {
        // 画面からはみ出ないようにする
        if (this.x < PLAYER_GROUND_LIMIT_LEFT) {
            this.x = PLAYER_GROUND_LIMIT_LEFT;
        }
        if (this.x > PLAYER_GROUND_LIMIT_RIGHT) {
            this.x = PLAYER_GROUND_LIMIT_RIGHT;
        }
    },

    clickLeft: function () {
        this.gotoAndPlay("left");
        this.x -= 4;
    },

    clickRight: function () {
        this.gotoAndPlay("right");
        this.x += 4;
    },

    update: function (app) {
        // 移動処理
        if (app.pointing.getPointingStart()) {
            this.direct = (this.direct === "left") ? "right" : "left";
        }
        switch (this.direct) {
            case "left":
                this.clickLeft();
                break;
            case "right":
                this.clickRight();
                break;
        }
        // 移動の限界
        this.moveLimit();
    },
});


/*
 * enemy
 */
tm.define("Enemy", {
    superClass: "tm.app.Sprite",

    init: function() {
        this.superInit("enemy", ENEMY_WIDTH*4, ENEMY_HEIGHT*4);
        this.speed = Math.rand(6, 12);
    },

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }
});

まずは、MainSceneをタッチするとEndSceneに移動する処理を消しておきます。

        // // 画面(シーンの描画箇所)をタッチした時の動作
        // this.addEventListener("pointingend", function(e) {
        //     // シーンの遷移
        //     e.app.replaceScene(EndScene());
        // });

次はプレイヤーの処理です。プレイヤーが最初に移動する方向をinit関数で決めておきます。

        // 移動の方向を保持
        this.direct = "left";

そして、update関数で移動処理を作っていきます。タッチを認識したら移動する方向を変えるようにします。

    update: function (app) {
        // 移動処理
        if (app.pointing.getPointingStart()) {
            this.direct = (this.direct === "left") ? "right" : "left";
        }
        switch (this.direct) {
            case "left":
                this.clickLeft();
                break;
            case "right":
                this.clickRight();
                break;
        }
        // 移動の限界
        this.moveLimit();
    },

app.pointing.getPointingStart()関数を呼び出すことで、タッチされたかどうかが分かります。タッチされたらdirect変数の中のデータ、”left”と”right”を入れ替えましょう。そして、現在どっちを向いているのかをswitchで判定したら、実際にプレイヤーを動かしていきます。

    clickLeft: function () {
        this.gotoAndPlay("left");
        this.x -= 4;
    },

    clickRight: function () {
        this.gotoAndPlay("right");
        this.x += 4;
    },

“left”だったら左方向に移動+アニメーション、”right”だったら右方向に移動+アニメーション、という具合ですね。そして最後に、画面外に出られないようにする処理を呼び出しています。

    moveLimit: function () {
        // 画面からはみ出ないようにする
        if (this.x < GROUND_LIMIT_LEFT) {
            this.x = GROUND_LIMIT_LEFT;
        }
        if (this.x > GROUND_LIMIT_RIGHT) {
            this.x = GROUND_LIMIT_RIGHT;
        }
    },

画面外に出てしまうような移動を行なっていたら、画面に収まる位置に戻すようにしています。これでプレイヤーが画面外に出ることはできません。

モンスターと接触したらゲームオーバーにしよう

サンプル

コードの解説

忘れてはならないのがプレイヤーとモンスターの当たり判定です。モンスターと接触したら、即ゲームオーバーにします。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};
var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;
var PLAYER_GROUND_LIMIT_LEFT  = PLAYER_WIDTH/2;
var PLAYER_GROUND_LIMIT_RIGHT = SCREEN_WIDTH - PLAYER_WIDTH/2;
var ENEMY_WIDTH  = 38;
var ENEMY_HEIGHT = 30;

/**
 * リソースの読み込み
 */
var ASSETS = {
    "player":   "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Animal]Chicken.png",
    "playerSS": "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/playerSS.tmss",
    "enemy":    "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Monster]Dragon_B_pochi.png",
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // Player
        this.player = Player().addChildTo(this);
        this.player.position.set(150, 600);

        // enemy
        this.enemyGroup = tm.app.CanvasElement().addChildTo(this);

        // スコア用カウントアップ
        this.timer = 0;

        // ラベル表示
        this.fromJSON(UI_DATA.main);
    },

    update: function (app) {
        // カウントアップを行う
        ++this.timer;

        // 制限時間を表示する
        this.timeLabel.text = "生き残ってる時間 : " + ((this.timer / 30) |0);

        // 敵の生成(難易度をどんどん上げる)
        if (this.timer % 30 === 0) {
        	for (var i = 0, n = (this.timer / 300); i < n; ++i) {
                var enemy = Enemy().addChildTo(this.enemyGroup);
                enemy.x = Math.rand(0, SCREEN_WIDTH);
                enemy.y = 0 - enemy.height;
        	}
        }

        var self = this;
        var ec = this.enemyGroup.children;
        ec.each(function(enemy) {
            if (self.player.isHitElement(enemy)) {
                app.replaceScene(EndScene(self.timer))
            };
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function(time) {
        // スコア計算
        RESULT_PARAM.score = (Math.floor(time*100/30)/100) + "秒生き残ることができました。";

        // スコア
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});


/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        // this.gotoAndPlay("left");
        // 移動の方向を保持
        this.direct = "left";
    },

    moveLimit: function () {
        // 画面からはみ出ないようにする
        if (this.x < PLAYER_GROUND_LIMIT_LEFT) {
            this.x = PLAYER_GROUND_LIMIT_LEFT;
        }
        if (this.x > PLAYER_GROUND_LIMIT_RIGHT) {
            this.x = PLAYER_GROUND_LIMIT_RIGHT;
        }
    },

    clickLeft: function () {
        this.gotoAndPlay("left");
        this.x -= 4;
    },

    clickRight: function () {
        this.gotoAndPlay("right");
        this.x += 4;
    },

    update: function (app) {
        // 移動処理
        if (app.pointing.getPointingStart()) {
            this.direct = (this.direct === "left") ? "right" : "left";
        }
        switch (this.direct) {
            case "left":
                this.clickLeft();
                break;
            case "right":
                this.clickRight();
                break;
        }
        // 移動の限界
        this.moveLimit();
    },
});


/*
 * enemy
 */
tm.define("Enemy", {
    superClass: "tm.app.Sprite",

    init: function() {
        this.superInit("enemy", ENEMY_WIDTH*4, ENEMY_HEIGHT*4);
        this.speed = Math.rand(6, 12);
    },

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }
});

ついでなので、生き残っている時間を表示するようにしておきます。

        // 制限時間を表示する
        this.timeLabel.text = "生き残ってる時間 : " + ((this.timer / 30) |0);

表示している文字を途中で変更したいときは、上記のように書きます。次はモンスターとプレイヤーの当たり判定です。

        var self = this;
        var ec = this.enemyGroup.children;
        ec.each(function(enemy) {
            if (self.player.isHitElement(enemy)) {
                app.replaceScene(EndScene(self.timer))
            };
        });

each関数を使うことで、enemyGroupに登録しているモンスター全てに対して行う処理を書くことができます。each関数に当たり判定の処理を渡すことで、簡単に総当りの当たり判定が作れますね。当たり判定の処理はtmlib.jsが用意していますので、たった一行で書くことができます。

self.player.isHitElement(enemy)

敵とヒットしてゲームオーバーになったら生き残った時間をそのまま使ってスコアにしたいですね。そこで、EndSceneに生き残った時間を渡す処理を書きます。

app.replaceScene(EndScene(self.timer))

生き残った時間を受け取る処理は次のように書きます。

tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function(time) { // ここ

このように、init関数は引数としてデータを受け取ることができます。受け取った値を使ってスコアを表示しましょう。

        // スコア計算
        RESULT_PARAM.score = (Math.floor(time*100/30)/100) + "秒生き残ることができました。";

これで生き残った時間を表示できるようになりました。ゲームとしてはこれで完成ですがちょっとした味付けを次項でやってみます。

Categories
game JavaScript tmlib.js Tutorial

Ex:「音をつけよう & iPhoneを傾けたら移動するようにしてみよう」 – tmlib.js入門用チュートリアル!避けゲーを作ってみよう

BGMを再生しよう

サンプル

コードの解説

tmlib.jsは音の再生もサポートしているので、BGMを鳴らしてみます。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};
var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;
var PLAYER_GROUND_LIMIT_LEFT  = PLAYER_WIDTH/2;
var PLAYER_GROUND_LIMIT_RIGHT = SCREEN_WIDTH - PLAYER_WIDTH/2;
var ENEMY_WIDTH  = 38;
var ENEMY_HEIGHT = 30;

/**
 * リソースの読み込み
 */
var ASSETS = {
    "player":   "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Animal]Chicken.png",
    "playerSS": "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/playerSS.tmss",
    "enemy":    "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Monster]Dragon_B_pochi.png",
    "bgm":      "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/Comical01_Koya_short2.mp3",
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // BGM再生
        this.bgm = tm.asset.AssetManager.get("bgm");
        this.bgm.setVolume(1.0).setLoop(true).play();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // Player
        this.player = Player().addChildTo(this);
        this.player.position.set(150, 600);

        // enemy
        this.enemyGroup = tm.app.CanvasElement().addChildTo(this);

        // スコア用カウントアップ
        this.timer = 0;

        // ラベル表示
        this.fromJSON(UI_DATA.main);
    },

    update: function (app) {
        // カウントアップを行う
        ++this.timer;

        // 制限時間を表示する
        this.timeLabel.text = "生き残ってる時間 : " + ((this.timer / 30) |0);

        // 敵の生成(難易度をどんどん上げる)
        if (this.timer % 30 === 0) {
        	for (var i = 0, n = (this.timer / 300); i < n; ++i) {
                var enemy = Enemy().addChildTo(this.enemyGroup);
                enemy.x = Math.rand(0, SCREEN_WIDTH);
                enemy.y = 0 - enemy.height;
        	}
        }

        var self = this;
        var ec = this.enemyGroup.children;
        ec.each(function(enemy) {
            if (self.player.isHitElement(enemy)) {
                self.bgm.stop();
                app.replaceScene(EndScene(self.timer))
            };
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function(time) {
        // スコア計算
        RESULT_PARAM.score = (Math.floor(time*100/30)/100) + "秒生き残ることができました。";

        // スコア
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});


/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        // this.gotoAndPlay("left");
        // 移動の方向を保持
        this.direct = "left";
    },

    moveLimit: function () {
        // 画面からはみ出ないようにする
        if (this.x < PLAYER_GROUND_LIMIT_LEFT) {
            this.x = PLAYER_GROUND_LIMIT_LEFT;
        }
        if (this.x > PLAYER_GROUND_LIMIT_RIGHT) {
            this.x = PLAYER_GROUND_LIMIT_RIGHT;
        }
    },

    clickLeft: function () {
        this.gotoAndPlay("left");
        this.x -= 4;
    },

    clickRight: function () {
        this.gotoAndPlay("right");
        this.x += 4;
    },

    update: function (app) {
        // 移動処理
        if (app.pointing.getPointingStart()) {
            this.direct = (this.direct === "left") ? "right" : "left";
        }
        switch (this.direct) {
            case "left":
                this.clickLeft();
                break;
            case "right":
                this.clickRight();
                break;
        }
        // 移動の限界
        this.moveLimit();
    },
});


/*
 * enemy
 */
tm.define("Enemy", {
    superClass: "tm.app.Sprite",

    init: function() {
        this.superInit("enemy", ENEMY_WIDTH*4, ENEMY_HEIGHT*4);
        this.speed = Math.rand(6, 12);
    },

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }
});

画像と同じように音楽もロードすることができます。MainSceneが開始するときにBGMがなり始めるようにします。

        // BGM再生
        this.bgm = tm.asset.AssetManager.get("bgm");
        this.bgm.setVolume(1.0).setLoop(true).play();

ボリュームやBGMをループして再生する設定など行った後、play関数で音の再生を開始します。

 

このままだと、EndSceneになってもBGMが鳴り続けてしまいます。EndSceneに遷移する前にBGMをストップしておきます。

                self.bgm.stop();

ジャイロセンサーを使って、スマートフォンを傾けても移動できるようにしよう

サンプル

コードの解説

ジャイロセンサーとは、iPhoneの傾きを検知するセンサーです。tmlib.jsでは、このセンサーをゲームに応用しやすいようにしてくれています。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};
var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;
var PLAYER_GROUND_LIMIT_LEFT  = PLAYER_WIDTH/2;
var PLAYER_GROUND_LIMIT_RIGHT = SCREEN_WIDTH - PLAYER_WIDTH/2;
var ENEMY_WIDTH  = 38;
var ENEMY_HEIGHT = 30;

/**
 * リソースの読み込み
 */
var ASSETS = {
    "player":   "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Animal]Chicken.png",
    "playerSS": "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/playerSS.tmss",
    "enemy":    "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Monster]Dragon_B_pochi.png",
    "bgm":      "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/Comical01_Koya_short2.mp3",
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};


/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // BGM再生
        this.bgm = tm.asset.AssetManager.get("bgm");
        this.bgm.setVolume(1.0).setLoop(true).play();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // Player
        this.player = Player().addChildTo(this);
        this.player.position.set(150, 600);

        // enemy
        this.enemyGroup = tm.app.CanvasElement().addChildTo(this);

        // スコア用カウントアップ
        this.timer = 0;

        // ラベル表示
        this.fromJSON(UI_DATA.main);
    },

    update: function (app) {
        // カウントアップを行う
        ++this.timer;

        // 制限時間を表示する
        this.timeLabel.text = "生き残ってる時間 : " + ((this.timer / 30) |0);

        // 敵の生成(難易度をどんどん上げる)
        if (this.timer % 30 === 0) {
            for (var i = 0, n = (this.timer / 300); i < n; ++i) {
                var enemy = Enemy().addChildTo(this.enemyGroup);
                enemy.x = Math.rand(0, SCREEN_WIDTH);
                enemy.y = 0 - enemy.height;
            }
        }

        var self = this;
        var ec = this.enemyGroup.children;
        ec.each(function(enemy) {
            if (self.player.isHitElement(enemy)) {
                self.bgm.stop();
                app.replaceScene(EndScene(self.timer))
            };
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function(time) {
        // スコア計算
        RESULT_PARAM.score = (Math.floor(time*100/30)/100) + "秒生き残ることができました。";

        // スコア
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});


/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        // 移動の方向を保持
        this.direct = "left";
        // スマホかどうか判断
        this.isMobile = tm.isMobile;
        // スマホだったら加速度を使うので、タッチ入力での移動を行わない
        this.update = (this.isMobile) ? this.updateMobile : this.updateNotMobile;
    },

    moveLimit: function () {
        // 画面からはみ出ないようにする
        if (this.x < PLAYER_GROUND_LIMIT_LEFT) {
            this.x = PLAYER_GROUND_LIMIT_LEFT;
        }
        if (this.x > PLAYER_GROUND_LIMIT_RIGHT) {
            this.x = PLAYER_GROUND_LIMIT_RIGHT;
        }
    },

    clickLeft: function () {
        this.gotoAndPlay("left");
        this.x -= 4;
    },

    clickRight: function () {
        this.gotoAndPlay("right");
        this.x += 4;
    },

    updateNotMobile: function (app) {
        // 移動処理
        if (app.pointing.getPointingStart()) {
            this.direct = (this.direct === "left") ? "right" : "left";
        }
        switch (this.direct) {
            case "left":
                this.clickLeft();
                break;
            case "right":
                this.clickRight();
                break;
        }
        // 移動の限界
        this.moveLimit();
    },

    updateMobile: function (app) {
        // 移動処理:モバイルなら加速度センサーを利用
        var gravity = app.accelerometer.gravity;
        this.x -= gravity.y * 1.7;

        // 移動方向によってアニメーション
        if (gravity.y > 0) { this.gotoAndPlay("left"); }
        else               { this.gotoAndPlay("right"); }

        // 移動の限界
        this.moveLimit();
    },
});

/*
 * enemy
 */
tm.define("Enemy", {
    superClass: "tm.app.Sprite",

    init: function() {
        this.superInit("enemy", ENEMY_WIDTH*4, ENEMY_HEIGHT*4);
        this.speed = Math.rand(6, 12);
    },

    update: function() {
        this.y += this.speed;

        // 画面から見えなくなったら消す
        if (this.y > SCREEN_HEIGHT + this.height) {
            this.remove();
        }
    }
});

しかし、プレイヤークラスのupdateを上記の処理にそのまま書き換えてしまうとPCではプレイヤーを操作することはできません。その対策として、PC用のupdateとスマートフォン用のupdateを別に作っておきます。そして、スマートフォンなのかPCなのかを判断してupdateを適切にセットすると、とりあえずiPhoneとPCの区別をつけることができます。

        // スマホかどうか判断
        this.isMobile = tm.isMobile;
        // スマホだったら加速度を使うので、タッチ入力での移動を行わない
        this.update = (this.isMobile) ? this.updateMobile : this.updateNotMobile;

「正確にジャイロセンサーが付いていたら、ジャイロセンサーを使った操作に切り替える」という処理ではないので気をつけてくださいね。

 
 

これでチュートリアルは終了です。お疲れ様でした!落ちゲーのゲームも色々処理を追加しようと思えばたくさん思いつくとおもいます。このチュートリアルでゲームを作り始めてくれたりするととても嬉しいです。

Categories
game JavaScript tmlib.js Tutorial

04:「画像を表示してアニメーションさせてみよう(スプライトアニメーション)」 – tmlib.js入門用チュートリアル!避けゲーを作ってみよう

ローディング画面を使って画像を読み込もう!

サンプル

コードの解説

タイトル画面が表示される前に、ローディングの画面を挟んで画像をロードしてみます。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};

/**
 * リソースの読み込み
 */
var ASSETS = {
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // ラベル表示
        this.fromJSON(UI_DATA.main);

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});

このように読み込みたい画像のURLを書いた変数、ASSETSを用意します。ASSETSの”backMap”とは画像の別名です。画像に別名をつけることで、URLを変更しても他のプログラム箇所は書き換えなくてよくなっています。

画像を表示しよう

サンプル

コードの解説

画像を表示してみます。マップの画像を表示させてみましょう。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};

/**
 * リソースの読み込み
 */
var ASSETS = {
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // ラベル表示
        this.fromJSON(UI_DATA.main);

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});

これで画像を表示することができました。次はアニメーションをさせてみましょう。

スプライトアニメーションをさせてみよう

サンプル

コードの解説

プレイヤークラスを用意しちゃいましょう。初めて自分用のクラスを作りますが、作り方は既存のクラスの拡張と同じです。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};
var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;

/**
 * リソースの読み込み
 */
var ASSETS = {
    "player":   "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/[Animal]Chicken.png",
    "playerSS": "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/playerSS.tmss",
    "backMap":  "http://rawgithub.com/omatoro/tmlib.js_tutorial_avoidgame/gh-pages/rsc/map.png",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    var loadingScene = tm.app.LoadingScene({
        assets: ASSETS,
        nextScene: TitleScene,
        width: SCREEN_WIDTH,
        height: SCREEN_HEIGHT,
    });

    // シーンの切り替え
    app.replaceScene(loadingScene);

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // Map
        this.map = tm.app.Sprite("backMap", SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this);
        this.map.position.set(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);

        // Player
        this.player = Player().addChildTo(this);
        this.player.position.set(150, 600);

        // ラベル表示
        this.fromJSON(UI_DATA.main);

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});


/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        this.gotoAndPlay("left");
    },
});

画像の追加や、MainSceneへの処理の追加はMapクラスと同じですね。今回違うのは、プレイヤークラスの作り方です。プレイヤークラスをちょっと詳しくみてみましょう。

var PLAYER_WIDTH  = 20;
var PLAYER_HEIGHT = 16;
/*
 * player
 */
tm.define("Player", {
    superClass: "tm.app.AnimationSprite",

    init: function () {
        this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);
        this.gotoAndPlay("left");
    },
});

プレイヤーのサイズを変数として作っています。サイズは幅20pixel x 高さ16pixelです。画像全体の大きさではないことに注意してください。キャラクターのサイズです。画像を見ると分かりますが、一つ一つの動きごとに絵が書かれているので画像を見たほうが分かりやすいでしょうか。

 

 

流用するクラスは”tm.app.AnimationSprite”となっています。これはスプライトアニメーションを簡単に行えるようにtmlib.jsが用意しているクラスです。
 

superInitにアニメーションの仕方を記述しているファイルである”playerSS”を渡すことで、アニメーションができるようになります。

this.superInit("playerSS", PLAYER_WIDTH*4, PLAYER_HEIGHT*4);

サイズを4倍にしているのは、プレイヤーの表示を大きくするためです。元の画像が小さいので。
 
これでアニメーションの準備は終わりです。次のコードでアニメーションが開始されます。

this.gotoAndPlay("left");

“left”を指定することで、左方向に移動しているようなアニメーションが再生されます。実際にindex.htmlをブラウザで開くとアニメーションされますのでチェックしてみてください。

 

次は、アニメーションの仕方を指定する方法です。アニメーションの仕方は”playerSS”ファイルに書かれています。中身はこんな感じです。

{
    "image": "player",
    "frame": {
        "width":20,
        "height":16,
        "count": 24
    },
    "animations": {
        "down": {
            "frames": [1, 2, 1, 0],
            "next": "down",
            "frequency": 5
        },
        "up": {
            "frames": [19, 20, 19, 18],
            "next": "up",
            "frequency": 5
        },
        "left": {
            "frames": [7, 8, 7, 6],
            "next": "left",
            "frequency": 5
        },
        "right": {
            "frames": [13, 14, 13, 12],
            "next": "right",
            "frequency": 5
        },
        "upleft": {
            "frames": [16, 17, 16, 15],
            "next": "upleft",
            "frequency": 5
        },
        "upright": {
            "frames": [22, 23, 22, 21],
            "next": "upright",
            "frequency": 5
        },
        "downleft": {
            "frames": [4, 5, 4, 3],
            "next": "downleft",
            "frequency": 5
        },
        "downright": {
            "frames": [10, 11, 10, 9],
            "next": "downright",
            "frequency": 5
        }
    }
}

jsonファイルの形式になっています。画像名や画像のサイズ、アニメーション方法などをまとめて書いています。

Categories
game JavaScript tmlib.js Tutorial

03:「文字を表示してみよう」 – tmlib.js入門用チュートリアル!避けゲーを作ってみよう

ラベル(文字)を表示してみよう

サンプル

コードの解説

変数UI_DATAに文字を描画するのに必要なデータを作っておきます。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};
var UI_DATA = {
    main: { // MainScene用ラベル
        children: [{
            type: "Label",
            name: "timeLabel",
            x: 200,
            y: 120,
            width: SCREEN_WIDTH,
            fillStyle: "white",
            // text: "残り時間を表示する",
            text: " ",
            fontSize: 40,
            align: "left"
        }]
    }
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    // シーンの切り替え
    app.replaceScene(TitleScene());

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // ラベル表示
        this.fromJSON(UI_DATA.main);

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});

文字の色や、フォントのサイズ、表示する場所など指定できますね。文字に関することを全てセッティングします。セッティングが終わったら、次の一行で画面に表示することができます。

            // ラベル表示
            this.fromJSON(UI_DATA.main);

文字以外にも、色々なものを表示することができますが、今回のゲームでは使わないので説明はしません。もし、色んな図形を表示してみたい!って方がいらっしゃれば、次の記事を参考にしてみてください。

 
『03:「ボタンや文字など色々表示してみよう」 – JavaScriptライブラリ「tmlib.js」でシンプルなタッチゲームを超簡単に作るチュートリアル』

Categories
game JavaScript tmlib.js Tutorial

02:「シーンを作ろう」 – tmlib.js入門用チュートリアル!避けゲーを作ってみよう

シーンを作ってみよう

サンプル

コードの解説

今回はtitleを表示させますので、titleが最初に表示されるようにmain.jsを作り変えましょう。シーンの切り替え処理を追加した後、シーンを切り替えた後のtitle画面の処理を追加します。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    // シーンの切り替え
    app.replaceScene(TitleScene());

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });
    },
});

新しくtitle用のシーンを追加しました。title画面を簡単に作成できるようにtmlib.jsが用意しているので、それを上書き(クラスの継承)を行なって実装します。これだけのコードの追加でゲームのタイトルを作ることができます。

もうちょっと詳しく解説

シーンの処理のコードについてもう少し踏み込んで解説していきます。

/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });
    },
});

tm.main関数とはまた少し違った形になっていますね。”TitleScene”とはクラスの名前です。tm.defineという関数を使ってクラスを作成します。
 
クラスの生成処理はJavaScript標準の機能ではなく、tmlib.jsがサポートしている機能です。JavaScriptではない言語でお馴染みのクラスが簡単に作れます。クラスという言葉に覚えが無い方は、またコレもおまじないだと思ってください。
 
title画面として必要な機能は、tm.app.TitleSceneクラスで用意されています。この機能を流用(継承)している感じです。以下のコードで流用しますよと明言しています。

        superClass : "tm.app.TitleScene",

このように、コードを流用することで複雑な処理も簡単に書けるようになっています。自分で作ったクラスも流用していけばこれから先も楽ができますね。
 
次はinit関数です。クラス内のinit関数は、このクラスが使われた際に最初に呼ばれる関数です。(コンストラクタとも言います)

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });
    },

クラスを使った際、最初に呼ばれる処理なので変数の初期化などに使います。また、this.superInit関数を呼び出すことで、流用元(継承元)であるtm.app.TitleSceneクラスのinit関数を呼び出せます。これで連鎖的に初期化の処理が呼べます。

複数のシーンを用意しよう

サンプル

コードの解説

タイトルを描画するシーンは作ったので、これからゲームの動作を作っていくシーン(MainScene)と、ゲームが終了した時のシーン(EndScene)を作っておきましょう。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    // シーンの切り替え
    app.replaceScene(TitleScene());

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },
});

EndSceneはゲームが終了したときのシーンですね。スコアの表示など行います。
 
EndSceneもtmlib.jsに専用のクラスが準備されています。Twitterへの投稿処理や、戻るボタン(Backボタン)が既に組み込まれています。いたせり付くせりですね。RESULT_PARAM変数のurlを変えると、Twitterでつぶやく時に表示するリンクなどを変えることができます。ハッシュなども変更できます。

複数のシーンを繋げよう

サンプル

コードの解説

作ったシーンをつなげてみます。繋げ方は、tm.main関数で使ったreplaceScene関数を使います。ハイライトしてる部分ですね。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;
var RESULT_PARAM = {
        score: 256,
        msg:      "【避けゲー制作チュートリアル】",
        hashtags: ["omatoro", "tmlibチュートリアル"],
        url:      "http://omatoro.github.io/tmlib.js_tutorial_avoidgame/",
        width:    SCREEN_WIDTH,
        height:   SCREEN_HEIGHT,
        related:  "tmlib.js Tutorial testcording",
};

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    // シーンの切り替え
    app.replaceScene(TitleScene());

    // tmlibの実行
    app.run();
});


/**
 * TitleScene
 */
tm.define("TitleScene", {
    superClass : "tm.app.TitleScene",

    init : function() {
        this.superInit({
            title :  "避けゲー制作チュートリアル",
            width :  SCREEN_WIDTH,
            height : SCREEN_HEIGHT
        });

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(MainScene());
        });
    },
});


/**
 * MainScene
 */
tm.define("MainScene", {
    superClass : "tm.app.Scene",

    init : function() {
        this.superInit();

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });
    },
});


/**
 * EndScene
 */
tm.define("EndScene", {
    superClass : "tm.app.ResultScene",

    init : function() {
        this.superInit(RESULT_PARAM);
    },

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        e.target.app.replaceScene(TitleScene());
    },
});

実行してみて、画面をタッチ(or クリック)してみてください。画面が切り替わるのが分かります。

もうちょっと詳しく解説

シーンを遷移させるために画面をタッチ(or クリック)した判定を作る必要がありますが、またこれもtmlib.jsで既に用意されています。スマートフォンのタッチとマウスのクリック同時に対応できるというスグレモノです。

        // 画面(シーンの描画箇所)をタッチした時の動作
        this.addEventListener("pointingend", function(e) {
            // シーンの遷移
            e.app.replaceScene(EndScene());
        });

addEventListener関数を使ってタッチイベントを登録します。”pointingend”とありますね? 言葉の通りタッチが終了した瞬間(指を離した瞬間)にこの処理が呼び出されます。じゃあ”どこ”をタッチした時の処理なのかというと、this.addEventListenerとなっていますのでthisをタッチした時の処理です。thisはこのクラスを指しているので、シーン全体(画面全体)が判定範囲となります。

 

EndSceneは画面全体ではなく、ボタンを押したときのみシーンを移動します。Backボタンを押したときの動作を作るときは次のように記述します。

    // Backボタンを押したらTitleSceneに戻る
    onnextscene: function (e) {
        // ここに処理を書く
    },

いかがでしたか?シーンの作成とシーンをつなぐ処理も簡単にできましたね。シーンの作成まではどのゲームにも利用できますので、ひな形として保存いておくと次に繋げやすくなるのでオススメです。

Categories
game JavaScript tmlib.js Tutorial

01:「最初の一歩、画面を表示しよう」 – tmlib.js入門用チュートリアル!避けゲーを作ってみよう

画面を表示しよう!

サンプル

コードの解説

まず最初に、実行するmain.jsと、HTMLファイルであるindex.htmlファイルを作ってから始まります。中に記述しているコードは次のようになってます。

<!DOCTYPE html>

<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, user-scalable=no" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

        <title>避けゲー制作チュートリアル</title>

        <!-- tmlib.js -->
        <script src="http://rawgithub.com/phi1618/tmlib.js/88b843853c9aa94d2a0a533478ed901264fa5bec/build/tmlib.js"></script>

        <!-- game -->
        <script src="js/main.js"></script>
    </head>
    <body>
        <canvas id="world"></canvas>
    </body>
</html>

HTMLでtmlib.jsのロードを行なっています。tmlib.jsを利用するにはcanvasを作っておかないといけません。準備はこれだけです。コレ以降、index.htmlは触りません。
 
次はmain.jsです。tmlib.jsを動かすJavaScriptのコードとなります。

/**
 * ゲーム用定数作成
 */
var SCREEN_WIDTH  = 960;
var SCREEN_HEIGHT = 640;

/**
 * ゲーム起動処理
 */
tm.main(function() {
    // アプリケーション作成
    var app = tm.app.CanvasApp("#world");
    app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面サイズに合わせる
    app.fitWindow(); // リサイズ対応
    app.background = "rgb(0, 0, 0)"; // 背景色をセット

    // tmlibの実行
    app.run();
});

tm.main関数からアプリケーションの処理を開始していきます。スクリーンサイズなどのアプリケーションの設定を行ってapp.run()を行うことで、アプリケーション実行開始です。画面を表示する儀式と思ってもらえたらと思います。

 
 

簡単に画面を表示することができました。

Categories
game JavaScript Node.js tmlib.js

node.js(socket.io) x tmlib.jsを使ってオンラインゲームを作ってみた!

node.js(socket.io) + tmlib.jsでオンラインゲームを制作中です。制作中なので、満足に動くかどうかもまだ分かっていない状態ですが、ひとまず公開しておこうと思います。今後処理を追加したり、アップデートしたらTwitterにて呟いていきます。

 

 

Categories
game JavaScript tmlib.js

JavaScriptライブラリ「tmlib.js」で作ったローグライクゲームを改良してみた – RoguePlus

前の記事で紹介した、「JavaScriptライブラリ「tmlib.js」でローグライクを作ってみた」を改良してみました。改良した部分は、見た目の変更が大半で、中のゲーム部分についてはほとんど変わっていません。前からゲームの公開自体はしてましたので、既に触って頂いてる方もおられるかもしれません。

 

Categories
JavaScript

私がenchant.jsではなくtmlib.jsでゲームを作る5つの理由

ゲーム制作に使えるJavaScriptライブラリの比較と、私がtmlib.js(ティーエムリブ ドット ジェーエス)を使うその理由についてです。JavaScriptを勉強後enchant.jsを触っていましたが、tmlib.jsにサクっと乗り換えた理由をまとめてみました。enchant.jsを使わずにわざわざ無名のtmlib.jsを使っているのか、その理由はすぐに分かると思います。ついでに私見ばかり言っても仕方ないので、機能面から見た比較もしています。