シューティングゲームの追尾弾
今回はシューティングゲームの追尾弾です。ホーミングミサイルと呼ばれるあいつのことです。
解説前にサンプルプログラムとソースコードです。
サンプル※要FlashPlayer
ソースコード(本体)
// 初期化
if (!flg_init) {
// 弾クラスをimport
import Bullet;
// sinを設定
var val_sin:Array = new Array();
for (i = 0; i <= 360; i++) {
val_sin[i] = Math.sin(i * Math.PI / 180);
}
// cosを設定
var val_cos:Array = new Array();
for (i = 0; i <= 360; i++) {
val_cos[i] = Math.cos(i * Math.PI / 180);
}
// 弾スピード設定
var sp:Number = 3;
// 追跡限界角度設定
var pur_theta:Number = 5;
// 判定フレーム設定
var judge:Number = 10;
var frame:Number = judge + 1;
// 弾作成
var bullet = new Bullet(1);
bullet.mc._x = enemy._x;
bullet.mc._y = enemy._y;
// 自機方向への速度を算出
var d:Number = GetDistancePointToPoint(jiki._x, jiki._y, enemy._x, enemy._y);
var vx1:Number = (jiki._x - enemy._x) / d;
var vy1:Number = (jiki._y - enemy._y) / d;
// 速度を設定
bullet.vx = vx1 * sp;
bullet.vy = vy1 * sp;
// 追跡用変数宣言
var vx2:Number;
var vy2:Number;
var vx3:Number;
var vy3:Number;
var px:Number;
var py:Number;
// 初期化フラグオン
flg_init = true;
}
// 弾の再初期化
if (bullet.mc._x < 0 || bullet.mc._x > 300 || bullet.mc._y < 0 || bullet.mc._y > 300) {
// フレームの初期化
frame = judge + 1;
// 弾位置の初期化
bullet.mc._x = enemy._x;
bullet.mc._y = enemy._y;
// 速度の初期化
d = GetDistancePointToPoint(jiki._x, jiki._y, enemy._x, enemy._y);
vx1 = (jiki._x - enemy._x) / d;
vy1 = (jiki._y - enemy._y) / d;
// 速度を設定
bullet.vx = vx1 * sp;
bullet.vy = vy1 * sp;
}
else {
// 追跡処理
if (frame++ % judge == 0) {
// 自機と弾位置との相対ベクトル
px = jiki._x - bullet.mc._x;
py = jiki._y - bullet.mc._y;
// 現在自機位置への速度を算出
d = GetDistancePointToPoint(jiki._x, jiki._y, bullet.mc._x, bullet.mc._y);
vx1 = px / d;
vy1 = py / d;
// 元速度からの限界角度速度を算出(オイラー回転)
vx2 = val_cos[pur_theta] * bullet.vx - val_sin[pur_theta] * bullet.vy;
vy2 = val_sin[pur_theta] * bullet.vx + val_cos[pur_theta] * bullet.vy;
// 2つのベクトルの内積を比較
if (bullet.vx * vx1 + bullet.vy * vy1 >= bullet.vx * vx2 + bullet.vy * vy2) {
bullet.vx = vx1 * sp;
bullet.vy = vy1 * sp;
}
else {
// 負の限界角度の速度を算出(オイラー回転)
vx3 = val_cos[360 - pur_theta] * bullet.vx - val_sin[360 - pur_theta] * bullet.vy;
vy3 = val_sin[360 - pur_theta] * bullet.vx + val_cos[360 - pur_theta] * bullet.vy;
// 2つのベクトルの内積を比較
if (px * vx2 + py * vy2 >= px * vx3 + py * vy3) {
bullet.vx = vx2;
bullet.vy = vy2;
}
else {
bullet.vx = vx3;
bullet.vy = vy3;
}
}
}
// 弾の移動
bullet.Move();
}
// 距離計算関数
function GetDistancePointToPoint(p1_x:Number, p1_y:Number, p2_x:Number, p2_y:Number) : Number
{
// X
var x:Number = Math.pow((p1_x - p2_x), 2);
// Y
var y:Number = Math.pow((p1_y - p2_y), 2);
// 距離計算
return Math.sqrt(x + y);
}
Bullet.as
// 弾クラス
class Bullet
{
// 弾MC
public var mc:MovieClip;
// 速度
public var vx:Number;
public var vy:Number;
// コンストラクタ
public function Bullet(deps:Number)
{
// 弾インスタンスを作成
this.mc = _root.attachMovie("bullet", "bullet" + deps, deps);
}
// 移動処理
public function Move()
{
this.mc._x += this.vx;
this.mc._y += this.vy;
}
}
キーで自機を操作してみて下さい。
初期位置に向かって放たれた弾が移動位置へと修正されて向かってくるのがお分かりでしょうか?
それでは解説です。
追尾弾を作成しようとして、最初に考える方法は狙い打ち弾の応用だと思います。
EnterFrame毎に移動した自機へ狙い打ち弾の処理をするという方法です。
しかしこれをやってしまうとゲームになりません。なぜならどんな操作を用いても弾を避けることが出来なくなるからです。
ではどうするのかというと、弾の角度修正を制限すればよいのです。
例を出すと自機と弾の角度が30度であったとしても、15度しか修正させないようにする方法などが挙げられます。
しかしこの方法だと、狙い撃ち弾のように単純にすむ話ではなくなってしまいます。
角度を制限するということは元のベクトルの角度を知る必要が生じます。
15度しか修正してはいけないと設定するにしても何度から15度の制限かが分からないからです。
自機方向への速度から角度を求めることは可能ですが、ここはもう少し簡単なやり方で行います。
行列を用いたオイラー回転で、弾から自機へのベクトルから制限角度への速度を算出します。
// vx,vy(弾から自機への速度)theta(制限角度のラジアン)
vx´ = cos(theta) * vx - sin(theta) * vy;
vy´ = sin(theta) * vx + cos(theta) * vy;
これで制限角度への速度を算出することが出来ました。
次に自機方向への角度と制限角度への角度ではどちらが近いのかを比較します。
これはベクトルの内積を用いて比較することが出来ます。
初期位置へのベクトルに対する、現在の自機方向へのベクトルの内積と制限角度への内積を比較します。
内積は射影ですので、角度が大きい方が小さくなります。つまり内積の大きい方が近いということになります。
// ベクトルの成分から内積を求める公式
a・b = ax・bx + ay・by
自機方向への速度の内積が大きい場合はよいのですが、制限角度への速度の内積が大きい場合は、逆方向の角度も考えないといけません。
つまり(360 - 制限角度)ですね。これも内積を比較すればOKです。