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

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

#自機の弾と敵との当たり判定を作ってみたい
自機も敵も自機の弾も敵の弾もできたので、とりあえずシューティングゲームとして最低限必要なオブジェクトは作れたから当たり判定を作りたい。
まずは、自機の弾と敵が当たった時の判定をやりたい。

やりたいことは以下です。
・自機の弾１つに対し、敵とぶつかっているかの判定を、画面上のすべての敵で行う。
・自機の弾と敵がぶつかっていたら、自機の弾を消して、代わりに爆発のオブジェクトを表示。敵の硬さを減少させる。
・敵の硬さが0の場合は爆発の音を出して敵を消す。敵のスコア分スコアを増加させる。

##爆発のオブジェクトを作る
こうやってまとめると、爆発のオブジェクトを作らなきゃなので、作ってみました。
まず爆発の絵が必要だな。
こんな絵を[GIMP](https://www.gimp.org/)使って描きました。
![EXPLOSION.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/38875a69-b1de-50b2-849f-4793175dd0fd.gif)
後ろ白いと判りにくいので、黒くしてみました。
![EXPLOSION.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/46cb0ef0-945c-0187-f69c-c03dc5271fa6.gif)
32*32の絵を16コマ分描いたけど、しんどかった。
アニメーターの人ってすごいなと思いますよ・・・。

決まったパターンのアニメーションを行うには、[phina.js が面白そうなのでお勉強中　その１](https://qiita.com/hirotyankoutei/items/a4b0d6a629bbbd3dcd02)で勘違いしていたspritesheetを使えばいいのかな。
こんな風に過去の失敗とか勘違いも後々役立ってくるので、実際に手を動かして失敗を積み重ねるのって大事だなと思う。

ちなみに、爆発処理のサンプルが「[[phina.js-Tips]フレームアニメーションの終了を検知する](https://qiita.com/alkn203/items/4a5bac39b2ba8c237905)」にあって、ほぼそのまま使わせていただきました。
こういうサンプル置いてくれてるとむっちゃ助かる。

ASSETSはこんな感じになりました。

```javascript
// アセット
var ASSETS = {
	// 画像
	image: {
		'ship': 'ship.gif',
		'enemy': 'enemy.gif',
		'bullet': 'bullet.gif',
		'explosion': 'EXPLOSION.gif',
	},
	
	// スプライトシート
	spritesheet: {
		'explosion_ss':
		{
			"frame": {
				"width": 32,
				"height": 32,
				"cols": 4,
				"rows": 4,
			},
			// アニメーション
			"animations" : {
				"start": {
					"frames": Array.range(16),
					"next": "",
					"frequency": 1,
				},
			}
		},
	},
	
	// 音
	sound: {
		'shoot': 'shoot.wav',
		'bom': 'bom.wav',
	},
};
```
画像に EXPLOSION.gif を追加して、音に bom.wav を追加しました。
音は[効果音めーかー](https://www.vector.co.jp/soft/win95/art/se045171.html)を使って作りました。

爆発クラスはこんな感じ

```javascript
// 爆発クラス
phina.define('Explosion', {
	// Spriteを継承
	superClass: 'Sprite',
	init: function(x, y) {
		// 親クラスの初期化
		this.superInit("explosion", 32, 32);
		// 座標の設定
		this.x = x;
		this.y = y;
		
		// SpriteSheetをスプライトにアタッチ
		this.anim = FrameAnimation('explosion_ss').attachTo(this);
		// スプライトシートのサイズにフィットさせない
		this.anim.fit = false;
		//アニメーションを再生する
		this.anim.gotoAndPlay('start');
	},
	update: function(app) {
		// アニメーションが終わったら自身を消去
		if (this.anim.finished) {
			this.remove();
			delete this;
		}
	},		
 });
```
爆発の位置をパラメータでもらって、表示して、終わったら消すだけ。
アニメーションの面倒な処理はスプライトシートがやってくれるからむっちゃ簡単。

##当たり判定ってどうやるのだ？
さて、当たり判定を作ろうと思うのですが、当たり判定ってどうやるのだ？
２つのスプライトの中心の距離が小さくなったら当たっているって考えるんだろうけど、簡単に判定する方法無いのかな？
っと思って調べてみました。

一番簡単なのは、[[phina.js-Tips-41] 当たり判定を行う（矩形判定）](https://qiita.com/alkn203/items/2e567ee47a4a1a8b8ee4)で載ってるhitTestElementメソッドを使う方法ですね。
でもこれは、スプライトの矩形の大きさで判定してしまうので、シューティングで使うには使いにくいかな。
自機が、グラフィック的に敵弾に当たっていないのに当たっていると判定されてしまうのは問題だな・・・。

[Collisionクラスを使った当たり判定](https://qiita.com/alkn203/items/a5331753d5c89399c1d8)ってのを使うと、円とか矩形とか、好きな大きさで判定できるのか。これを使ってみよう。

```javascript
		// 敵との当たり判定
		// 敵用のグループの要素全てと当たり判定する
		mainScene.group.enemyGroup.children.forEach(enemy => {
			// 敵から当たり判定用の円を作成
			var enemyCircle = Circle(enemy.x, enemy.y, enemy.radius);
			// 当たり判定
			if (Collision.testCircleRect(enemyCircle, this)) {
				// 爆発を表示
				Explosion(this.x, this.y).addChildTo(mainScene.group.explosionGroup);
				// 敵から硬さを減少させる
				enemy.hardness--;
				// 弾を消す
				this.remove();
				delete this;
			}
		});
```
弾１発と敵との当たり判定はこんなふうになりました。
弾１発を動かすupdate 処理時に当たり判定を行います。
敵は mainScene.group.enemyGroup に表示させると決めているので、enemyGroupの全てのchildrenでチェックすれば全ての敵とチェックできることになります。

敵の位置に当たり判定用の円を作成します。
自機の弾は矩形判定でいいので、自機の弾のスプライトをそのまま使います、
で、Collision.testCircleRect で円と矩形の当たり判定を行っています。
自機の弾が敵に当たっている場合は、爆発を表示、敵から硬さを減少させて、自機の弾を消します。

一方、敵の方では update 処理時に敵の硬さをチェックし、硬さが無くなっていれば爆発音を出して点数を加算、敵を削除します。
こんな感じ。

```javascript
		// 固さが0以下の場合
		if (this.hardness <= 0) {
			// 爆発音を出す
			SoundManager.play('bom');
			// 点数を加算
			mainScene.score += this.bulletScore;
			mainScene.scoreLabel.text = 'SCORE:'+ ('0000000000' + mainScene.score).slice(-10);
			// 画面から消す
			this.remove();
			delete this;
		}
```

最後に、自機の弾クラスと、爆発クラスと、敵クラスがどうなったか載せておきます

自機の弾クラス

```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;
		
		// 敵との当たり判定
		// 敵用のグループの要素全てと当たり判定する
		mainScene.group.enemyGroup.children.forEach(enemy => {
			// 敵から当たり判定用の円を作成
			var enemyCircle = Circle(enemy.x, enemy.y, enemy.radius);
			// 当たり判定
			if (Collision.testCircleRect(enemyCircle, this)) {
				// 爆発を表示
				Explosion(this.x, this.y).addChildTo(mainScene.group.explosionGroup);
				// 敵から硬さを減少させる
				enemy.hardness--;
				// 弾を消す
				this.remove();
				delete this;
			}
		});
		
		// 弾が上まで行った場合は消す
		if (this.bottom < 0) {
			this.remove();
			delete this;
		}
	},		
 });
```
爆発クラス

```javascript
// 爆発クラス
phina.define('Explosion', {
	// Spriteを継承
	superClass: 'Sprite',
	init: function(x, y) {
		// 親クラスの初期化
		this.superInit("explosion", 32, 32);
		// 座標の設定
		this.x = x;
		this.y = y;
		
		// SpriteSheetをスプライトにアタッチ
		this.anim = FrameAnimation('explosion_ss').attachTo(this);
		// スプライトシートのサイズにフィットさせない
		this.anim.fit = false;
		//アニメーションを再生する
		this.anim.gotoAndPlay('start');
	},
	update: function(app) {
		// アニメーションが終わったら自身を消去
		if (this.anim.finished) {
			this.remove();
			delete this;
		}
	},		
 });
```

敵クラス

```javascript
// 敵の基本クラス
phina.define('Enemy', {
	// Spriteを継承
	superClass: 'Sprite',
	// 初期化
	init: function(image, sizeX, sizeY) {
		// 親クラスの初期化
		this.superInit(image, sizeX, sizeY);
		
		// プロパティの初期化
		this.x = 0;
		this.y = 0;
		// 硬さ
		this.hardness = 1;
		// 弾を当てた時のスコア
		this.bulletScore = 10;
		// 敵のフレームカウント数
		this.frameCount = 0;
	},
	update: function(app) {
		// 固さが0以下の場合
		if (this.hardness <= 0) {
			// 爆発音を出す
			SoundManager.play('bom');
			// 点数を加算
			mainScene.score += this.bulletScore;
			mainScene.scoreLabel.text = 'SCORE:'+ ('0000000000' + mainScene.score).slice(-10);
			// 画面から消す
			this.remove();
			delete this;
		}
		
		// フレームカウントを進める
		this.frameCount++;			
		// 動かす
		this.move();
		
		// 画面の外に出たら消す
		if (this.right < 0 || this.left > mainScene.width || this.bottom < 0 || this.top > mainScene.height) {
			this.remove();
			delete this;
		}
	},
	// 敵を動かすメソッド	
	move: undefined		// 継承先で設定
	,
 });
```

##今日の成果
今日の成果をここに上げました。
自機の弾が敵に当たるとスコアが加算されます。
自機はやられないので無敵モードだな・・・。
http://hirotyan.my.coocan.jp/phinajs/Shooting/006/index.html

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