BlackSheep-LSL@Wiki prim間通信
※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。


はじめに


しばしばカツラなどに組み込まれている色変更のスクリプトを作ってみましょう。
単純に色を変えるだけのスクリプトであれば、ここまで読んできた方には簡単に出来ると思います。
ですが、今回は特にprim間の通信を行う方法使って、拡張性の高いスクリプトを考えてみたいと思います。

prim間通信とは、llMessageLinked()という関数を使い、リンクされているprim同士でメッセージをやり取りする方法です。
これを使うと、一つのprimを操作したときに、別のprimを操作することができるようになります。

色を変える


primの色を変えるのは簡単です。
llSetColor()関数を使います。

 llSetColor(vector color, integer face)

この関数の引数について説明しておきます。

  • vector color

今までも何度か出てきた色を指定するためのvector型引数です。
<R, G, B>の形式で、0.0~1.0の数字で赤・緑・青の色の強さを混ぜ合わせるものでしたね。
詳しくは「照明を作ろう」の回を参照して下さい。

  • integer face

primのどの面の色を変えるかの指定です。
面の番号については「看板を作ろう」の回で触れました。

例えば立方体primの上の面だけ赤色にしたいという場合は、
 llSetColor(<1.0, 0.0, 0.0>, 0);
このようになります。

リンクされている他のprimの色を変えるための関数もあります。
llSetLinkColor()関数です。

 llSetLinkColor(integer linknumber, vector color, integer face)

  • integer linknumber

色を変えたいprimのリンクナンバーを指定します。
リンクナンバーについては「ベンダーを作ろう」の回で説明しています。
この引数以外はllSetColor()関数と同じです。

例えばルートprimの全面を青に変えたいときは、
 llSetLinkColor(1, <0.0, 0.0, 1.0>, ALL_SIDES);
こんな感じですね。

すでに慣れてる皆さんであれば、これだけわかれば色を変えるスクリプトが出来るかと思います。

簡易版カラーチェンジャー


試しにllSetLinkColor()を使ったカラーチェンジャーを作ってみましょう。

list linknumbers=[ // 色を変えるリンクナンバーの一覧
  1,2,3
];

list colors = [ // 色名のリスト
  "red", "green","blue", 
  "yellow", "cyan","magenta", 
  "pink","orange", "purple",
  "grey","white","black"
];

list rgb = [ // 色名に対応したRGB値のリスト
  <1.0,0.0,0.0>, <0.0,1.0,0.0>, <0.0,0.0,1.0>, 
  <1.0,1.0,0.0>, <0.0,1.0,1.0>, <1.0,0.0,1.0>, 
  <0.965,0.668,0.648>, <0.936,0.504,0.0586>, <0.652,0.340,0.656>, 
  <0.5,0.5,0.5>, <1.0,1.0,1.0>, ZERO_VECTOR 
];

integer handle; // listenハンドル
integer channel=7; // listenチャンネル

default{
  touch_start(integer detected){
    if (llDetectedKey(0) == llGetOwner()){
      // オーナーがタッチした場合のみ作動
       handle=llListen(channel, "", llGetOwner(), "");
      llDialog(llGetOwner(),"Select Color",colors,channel);
      llSetTimerEvent(60.0);
    }
  }
  
  timer(){ // ダイアログのタイムアウト
    llSetTimerEvent(0.0);
    llWhisper(0,"Time out! Touch again for change color.");
    llListenRemove(handle);
  }

  listen(integer ch, string name, key id, string msg){
    llSetTimerEvent(0.0);
    llListenRemove(handle);
    integer i = llListFindList(colors, [msg]);
      // 色名のインデックスを取得
    if (i != -1){
     // インデックスが有効だった場合(リストにある色名だった場合)
      vector c = llList2Vector(rgb,i); // インデックスに対応するRGB値を取得
      integer j;
      for (j = 0; j < llGetListLength(linknumbers);j++){
        // リンクナンバーの一覧を一つずつ処理
        llSetLinkColor(llList2Integer(linknumbers,j), c, ALL_SIDES); // 色変更
      }
    }
  }
}

照明のスクリプトに似ています。
違うのはリンクに対応したところと、照明効果ではなく色を変更しているという点だけですね。

このスクリプトを試す際には3つ以上のprimがリンクされているオブジェクトを使って下さい。
リンクナンバー1~3のprimのみ色が変わります。

拡張性の問題


簡易版カラーチェンジャーをカツラに組み込むことを考えてみます。
どのprimの色を変更するかはリスト型変数linknumbersで管理していますので、linknumbersの定義さえ変えればOKです。

・・・・・・とは言っても・・・・・・。

カツラ・オブジェクトは、物にもよりますが、百個以上のprimがリンクされているものも珍しくありません。
200、300個というものも中にはあります。
髪の毛のprimだけは色を変え、髪飾りやリボンなどはそのままにしておきたいという場合もあるでしょう。
となると、100や200のprimのうち、色を変えるべきprimのリンクナンバーを調べ、それらを全てlinknumbersに書いていかなくてはなりません。
さらに、前髪をちょっと追加したり、余分な髪の毛primを一つ取り除いたりすると、それだけでリンクナンバーが変わってきます。
そのたびにリンクナンバーの調べなおしとlinknumbersの修正を行うとなると、これはもう面倒どころか戦意喪失しかねない手間になります。

数個のprimで構成されるものであれば、簡易版カラーチェンジャーのスクリプトでも良いのですが、カツラに組み込むのは少々非現実的でしょう。
もっと良い方法は無いものでしょうか?

そこでprim間通信を利用する方法を考えます。

prim間通信の仕組み


リンクされているprim間の通信には、llMessageLinked()関数とlink_message?イベントを使います。
llMessageLinked()関数が送信元、link_message?イベントが受信側です。
今回の場合は、ルートプリムから「色を変えろ」というメッセージを送信し、子プリムでそれを受信して色を変えるような仕組みを考えます。


子プリムには受信用のスクリプトを入れておかなければいけません。
今まではオブジェクトの中には一つのスクリプトだけ入れていましたが、実はスクリプトはオブジェクトの中に複数入れておくことができます。

buildツールでオブジェクトの編集を行う際、「Edit linked parts」(日本語版では「リンクされたパーツを編集」とかそんなん)にチェックを入れると、子プリムだけを選択することができます。
その状態でコンテンツを開き、スクリプトを作成すると、子プリム内にスクリプトを作ることが可能です。

子プリムのスクリプトにはメッセージの受信イベントと、受信したメッセージに応じて色を変える仕組みを書いておきます。
あとはこの子プリムをコピーして使うことで、色変更可能なパーツをどんどん増やすことができます。
色を変えたくないパーツに関しては、スクリプトの入っていないprimを使えば良いのです。

llMessageLinked()関数とlink_message?イベントの詳細を見てみましょう。

llMessageLinked関数


 llMessageLinked(integer linknum, integer num, string str, key id)

リンクされたprimにメッセージを送る関数です。

  • integer linknum

送信先のprimのリンクナンバーを指定します。
全てのprimに送信する場合はLINK_SETという値を使います。
全ての子プリムに送信する場合はLINK_ALL_CHILDREN、自分以外のprimに送る場合はLINK_ALL_OTHERS、ルートプリムに送る場合はLINK_ROOT、などの値が使えます。
今回はプリムの数が変わっても対応が効くよう、LINK_SET(全プリムに送信)を使います。

  • integer num

送信する数値です。
送りたい数値を好きなように指定できます。
今回は使いませんので、固定値0を指定します。

  • string str

送信する文字列です。
送りたい文字列を好きなように指定できます。
今回はこの引数に"<1.0,0.0,0.0>"などの色データを指定して送信します。

  • key id

送信するkey型データです。
送りたいkey型データを好きなように指定できます。
今回は使いませんので、NULL_KEYを指定します。

link_messageイベント


 link_message(integer sender_num, integer num, string str, key id)

llMessageLinked()関数で送信されたメッセージを受信したときに発生するイベントです。

  • integer sender_num

送信してきたプリムのリンクナンバーが入ってきます。
今回はルートプリムからメッセージを送信しますので、ここには1の値が入ってきます。

  • integer num, string str, key id

メッセージの内容です。
それぞれllMessageLinked()関数で指定された値が入ってきます。
今回は文字列型のメッセージのみ使いますので、着目すべきはstrの値です。

カラーチェンジャースクリプト


簡単なほうから行きます。
メッセージを受信して色を変える、子プリムのスクリプトです。

default {
  link_message(integer sender_num, integer num,
               string str, key id){
    vector c = (vector)str;
    llSetColor(c, ALL_SIDES);
  }
}

これだけです。
受信した文字列型メッセージ(str)をvector型に変換し、llSetColor()関数を使って色を変えます。
「何色に変えるか?」は送信側(ルートプリムのスクリプト)で指定しますので、子プリムのほうは何も考えずに受信した色に変えるだけです。

次にルートプリムのスクリプトですが、先ほどの簡易版カラーチェンジャーとほとんど同じです。
llSetLinkColor()関数で色を変えていた部分を、llMessageLinked()関数に置き換えるだけですね。

list colors = [
  "red", "green","blue", 
  "yellow", "cyan","magenta", 
  "pink","orange", "purple",
  "grey","white","black"
];

list rgb = [
  <1.0,0.0,0.0>, <0.0,1.0,0.0>, <0.0,0.0,1.0>, 
  <1.0,1.0,0.0>, <0.0,1.0,1.0>, <1.0,0.0,1.0>, 
  <0.965,0.668,0.648>, <0.936,0.504,0.0586>, <0.652,0.340,0.656>, 
  <0.5,0.5,0.5>, <1.0,1.0,1.0>, ZERO_VECTOR 
];

integer handle;
integer channel=7;

default{
  touch_start(integer detected){
    if (llDetectedKey(0) == llGetOwner()){
      handle=llListen(channel, "", llGetOwner(), "");
      llDialog(llGetOwner(),"Select Color",colors,channel);
      llSetTimerEvent(60.0);
    }
  }
  
  timer(){
    llSetTimerEvent(0.0);
    llWhisper(0,"Time out! Touch again for change color.");
    llListenRemove(handle);
  }

  listen(integer ch, string name, key id, string msg){
    llSetTimerEvent(0.0);
    llListenRemove(handle);
    integer i = llListFindList(colors, [msg]);
    if (i != -1){
      vector c = llList2Vector(rgb,i);
      llSetColor(c, ALL_SIDES);
      llMessageLinked(LINK_SET, 0, (string)c, NULL_KEY);
    }
  }
}

簡易版カラーチェンジャーでは、色を変えたいパーツ全てに対していちいちllSetLinkColor()関数を実行していましたが、今回はllMessageLinked()で全プリムに対して色データを送信するだけです。
これだけで、受信用スクリプトの入っているプリムは全て色が変わります。
リンクナンバーがどのように変わっても問題はありません。

ロングタッチ


さて。
以上でカラーチェンジャーとしては機能しますが、もう一工夫しましょう。
今のままだと、クリックしたときにダイアログが開きます。
機能的には問題はありませんが、カツラなどにこのスクリプトを入れた場合、アバターの向きを変えようと思ってうっかりカツラをクリックすると、そのたびにダイアログが表示されてしまいます。
実際にそのようなカツラを身に着けてみるとわかりますが、なかなかにウットオシイものです。

そこで、一定時間マウスボタンを押しっぱなしにした場合にのみ、ダイアログが表示されるような仕組みに変えてみたいと思います。
以下のようになります。

list colors = [
  "red", "green","blue", 
  "yellow", "cyan","magenta", 
  "pink","orange", "purple",
  "grey","white","black"
];

list rgb = [
  <1.0,0.0,0.0>, <0.0,1.0,0.0>, <0.0,0.0,1.0>, 
  <1.0,1.0,0.0>, <0.0,1.0,1.0>, <1.0,0.0,1.0>, 
  <0.965,0.668,0.648>, <0.936,0.504,0.0586>, <0.652,0.340,0.656>, 
  <0.5,0.5,0.5>, <1.0,1.0,1.0>, ZERO_VECTOR 
];

integer handle;
integer channel=7;

integer counter=0;

default{
  touch_start(integer detected){
    if (llDetectedKey(0) == llGetOwner()){
      counter=0;
    }
  }

  touch(integer detected){
    if (llDetectedKey(0) == llGetOwner()){
      if (counter < 50){
        counter ++;
      }else if (counter == 50){
        counter ++;
        handle=llListen(channel, "", llGetOwner(), "");
        llDialog(llGetOwner(),"Select Color",colors,channel);
        llSetTimerEvent(60.0);
      }
    }
  }
  
  timer(){
    llSetTimerEvent(0.0);
    llWhisper(0,"Time out! Touch again for change color.");
    llListenRemove(handle);
  }

  listen(integer ch, string name, key id, string msg){
    llSetTimerEvent(0.0);
    llListenRemove(handle);
    integer i = llListFindList(colors, [msg]);
    if (i != -1){
      vector c = llList2Vector(rgb,i);
      llSetColor(c, ALL_SIDES);
      llMessageLinked(LINK_SET, 0, (string)c, NULL_KEY);
    }
  }
}

touch?イベントは「マウスボタンを押している間、繰り返し発生する」イベントです。
前にちょろっと説明しました。

このスクリプトではcounterという変数を用意し、タッチを開始したとき(touch_start?イベント)にcounterを0に初期化し、タッチし続けている間(touch?イベント)counterを増加させます。
そしてcounterが50に達した時点で、ダイアログを表示しています。

私の環境では1秒間に10回ほどtouch?イベントが発生していましたので、50だと約5秒です。
5秒間マウスボタンを押しっぱなしにしないとダイアログが出ませんので、うっかりカツラをクリックしたとしても、ダイアログに煩わされることはなくなるでしょう。

念のために補足しますが、touch?イベントにはバグがあるという話があります。
マウスボタンを離しているにも関わらず、touch?イベントが発生し続けるという現象があるようですので、使用の際には注意して下さい。

今回のポイント


  • 色の変更:

 llSetColor(vector color, integer face)

  • リンクされているprimの色の変更:

 llSetLinkColor(integer linknumber, vector color, integer face)

  • prim間メッセージの送信:

 llMessageLinked(integer linknum, integer num, string str, key id)

  • prim間メッセージの受信:

 link_message(integer sender_num, integer num, string str, key id)

リンクされているprim同士であればllMessageLinked()関数が使えます。
リンクされていないprim同士の場合はlistenを使うことになります。
もちろん、listenはリンクされているprim間でも有効ですが、以前書いたとおりlistenは負荷の原因となる可能性が高く、多用はお勧めできません。
llMessageLinked()関数が使える場合は極力llMessageLinked()関数を使うべきでしょう。

llMessageLinked()関数はlisten同様、非常に汎用性が高いので、ぜひ覚えて活用してみて下さい。

名前:
コメント: