WordPress へようこそ。こちらは最初の投稿です。編集または削除し、コンテンツ作成を始めてください。
JavaScript正規表現 怒濤の100サンプル!!
文字列の検索や置換をするときに便利な正規表現をまとめてみました。簡単なものから順に並べているので、実行しながら遊んでみてください。
JavaScript正規表現 怒濤の100サンプル!!
文字列の検索や置換をするときに便利な正規表現をまとめてみました。簡単なものから順に並べているので、実行しながら遊んでみてください。
tmlib.js入門用チュートリアル!避けゲーを作ってみよう
上から落ちてくるモンスターを避ける、というシンプルな避けゲーを作るチュートリアルです。tmlib.jsでサクッと作っちゃいましょう。最終的に作るゲームは以下のサンプルになります。
「分かりやすい日本語を書くための」なんて文章家の方から怒られそうで大それた記事ですが、私自身はまだ分かりやすい文章を書けたためしはありません。習得中です。取り敢えずド素人の私が「こんな感じのことを考えて書けばいいのか」くらいの指針を持てた書籍を紹介します。
※ Amazonのレビュー数が多いもの、評価が高いものを適当にポチって何冊か読んだ結果です。
※ 他に良書と言われる書籍をご存知の方がいらっしゃれば、Twitterで教えてくれると嬉しいです。
※ アフィ記事なのでご注意を。
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) + "秒生き残ることができました。";
これで生き残った時間を表示できるようになりました。ゲームとしてはこれで完成ですがちょっとした味付けを次項でやってみます。
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;
「正確にジャイロセンサーが付いていたら、ジャイロセンサーを使った操作に切り替える」という処理ではないので気をつけてくださいね。
これでチュートリアルは終了です。お疲れ様でした!落ちゲーのゲームも色々処理を追加しようと思えばたくさん思いつくとおもいます。このチュートリアルでゲームを作り始めてくれたりするととても嬉しいです。