モンスターを表示しよう
サンプル
コードの解説
モンスターを表示して動かしてみます。プレイヤークラスと同じような要領でクラスを作成します。
/** * ゲーム用定数作成 */ 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) + "秒生き残ることができました。";
これで生き残った時間を表示できるようになりました。ゲームとしてはこれで完成ですがちょっとした味付けを次項でやってみます。