文字列の検索や置換をするときに便利な正規表現をまとめてみました。簡単なものから順に並べているので、実行しながら遊んでみてください。
文字列の検索や置換をするときに便利な正規表現をまとめてみました。簡単なものから順に並べているので、実行しながら遊んでみてください。
文字列の検索や置換をするときに便利な正規表現をまとめてみました。簡単なものから順に並べているので、実行しながら遊んでみてください。
上から落ちてくるモンスターを避ける、というシンプルな避けゲーを作るチュートリアルです。tmlib.jsでサクッと作っちゃいましょう。最終的に作るゲームは以下のサンプルになります。
node.jsを使ったサンプル集です。ゲーム制作でサンプル作ってたら溜まったので整理がてら公開します。node.jsをローカルで実行できる環境を作ってない方は、「初心者でも安心!?Node.jsをインストールするなら仮想サーバを使おう」を参考にしてみてください。また、socket.ioやmongoDBを利用する際は、都度インストールしてください。
※ タイトルを「node.js 怒濤の50サンプル!! – websocket編」から「node.js 怒濤の50サンプル!! – socket.io編」に変更
※ 厳密には異なるものとは知らず誤解を招くタイトルだったためです。申し訳ありません。
ローグライク風アクションオンラインゲームを制作しながら気づいたことやタメになった記事のまとめです。運用時の規模が10人から100人ほどの規模を想定しています。小規模オンラインゲームにおいて考慮しなければならない点をひと通りまとめてみました。
モンスターを表示して動かしてみます。プレイヤークラスと同じような要領でクラスを作成します。
/** * ゲーム用定数作成 */ 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) + "秒生き残ることができました。";
これで生き残った時間を表示できるようになりました。ゲームとしてはこれで完成ですがちょっとした味付けを次項でやってみます。
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;
「正確にジャイロセンサーが付いていたら、ジャイロセンサーを使った操作に切り替える」という処理ではないので気をつけてくださいね。
これでチュートリアルは終了です。お疲れ様でした!落ちゲーのゲームも色々処理を追加しようと思えばたくさん思いつくとおもいます。このチュートリアルでゲームを作り始めてくれたりするととても嬉しいです。
タイトル画面が表示される前に、ローディングの画面を挟んで画像をロードしてみます。
/** * ゲーム用定数作成 */ 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ファイルの形式になっています。画像名や画像のサイズ、アニメーション方法などをまとめて書いています。
変数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」でシンプルなタッチゲームを超簡単に作るチュートリアル』
今回は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) { // ここに処理を書く },
いかがでしたか?シーンの作成とシーンをつなぐ処理も簡単にできましたね。シーンの作成まではどのゲームにも利用できますので、ひな形として保存いておくと次に繋げやすくなるのでオススメです。