#敵の弾を飛ばしてみる
シューティングゲームでは、敵が弾を撃ってきます。
しかも、自機の方向に斜めに飛んできます。
これを作ってみたいです。
##敵の弾が自分の方向に飛んでくるってのはどういうこと?
ってのを考えると、まず物が動くのだから、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座標が5動いた時に、Y座標が3動くような斜め方向に移動させたい場合、X座標が1動いたらY座標は 3 ÷ 5 動けばいいことになります。
逆に、X座標が3動いた時に、Y座標が5動くような斜め方向に移動させたい場合、
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座標が5でY座標が3方向の角度を求めるのは、javascriptでは atan2 という関数があって、
角度(ラジアン) = atan2(Y座標, X座標)
で求められます。
ただし、javascriptのY座標と数学のY座標は向きが逆なので、上の図だと、
角度(ラジアン) = atan2(-3, 5)
で求められます。
そこで、Aオブジェクトから見たBオブジェクトの角度を求める関数を作りました。
/**
* 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 って名前で ASSETS に設定してます。
敵の弾のクラスのパラメータは、
弾を生成する場所のX座標、Y座標
飛ばしたい角度(ラジアン)
弾のスピード
があれば良いですね。
移動量の計算のY座標に-1を掛けてるのは、javascriptのY座標と数学のY座標は向きが逆だからです。
/**
* 敵の弾を作成するクラス
*/
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 ではどうすればいいのかよく解らないです。下手すると、グローバルちっくな変数が大量生産されそうな気がする・・・。
自機方向に弾を飛ばすには、自機までの角度を計算して、弾を生成すればいいだけ。
// 自機までの角度を算出
var angle = createAngle(this, ship);
// 弾を発射
EnemyBullet(this.x, this.y, angle, 4).addChildTo(mainScene.group.enemyBulletGroup);
こんな感じかな。
敵1クラス全体だとこんな感じ。
/**
* 敵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