ソラマメブログ

2007年04月05日

テレポート

初心者さん向けのスクリプト解説記事を書いたところ、
「んなことはわかってる!もっと役に立つことを書かんかい!」
というお叱りを受けたので(笑)、ある程度スクリプトのことはわかっている人向けに、スクリプトの小技を紹介するカテゴリを設けました。

まずは有名どころから。
「テレポートハック」と呼ばれるスクリプトです。
最初にスクリプトコードを載せておきます。


01:  vector offset = <0.0, 0.0, 10.0>; // テレポート位置
02:  rotation rot = ZERO_ROTATION;
03:  
04:  default {
05:    state_entry(){
06:      llSitTarget(offset, rot);
07:    }
08:    changed(integer change){
09:      if (change & CHANGED_LINK){
10:        key k = llAvatarOnSitTarget();
11:        if (k != NULL_KEY) {
12:          llUnSit(k);
13:        }
14:      }
15:    }
16:  }


ご覧の通り、それほど複雑なものではありません。
このスクリプトのポイントは2つです。
1つは、llSitTarget()を使って、テレポート位置を設定しているところです(6行目)。
llSitTarget()はそのオブジェクトにアバターがsitしたときの位置と回転角度を設定する関数です。
ここではスクリプトの先頭で変数を定義し、offsetというvector型の変数に位置を、rotというrotation型の変数で回転角度を設定しています(1-2行目)。
アバターがこのオブジェクトに座ると、オブジェクトの中心からoffset分だけ離れた位置に、rot分回転して座ることになります。
今回はZ軸方向に10m離れた位置をoffsetとしているので、アバターはオブジェクトの10m上空に座ることになります。

ポイントの2番目は、llUnSit()関数を使って座ったアバターを強制的に立たせているところです(12行目)。
オブジェクトにアバターが座っているかどうかは、changedイベント(8行目)とllAvatarOnSitTarget()関数(10行目)で判定します(sitイベントとかunsitイベントがあると楽なのですが、存在しません)。
アバターがオブジェクトの上にsitする行為は、内部的にはlinkとして捉えられます。
changedイベントはオブジェクトの状態が変化したときに起こるイベントですが、何が変化したのかはchangedイベントの引数changeで知ることができます。
引数changeはビットフラグになっていますので、判定する際には&演算を行ってマスクします(9行目)。
ビット演算についてよくわからない方は、
  if (change & CHANGED_LINK)
このif文で判定ができると覚えておくだけでも良いでしょう。このif文がTRUEのときは、オブジェクトのlink状態が変化しています。
今回はlinkが変わったかどうかを判定するのでCHANGED_LINKを使っていますが、例えばオーナーが変わったかどうかを判定する際にはCHANGED_OWNER、オブジェクトインベントリの中身が変わったかどうかを判定するにはCHANGED_INVENTORYを使ったりします。

次にllAvatarOnSitTarget()関数を使っています(10行目)。
この関数は、オブジェクトの上に座っているアバターのUUIDを取得する関数です。
誰も座ってないときにはNULL_KEYが返ります。
逆に言えば、NULL_KEY以外の値のときは誰かが座っているということですので、ここでは誰かが座っていたら問答無用で、llUnSit()関数を発動しています。
llUnSit()関数は指定したkeyのアバターを強制的に立たせる関数です。

以上2つのポイントを実装することにより、擬似テレポーターが実現できます。
アバターがこのオブジェクトにsitすると、上空10mの位置に移動し、即座に立つことになりますので、建物などで上の階に移動するスイッチを作る際に役立つでしょう。
オブジェクトの設定を「クリックしたときに座る」ようにしておくと、ワンタッチでテレポート可能になります。

なお、この方法で移動できる距離はXYZいずれの方向に関しても最大300mまでです。
上空600mのスカイハウスなどを作る際には別途工夫が必要です。
オブジェクトの角度にも注意して下さい。
llSitTarget()はオブジェクトのローカル座標で動作しますので、オブジェクトが横に倒れていたりすると、上空に飛ばすつもりが真横に飛ばされることになります。

また、llSitTarget()はアバターが座った状態でセットしても、そのアバターの位置を変更してくれません(有効になるのは次に座ったアバターの位置です)。
ですので、複数のテレポート先を選択できるような仕組みにする際には注意して下さい。llSitTarget()を使ったテレポートでは、座らせた後にテレポート先を選択させることができません。。
まずテレポート先を選択し、それから座る、という2段階のオペレーションになりますので、利用する立場からすると少々面倒になります。

シンプルなオペレーションで、複数のテレポート先に対応するには以下のようなスクリプトを使います。


01:  vector mSitPos = <0.0,0.0,0.1>;
02:  rotation mSitRot = ZERO_ROTATION;
03:  string mSitText = "Teleport";
04:  
05:  key mAvatar = NULL_KEY;
06:  integer mRun = FALSE;
07:  integer mListenHandle = 0;
08:  integer mListenChannel = -1;
09:  list mTeleport = [
10:    "place1",
11:    "place2"
12:  ];
13:  
14:  list mTeleportPoint = [
15:    <30.0,30.0,600>,
16:    <30.0,30.0,500>
17:  ];
18:  
19:  teleport( vector destpos ) {
20:    integer jumps = (integer)(llVecDist(destpos, llGetPos()) / 10.0) + 1;
21:    list rules = [ PRIM_POSITION, destpos ];
22:    integer count;
23:    for (count = 0; count < jumps; count ++ ){
24:      llSetPrimitiveParams( rules );
25:    }
26:  }
27:  
28:  default {
29:    state_entry(){
30:      llSitTarget(mSitPos,mSitRot);
31:      llSetSitText(mSitText);
32:      mRun = FALSE;
33:    }
34:    
35:    on_rez(integer start_param){
36:      llResetScript();
37:    }
38:    
39:    changed(integer change){
40:      if (change & CHANGED_LINK){
41:        llSleep(0.5);
42:        mAvatar = llAvatarOnSitTarget();
43:        if (mAvatar != NULL_KEY) {
44:          mListenHandle = llListen(mListenChannel,"",NULL_KEY,"");
45:          llDialog(mAvatar, "Where you go?",mTeleport,mListenChannel);
46:          llSetTimerEvent(30);
47:        }
48:      }
49:    }
50:    
51:    timer(){
52:      llWhisper(0,"Timeout!");
53:      llSetTimerEvent(0);
54:      llUnSit(mAvatar);
55:      mAvatar = NULL_KEY;
56:      llListenRemove(mListenHandle);
57:    }
58:    
59:    listen(integer channel, string name, key id, string message){
60:      if (channel == mListenChannel){
61:        if (id == mAvatar){
62:          llSetTimerEvent(0);
63:          llListenRemove(mListenHandle);
64:          integer tid = llListFindList(mTeleport, [message]);
65:          if (tid != -1){
66:            vector orgPoint = llGetPos();
67:            llWhisper(0,"Teleport to " + message + ", please wait.");
68:            mRun = TRUE;
69:            teleport(llList2Vector(mTeleportPoint,tid));
70:            llWhisper(0,"Teleport complete.");
71:            llUnSit(mAvatar);
72:            teleport(orgPoint);
73:          }else{
74:            llWhisper(0,"Teleporter received a illegal message!");
75:            llUnSit(mAvatar);
76:            mAvatar = NULL_KEY;
77:          }
78:        }
79:      }
80:    }
81:  }


とたんに複雑になりましたが、このスクリプトの肝はteleport関数です(19-26行目)。
このユーザー関数は、移動先のvectorを引数として、オブジェクトを移動先まで10mずつ動かします。
なぜ10mずつかというと、llSetPrimitiveParams()関数によるオブジェクトの移動距離が最大で10mまでに制限されているためです。
20行目でllVecDist()関数を使って移動先までの距離を取得し、それを10で割っています。仮に距離が100mだったとしたら、移動は10mごとですので、10回の移動をしなければなりません。変数jumpsは移動先に到達するまでに必要な移動の回数になります。
21行目はllSetPrimitiveParams()関数に渡すためのパラメータ設定です。PRIM_POSITIONは「位置を設定しろ」という定数パラメータで、位置は引数destposをそのまま使っています。
23-25行目のループが実際の移動になります。変数jumpsの回数分だけ移動を繰り返しています。
llSetPrimitiveParams()関数はprimのあらゆるパラメータを設定できる関数ですが、ここでは単に位置をdestposにするよう指定しているだけです。
先ほど書いたように、移動距離の最大は10mですので、オブジェクトは位置destposに向かって10mずつ移動していきます。
llSetPrimitiveParams()関数の実行には0.2秒かかるため、秒速50mになります。従って600m上空に移動するには12秒ほどかかる計算です。

このスクリプトは、アバターが座ったとき、まず移動先を選択するダイアログを表示します(39-49行目)。
ダイアログの使い方については省略するとして、プレイヤーが移動先を選択すると、オブジェクトはアバターを座らせたまま、teleport関数で移動先に向かいます(59-69行目)。
移動先に到着すると、メッセージとともにアバターを強制的に立たせます(70-71行目)。
それからオブジェクトは元の位置へと戻っていきます(72行目)。

座ると勝手にダイアログが出て選択するだけですのでオペレーションはシンプルです。
基本的には移動距離の制限もありません(ただしSIM内に限ります)。
難点はテレポートに時間がかかってしまうこと、それと地面を突き抜けての移動ができないことです。
例えば移動経路に盛り上がった地面(山)があったりすると、そこでオブジェクトは引っかかってしまい、目的地に到達できません。

なお、このスクリプトは地面に引っかかってしまった場合や、移動中にアバターが立ち上がってしまった場合などを考慮していませんので、完全なものではありません。
あくまでも基本的なところを実装したものですので、実際に使う際には改良が必要になるでしょう。

【余談】
以前のバージョンでは、llSetPrimitiveParams()関数に複数の移動を同時に指定することができました。
例えばパラメータとして、
[ PRIM_POSITION, destpos, PRIM_POSITION, destpos, PRIM_POSITION, destpos, PRIM_POSITION, destpos ]
このように4回分の移動を指定すると、一度のllSetPrimitiveParams()関数呼び出しで40mの移動が可能でした(内部的には10mの移動を4回行っていたと思われる)。
ですので長距離の移動の場合も一瞬でテレポートできていたのですが、現在は一度のllSetPrimitiveParams()関数呼び出しで一回の移動しかできなくなったため、必要な回数分だけllSetPrimitiveParams()関数を使わなければならなくなったようです。


追記:2007-4-7

最新バージョンでは再び一瞬のテレポートが可能になりました。
teleportユーザー関数を以下のようにすると、一度のSetPrimitiveParams()関数で複数回の移動が可能です(長距離であっても一瞬で移動できる)。


teleport( vector destpos ) {
  integer jumps = (integer)(llVecDist(destpos, llGetPos()) / 10.0) + 1;
  list rules = [ PRIM_POSITION, destpos ];
  integer count = 1;
  while ( ( count = count << 1 ) < jumps){
    rules = (rules=[]) + rules + rules;
  }
  llSetPrimitiveParams( rules + llList2List( rules, (count - jumps) << 1, count) );
}



同じカテゴリー(スクリプト小技)の記事画像
リモートロード
同じカテゴリー(スクリプト小技)の記事
 モジュール化 (2007-09-04 12:15)
 高度なカメラ制御 (2007-07-19 12:15)
 リモートロード (2007-05-29 18:02)
 Emailの送受信 (2007-05-23 15:18)
 鍵をかける (2007-05-10 16:24)
 パーティクル (2007-04-09 17:07)
この記事へのトラックバック
この間の1プリム椅子にscriptを組み込んで、座ったとたんに別の位置へ一瞬でワープする装置に仕立て上げました。この椅子は、これからたまにギャラリーに湧き出るはずです。そちらの詳...
瞬間ワープ はまりポイント【Harayoki's】at 2007年04月27日 20:40
試しに作ってみました。いとうSNSの火曜夜10時からのチャットで、こちらもご利用ください。 場所は、Nippori 207,86,22です。ぜひ遊びに来てくださいね。 (追記) まだ家具を並べただけで...
[セカンドライフ]W-ZERO3応援団セカンドライフ分室を作成【伊藤浩一のW-ZERO3応援団/Advanced】at 2007年07月30日 16:47
 
Runarin殿依頼のDiamond Smileを無事作り終え申したが、彼女はそれを販売致すショップRuna & Rinの700mをば超え
700m超え上空テレポーター修練をお伝え致す【Rinsui SL+ Making Blog】at 2007年09月18日 22:12
この記事へのコメント
あれ?
関数の定義は、teleportなのに、呼び出し先では te"r"eport
となっていませんか?
Posted by ima at 2007年06月14日 20:58
>imaさん

おお、確かにオオボケかましてますね(^^;
ご指摘ありがとうございます。
修正しておきました。
Posted by Miz at 2007年06月15日 09:36
面白い情報に感謝
Posted by essequess at 2011年09月08日 11:50
 
<ご注意>
書き込まれた内容は公開され、ブログの持ち主だけが削除できます。