制限時間内に画面上のハートを全てタッチする、という超シンプルなゲームを作るチュートリアルです。タッチゲーって言うんでしょうか。tmlib.jsでサクッと作っちゃいましょう。最終的に作るゲームは以下のサンプルになります。
Month: May 2013
画面を表示しよう!
サンプル
コードの解説
まず最初に、実行する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> <script src="http://rawgithub.com/phi1618/tmlib.js/6ee7b4d17ea0c0edac203920fb80bb755fa1cf18/build/tmlib.js"></script> <script src="js/main.js"></script> </head> <body> <canvas id="world"></canvas> </body> </html>
HTMLでtmlib.jsのロードを行なっています。tmlib.jsを利用するにはcanvasを作っておかないといけません。準備はこれだけです。次は実際にtmlib.jsを動かすコードとなります。
/** * ゲーム起動処理 */ var game = game || {}; (function(ns) { tm.main(function() { // スクリーンサイズ ns.SCREEN_WIDTH = 640; ns.SCREEN_HEIGHT = 960; // アプリケーション作成 ns.app = tm.app.CanvasApp("#world"); ns.app.resize(ns.SCREEN_WIDTH, ns.SCREEN_HEIGHT); // 画面サイズに合わせる ns.app.fitWindow(); // リサイズ対応 ns.app.background = "rgb(0, 0, 0)"; // 背景色をセット // tmlibの実行 ns.app.run(); }); })(game);
tm.main関数からアプリケーションの処理を開始していきます。スクリーンサイズなどのアプリケーションの設定を行ってapp.run()を行うことで、アプリケーション実行開始です。画面を表示する儀式と思ってもらえたらと思います。
もうちょっと詳しく解説
var game = game || {}; (function(ns) { tm.main(function() { // スクリーンサイズ ns.SCREEN_WIDTH = 640; // 以下続く... }); })(game);
main.jsの記述ですが、人によると見慣れない書き方がいくつかあるかと思います。
var game = game || {};
これはネームスペースと言われるもので、このgameという変数の中に全てデータをいれこんでいきます。なのでグローバル領域を汚しません。
(function(ns) {})(game);
これも同じくネームスペースの処理ですね。関数の中に変数を閉じ込めています。よく分からない!という方はオマジナイだと思ってもらって差し支えはありません。
定数を別ファイルに分けよう
サンプル
コードの解説
ゲームを起動するために使った値は、今後ゲームを新しく作る際に作りやすいよう別ファイルで管理しましょう。新しくparam.jsというファイルを作るので、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/6ee7b4d17ea0c0edac203920fb80bb755fa1cf18/build/tmlib.js"></script> <!-- game --> <script src="js/param.js"></script> <script src="js/main.js"></script> </head> <body> <canvas id="world"></canvas> </body> </html>
新しくファイルを追加したので、読み込むJavaScriptのファイルが増えましたね。param.jsというファイルにmain.jsファイルの記述を移します。
/** * ゲーム用ネームスペース作成、定数作成 */ var game = game || {}; (function(ns) { // スクリーンサイズ ns.SCREEN_WIDTH = 640; ns.SCREEN_HEIGHT = 960; })(game);
/** * ゲーム起動処理 */ // var game = game || {}; (function(ns) { tm.main(function() { // // スクリーンサイズ // ns.SCREEN_WIDTH = 640; // ns.SCREEN_HEIGHT = 960; // アプリケーション作成 ns.app = tm.app.CanvasApp("#world"); ns.app.resize(ns.SCREEN_WIDTH, ns.SCREEN_HEIGHT); // 画面サイズに合わせる ns.app.fitWindow(); // リサイズ対応 ns.app.background = "rgb(0, 0, 0)"; // 背景色をセット // tmlibの実行 ns.app.run(); }); })(game);
main.jsファイルで記述していた変数を別ファイルに移しました。ゲームを作る上で必ず必要になるようなデータはparam.jsに記述するようにすれば、管理がしやすくなるので今のうちにやっておきます。
nsという変数に直接登録した変数は、nsを呼び出すことで変数を使うことができます。次のようにです。
ns.app.resize(ns.SCREEN_WIDTH, ns.SCREEN_HEIGHT); // 画面サイズに合わせる
param.jsファイルでスクリーンサイズに関する変数を登録しているので、main.jsファイルからでもnsを通して呼び出せるようになります。
シーンを作ってみよう
サンプル
コードの解説
新しくシーンを追加したら、最初に起動するシーンを決めます。今回はtitleを表示させますので、titleが最初に表示されるように作ってみます。
/** * ゲーム起動処理 */ (function(ns) { tm.main(function() { // アプリケーション作成 ns.app = tm.app.CanvasApp("#world"); ns.app.resize(ns.SCREEN_WIDTH, ns.SCREEN_HEIGHT); // 画面サイズに合わせる ns.app.fitWindow(); // リサイズ対応 ns.app.background = "rgb(0, 0, 0)"; // 背景色をセット // シーンの切り替え ns.app.replaceScene(ns.TitleScene()); // tmlibの実行 ns.app.run(); }); })(game);
シーンの切り替え処理を追加します。tm.main関数でアプリケーションを作成したら、シーンに移動してゲーム処理が開始されます。ではシーン処理も見てみましょう。
/** * TitleScene */ (function(ns) { ns.TitleScene = tm.createClass({ superClass : tm.app.TitleScene, init : function() { this.superInit({ title : "タッチゲーム制作チュートリアル", width : ns.SCREEN_WIDTH, height : ns.SCREEN_HEIGHT }); }, }); })(game);
新しくsceneディレクトリの下に作ったtitle用のシーンです。titleを簡単に作成できるようにtmlib.jsが用意しているので、それを上書き(クラスの継承)を行なって実装します。これだけのコードの追加でゲームのタイトルを作ることができます。
もうちょっと詳しく解説
シーンの処理のコードについてもう少し踏み込んで解説していきます。
/** * TitleScene */ (function(ns) { ns.TitleScene = tm.createClass({ superClass : tm.app.TitleScene, init : function() { this.superInit({ title : "タッチゲーム制作チュートリアル", width : ns.SCREEN_WIDTH, height : ns.SCREEN_HEIGHT }); }, }); })(game);
tm.main関数とはまた少し違った形になっていますね。ns.TitleSceneとはクラスの名前です。tm.createClassという関数を使ってクラスを作成します。
通常JavaScriptでは、関数に対してnewを使うことでクラスを作成します。しかしJavaScriptのnewは厄介者なので使いたくありません。そのため、クラスの生成はtmlib.jsがサポートしてくれています。クラスの継承など必要な機能を提供してくれますので、存分に使わせてもらっています。クラスという言葉に覚えが無い方は、またコレもおまじないだと思ってください。
title画面として必要な機能は、tm.app.TitleSceneクラスで用意されています。この機能を流用(継承)している感じです。以下のコードで流用しますよと明言しています。
superClass : tm.app.TitleScene,
このように、コードを流用することで複雑な処理も簡単に書けるようになっています。次はinit関数ですね。クラス内のinit関数は、このクラスが使われた際に最初に呼ばれる関数です。
init : function() { this.superInit({ title : "タッチゲーム制作チュートリアル", width : ns.SCREEN_WIDTH, height : ns.SCREEN_HEIGHT }); },
変数などの初期化に使います。また、this.superInit関数を呼び出すことで、流用元(継承元)であるtm.app.TitleSceneクラスのinit関数を呼び出せます。これで連鎖的に初期化の処理が呼べます。
複数のシーンを用意しよう
サンプル
コードの解説
タイトルを描画するシーンは作ったので、ゲームを実行するシーンと、ゲームが終了した時のシーンを作っておきましょう。
/** * MainScene */ (function(ns) { ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); }, }); })(game);
これからゲームの処理を組み込んでいくMainSceneクラスです。
/** * EndScene */ (function(ns) { var RESULT_PARAM = { score: 256, msg: "【タッチゲーム制作チュートリアル】", hashtags: ["omatoro", "tmlibチュートリアル"], url: "http://testcording.com", width: ns.SCREEN_WIDTH, height: ns.SCREEN_HEIGHT, related: "tmlib.js Tutorial testcording", }; ns.EndScene = tm.createClass({ superClass : tm.app.ResultScene, init : function() { // スコア this.superInit(RESULT_PARAM); }, }); })(game);
EndSceneはゲームが終了したときのシーンですね。スコアの表示など行います。
EndSceneもtmlib.jsに専用のクラスが準備されています。Twitterへの投稿処理や、戻るボタン(Backボタン)が既に組み込まれています。いたせり付くせりですね。RESULT_PARAM変数のurlを変えると、Twitterでつぶやく時に表示するリンクなどを変えることができます。ハッシュなども変更できます。
複数のシーンを繋げよう
サンプル
コードの解説
作ったシーンをつなげてみます。繋げ方は、tm.main関数で使ったreplaceScene関数を使います。ハイライトしてる部分ですね。
/** * TitleScene */ (function(ns) { ns.TitleScene = tm.createClass({ superClass : tm.app.TitleScene, init : function() { this.superInit({ title : "タッチゲーム制作チュートリアル", width : ns.SCREEN_WIDTH, height : ns.SCREEN_HEIGHT }); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.MainScene()); }); }, }); })(game);
/** * MainScene */ (function(ns) { ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.EndScene()); }); }, }); })(game);
/** * EndScene */ (function(ns) { var RESULT_PARAM = { score: 256, msg: "【タッチゲーム制作チュートリアル】", hashtags: ["omatoro", "tmlibチュートリアル"], url: "http://testcording.com", width: ns.SCREEN_WIDTH, height: ns.SCREEN_HEIGHT, related: "tmlib.js Tutorial testcording", }; ns.EndScene = tm.createClass({ superClass : tm.app.ResultScene, init : function() { // スコア this.superInit(RESULT_PARAM); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.TitleScene()); }); }, }); })(game);
実行してみて、画面をタッチ(or クリック)してみてください。画面が切り替わるのが分かります。
もうちょっと詳しく解説
シーンを遷移させるために画面をタッチ(or クリック)した判定を作る必要がありますが、またこれもtmlib.jsで既に用意されています。スマートフォンのタッチとマウスのクリック同時に対応できるというスグレモノです。
// 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.TitleScene()); });
addEventListener関数を使ってタッチイベントを登録します。”pointingend”とありますね? 言葉の通りタッチが終了した瞬間(指を離した瞬間)にこの処理が呼び出されます。じゃあ”どこ”をタッチした時の処理なのかというと、this.addEventListenerとなっていますのでthisをタッチした時の処理です。thisはこのクラスを指しているので、シーン全体(画面全体)が判定範囲となります。
ラベル(文字)を表示してみよう
サンプル
コードの解説
変数UI_DATAに文字を描画するのに必要なデータを作っておきます。
/** * TitleScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはTitleSceneです。", fontSize: 30, align: "left" }] } }; ns.TitleScene = tm.createClass({ superClass : tm.app.TitleScene, init : function() { this.superInit({ title : "タッチゲーム制作チュートリアル", width : ns.SCREEN_WIDTH, height : ns.SCREEN_HEIGHT }); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.MainScene()); }); }, }); })(game);
文字の色や、フォントのサイズ、表示する場所など指定できますね。文字に関することを全てセッティングします。セッティングしたデータが出来れば、次の一行で画面に表示することができます。
// ラベル表示 this.fromJSON(UI_DATA.LABELS);
ついでなので、現在どのシーンなのか分かるように、全てのシーンにラベルを表示しておきましょう。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" }] } }; ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.EndScene()); }); }, }); })(game);
/** * EndScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはEndSceneです。", fontSize: 30, align: "left" }] } }; var RESULT_PARAM = { score: 256, msg: "【タッチゲーム制作チュートリアル】", hashtags: ["omatoro", "tmlibチュートリアル"], url: "http://testcording.com", width: ns.SCREEN_WIDTH, height: ns.SCREEN_HEIGHT, related: "tmlib.js Tutorial testcording", }; ns.EndScene = tm.createClass({ superClass : tm.app.ResultScene, init : function() { // スコア this.superInit(RESULT_PARAM); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.TitleScene()); }); }, }); })(game);
ボタンを表示してみよう
サンプル
コードの解説
EndSceneで使われているボタンを作ってみましょう。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" }] } }; var BUTTON_SIZE = 128; var BUTTON_COLOR = "green"; var BUTTON_TEXT = "ボタンです"; var BUTTON_X = ns.SCREEN_WIDTH/2; var BUTTON_Y = ns.SCREEN_HEIGHT/2; ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.EndScene()); }); // ボタンを作る var button = tm.app.GlossyButton(BUTTON_SIZE, BUTTON_SIZE, BUTTON_COLOR, BUTTON_TEXT); // 表示位置 button.position.set(BUTTON_X, BUTTON_Y); // 表示する this.addChild(button); }, }); })(game);
GlossyButtonというクラスを使うと、シンプルで綺麗なボタンが簡単に作ることができます。しかもマウスを重ねたりタッチすると光ります。ボタンのサイズや色、中に書くテキストを指定することができます。
// ボタンを作る var button = tm.app.GlossyButton(BUTTON_SIZE, BUTTON_SIZE, BUTTON_COLOR, BUTTON_TEXT);
次のposition.setで表示箇所を指定します。
// 表示位置 button.position.set(BUTTON_X, BUTTON_Y);
実際に表示するには、このシーンの子として追加する必要があります。addChild()に追加すると自動で描画してくれると思ったらOKです。
// 表示する this.addChild(button);
ハートを表示してみよう
サンプル
コードの解説
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" }] } }; var HEART_SIZE = 128; var HEART_X = ns.SCREEN_WIDTH/2; var HEART_Y = ns.SCREEN_HEIGHT/2; ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.EndScene()); }); // ハートを作る var heart = tm.app.HeartShape(HEART_SIZE, HEART_SIZE); // 表示位置 heart.position.set(HEART_X, HEART_Y); // 表示する this.addChild(heart); }, }); })(game);
ボタンと同じ要領で、簡単にハートを書くことができます。このハートを使ってゲームを作っていってみましょう。
HeartShapeではなく、StarShapeやCircleShapeに名前を変えると星形や円形を簡単に表示できます。サンプルを載せておきますので、弄ってみてくださいね。
たくさんのハートを表示してみよう
サンプル
コードの解説
ループを使ってまとめてハートを作ってみます。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.EndScene()); }); // たくさんハートを作る for (var i = 0; i < HEART_NUM; ++i) { // ハートを作る var heart = tm.app.HeartShape(HEART_SIZE, HEART_SIZE); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // 表示する this.addChild(heart); } }, }); })(game);
ハートの位置は、画面の端から端までのランダムな場所です。Math.rand()という関数を使うことで、ランダムな数を取得することができます。一つ気をつけてほしい箇所があります。0からns.SCREEN_WIDTHまでのランダム値になっていないですね。感覚的には以下のようなコードになりそうです。
// 表示位置 heart.position.set( Math.rand(0, ns.SCREEN_WIDTH), Math.rand(0, ns.SCREEN_HEIGHT));
しかし、上記のコードだとハートが画面端に見切れて表示されてしまいます。それを防ぐために、ハートの半分サイズ座標を狭めてランダム値を取得しています。
ハートの色を変えてみよう
サンプル
コードの解説
ハートの色もランダムになるようにしてみましょう。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 this.addEventListener("pointingend", function(e) { // シーンの遷移 e.app.replaceScene(ns.EndScene()); }); // たくさんハートを作る for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // 表示する this.addChild(heart); } }, }); })(game);
RGBA値で完全にランダムな値にしてしまうと、真っ黒なハートやどす黒いハートばかり出てしまうのでHSLAという表記法を利用しています。HSLAについては特に解説はしませんので、別途調べてみてください。
ハートをタッチしたら消えるようにしよう
サンプル
コードの解説
ハートをタッチ(or クリック)した時の処理を追加してみましょう。処理を作るヒントは、「シーンをタッチした時の処理」です。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // 画面(シーンの描画箇所)をタッチした時の動作 // this.addEventListener("pointingend", function(e) { // // シーンの遷移 // e.app.replaceScene(ns.EndScene()); // }); // たくさんハートを作る for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // ハートをタッチした時の処理を作る heart.interaction.enabled = true; // ヒット判定フラグをON heart.interaction.boundingType = "circle"; // 円形のヒット判定を行う heart.addEventListener("pointingend", function() { // 消す this.remove(); }); // 表示する this.addChild(heart); } }, }); })(game);
シーンをタッチした時の処理と同じく、addEventListener()関数を使います。違うのは、addEventListenerの左側に書いてある変数が、thisではなく、heartであることです。
heart.addEventListener("pointingend", function() { // 消す this.remove(); });
こうすることで、heartをタッチした時の処理を書くことができます。ハートを消すときは、remove()関数を使います。タッチしたときにremove()関数を読んであげると、タッチしたときにハートが消えるようになります。
制限時間を作ろう
サンプル
コードの解説
今回は追加する処理が多いですが、めげないでくださいね!
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" },{ type: "Label", name: "limitTimeLabel", x: 200, y: 120, width: ns.SCREEN_WIDTH, fillStyle: "white", text: " ", fontSize: 40, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; var LIMIT_TIME = 300; // 10秒 x 30フレーム = 300 ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // たくさんハートを作る for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // ハートをタッチした時の処理を作る heart.interaction.enabled = true; // ヒット判定フラグをON heart.interaction.boundingType = "circle"; // 円形のヒット判定を行う heart.addEventListener("pointingend", function() { // 消す this.remove(); }); // 表示する this.addChild(heart); } // 制限時間 this.limitTime = LIMIT_TIME; }, update: function (app) { // カウントダウンを行う --this.limitTime; // 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0); // 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { app.replaceScene(ns.EndScene()); } }, }); })(game);
複数の処理を追加したので、一つ一つ見ていきます。まず最初に、制限時間を表示するためのラベルを追加しました。以下のコードです。
// ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" },{ type: "Label", name: "limitTimeLabel", x: 200, y: 120, width: ns.SCREEN_WIDTH, fillStyle: "white", text: " ", fontSize: 40, align: "left" }] } };
次に、制限時間を設定するための変数が増えました。
var LIMIT_TIME = 300; // 10秒 x 30フレーム = 300
ゲームの処理は一秒間に30回行われるので、それを見越して10秒を制限時間に設定したい場合は300となります。この300という値をゲームの実行毎にマイナス1していくことで、10秒経ったのかどうかが分かるようにします。
次に、update()という関数が増えました。update()関数は、ゲームの処理毎に呼ばれる関数です。そのため、一秒間に30回呼ばれることになります。
update: function (app) { // カウントダウンを行う --this.limitTime; // 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0); // 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { app.replaceScene(ns.EndScene()); } },
カウントダウンを行なって、0になれば勝手にEndSceneに遷移するように処理を作ります。画面に表示する制限時間の計算方法も見ておきます。
// 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0);
this.limitTime変数は秒ではありません。ややこしいですが、一秒間に30回処理が行われる上で成り立っている300という数値です。30で割ることで現在の残り時間を取得することができます。”|0″というのはJavaScriptのテクニックの一つで、値を整数にします。割り算してるので小数点以下の数が出てしまいますが、”|0″のテクニックを使うことで整数になります。
全てハートを消したらゲームが終わるようにしよう
サンプル
コードの解説
ハートの数が今いくつなのか分かるように、「グループ」を作って管理するように変更しました。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" },{ type: "Label", name: "limitTimeLabel", x: 200, y: 120, width: ns.SCREEN_WIDTH, fillStyle: "white", text: " ", fontSize: 40, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; var LIMIT_TIME = 300; // 10秒 x 30フレーム = 300 ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // たくさんハートを作る this.heartGroup = tm.app.CanvasElement(); for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // ハートをタッチした時の処理を作る heart.interaction.enabled = true; // ヒット判定フラグをON heart.interaction.boundingType = "circle"; // 円形のヒット判定を行う heart.addEventListener("pointingend", function() { // 消す this.remove(); }); // 表示する this.heartGroup.addChild(heart); } this.addChild(this.heartGroup); // 制限時間 this.limitTime = LIMIT_TIME; }, update: function (app) { // カウントダウンを行う --this.limitTime; // 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0); // 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { app.replaceScene(ns.EndScene()); } // ハートが全部なくなったらEndSceneに遷移する if (this.heartGroup.children.length <= 0) { app.replaceScene(ns.EndScene()); } }, }); })(game);
ハイライトの部分をみてみると、this.addChild()としてシーンに追加するのはグループのみになってますね。そのグループに対してheartをaddChild()するので、シーンから見るとheartは子でなく孫になります。一つグループをかませることにより、heartの数を簡単に取得することができます。
// ハートが全部なくなったらEndSceneに遷移する if (this.heartGroup.children.length <= 0) { app.replaceScene(ns.EndScene()); }
これで、EndSceneに遷移する条件は「ハートが全部無くなる」か、「制限時間が無くなる」場合の2つとなりました。
スコアを表示しよう
サンプル
コードの解説
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" },{ type: "Label", name: "limitTimeLabel", x: 200, y: 120, width: ns.SCREEN_WIDTH, fillStyle: "white", text: " ", fontSize: 40, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; var LIMIT_TIME = 300; // 10秒 x 30フレーム = 300 ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // たくさんハートを作る this.heartGroup = tm.app.CanvasElement(); for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // ハートをタッチした時の処理を作る heart.interaction.enabled = true; // ヒット判定フラグをON heart.interaction.boundingType = "circle"; // 円形のヒット判定を行う heart.addEventListener("pointingend", function() { // 消す this.remove(); }); // 表示する this.heartGroup.addChild(heart); } this.addChild(this.heartGroup); // 制限時間 this.limitTime = LIMIT_TIME; }, update: function (app) { // カウントダウンを行う --this.limitTime; // 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0); // 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { app.replaceScene(ns.EndScene(0)); } // ハートが全部なくなったらEndSceneに遷移する if (this.heartGroup.children.length <= 0) { app.replaceScene(ns.EndScene(this.limitTime)); } }, }); })(game);
EndSceneに現在残っているリミット時間を渡すようにしています。そして、EndSceneに渡したデータをどうするのかと言うと……。
/** * EndScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはEndSceneです。", fontSize: 30, align: "left" }] } }; var RESULT_PARAM = { score: 256, msg: "【タッチゲーム制作チュートリアル】", hashtags: ["omatoro", "tmlibチュートリアル"], url: "http://testcording.com", width: ns.SCREEN_WIDTH, height: ns.SCREEN_HEIGHT, related: "tmlib.js Tutorial testcording", }; ns.EndScene = tm.createClass({ superClass : tm.app.ResultScene, init : function(leftTime) { // スコア計算 RESULT_PARAM.score = (leftTime * 100) |0; // スコア this.superInit(RESULT_PARAM); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // // 画面(シーンの描画箇所)をタッチした時の動作 // this.addEventListener("pointingend", function(e) { // // シーンの遷移 // e.app.replaceScene(ns.TitleScene()); // }); }, // Backボタンを押したらTitleSceneに戻る onnextscene: function (e) { e.target.app.replaceScene(ns.TitleScene()); }, }); })(game);
init関数に引数を記述しています。こうすると、クラスを呼び出した時にinit関数がデータを受け取ることができます。で、受け取ったデータはそのままスコアの計算に使っています。
// スコア計算 RESULT_PARAM.score = (leftTime * 100) |0;
これでスコアを作ることができました。ゲームの処理はこれで出来上がりです!お疲れ様でした!!次はゲームを彩ってくれる”音”を入れてみたいと思います。
もうちょっと詳しく解説
よく見ると……
// Backボタンを押したらTitleSceneに戻る onnextscene: function (e) { e.target.app.replaceScene(ns.TitleScene()); },
この処理も新たに追加していますね。これはコメントの通り、Backボタンを押した時の動作を書くための処理です。今まで画面をタッチしたらTitleSceneに戻っていましたが、Backボタンを押した時のみTitleSceneに行くよう変更しています。
BGMを鳴らしてみよう
サンプル
コードの解説
まず、音楽を入れるディレクトリを作っておきます。jsフォルダと同階層に「rsc」という名前のディレクトリを用意しています。音のファイルも用意しています。では音を読み込んでみましょう。音を読み込む用のコードも別のファイルにしておきます。名前はresource.jsです。
/** * リソースの読み込み */ tm.preload(function() { // 音 tm.sound.SoundManager.add("bgm", "http://rawgithub.com/omatoro/tmlib.js_tutorial_TouchGame/master/EX_%E9%9F%B3%E3%82%92%E9%B3%B4%E3%82%89%E3%81%9D%E3%81%86/01_BGM/rsc/Comical01_Koya.mp3"); });
新しくresource.jsというファイルを用意しました。tm.preload()関数内に読み込みたいファイルを記述します。次は読み込んだデータの呼び出し方です。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" },{ type: "Label", name: "limitTimeLabel", x: 200, y: 120, width: ns.SCREEN_WIDTH, fillStyle: "white", text: " ", fontSize: 40, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; var LIMIT_TIME = 300; // 10秒 x 30フレーム = 300 ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // たくさんハートを作る this.heartGroup = tm.app.CanvasElement(); for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // ハートをタッチした時の処理を作る heart.interaction.enabled = true; // ヒット判定フラグをON heart.interaction.boundingType = "circle"; // 円形のヒット判定を行う heart.addEventListener("pointingend", function() { // 消す this.remove(); }); // 表示する this.heartGroup.addChild(heart); } this.addChild(this.heartGroup); // 制限時間 this.limitTime = LIMIT_TIME; // BGM再生 this.bgm = tm.sound.SoundManager.get("bgm"); this.bgm.loop = true; this.bgm.play(); }, update: function (app) { // カウントダウンを行う --this.limitTime; // 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0); // 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { this.bgm.stop(); app.replaceScene(ns.EndScene(0)); } // ハートが全部なくなったらEndSceneに遷移する if (this.heartGroup.children.length <= 0) { this.bgm.stop(); app.replaceScene(ns.EndScene(this.limitTime)); } }, }); })(game);
tm.sound.SoundManager.get()関数を使うことで、読み込んだデータを取り出すことができます。取り出したデータをthis.bgm変数に入れ込み、ループして鳴るように設定しています。play()関数を呼べば音がなります。音を出すにはplay()関数を呼ぶだけです。簡単ですね。EndSceneに遷移する前に音を止めています。
// 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { this.bgm.stop(); app.replaceScene(ns.EndScene(0)); } // ハートが全部なくなったらEndSceneに遷移する if (this.heartGroup.children.length <= 0) { this.bgm.stop(); app.replaceScene(ns.EndScene(this.limitTime)); }
EndSceneではまた別の音を鳴らしたい!と思ったら、同じように追加してみてくださいね。
SE(サウンドエフェクト)を鳴らしてみよう
サンプル
コードの解説
SE用の音楽データを追加し、resource.jsファイルを書き換えます。
/** * リソースの読み込み */ tm.preload(function() { // 音 tm.sound.SoundManager.add("bgm", "http://rawgithub.com/omatoro/tmlib.js_tutorial_TouchGame/master/EX_%E9%9F%B3%E3%82%92%E9%B3%B4%E3%82%89%E3%81%9D%E3%81%86/02_SE/rsc/Comical01_Koya.mp3"); tm.sound.SoundManager.add("se", "http://rawgithub.com/omatoro/tmlib.js_tutorial_TouchGame/master/EX_%E9%9F%B3%E3%82%92%E9%B3%B4%E3%82%89%E3%81%9D%E3%81%86/02_SE/rsc/%5BSystem%5DCursor03_panop.ogg"); });
次は、ハートを消した時に音がなるようにMainSceneを修正してみましょう。
/** * MainScene */ (function(ns) { // ラベルのリスト var UI_DATA = { LABELS: { children: [{ type: "Label", name: "label", x: 40, y: 80, width: ns.SCREEN_WIDTH, fillStyle: "red", text: "ここはMainSceneです。", fontSize: 30, align: "left" },{ type: "Label", name: "limitTimeLabel", x: 200, y: 120, width: ns.SCREEN_WIDTH, fillStyle: "white", text: " ", fontSize: 40, align: "left" }] } }; var HEART_SIZE = 64; var HEART_NUM = 10; var HEART_PARAM = { fillStyle: "pink", }; var LIMIT_TIME = 300; // 10秒 x 30フレーム = 300 ns.MainScene = tm.createClass({ superClass : tm.app.Scene, init : function() { this.superInit(); // ラベル表示 this.fromJSON(UI_DATA.LABELS); // たくさんハートを作る this.heartGroup = tm.app.CanvasElement(); for (var i = 0; i < HEART_NUM; ++i) { // 色をつくる HEART_PARAM.fillStyle = tm.graphics.Color.createStyleHSLA(Math.rand(0, 360), 95, 75, 0.8); // ハートを作る var heart = tm.app.HeartShape( HEART_SIZE, HEART_SIZE, HEART_PARAM); // 表示位置 heart.position.set( Math.rand(HEART_SIZE/2, ns.SCREEN_WIDTH - HEART_SIZE/2), Math.rand(HEART_SIZE/2, ns.SCREEN_HEIGHT - HEART_SIZE/2)); // ハートをタッチした時の処理を作る heart.interaction.enabled = true; // ヒット判定フラグをON heart.interaction.boundingType = "circle"; // 円形のヒット判定を行う heart.addEventListener("pointingend", function() { // 音を出す tm.sound.SoundManager.get("se").clone().play(); // 消す this.remove(); }); // 表示する this.heartGroup.addChild(heart); } this.addChild(this.heartGroup); // 制限時間 this.limitTime = LIMIT_TIME; // BGM再生 this.bgm = tm.sound.SoundManager.get("bgm"); this.bgm.loop = true; this.bgm.play(); }, update: function (app) { // カウントダウンを行う --this.limitTime; // 制限時間を表示する this.limitTimeLabel.text = "残り時間 : " + ((this.limitTime / 30) |0); // 制限時間がなくなったらEndSceneに遷移する if (this.limitTime <= 0) { this.bgm.stop(); app.replaceScene(ns.EndScene(0)); } // ハートが全部なくなったらEndSceneに遷移する if (this.heartGroup.children.length <= 0) { this.bgm.stop(); app.replaceScene(ns.EndScene(this.limitTime)); } }, }); })(game);
これで音が鳴るようになりました。本当はもっと大変なんですけど、tmlib.jsが準備してくれているおかげですね。
GithubからJavaScriptを読み込み時は専用のドメインから!
chromeで以下のようなエラーが出た場合の解決方法がいまいち調べても分かりませんでした。
Refused to execute script from 'https://raw.github.com/mrdoob/stats.js/master/src/Stats.js' because its MIME type ('text/plain') is not executable, and strict MIME type checking is enabled.
ブラウザがデータをロードする際に脆弱性があるそうで、セキュリティ重視のため弾いてるようです。
以下のようにリンクを記述すると解決します。
<script src="http://rawgithub.com/mrdoob/stats.js/master/src/Stats.js"></script>
追記:現在上記の書き方は、テスト用やデモ用でサクッとやりたいときだけにしてくれとのことです。
CDNとして使いたいときは以下の書き方が推奨されています。
<script src="https://cdn.rawgit.com/mrdoob/stats.js/master/src/Stats.js"></script>
githubは、生データへアクセスするためのドメインを用意したみたいで、こっちからアクセスすればエラーが収まります。
無料のサービスのようです。githubとは関連はないそうでした。
Is RawGit associated with GitHub? Nope, RawGit is not associated with GitHub, Inc. in any way.
画像などのデータも同じエラーが起きることがあるようで、同じ方法で解決できます。
情報が無かったため、簡単なメモ書きですが参考になればと思います。