phina.js が面白そうなのでお勉強中　その１１

[phina.js が面白そうなのでお勉強中　その１０](https://qiita.com/hirotyankoutei/items/15da2c537e67d9a42ff2)
# タイトル画面を作る

前回 GAME OVER を作ったけど、戻るタイトル画面が無いのでタイトル画面を作ります。

やりたいことは以下です。
・タイトルの文字を表示して、文字がビカビカ光ったようにする。
・タイトルの背景はゲームの星を流用して表示する。
・Zキーを押したらゲーム開始。
・ゲームオーバー後、タイトル画面に戻ってくる。
・タイトルにはスコアとハイスコアが表示される。


## どうすれば実現可能か調べてみる
phina.js のお約束事で、ゲーム部分は DisplayScene を継承した MainScene を作れば、GameApp 生成時に startLabel: 'main' を指定すれば MainScene のインスタンスが生成されてゲームが動きます。
ということは、タイトル用に DisplayScene を継承した シーンを作ればいいのかな？
そもそも、なんで MainScene ってクラスが 'main' と言うラベルで呼び出せるのか？
そういえば、この辺をよく考えてみたことなかったな。
ってわけで調べてみました。

ここに説明がありました。
[[phina.js-Tips] デフォルトで用意されてるSceneについて知る](https://qiita.com/alkn203/items/c8ad8ad6c2a21fb3b0f7)
phina.js にはデフォルトで SplashScene、TitleScene、MainScene、ResultScene というシーンが設定されているらしい。
よく phina.js のサンプルで、同じような結果画面が表示されるのはそういうことか。

で、[[phina.js-Tips] 独自のSceneを作って遷移させる](https://qiita.com/alkn203/items/56bf4f5fd96de6e76c08) の内容が、今回やってみたいことです。
簡単に書くと、DisplayScene を継承した Scene を作っておき、Scene のリストを GameApp の scenesプロパティに渡せば良いらしい。その時に Scene のラベルを設定し、そのラベルで Scene を指定するっぽい。
これで、デフォルトシーンには関係なく、自分の好きなようにシーンを移動できるというわけか。

[[phina.js-Tips] Scene遷移で値を渡す](https://qiita.com/alkn203/items/744750353dbccc3c3e39) を見ると、シーン間で値の受け渡しができるそうなので、例えばメイン部分でのスコアをタイトル側で表示したりできるなあ。

良く解らなかったのは、新しいシーンを呼び出した際に、元のシーンはどうなってしまうのか、ということ。
タイトルシーン ⇒ メインシーン ⇒ タイトルシーン と移動した時に、2回目のシーンは1回目のシーンの情報が残っているのか？タイトルでハイスコアを表示したい場合、ハイスコアは2回目の表示で残っているのだろうか？

ちょっと実験した感じだと、少なくとも シーン移動時には毎回 init 処理が呼ばれているっぽいので、少なくとも init で変数やプロパティを初期化していると、毎回初期化されてしまうっぽい。

## TitleScene を作ってみる
まずは、表示したいタイトル画像を作ります。
ってそもそも、今作っているゲームのタイトルってなんだ？タイトル考えずに作ってきてたのかオレは！
とりあえず、「THE Shooting」にしました。ダイソー感漂うネーミングだな。
GIMPで以下の画像を作りました。
![title.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/45bce9de-9bd0-54ca-2a27-2dfdd5d839e9.gif)
全体を黒くして、文字部分を透明にしています。

作成した画像をアセットに追加します。

```javascript
// アセット
var ASSETS = {
	// 画像
	image: {
		'ship': 'ship.gif',
		'enemy': 'enemy.gif',
		'bullet': 'bullet.gif',
		'explosion': 'EXPLOSION.gif',
		'gameover' : 'GAMEOVER.gif',
		'title' : 'title.gif',
	},
	
```

TitleScene はゲーム本体とは異なるので、title.js として新しくファイルを作って、そこにまとめることにしました。
index.html で、このファイルを読み込むように追加します。

```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" />

    <title>Getting started | phina.js</title>
    <!-- phina.js を読み込む -->
    <script src='http://cdn.rawgit.com/phi-jp/phina.js/v0.2.0/build/phina.js'></script>

	<!-- シナリオ -->
    <script src="scenario.js"></script>
    <!-- メイン処理 -->
    <script src='main.js'></script>
    <!-- タイトル -->
    <script src='title.js'></script>
    <!-- 敵 -->
    <script src="enemy.js"></script>
  </head>
  <body>

  </body>
</html>
```

さて、MainScene を参考に、TitleScene をざっくりと作ります。
MainScene の星の表示をほぼそのまま持ってきました。
タイトル画像表示用と、背景の星表示用に、２つのグループを作っています。
ハイスコアの値が初期化されないように、グローバル変数チックな場所で宣言しています。

```javascript
// ハイスコア
var hiscore = 0;

// TitleScene クラスを定義
phina.define('TitleScene', {
	superClass: 'DisplayScene',
	init: function(param) {
		this.superInit(param);
		// 背景色を指定
		this.backgroundColor = '#0F0F0F';
		
		// グループを生成する
		this.group = {
			// 背景用
			 backgroungGroup: DisplayElement().addChildTo(this),
			 // タイトル用
			 titleGroup: DisplayElement().addChildTo(this),			 
		};
		// グループの大きさをこのシーンに合わせる
		for (var groupKey in this.group) {
			this.group[groupKey].width = this.width;
			this.group[groupKey].height = this.height;
		}

		// 星を生成
		(50).times(function() {
			Star(this.group.backgroungGroup, Random.randint(0, this.width), Random.randint(0, this.height));
		}, this);
		
	},
	update: function (app) {
		
		// ランダムで星を生成する
		if (Random.randint(1, 3) == 1) {
			Star(this.group.backgroungGroup, Random.randint(0, this.width), 0);
		}
	},
});
```

メイン処理で、TitleScene を呼び出せば、とりあえず TitleScene の表示はできました。

```javascript
// メイン処理
phina.main(function() {
	// アプリケーション生成
	var app = GameApp({
		startLabel: 'title', // タイトルシーンから開始する
		scenes: [
			{
				className: 'TitleScene',
				label: 'title',
				nextLabel: 'main',
			},
			{
				className: 'MainScene',
				label: 'main',
				nextLabel: 'title',
		 	},
		],
		// アセット読み込み
		assets: ASSETS,
		width: SC_WIDTH,
		height: SC_HEIGHT,
	});
	
	// アプリケーション実行
	app.run();
});
```
titleシーン の後に、mainシーン、mainシーン の後に titleシーン を呼び出すようにしています。

次に、TitleScene を作り込んでいきます。
タイトルの画像を表示して、ビカビカ光らせます。
光らせる方法は、画像の下に四角いシェイプを置いて、これの色を交互に変化させます。
init に以下を追加

```javascript
		// タイトル画像
		this.titleImage = Sprite('title');
		this.titleImage.x = this.gridX.center();
		this.titleImage.y = this.height / 3;
		// タイトル画像の下のシェイプを設定
		this.shape = Shape({
				backgroundColor: '#FFFFFF',
				stroke: false,
				padding: 0,
			});
		this.shape.width = this.titleImage.width;
		this.shape.height = this.titleImage.height;
		this.shape.x = this.titleImage.x;
		this.shape.y = this.titleImage.y;
		// シェイプを設定
		this.shape.addChildTo(this.group.titleGroup);
		// シェイプの上にタイトル画像を設定
		this.titleImage.addChildTo(this.group.titleGroup);
```

で、update で色を変化させます。

```javascript
		// タイトルの後ろのシェイプの色を変える
		if (this.shape.backgroundColor == '#FFFFFF') {
			this.shape.backgroundColor = '#0088FF';
		} else {
			this.shape.backgroundColor = '#FFFFFF';
		}		
```
これで、タイトルの画像がビカビカするようになりました。

次に、スコアとハイスコアを表示させます。
[その10](https://qiita.com/hirotyankoutei/items/15da2c537e67d9a42ff2)で作った数値ラベルクラスを使って表示させています。
init に以下を追加

```javascript
		// スコア
		var score = 0;
		// パラメータにスコアがある場合はそれを表示
		if (param.score) {
			score = param.score;
		}
		// スコア表示
	    var scoreLabel = NumberLabel('SCORE:', 100, 30, 10, 15, 'white').addChildTo(this.group.titleGroup);
	    scoreLabel.setValue(score);
		
		// ハイスコア
		// スコアがハイスコアより大きい場合はハイスコアを更新する
		if (hiscore < score) {
			hiscore = score;
		}
		// ハイスコア表示
	    var hiscoreLabel = NumberLabel('HISCORE:', 91, 15, 10, 15, 'white').addChildTo(this.group.titleGroup);
	    hiscoreLabel.setValue(hiscore);
```
ゲーム本体でゲームオーバーになってタイトルに戻ってくる時に、param.score としてスコアを渡してもらう前提です。もし param.score があれば、スコアにはそれを表示し、無ければ 0を表示しています。
スコアがハイスコアより大きい場合は、ハイスコアをスコアの値にします。

最後に、Zキーを押したらゲーム開始するようにすれば完成。
でも、Zキーを押したらゲーム開始することが解るようにラベルをつけておこう。
init に以下を追加

```javascript
		// ラベルを設定
		this.lavel = Label().addChildTo(this.group.titleGroup);
		this.lavel.fontSize = 24;
		this.lavel.fontWeight = 'bold';
		this.lavel.fill = this.shape.backgroundColor;
		this.lavel.text = "HIT [Z] KEY!";
		this.lavel.x = this.gridX.center();
		this.lavel.y = this.height * 2 / 3;
```
update でZキーを押した時の処理を追加。
せっかくなんで、追加したラベルの色もビカビカ光らせてしまおう。

```javascript
		// ラベルをシェイプと同じ色にする
		this.lavel.fill = this.shape.backgroundColor;

		var key = app.keyboard;
		// Zキーが押された場合、ゲームを開始
		if (key.getKey('z')) {
			this.exit();
		}
```

これで、タイトルの作成は完成。
title.js を全部載せるとこんな感じ。

```javascript
// ハイスコア
var hiscore = 0;

// TitleScene クラスを定義
phina.define('TitleScene', {
	superClass: 'DisplayScene',
	init: function(param) {
		this.superInit(param);
		// 背景色を指定
		this.backgroundColor = '#0F0F0F';
		
		// グループを生成する
		this.group = {
			// 背景用
			 backgroungGroup: DisplayElement().addChildTo(this),
			 // タイトル用
			 titleGroup: DisplayElement().addChildTo(this),			 
		};
		// グループの大きさをこのシーンに合わせる
		for (var groupKey in this.group) {
			this.group[groupKey].width = this.width;
			this.group[groupKey].height = this.height;
		}		
		
		// 星を生成
		(50).times(function() {
			Star(this.group.backgroungGroup, Random.randint(0, this.width), Random.randint(0, this.height));
		}, this);
		
		// スコア
		var score = 0;
		// パラメータにスコアがある場合はそれを表示
		if (param.score) {
			score = param.score;
		}
		// スコア表示
	    var scoreLabel = NumberLabel('SCORE:', 100, 30, 10, 15, 'white').addChildTo(this.group.titleGroup);
	    scoreLabel.setValue(score);
		
		// ハイスコア
		// スコアがハイスコアより大きい場合はハイスコアを更新する
		if (hiscore < score) {
			hiscore = score;
		}
		// ハイスコア表示
	    var hiscoreLabel = NumberLabel('HISCORE:', 91, 15, 10, 15, 'white').addChildTo(this.group.titleGroup);
	    hiscoreLabel.setValue(hiscore);
		
		// タイトル画像
		this.titleImage = Sprite('title');
		this.titleImage.x = this.gridX.center();
		this.titleImage.y = this.height / 3;
		// タイトル画像の下のシェイプを設定
		this.shape = Shape({
				backgroundColor: '#FFFFFF',
				stroke: false,
				padding: 0,
			});
		this.shape.width = this.titleImage.width;
		this.shape.height = this.titleImage.height;
		this.shape.x = this.titleImage.x;
		this.shape.y = this.titleImage.y;
		// シェイプを設定
		this.shape.addChildTo(this.group.titleGroup);
		// シェイプの上にタイトル画像を設定
		this.titleImage.addChildTo(this.group.titleGroup);
		
		// ラベルを設定
		this.lavel = Label().addChildTo(this.group.titleGroup);
		this.lavel.fontSize = 24;
		this.lavel.fontWeight = 'bold';
		this.lavel.fill = this.shape.backgroundColor;
		this.lavel.text = "HIT [Z] KEY!";
		this.lavel.x = this.gridX.center();
		this.lavel.y = this.height * 2 / 3;
	},
	update: function (app) {
		
		// タイトルの後ろのシェイプの色を変える
		if (this.shape.backgroundColor == '#FFFFFF') {
			this.shape.backgroundColor = '#0088FF';
		} else {
			this.shape.backgroundColor = '#FFFFFF';
		}		
		// ラベルをシェイプと同じ色にする
		this.lavel.fill = this.shape.backgroundColor;
		
		// ランダムで星を生成する
		if (Random.randint(1, 3) == 1) {
			Star(this.group.backgroungGroup, Random.randint(0, this.width), 0);
		}
		
		var key = app.keyboard;
		// Zキーが押された場合、ゲームを開始
		if (key.getKey('z')) {
			this.exit();
		}
	},
});
```

ゲームオーバー時に TitleScene に戻るようにすれば完成。
その際に、スコアをタイトルシーンに渡します。
MainScene のゲームオーバーの処理を修正します。
仮に設定していた、ゲームオーバー時の変数の初期化処理は全て削除しました。
ゲームオーバー後の処理は、タイトルに戻っているだけ。シンプルになりました。

```javascript
		// GAMEOVERの場合
		if (this.isGameOver && !this.gameOver.visible) {
			// 自機を非表示
			ship.hide();
			// GAMEOVER表示			
			this.gameOver.show();
			// GAMEOVERの処理
			this.gameOver.tweener.wait(5000).call(
				function() {
					// タイトルへ戻る
					mainScene.exit({score: mainScene.score});
				}
			).play();
		}
```

## 今日の成果
今日の成果をここに上げました。
タイトル画面でZキーを押すとゲームが始まり、ゲームオーバーになるとタイトルに戻ります。
http://hirotyan.my.coocan.jp/phinajs/Shooting/009/index.html


[phina.js が面白そうなのでお勉強中　その１２](https://qiita.com/hirotyankoutei/items/8a7e0177d502ed54a22c)