ホーミング弾の作り方

sabro

2007年12月01日 21:06


以前ちらっと書いたとおり、Spirittanにはホーミング弾が存在します。その作り方がなかなか難しかったので、ちょっと公開してみます。

ホーミングというとまず思い浮かぶのは、llSensorとllMoveToTargetの組み合わせによる追尾型のアタッチメントですが、llMoveToTargetをホーミング弾に使用すると、どうしてもカクカクした動きになり見栄えがよくありません。

そこで、SpirittanではllSetForce関数で弾に力を加えることで、徐々に曲がっていく感じの弾を作ってみました。

以下は、弾に埋め込んであるスクリプトのホーミング関連部分のみ抜き出したものです。
// ホーミング対象(敵)のKey
key enemyKey = "xxxx-xxxx-xxxx-xxxx";

// 弾が発射された地点
vector firstPos;

// 敵の現在地へ弾を移動させるために必要な力を計算する
vector getForce(vector enemyPos)
{
    vector myPos = llGetPos();
    vector vel = llGetVel();
    vector acc = llGetAccel();

	// 弾と敵の距離
	vector distance = enemyPos - myPos;

	// 弾発射地点から見て、敵より弾の方が遠い位置にあれば力は加えない
	vector firstPosDistance1 = enemyPos - firstPos;
	vector firstPosDistance2 = llGetPos() - firstPos;
	if(llVecMag(firstPosDistance2) > llVecMag(firstPosDistance1))
	{
		return ZERO_VECTOR;
	}

	// 弾を敵の現在地へホーミングさせるために必要な、現在の速度と直角方向の加速度
	float theta = llRot2Angle(llRotBetween(distance, vel));
    float velMag = llVecMag(distance) * llCos(theta);
    float time = velMag / llVecMag(vel);
    vector newacc = (2 * (enemyPos - (myPos + time * vel)) / 
    	llPow(time, 2)) - acc;

	// 加速度が大きすぎる場合は補正
	if(llVecMag(newacc) > 10)
	{
		newacc = newacc * 10 / llVecMag(newacc);
	}

	// 力 = 質量 × 加速度    
    return llGetMass() * newacc;
}


default
{
	on_rez(integer start_param)
	{
		// 弾を浮かせる設定
		vector v = llGetPos();
		llSetHoverHeight(v.z - llGround(), FALSE, 1.0);

		// 1秒ごとに敵をサーチ
		llSensorRepeat("", enemyKey, AGENT, 100, PI, 1.0);

		// Rezされた位置を記録しておく
		firstPos = llGetPos();
	}

	sensor(integer total_number)
	{
		vector pos = llDetectedPos(0);

		// 敵が見つかった場合、弾をホーミングさせるために力を加える
		llSetForce(getForce(pos), FALSE);
	}
}

このスクリプトには、次の要素があります。
グローバル変数
  • enemyKey (ホーミング対象(敵)のKey、実際は発射直後に取得)
  • firstPos (弾が発射された地点)

グローバル関数
  • getForce (ホーミングさせるために弾に加える力を計算)

defaultステートのイベント
  • on_rez (弾が生成された直後の処理)
  • sensor (敵を発見した際の処理)


大まかに説明すると、on_rez時にllSensorRepeatで毎秒サーチを行うよう設定し、敵が見つかったらsensorイベントで弾に力を加えます。加える力の大きさは、getForce関数で計算しています。

このスクリプトの肝は、弾に加える力を計算するgetForce関数です。他の部分は特に難しいことはしていないので、この関数に絞って解説します。


最初に弾の位置、速度、加速度を取得した後、敵⇔弾間の距離を取得します。
	// 弾と敵の距離
	vector distance = enemyPos - myPos;



次に、発射地点から敵までの距離と、弾までの距離をチェックして、弾の方が遠い場合は加える力をZERO_VECTORにしています。こうしておかないと、弾をよけても後ろからホーミングしてしまいます。llVecMag関数でベクトルの大きさを取得して距離を比較しています。
	// 弾発射地点から見て、敵より弾の方が遠い位置にあれば力は加えない
	vector firstPosDistance1 = enemyPos - firstPos;
	vector firstPosDistance2 = llGetPos() - firstPos;
	if(llVecMag(firstPosDistance2) > llVecMag(firstPosDistance1))
	{
		return ZERO_VECTOR;
	}

後ろからホーミング



チェックが終わった後は、弾に加えるべき加速度を計算しています。これは、以下のような手順で行います。
  1. 弾⇔敵を結ぶ直線と、弾の進行方向の間の角θを求める
  2. llCos関数を使って、図のような弾進行方向の進むべき距離velMagを計算する
  3. 先ほど求めたvelMagを進むのに、今のスピードでどれだけの時間が掛かるか求める
  4. 物理の公式を使って、距離、時間から赤い矢印方向へ必要な加速度を求める
	// 弾を敵の現在地へホーミングさせるために必要な、現在の速度と直角方向の加速度
	float theta = llRot2Angle(llRotBetween(distance, vel));
    float velMag = llVecMag(distance) * llCos(theta);
    float time = velMag / llVecMag(vel);
    vector newacc = (2 * (enemyPos - (myPos + time * vel)) / 
    	llPow(time, 2)) - acc;

4で使う公式は、等加速度直線運動の公式です。あったねこんなの。

距離 = 初速 × 時間 + 1/2 × 加速度 × 時間^2

図の赤い矢印の方向への初速は0なので右辺の第1項は0になります。時間tは手順3で取得済みです。距離Sは「enemyPos - (myPos + time * vel)」で求めています。あとは、加速度が左辺にくるように整理してから、現在の加速度accを引くことで加えるべき加速度newaccが求められます。


求まった加速度が大きすぎる場合は、補正をかけます。今回の例では加速度ベクトルの大きさが10より大きかった場合は、大きさが10になるように調整しています。これをしておかないと弾がものすごいスピードで曲がってきて不自然な動きになります。
	// 加速度が大きすぎる場合は補正
	if(llVecMag(newacc) > 10)
	{
		newacc = newacc * 10 / llVecMag(newacc);
	}
不自然な弾の動き



最後に運動方程式(F=ma)を使って、加速度から力を求めて返します。
	// 力 = 質量 × 加速度    
    return llGetMass() * newacc;


あとは、llSetForce関数の第2引数をFALSEにして、グローバル軸にたいして力をかけてあげれば完了です。

完成した弾
LSL