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

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

# 弾を発射するのだ！
とりあえず、シューティングゲームを作っているからには、弾が発射できなければいけない。
ってなわけで、自機の弾を作ってみました。

## やっぱ連打です！
で、自分的にシューティングゲームの弾はこうあるべき、という仕様があります。
　1. キーを押しっぱなしにした場合、一定間隔で弾がでること。
　2. 連打すると、もっと弾が出るようになること。
連打はシューティングゲームの醍醐味ですよ！
これを実装したい！

一定間隔で＝一定のフレームで、って考えればいいかな。

連打＝押した時に弾を出すが、フラグが立って、フラグが立っている時は弾が出なくなる。押していない場合はフラグが寝る。と考えればいいかな。

## ボタンは「z」を使用
本当はスペースキーを使いたい所なんだけど、多くのゲームはZキー近辺を使っています。
これ、なんでかなー？って思ってたのですが、キーボードによっては、カーソルキーとスペースキーの組み合わせでキーが反応しなくなることがあるからじゃないかなあと思ってます。
うちのhpのキーボードがそうだし。

## 音も出してみる
ASSETSで音声ファイルを読み込んでおけば、
SoundManager.play('名称');
で音が鳴らせるそうなんで、試してみました。簡単。

## 自弾の実装
自弾はこんなふうになりました

```javascript
// 自弾クラス
phina.define('MyBullet', {
	// Spriteを継承
	superClass: 'Sprite',
	init: function(x, y) {
		// 親クラスの初期化
		this.superInit("ship", 16, 16);
		// 座標の設定
		this.x = x;
		this.y = y;
		// 弾の速度
		this.speed = 16;
		// 画像から弾のフレームだけを設定
		this.frameIndex = 6;
	},
	update: function(app) {
		// 弾の移動
		this.y -= this.speed;
		
		// 弾が上まで行った場合は消す
		if (this.bottom < 0) {
			this.remove();
			delete this;
		}
	},		
 });
```

画像を自機と一緒にしてしまったから、面倒なことになってます。
phina.jsでは、画像ファイルから特定の範囲を画像にすることができないっぽい。
自機を32*32で描いて、弾を16*16で描いたから、フレームがややこしいことになってます。
画像を16*16に区切った（0から数えて）6番目だけを使っています。
これはファイルを別々にすべきだったかな。
ちなみに、こんな画像です。
![ship.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/b474e1d3-9dee-ea38-bfbe-f547cf18b887.gif)
透過GIFなので、背景白だと全然判らないな・・・。
背景黒く塗るとこんな感じ。
![ship.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/b3a5be47-48e4-cc6b-6af0-dd4c5f9bea5b.gif)
ちなみに[GIMP](https://www.gimp.org/)で描いてます。

弾が画面上まで行ったら消しているんだけど、消し方がこれで良いのかよく解らなかった。
画面から消して、その後オブジェクトをdeleteしているからいいんじゃないかと思ってます。
いくら連打して弾を撃っても、このクラスが全部やってくれるから、オブジェクト指向言語ってほんと楽だなあ。
オブジェクト指向言語は、弾幕のために存在すると言っても過言ではない！

## 自機から弾を発射する部分の実装
弾を撃つのに使用するクラス変数のようなプロパティを自機クラスのinitに追加。

```javascript
// 自機クラス
phina.define('Ship', {
	// Spriteを継承
	superClass: 'Sprite',
	// 初期化
	init: function() {
		// 親クラスの初期化
		this.superInit("ship", 32, 32);
		this.frameIndex = 1;
		
		// 自機の速度
		this.speed = 8;
		// 連打のフラグ
		this.triggerFlag = false;
		// 弾を撃ってからのフレーム数
		this.trigerFrameCount = 0;
	},

```


弾を発射する処理はこんな風になりました。
自機クラスのupdate処理に書いてます。


```javascript
		// 弾発射
		if (key.getKey('z')) {
			// 8フレームに１回、または連打した場合は自弾を発射する
			if (this.trigerFrameCount % 8 == 0 || !this.triggerFlag) {
				// 自弾を生成
				var myBullet = MyBullet(this.x, this.y).addChildTo(this.parent);
				// 発射音を鳴らす
				SoundManager.play('shoot');
				// 連打のフラグを立てる
				this.triggerFlag = true;
			}
			// 弾のフレーム数をインクリメントする
			this.trigerFrameCount++;			
		} else {
			// 連打のフラグを寝かせる
			this.triggerFlag = false;
			// 弾のフレーム数を0にする
			this.trigerFrameCount = 0;
		}

```

Zボタン押しっぱなしだと、8フレームごとに弾が出て、連打するともっと出る、っていう動きになりました。

ちなみに、発射音は[効果音メーカー](https://www.vector.co.jp/soft/win95/art/se045171.html)で作りました。

## 今日の成果

今日の成果をここに上げました。
http://hirotyan.my.coocan.jp/phinajs/Shooting/002/index.html

自機が動いて弾が出るだけだけど、ここまでできるとむっちゃ楽しい！

```javascript
// phina.js をグローバル領域に展開
phina.globalize();
// 画面サイズ
var SC_WIDTH = 320;
var SC_HEIGHT = 480;

// アセット
var ASSETS = {
	// 画像
	image: {
		'ship': 'ship.gif',
	},
	sound: {
		'shoot': 'shoot.wav',
	},
};

// MainScene クラスを定義
phina.define('MainScene', {
	superClass: 'DisplayScene',
	init: function(option) {
		this.superInit(option);
		// 背景色を指定
		this.backgroundColor = '#000000';
		// スプライトを作成
		var ship = Ship().addChildTo(this);
		// 初期位置
		ship.x = this.gridX.center();
		ship.y = this.gridY.center(5);		
		
		
	},
});

// 自機クラス
phina.define('Ship', {
	// Spriteを継承
	superClass: 'Sprite',
	// 初期化
	init: function() {
		// 親クラスの初期化
		this.superInit("ship", 32, 32);
		// 画像フレームの初期値
		this.frameIndex = 1;
		
		// 自機の速度
		this.speed = 8;
		// 連打のフラグ
		this.triggerFlag = false;
		// 弾を撃ってからのフレーム数
		this.trigerFrameCount = 0;
	},
	update: function(app) {
		var key = app.keyboard;
		// 上下左右移動
		// 画像フレームを一旦初期化する
		this.frameIndex = 1;
		if (key.getKey('left')) {	
			this.x -= this.speed;
			// 画像フレームを左移動に変更
			this.frameIndex = 0;
		}
		if (key.getKey('right')) {
			this.x += this.speed;
			// 画像フレームを右移動に変更
			this.frameIndex = 2;
		}
		if (key.getKey('up')) {
			this.y -= this.speed;
		}
		if (key.getKey('down')) {
			this.y += this.speed;
		}
		// 端まで行った場合は動かさない
		if (this.left <  0) {
			this.left = 0;
		} else if (this.right > this.parent.width) {
			this.right = this.parent.width;
		}
		if (this.top < 0) {
			this.top = 0;
		} else if (this.bottom > this.parent.height) {
			this.bottom = this.parent.height;
		}
		
		// 弾発射
		if (key.getKey('z')) {
			// 8フレームに１回、または連打した場合は自弾を発射する
			if (this.trigerFrameCount % 8 == 0 || !this.triggerFlag) {
				// 自弾を生成
				var myBullet = MyBullet(this.x, this.y).addChildTo(this.parent);
				// 発射音を鳴らす
				SoundManager.play('shoot');
				// 連打のフラグを立てる
				this.triggerFlag = true;
			}
			// 弾のフレーム数をインクリメントする
			this.trigerFrameCount++;			
		} else {
			// 連打のフラグを寝かせる
			this.triggerFlag = false;
			// 弾のフレーム数を0にする
			this.trigerFrameCount = 0;
		}
	},		
 });

// 自弾クラス
phina.define('MyBullet', {
	// Spriteを継承
	superClass: 'Sprite',
	init: function(x, y) {
		// 親クラスの初期化
		this.superInit("ship", 16, 16);
		// 座標の設定
		this.x = x;
		this.y = y;
		// 弾の速度
		this.speed = 16;
		// 画像から弾のフレームだけを設定
		this.frameIndex = 6;
	},
	update: function(app) {
		// 弾の移動
		this.y -= this.speed;
		
		// 弾が上まで行った場合は消す
		if (this.bottom < 0) {
			this.remove();
			delete this;
		}
	},		
 });


// メイン処理
phina.main(function() {
	// アプリケーション生成
	var app = GameApp({
		startLabel: 'main', // メインシーンから開始する
		// アセット読み込み
		assets: ASSETS,
		width: SC_WIDTH,
		height: SC_HEIGHT,
	});
	// アプリケーション実行
	app.run();
});

```


[phina.js が面白そうなのでお勉強中　その４](https://qiita.com/hirotyankoutei/items/3a76bd011edc5fae53c7)