今回はタイトル画面とリザルト画面を実装してきます.
tmlib.js のシーンの切り替え機能を上手く使えば簡単に実装することができます.
Table of contents
チュートリアル目次
前半
後半
サンプル
今回作るサンプルです.
タイトル画面から始まってタッチすると ゲームが開始されるのがわかるかと思います.
更に, ゲームを終了するとリザルト画面に遷移し ツイートできるようになっています.
ソースコード
main.js
tm.app.Scene
を継承して TitleScene
, ResultScene
を定義しています.
/* * main.js */ /* * contant */ var SCREEN_WIDTH = 680; // スクリーン幅 var SCREEN_HEIGHT = 960; // スクリーン高さ var SCREEN_CENTER_X = SCREEN_WIDTH/2; // スクリーン幅の半分 var SCREEN_CENTER_Y = SCREEN_HEIGHT/2; // スクリーン高さの半分 var PIECE_NUM_X = 5; // ピースの列数 var PIECE_NUM_Y = 5; // ピースの行数 var PIECE_NUM = PIECE_NUM_X*PIECE_NUM_Y; // ピース数 var PIECE_OFFSET_X = 90; // ピースオフセットX var PIECE_OFFSET_Y = 240; // ピースオフセットY var PIECE_WIDTH = 120; // ピースの幅 var PIECE_HEIGHT = 120; // ピースの高さ var FONT_FAMILY_FLAT= "'Helvetica-Light' 'Meiryo' sans-serif"; // フラットデザイン用フォント /* * main */ tm.main(function() { // アプリケーションセットアップ var app = tm.app.CanvasApp("#world"); // 生成 app.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // サイズ(解像度)設定 app.fitWindow(); // 自動フィッティング有効 app.background = "rgba(250, 250, 250, 1.0)";// 背景色 app.replaceScene( TitleScene() ); // シーン切り替え // 実行 app.run(); }); /* * ゲームシーン */ tm.define("GameScene", { superClass: "tm.app.Scene", init: function() { this.superInit(); var self = this; // カレント数 self.currentNumber = 1; // ピースグループ this.pieceGroup = tm.app.CanvasElement(); this.addChild(this.pieceGroup); // 数字配列 var nums = [].range(1, PIECE_NUM+1); // 1~25 nums.shuffle(); // シャッフル // ピースを作成 for (var i=0; i<PIECE_NUM_Y; ++i) { for (var j=0; j<PIECE_NUM_X; ++j) { // 数値 var number = nums[ i*PIECE_NUM_X+j ]; // ピースを生成してピースグループに追加 var piece = Piece(number).addChildTo(this.pieceGroup); // 座標を設定 piece.x = j * 125 + PIECE_OFFSET_X; piece.y = i * 125 + PIECE_OFFSET_Y; // タッチ時のイベントリスナーを登録 piece.onpointingstart = function() { // 正解かどうかの判定 if (this.number === self.currentNumber) { // クリアかどうかの判定 if (self.currentNumber === PIECE_NUM) { // リザルト画面に遷移 self.app.replaceScene(ResultScene({ time: self.timerLabel.text, })); } self.currentNumber += 1;// インクリメント this.disable(); // ボタン無効 } }; } } // タイマーラベル this.timerLabel = tm.app.Label("").addChildTo(this); this.timerLabel .setPosition(650, 160) .setFillStyle("#444") .setAlign("right") .setBaseline("bottom") .setFontFamily(FONT_FAMILY_FLAT) .setFontSize(128); // タイトルボタン var titleBtn = tm.app.FlatButton({ width: 300, height: 100, text: "TITLE", bgColor: "#888", }).addChildTo(this); titleBtn.position.set(180, 880); titleBtn.onpointingend = function() { self.app.replaceScene(TitleScene()); }; // リスタートボタン var restartBtn = tm.app.FlatButton({ width: 300, height: 100, text: "RESTART", bgColor: "#888", }).addChildTo(this); restartBtn.position.set(500, 880); restartBtn.onpointingend = function() { self.app.replaceScene(GameScene()); }; }, onenter: function(e) { e.app.pushScene(CountdownScene()); this.onenter = null; }, update: function(app) { // タイマー更新 var time = ((app.frame/app.fps)*1000)|0; var timeStr = time + ""; this.timerLabel.text = timeStr.replace(/(\d)(?=(\d\d\d)+$)/g, "$1."); } }); /* * ピースクラス */ tm.define("Piece", { superClass: "tm.app.Shape", init: function(number) { this.superInit(PIECE_WIDTH, PIECE_HEIGHT); // 数値をセット this.number = number; this.setInteractive(true); this.setBoundingType("rect"); var angle = tm.util.Random.randint(0, 360); this.canvas.clearColor("hsl({0}, 80%, 70%)".format(angle)); this.label = tm.app.Label(number).addChildTo(this); this.label .setFontSize(70) .setFontFamily(FONT_FAMILY_FLAT) .setAlign("center") .setBaseline("middle"); }, disable: function() { this.setInteractive(false); var self = this; this.tweener .clear() .to({scaleX:0}, 100) .call(function() { self.canvas.clearColor("rgb(100, 100, 100)"); }.bind(this)) .to({scaleX:1, alpha:0.5}, 100) } }); tm.define("CountdownScene", { superClass: "tm.app.Scene", init: function() { this.superInit(); var self = this; var filter = tm.app.Shape(SCREEN_WIDTH, SCREEN_HEIGHT).addChildTo(this); filter.origin.set(0, 0); filter.canvas.clearColor("rgba(250, 250, 250, 1.0)"); var label = tm.app.Label(3).addChildTo(this); label .setPosition(SCREEN_CENTER_X, SCREEN_CENTER_Y) .setFillStyle("#888") .setFontFamily(FONT_FAMILY_FLAT) .setFontSize(512) .setAlign("center") .setBaseline("middle"); label.tweener .set({ scaleX: 0.5, scaleY: 0.5, text: 3 }) .scale(1) .set({ scaleX: 0.5, scaleY: 0.5, text: 2 }) .scale(1) .set({ scaleX: 0.5, scaleY: 0.5, text: 1 }) .scale(1) .call(function() { self.app.frame = 0; self.app.popScene(); }); }, }); tm.define("TitleScene", { superClass: "tm.app.Scene", init: function() { this.superInit(); this.fromJSON({ children: [ { type: "Label", name: "titleLabel", text: "FlaTM Touch", x: SCREEN_CENTER_X, y: 200, fillStyle: "#444", fontSize: 60, fontFamily: FONT_FAMILY_FLAT, align: "center", baseline: "middle", }, { type: "Label", name: "nextLabel", text: "TOUCH START", x: SCREEN_CENTER_X, y: 650, fillStyle: "#444", fontSize: 26, fontFamily: FONT_FAMILY_FLAT, align: "center", baseline: "middle", } ] }); this.nextLabel.tweener .fadeOut(500) .fadeIn(1000) .setLoop(true); }, onpointingstart: function() { this.app.replaceScene(GameScene()); }, }); tm.define("ResultScene", { superClass: "tm.app.Scene", init: function(param) { this.superInit(); this.fromJSON({ children: [ { type: "Label", name: "timeLabel", x: SCREEN_CENTER_X, y: 320, fillStyle: "#888", fontSize: 128, fontFamily: FONT_FAMILY_FLAT, text: "99.999", align: "center", }, { type: "FlatButton", name: "tweetButton", init: [ { text: "TWEET", bgColor: "hsl(240, 80%, 70%)", } ], x: SCREEN_CENTER_X-160, y: 650, }, { type: "FlatButton", name: "backButton", init: [ { text: "BACK", bgColor: "hsl(240, 0%, 70%)", } ], x: SCREEN_CENTER_X+160, y: 650, }, ] }); this.timeLabel.text = param.time; var self = this; // tweet ボタン this.tweetButton.onclick = function() { var twitterURL = tm.social.Twitter.createURL({ type : "tweet", text : "tmlib.js チュートリアルゲームです. Time: {time}".format(param), hashtags: "tmlib,javascript,game", url : "http://tmlife.net/?p=9781", // or window.document.location.href }); window.open(twitterURL); }; // back ボタン this.backButton.onpointingstart = function() { self.app.replaceScene(TitleScene()); }; }, });
解説
fromJSON を使ってサクッとUIを作ろう
fromJSON
は json データから自身のプロパティや子要素の生成を行うことができる
超便利関数です.
this.fromJSON({ children: [ { type: "Label", name: "titleLabel", text: "FlaTM Touch", x: SCREEN_CENTER_X, y: 200, fillStyle: "#444", fontSize: 60, fontFamily: FONT_FAMILY_FLAT, align: "center", baseline: "middle", }, { type: "Label", name: "nextLabel", text: "TOUCH START", x: SCREEN_CENTER_X, y: 650, fillStyle: "#444", fontSize: 26, fontFamily: FONT_FAMILY_FLAT, align: "center", baseline: "middle", } ] });
children
で指定している情報を持った子要素を生成しています.
こうやってデータ化しておくことで後々の調整が非常に楽になります.
Tweet ボタンを設置しよう
tmlib.js には tm.social.Twitter
というオブジェクトがあり
このオブジェクトに定義されてる createURL()
で簡単に tweet 用 URL を作成することができます.
// tweet ボタン this.tweetButton.onclick = function() { var twitterURL = tm.social.Twitter.createURL({ type : "tweet", text : "tmlib.js チュートリアルゲームです. Time: {time}".format(param), hashtags: "tmlib,javascript,game", url : "http://tmlife.net/?p=9781", // or window.document.location.href }); window.open(twitterURL); };
createURL
の引数はオブジェクトで対応しているプロパティは下記になります.
name | description | example |
---|---|---|
type | タイプ(tweet/retweet/favorite/user) | “tweet” |
tweet_id | 対象となる Tweet | “210219483959263232” |
in_reply_to | 返信する対象となる Tweet | “210219483959263232” |
text | テキスト | “Test” |
screen_name | スクリーンネーム | “phi_jp” |
hashtags | ハッシュタグ | “javascript,tmlibjs” |
url | url | “http://tmlife.net” |
via | ~から | “phi_jp” |
related | 関連ワード | “tmlib.js tmlife” |
ここで生成された url
を window.open
に渡すことで
ツイート画面を開くことができます.
window.open(twitterURL);
シーン遷移させよう
TitleScene
-> GameScene
-> ResultScene
という流れで
遷移させます.
まずは最初の起動シーンを GameScene
から TitleScene
に変更します.
app.replaceScene( TitleScene() ); // シーン切り替え
これでタイトル画面から起動するようになります.
次に GameScene
でゲームが終わった際の処理を修正します.
今までは alert
で結果を表示していましたが,
replaceScene
を使って ResultScene
を呼ぶようにします.
// リザルト画面に遷移 self.app.replaceScene(ResultScene({ time: self.timerLabel.text, }));
これでシーンが繋がって一連のシーケンスを実装することができました. 実際に遊んでみてください♪
まとめ
おさえておいて欲しいポイント
tm.social.Twitter
のcreateURL()
で簡単に Twitter URL を生成することができるapp
のreplaceScene
を使うことでシーンをつなげることができる
どうですか? もう完全にゲームですね:D
ここまでで詰まったりうまく動かないなどありましたら 気軽に @phi_jpまでご連絡ください.
次回で最後です. BGM や SE を組み込んでゲームを華やかにしましょう♪