セカンドライフでプログラム3(はねるボールを作ろう編)

セカンドライフ制作
スポンサーリンク

こんばんは。

セカンドライフでは、どんなものでも手軽に作れてしまいます。
obj
たとえば、右クリックメニューの制作を選ぶと、
いくつか好きな形のプリムを選べます。
上のメニューから分かるとおり、13種類ありまして、
さらにこのプリムそれぞれで、変形させたりもできて、より複雑な形を作れます。

また、プリムが基本的な形なるのですが、
1つのプリムを好きな形へ変形できるスカルプテッド・プリムや、
ほかのツールで作成したメッシュも利用できます。

今回は、「跳ねるボール」を作ってみたいと思います。
まずは球を選んで、跳ねるために物理オブジェクトに設定して、
特徴の材質をゴムにすれば、実際の世界のように上から落とせば跳ねるようになります。

ここまでは普通の感じで面白くないと思いますので、
今回はプログラムで跳ねるボール作ってみたいと思います。
ハイ、こっちが今回の記事のメインです。

nakami
プログラムはどんなプリムにも付属することができて、プリムを動かすことができます。
プログラムは中身の「新しいスクリプト」をクリックすれば無料で、作れます。
なお、「物理」は外しておいてください。
これは、後述するある関数を使用して、永遠に跳ねるボールを作りたいからです。
あと、「ファントム」はあってもなくてもどっちでもいいです。
ファントムがない方が座ることができますので、チェックを外しておくといいかも。

さて、実際にプログラムで動かすとなると、位置を取得する関数と、位置を設定する関数が重要になってきます。
その中でも物体の座標を設定できるllSetPos関数なわけですが、実は問題があります。

今回、ボールを跳ねさせたいので、動きをアニメーションのようにつける必要があります。
この時、while文等で、何も考えずに無限ループさせて、
少しずつ位置をずらすことを考えると思うのですが、これは恐らくSIMに負担をかけてしまう点と、
1関数呼ぶごとにエネルギーを消費してしまうため管理がしにくく、移動速度が安定しないような気がするわけです。

そこで、やっぱり物体を動かすとなると
通常は、次のように時間を操作できる関数を利用するパターンが必要となってきます。
つまり下記のような割り込みとポーリングの2種類のどちらかとなります。
llSetTimerEvent のタイマーイベントで定期的に動かす(イベント、割り込み※ といったもの)
llSleep で無限ループ(ポーリング、処理ループ方式 といったもの)
※セカンドライフのLSLでは多重割り込みは発生しません

ただ今日は、上の2種類のどれでもない方法で実装したいと思います。
今までのは前振りです。すみません。

今回使うのは何かといいますと、「llSetKeyframedMotion」を使います。ジャジャーン!!

これ、何がすごいかというと、
予めいろいろ動きを設定できるわけです。
また、最初に1関数呼ぶだけで、後は勝手に動きをしてくれるため、
負担も恐らくそんなにかからずスムーズに動くはずです。

ただもちろんこれにも欠点がありまして、
装備品には使えません!
物理属性には使えません!

まあ、なめらかに表示できるという利点があるので、ほかの欠点は、目をつむりましょう。

さっそくプログラムです。
llSetKeyframedMotion の関数は次のようになっています。

Function: llSetKeyframedMotion( list keyframes, list options );

第1引数と第2引数ともに、配列(リスト)を入れます。
第1引数には、キー(位置、回転、時間)を並べたリストを入れて、
第2引数には、オプション(オプション名、パラメータ値)を並べたリストを入れます。

つまり、あらかじめ跳ねるボールのモーションを入れておけば、
このモーションに沿ったアニメーションを続けるため、永遠に跳ね続けるというわけです。
詳しい解説は、セカンドライフ ミタコト キイタコト様の
[LSL] llSetKeyframedMotion を使ってみた」を参照してください。
なお、1つ注意点として、ここで設定する座標位置は、現在位置からの相対座標となります。
たとえば、現在地から1秒後に0.5mの地点、2秒後に1.2mの地点まで進むとすると、
0.5m(1秒)、0.7m(1秒)と各進んだ位置から相対座標と所要時間を設定します。

さて、モーションを作るにしても1つ1つ、
値を設定するのも面倒なので、自由落下の計算で自動設定しちゃいましょう。
ただそういう計算をするためには、現在のオブジェの地面からの高さを知りたいことに気付くはずです。
オブジェが存在する位置から、地面までの距離を調べるにはどうすればいいでしょうか。
これも便利な関数があります。llCastRayです。

Function: list llCastRay( vector start, vector end, list options );

この関数は、名前から想像するようにレイを飛ばして、
途中に存在するオブジェクトとの当たり座標を求めることができる関数です。
つまり、今存在するボールの位置から、地面へレイを飛ばして、1番初めに当たった位置が地面。
あとは、ボールの位置と地面の位置との差が高さとなります。
ちなみに、この関数は、当たった位置を調べることもできるのですが、法線ベクトルも取得ができます。
法線ベクトルとは、ある位置の表面から、外側へ垂線を出したときの単位ベクトルです。
このベクトルがあると、斜めにぶつかったら、その方向へ跳ね返るような計算も可能です。

さて、これらの話をまとめて作ったプログラムが下記となります。
オブジェクトをクリックすると、そのクリックした位置から地面へ垂直落下して跳ね返り、
永遠に跳ね返り運動を続けます。

プログラムには、これまでの話に合ったように、
地面からオブジェクトまでの距離を計算する関数や、
自由落下のアニメーションを作成する関数などが見て取れると思います。

ちなみに、途中で使われている「llOwnerSay」というのは、
自分のチャットに表示できるデバッグのようなものです。
多分皆さんもこれからよく使うようになると思います。

vector getGroundPosition(vector start) {
	// 最終位置
	vector end = <start.x, start.y, 0>;
	// オプション
	list options = [
		// 無視対象
		// RC_REJECT_TYPES, RC_REJECT_AGENTS | RC_REJECT_NONPHYSICAL,
		// 検出数
		RC_MAX_HITS, 1
	];
	list results = llCastRay(start, end, options);
	integer length = llGetListLength(results);
	integer calc = llList2Integer(results, length - 1);
	// 計算失敗 or 何もヒットなし(床なし)
	if((calc < 0) || (length == 1)) {
		return(start);
	}
	return(llList2Vector(results, 1));
}

vector getRegionPositionForRegionObject() {
	return(llGetRootPosition());
}


float getMyPrimHeight() {
	key myprimkey = llGetKey();
	list box = llGetBoundingBox(myprimkey);
	vector min_corner = llList2Vector(box, 0);
	vector max_corner = llList2Vector(box, 1);
	return(max_corner.z - min_corner.z);
}

mes(vector x) {
	llOwnerSay((string) x);
}

initSmoothMove() {

	float height = getMyPrimHeight();
	vector myposition = getRegionPositionForRegionObject();
	vector groundposition = getGroundPosition(myposition - <0.0, 0.0, height * 0.5>);
	
	float myz		= myposition.z;
	float groundz	= groundposition.z + height / 2;	// 自分の大きさの補正
	// 自分から地面までの距離
	float delta		= myz - groundz;
	
//	llOwnerSay("resion height " + (string)myz);
//	llOwnerSay("obj	height " + (string)height);
//	llOwnerSay("ground height " + (string)groundposition.z);
//	llOwnerSay("delta  height " + (string)delta);
	
	// 物理エンジンで使われるオブジェクトの形状タイプを設定する
	llSetPrimitiveParams([PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
	// 単体プリムの場合は llSetPrimitiveParams
	// 複数プリムの場合は llSetLinkPrimitiveParamsFast
	
	// KFM_MODE キーフレームのモード
	// KFM_FORWARD		A –> B –> C
	// KFM_REVERSE		C –> B –> A
	// KFM_LOOP			A –> B –> C -> A –> B –> C ...
	// KFM_PING_PONG	A –> B –> C –> B –> A –> B ...
	
	// KFM_DATA 		キーフレームのデータ型
	// DEFAULT			<ベクター型の位置>, <ローテーション型の回転>, float 型の秒数
	// KFM_TRANSLATION	<ベクター型の位置>, float 型の秒数
	// KFM_ROTATION		<ローテーション型の回転>, float 型の秒数
	
	// KFM_COMMAND キーフレームへコマンド
	// KFM_CMD_PAUSE	一時停止
	// KFM_CMD_STOP		停止
	// KFM_CMD_PLAY		再生
	
	// 時間は45fpsのフレーム単位の時間で設定すること
	
	// 初期化
	llSetKeyframedMotion([], []);
	
	{
//		llOwnerSay("init start");
		integer sampling_freq	= 45;
		float flame_time		= 1.0 / (float)sampling_freq * 5;
		float g					= -0.05;
		float vz				= 0.0;
		float z					= 0.0;
		integer i = 0;
		list motion = [];
		float old_z = 0.0;
		for(i = 0;i < 60;i++) {
			z	+= vz;
			vz	+= g;
			motion += [<0.0, 0.0, z - old_z>, flame_time];
			old_z = z;
			if(z < -delta) {
				jump label_1;
			}
		}
		@label_1;
//		llOwnerSay("init end");
		llSetKeyframedMotion(motion, [KFM_DATA, KFM_TRANSLATION, KFM_MODE, KFM_PING_PONG] );
	}
	
	llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
}

start() {
	initSmoothMove();
	llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_PLAY]);
}

stop() {
	llSetPrimitiveParams([PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
	llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
	llSetKeyframedMotion([], []);
}


default {
	
	state_entry() {
		stop();
	}
	
	on_rez(integer start) {
		stop();
		llResetScript();
	}
	
	changed(integer change) {
		stop();
		llResetScript();
	}
	
	touch_start(integer total_number) {
		start();
	}
}

ちなみに紹介ですが、上のプログラムでみんな大好きポッピンを作ってみました。
跳ね続けます。
poppin256

えっ!ボールじゃないって?

コメント

タイトルとURLをコピーしました