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

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

#敵の弾を飛ばしてみる
シューティングゲームでは、敵が弾を撃ってきます。
しかも、自機の方向に斜めに飛んできます。
これを作ってみたいです。
##敵の弾が自分の方向に飛んでくるってのはどういうこと？
ってのを考えると、まず物が動くのだから、1フレームごとのX座標とY座標の移動量が解ればいいってことですな。
1フレームごとのX座標とY座標の移動量ってのは、
　X座標の移動量　＝　1あたりのX座標の移動量　×　速さ
　Y座標の移動量　＝　1あたりのY座標の移動量　×　速さ
ってことだから、
まずは 1あたりのX座標、Y座標の位置が出せれば良いことになります。
##「1あたりの」とは何ぞや？
何に対する1あたりなのかがさっぱり謎なんだけど、とりあえず上の計算式から判ることは、速さが1の時の移動量ってことですよね。
速さが2だったら2倍になって、速さが0.5だったら半分になる、ぐらいの感じでいいと思う。
##1あたりの出し方（簡単編）
「1あたりの」っていうのを、X座標、またはY座標の長さの大きい方が長さ1の時のY座標、またはX座標の位置って考えると、むっちゃ簡単です。
X座標、またはY座標の長さが大きい方を1にするので、Y座標、またはX座標の長さが小さいほうは0.なんぼになって、1より小さくなるから斜めに動きますよね。
言葉で書くと解りにくいので絵にしました。
![X座標が大きい.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/d13a47be-fdd6-d021-00d9-92efb0bd4951.png)
X座標が5動いた時に、Y座標が3動くような斜め方向に移動させたい場合、X座標が1動いたらY座標は 3 ÷ 5 動けばいいことになります。
逆に、X座標が3動いた時に、Y座標が5動くような斜め方向に移動させたい場合、
![Y座標が大きい.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/4b61a4a9-fb9f-1572-9fd7-e9ef17b703ea.png)
Y座標が1動いたらX座標は 3 ÷ 5 動けばいいことになります。

算数だけで理解できます。
小学生のころにこれを教えてもらってむっちゃ感動した記憶が。

ただし、これは斜め45°の場合に、X座標もY座標も1ずつ進むので、一番早くなり、弾の速さが一定でない、という欠点があります。
直角二等辺三角形的にはルート2になるので、1.41421356（ひとよひとよにひとみごろ）倍の速さで動くことになります。
##1あたりの出し方（かっこいい編）
速さを一定にして動かしたい場合、どうすればいいのかですが、「1あたりの」が同じ長さだったら同じ速さになるはずです。
そこで、「1あたりの」を、X座標が1でY座標が0の時の長さ、または、Y座標が1でX座標が0の時の長さ って考えます。つまり長さ1です。
これがどの方向に向いても長さ1になればいいので、長さ1の円上のどこかの座標ってことになります。
「どこか」っていうと、飛ばしたい方向ですよね。

長さ1の円の座標、って言ったら、三角関数ですよ！
三角関数って生活の何の役にも立たない、とか言ってる人いますけど、役立つのですよ！
三角関数はシューティングゲームで斜めに弾を飛ばすためにあると言っても過言ではない！（と思ってる。）
##で、三角関数ってなんだっけ？
自分の中では、半径1の円の、とある角度の座標を求める場合、
　X座標　＝　コサイン（角度）
　Y座標　＝　サイン（角度）
で求められる、という理解です。

この角度というのがJavascriptの三角関数では45°とかの度ではなくて、ラジアンを使っているので、一瞬？？？となるのですが、
　180°　＝　1πラジアン
　360°　＝　2πラジアン
　 90°　＝　1/2πラジアン
って覚えておけば、なんとなくイメージできるかと思います。

##角度はどうやって求めるのか？
![X座標が大きい.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/d13a47be-fdd6-d021-00d9-92efb0bd4951.png)
こういう、X座標が5でY座標が3方向の角度を求めるのは、javascriptでは atan2 という関数があって、
　角度（ラジアン）　＝　atan2(Y座標, X座標)
で求められます。
ただし、javascriptのY座標と数学のY座標は向きが逆なので、上の図だと、
　角度（ラジアン）　＝　atan2(-3, 5)
で求められます。
そこで、Aオブジェクトから見たBオブジェクトの角度を求める関数を作りました。

```javascript
/**
 * 2つのスプライト間の角度（ラジアン）を生成する関数
 * from 開始スプライトオブジェクト
 * to   方向スプライトオブジェクト
 */
function createAngle(from, to) {
	var x = to.x - from.x;
	var y = to.y - from.y;
	
	return Math.atan2(-y, x);
}
```

##敵の弾を作ってみる
斜めに弾を飛ばす方法が解ったので、敵の弾のクラスを作ってみます。
その前に、弾の画像を作りました。8×8の弾です。
![bullet.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191264/fc34415e-6115-1028-43a2-415395763c8b.gif)
これを、bullet って名前で ASSETS に設定してます。

敵の弾のクラスのパラメータは、
　弾を生成する場所のX座標、Y座標
　飛ばしたい角度（ラジアン）
　弾のスピード
があれば良いですね。

移動量の計算のY座標に-1を掛けてるのは、javascriptのY座標と数学のY座標は向きが逆だからです。

```javascript
/**
 * 敵の弾を作成するクラス
 */
phina.define('EnemyBullet', {
	// Spriteを継承
	superClass: 'Sprite',
	/** コンストラクタ
	*	x		X座標
	*	y		Y座標
	*   angle   角度（ラジアン）
	*   speed   弾のスピード
	*/
	init: function(x, y, angle, speed) {
		// 親クラスの初期化
		this.superInit('bullet', 8, 8);
		
		// プロパティの初期化
		this.x = x;
		this.y = y;
		this.angle = angle;
		this.speed = speed;
		
		// 移動量を計算
		this.moveX = this.speed * Math.cos(this.angle);
		this.moveY = this.speed * Math.sin(this.angle) * -1;
	},
	update: function(app) {
		// 弾の移動処理
		this.x += this.moveX;
		this.y += this.moveY;
		
		// 画面の外に出たら消す
		if (this.right < 0 || this.left > mainScene.width || this.bottom < 0 || this.top > mainScene.height) {
			this.remove();
			delete this;
		}
	},
});
```

前回作った敵クラスに、弾を飛ばす処理を入れてみます。
敵から自機の座標が判らないといけないので、自機のshipっていう変数は、グローバルちっくな変数にしました。
この辺、phina.js ではどうすればいいのかよく解らないです。下手すると、グローバルちっくな変数が大量生産されそうな気がする・・・。

自機方向に弾を飛ばすには、自機までの角度を計算して、弾を生成すればいいだけ。

```javascript
// 自機までの角度を算出
var angle = createAngle(this, ship);
// 弾を発射
EnemyBullet(this.x, this.y, angle, 4).addChildTo(mainScene.group.enemyBulletGroup);

```

こんな感じかな。
敵１クラス全体だとこんな感じ。

```javascript
/**
 * 敵1クラス
 * ジグザグ移動
 */
phina.define('Enemy01', {
	// Enemyを継承
	superClass: 'Enemy',
	// 初期化
	init: function() {
		// 親クラスの初期化
		this.superInit('enemy', 32, 32);
		
		// プロパティの設定
		this.frameIndex = 2;
		this.score = 100;
		this.hardness = 1;
		
		// 移動速度を決定
		this.speed = 4;
		
		// 初期値を設定
		this.x = Random.randint(0, mainScene.width);
		this.y = 0;
		// 横方向の速度
		this.speedX = this.speed;
		// 縦方向の速度
		this.speedY = 2 * Random.randint(1, 3);
		
		// モード 1:左方向、-1：右方向
		this.mode = 1;	
		// 出現位置により移動方向を決める
		if (this.x > mainScene.width / 2) {
			this.speedX = -this.speed;
			this.mode = -1;
		}

	},
	// 敵を動かすメソッド	
	move: function () {
		this.x += this.speedX;
		this.y += this.speedY;
		// スピードを加算する。
		this.speedX += this.mode * 2;
		
		// スピードが元の4倍を超えた場合
		if (Math.abs(this.speedX) >= this.speed * 4) {
			// 方向を逆転する
			this.mode *= -1;
			// 一定の確率で弾を発射する
			if (Random.randint(0, 3) == 0) {
				// 自機までの角度を算出
				var angle = createAngle(this, ship);
				// 弾を発射
				EnemyBullet(this.x, this.y, angle, 4).addChildTo(mainScene.group.enemyBulletGroup);
			}
		}
		
		// 画像の決定
		if (Math.abs(this.speedX) < this.speed * 2) {
			// 正面
			this.frameIndex = 2;
		} else if (this.mode == 1) {
			// 右向き
			this.frameIndex = 3;
		} else {
			// 左向き
			this.frameIndex = 1;
		}
	},
 });
```

##今日の成果
今日の成果をここに上げました。
敵が自機方向に弾を撃ってきます。
http://hirotyan.my.coocan.jp/phinajs/Shooting/005/index.html


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