ソラマメブログ

2007年12月01日

ホーミング弾の作り方

以前ちらっと書いたとおり、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(<0.0,0.0,0.0>), 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;
図1


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



チェックが終わった後は、弾に加えるべき加速度を計算しています。これは、以下のような手順で行います。
  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;
図3
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物理

同じカテゴリー(LSL)の記事画像
オブジェクトのコンテンツに自分自身を入れて再帰Rezしたい
Babbage Linden さんが来ました
LSLCon2008 開幕
LSL-Editorでユーザ定義変数も補完する
パーティクル小ネタ
物理オブジェクトの回転を防ぐ
同じカテゴリー(LSL)の記事
 LSLのC#実装は凍結らしいです (2010-07-02 03:23)
 LSLに導入されるC#ってバージョン1.0の予定なの? (2010-06-27 14:26)
 Eclipse+VrapperでのLSL開発 (2010-02-20 22:55)
 Web連携するときのオブジェクトの識別子 (2009-08-15 17:55)
 インワールドからWebサーバへポーリング (2009-04-28 23:50)
 オブジェクトのコンテンツに自分自身を入れて再帰Rezしたい (2008-12-17 01:55)

Posted by sabro at 21:06│Comments(6)LSL
この記事へのコメント
はじめましてNITといいます~
最近物理系いじりまくってるのでとても面白く読めました
座標求めてインパルスとかいろいろやってましたが
フォースを使えば滑らかに動きますものね~
このホーミングベリーグッドです~。いただきます♪
Posted by NIT Dinzeo at 2007年12月11日 12:55
お役に立って何よりでした^^
正直この記事の内容を使いたい人っていないんじゃ、と思ってたので使ってもらえたらうれしいです~。

NITさんって名前、なんか聞いたことあると思ったら、前にメテオの記事読ませていただいた人ですね~。あれおもしろそうなので、うちのゲームで使わせてもらおうかなとか考えてました ̄∇ ̄
Posted by sabrosabro at 2007年12月11日 22:08
ぶ!wwwごめんなさいw書きっぱなしだったwww
一か月遅れのレスww
今ちょっと別に重たいもの作ってて(一か月も…)、しばらくソラマメ放置でしたw
なんか作る方向のベクトルが似てるっぽいからいずれ
お会いしたいですね~
で、今重たいものの制作と別に ガンダムのファンネルっぽいのを
作ってくれと言われてます・・・ついにホーミングの処理を
流用させてもらう時が来そうですww
ではではまた~♪
Posted by NIT Dinzeo at 2008年01月15日 11:42
ファンネルとはまた面白そうですね^^
SLでお会いしたときは、よろしくです~
Posted by sabrosabro at 2008年01月15日 23:02
はじめましてfuuAllenです。

最近 自動追尾するミサイルを作りたくて・・・
参考になるスクリプトを探していたら ここに辿り着きました。

初歩的なスクリプトしか解らない私にとって
サンプルのスクリプトを見たら早くも挫折しそうな予感がしてきました^^;

スクリプトって難しいですねorz
Posted by fuuallen at 2008年02月07日 03:07
fuuAllenさん、こんにちは。

ブログ拝見させてもらいましたが、服、スカルプとか、すでにすごい腕前なのに、さらにスクリプトを覚えるなんて、すごい向上心ですね^^

物理系の関数だけ覚えれば、後は中学物理の応用でなんとかなるのでがんばってください~。

私も、見習って、アニメーションやスカルプを頑張らねば ><
Posted by sabro at 2008年02月07日 10:08
上の画像に書かれている文字を入力して下さい
 
<ご注意>
書き込まれた内容は公開され、ブログの持ち主だけが削除できます。