BlackSheep-LSL@Wiki 衝突判定

※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。


はじめに


初級スクリプトとして触れておきたい事項も最後となりました。
最後に扱うのはオブジェクトの衝突についてです。

物理オブジェクトを扱いだすと、いろいろな場面で衝突判定をしたりするのですが、通常のオブジェクトにおける衝突判定も実は結構有用です。
例えば以前、センサーを利用した自動ドアを作りました。
あのスクリプトは衝突を用いて書き直すことが可能です。

衝突・・・なんていうと猛スピードで激突するイメージかもしれませんが、lslで言う衝突はもっと軽いものも含みます。
例えばアバターが歩いてきてオブジェクトを踏んだとき、それだけで衝突のイベントが起きるのです。
自動ドアの前にマットを用意しておいて、それを踏んだらドアが開く、という古典的な仕掛けが実現できます。

実際に衝突を使った自動ドアを作ってみましょう。

衝突イベント


衝突イベントには3種類あります。
いや、正確には6種類ですが、まずは基本の3つからで良いでしょう。

 collision_start(integer num_detected){
   // 衝突が始まったときの処理
 }

 collision(integer num_detected){
   // 衝突し続けているときの処理
 }

 collision_end(integer num_detected){
   // 衝突が終わったときの処理
 }

タッチイベントと似ていますね。
実際、それぞれのイベントの意味は3つのタッチイベントと同じです。
通常使うのはcollision_start?イベントです。
ぶつかった瞬間に発生するイベントですので、単発的に「ぶつかったとき」の処理をするにはこれを使います。

引数num_detectedは「衝突している数」を意味します。
このあたりもタッチイベントと同様です。
具体的に「誰/何が衝突したのか」「衝突した位置はどこか」「衝突したもののUUIDは何か」などは、先日センサーのときに出てきたDetected系の関数を使って取得します。
例えば衝突したものの名前を得るにはllDetectedName()関数、UUIDを得るにはllDetectedKey()です。

それほど難しくないと思いますので少々余談をします。

これらの衝突イベントは、ファントム・オブジェクトでは通常発生しません。
ファントム・オブジェクトとは、buildツールの「オブジェクト」タグで「ファントム」にチェックが入っているオブジェクトのことです。
そこにあるように見えますが、ぶつかろうとするとスルリと通り抜けてしまうオブジェクトですね。

要するに「幻」であるため、衝突イベントも起こらないようになっているのですが、時にはファントム・オブジェクトでも衝突判定を行いたい場合があります。
例えば目に見えないファントム・オブジェクトを特定位置に配置しておいて、そこを誰かが通ったときに何か仕掛けを動かしたい、なんて時ですね。
その場合は、以下の関数を使います。

llVolumeDetect?(integer detect);

引数にはTRUEかFALSEを指定します。
TRUEを指定すると、ファントム・オブジェクトで衝突イベントが起こるようになります。
FALSEにすると衝突イベントが起こらなくなります。
この関数はルートプリム内のスクリプトで実行しなければなりません。

ただし、この方法で発生するのはcollision_start?イベントとcollision_end?イベントのみです。
ファントムオブジェクトでcollision?イベントを発生させることはできませんので注意して下さい。

また、ファントムではないオブジェクトでllVolumeDetect?関数を使うと、オブジェクトがファントムになってしまいます(^^;

ま、余談はこのくらいで・・・。

衝突版自動ドア


では自動ドアを作ってみましょう。
前に作ったドアを再利用してもいいですが、衝突判定するためのマット等を追加して下さい。
ルートプリムはマットに設定します。

今回はドアの部分だけをスライドさせますが、複数の子プリムをまとめて動かすことはできません。
それぞれをバラバラに動かしますので、あまりプリムの数が多いと動きが怪しくなります。

こんな感じのドアがベストではないでしょうか。


ではスクリプトです。
ルートプリムに仕込むメインのスクリプトと、子プリムに仕込むスクリプトの二種類があります。
メインスクリプトは衝突判定を行い、子プリムのスクリプトは開く/閉じる動作を行います。
スクリプト間の通信は、以前使ったllMessageLinked関数を使います。

まずはメインのスクリプトから。

default {
  collision_start(integer num_detected){
    llMessageLinked(LINK_SET, 1, "", NULL_KEY);
  }
}

いいのかこれだけで・・・って感じですが(^^;

ルートプリム(マット部分)に仕込むメインスクリプトは、あくまでも衝突の判定だけを行い、誰かがぶつかったらリンクメッセージを飛ばすだけです。
これだけですとアバターだけでなく、オブジェクトがぶつかっただけでも反応しますので、アバター限定にするのであれば、

default {
  collision_start(integer num_detected){
    if (llDetectedType(0) & AGENT){
      llMessageLinked(LINK_SET, 1, "", NULL_KEY);
    }
  }
}

このようにします。

llDetectedType()関数は衝突したのが何なのか(アバター?オブジェクト?)を取得するための関数です。
これに「& AGENT」と付けてやることで、アバターかどうかの判定ができます。

続いて子プリム(ドア部分)のスクリプトです。

vector close_pos;
vector open_pos = <0.0, 1.0, 0.0>;
integer opened = FALSE;

default{
  state_entry(){
    close_pos = llGetLocalPos();
  }
  
  link_message(integer sender, integer num, string str, key id) {
    if (!opened){
      llSetPos(close_pos + open_pos);
      opened = TRUE;
    }
    llSetTimerEvent(5.0);
  } 

  timer() {
    if (opened){
      llSetPos(close_pos);
      opened = FALSE;
    }
    llSetTimerEvent(0.0);
  }
}

こちらも複雑ではないですね。
前に作ったセンサーのドアからの改造です。

センサーのときは、誰かを検知したらオープン、誰も検知しなかったらクローズにしていましたが、今回はリンクメッセージを受け取ったらオープンし、最後にメッセージを受け取ってから5秒たつと自動的にクローズします。

また、ステートエントリー内で「閉まったときの位置」を取得している部分に注意して下さい。
センサーのときはここでllGetPos()を使っていましたが、今回はllGetLocalPos()を使っています。

llGetLocalPos()関数は、子プリムの相対位置を取得するための関数です。
子プリムでllSetPos?()を使うときには、相対位置での指定になりますので、close_posに絶対位置を保持してしまうとおかしなことになります。


最終回にして説明する基本的なことですが(^^;
相対座標と絶対座標とは、上記のような関係です。

llSetPos?()はルートプリムで使うときには絶対座標を使いますが、子プリムで使うときには相対座標です。
今回は子プリムをllSetPos?()で動かしますので、相対座標を使わなければなりません。
ですので、絶対座標50,25,21ではなく、相対座標0,0,1のほうを取得しているのです。

今回のポイント


  • 衝突イベント:

 collision_start(integer num_detected){
   // 衝突が始まったときの処理
 }

 collision(integer num_detected){
   // 衝突し続けているときの処理
 }

 collision_end(integer num_detected){
   // 衝突が終わったときの処理
 }

  • 絶対座標と相対座標:

関数 ルート 子プリム
llSetPos 絶対座標 相対座標
llGetPos 絶対座標 絶対座標
llGetLocalPos 絶対座標 相対座標

これだけでもややこしいかもしれませんが、アタッチメントになるとさらに複雑ですw

アタッチメントの場合:
関数 ルート 子プリム
llSetPos アバターとの相対座標 ルートとの相対座標
llGetPos アバターの絶対座標 ルートとの相対座標
llGetLocalPos アバターとの相対座標 ルートとの相対座標

最後に


初級スクリプト講座はこれにて一旦幕としたいと思います。
スクリプトを学んでいく上で基本的なことは一通り書いてきたつもりですが、おそらく十分ではないことも承知しています。
今後も単発的に「これは書いておいたほうがいいなぁ」と思うことは記事にしていくつもりでいます。

私がこの一連の記事を書いた動機は、多くの人にlslで遊んでいただきたかったからです。
スクリプトを仕込んだ商品を売っている立場としては、ノウハウを書くことはある意味自分の首を絞めることになるのですが、自分の商品が売れる以上に、多くの面白いものがSL内に出現したほうがSL全体にとってプラスであることは言うまでもないことでしょう。
私のつたない記事を読んでスクリプトに親しんだ皆さんが、楽しいアイテムをいろいろと開発して下さったなら、それ以上に嬉しいことはありません。

なるべく詳細な解説を心がけたのは、スクリプトはオープンであるべきだと思っていたからです。
lslの解説本を発行する企画などもあるようですが(あるいはもう出版されたりしているのかな?w)、スクリプトのスキルをオープンなものにしていくのが目的ならば、必ずしも書籍化の必要は無いと思っています。
むしろコメントやメール等で読者さんと直接にコミュニケーションを取れるWebのほうが目的にはかなっているはずです。
そういう意味ではSLをネタにして商売しようとする方々に対する挑戦でもありましたw

私はあくまでもSLはツールに過ぎないと思っています。
lslも同じです。
勝負すべきところは「ツールを使って何を作るか」であって、ツールそのものを売りにするのはリンデンだけで十分ですw
そのリンデンでさえ、クライアントやサーバーのオープン化を進めているのですから、lslの情報もやはりオープンかつフリーであるべきなのです。

そんな気持ちを隠しつつ連載を続けてきましたが、正直な話、毎日これだけの記事を更新するのは非常に大変でした(^^;
続けてこれたのは読者の皆さんの応援があったからこそです。
特に「書籍になってもおかしくない内容だ」「ガイドブックより詳細だ」などの評価にはニンマリしておりましたw

急いで書き散らしたところもありますので、コード等に不備があったり、十分に説明がなされていなかったりした点については深くお詫びいたします。
気づき次第修正していますので(すぐ直せるのもWebの利点ですよね)、おかしな点などありましたらご指摘下さい。
また、質問等もご遠慮なくコメントしてくださって構いません(^^

では、ほぼ一ヶ月にわたってお付き合い下さった読者の皆様に感謝しつつ、これにて初級スクリプトの終わりと致します。
ありがとうございました。