BlackSheep-LSL@Wiki ダイアログを使おう
※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。


はじめに


照明スクリプトだけで三回にも及んでしまいました(^^;
ですが、照明スクリプト一回目の「ユーザー関数」、二回目の「listen」、そして今回の「ダイアログ」はlslの中でも使用頻度の高いものばかりですので、ぜひとも使い方をマスターしていただきたい部分です。
この回から初めて見たという方は、第六回の「照明を作ろう」から戻ってお読みいただくのが良いかと思います。
照明のスクリプト作成について順を追って解説しております。

さすがに照明スクリプトがこれだけ続くと、いいかげん目がチカチカして来そうですw
今回は頑張って完成まで行き着きたいと思います。さて照明スクリプトですが、前回までで、チャットで色名を発言することで照明の色が変化するところまで出来ています。
照明としての機能はすでに実装済みですが、いちいちチャットで色名を入力するのが手間だという問題が残っています。
今回は「ダイアログ」を使うことで、より容易に色の変更が可能な照明を作り、一応の完成としたいと思います。

ダイアログ


ダイアログとは、確認メッセージの表示や簡単な入力などに使われる小さなウィンドウのことです。
SLでもいろいろなところで使われていますので見たことがある人も多いかと思いますが、画面の右上に出てくる青いウインドウですね。
いくつかのボタンが表示されていて、それを押すことで操作します。


複数の選択肢の中から一つを選ぶ操作には、ダイアログが最適です。
ダイアログウインドウに選択可能な色名を表示し、好みの色を押すと照明の光がその色になるようにすれば、非常に容易なオペレーションになるでしょう。

さっそく見ていきましょう。
まずはダイアログを表示する命令からです。

llDialog関数


 llDialog(key id, string message, list buttons, integer chat_channel);

これまた複雑そうな命令です・・・。
引数が4つありますが、順番に説明していきましょう。

  • key id

ダイアログを表示するアバターのUUIDを指定します。
UUIDというのは、前回も出てきましたが、SL内の全てのものに割り振られているユニークなIDのことです。
ダイアログはここに指定したUUIDを持つアバターの画面に表示されます。
つまり、照明を操作している人のUUIDを指定すればいいわけですね。

  • string message

ダイアログに表示するメッセージです。
「Yes or No?」とか「select color」とか、ダイアログが何のために表示されているのか理解できるようにするためのメッセージです。
日本語も使えたような気がしますが(ちょっと未確認です、すみません(^^;)、特殊な場合を除いては英文を使うべきでしょう。
string型ですので""で囲みます。

  • list buttons

ダイアログに表示するボタンのデータです。
list型は初めて登場する型ですね。
こんなふうに書きます。

 ["red", "green", "blue", "off"]

[]の中に、文字列を,で区切って並べています。
データが複数並んでいますので、list型と言います。
今回はボタンの名前のリストなのでstring型(文字列)ですが、list型には文字列以外のデータを入れることもできます。

 [1, 3, 5, 7, 9]
 [1, "one", 2, "two"]
 ["home", <10.0, 43.0, 11.0>, "shop", <40.0, 12.0, 200.0>]

上記のような書き方はどれも有効です。
用途に応じて様々な型のデータを格納することができますので、まとまった一連のデータを管理する際にはlist型はとても便利です。

なお、今回使うのはstring型のデータを並べたlistです。
並べた文字列が、ボタンの名前として表示されます。
ボタンの名前にはstring型しか使えません。他の型を並べるとエラーになりますので注意しましょう。
lslのダイアログでは、ボタンの数は12個までになっていますので、最大12個まで並べて書くことができます。

以下のような空白のボタン名はエラーになります(↓3番目が空白)。

 ["red", "green", "", "off"]

また、ボタン名の最大長は24文字です。
ですが24文字以下でも、あまり長いボタン名だと後半が表示されないので注意しましょう。

 ["changing color to red", "green", "blue", "off"]

上の例だと、最初のボタンは"changing..."あたりで切れるので、何色に変えるかわからなくなります。

  • integer chat_channel

ダイアログのボタンを押したときに、選択結果を送信するチャットチャンネルを指定します。
チャットチャンネルは前回も出てきました。

ダイアログは、ボタンを押すとそのボタンの名前をチャットで発言するようになっています。
例えば、"red"というボタンを押すと、チャットで"red"と発言されます。
もしもダイアログのチャンネルを0(通常チャットのチャンネル)にすると、ボタンを押すたびにチャットにログが流れることになり、わかりやすい反面、うっとおしくなります。
ですので一般的にはこのチャンネルは0以外に設定します。

前回は触れませんでしたが、実はチャットのチャンネルにはマイナス値もあります(-1チャンネル、-2チャンネルなど)。
マイナスのチャットチャンネルは、スクリプト以外からは使えません。

チャットチャンネルについてちょっと整理しておきましょう。

チャンネル -1以下 0 1以上
通常ログへの表示 なし あり なし
チャット欄からの発言 不可能 可能 可能(/1 message)
スクリプトからの発言 可能 可能 可能

listenで処理するコマンドなどは、通常ログに表示されるとうっとおしいので、0以外を使います。
チャットからコマンドを入力可能にしたい場合は1以上のチャンネルにします。
チャットからのコマンド入力を不可能にし、ダイアログなどからのみ制御可能にする場合は-1以下を使います。

余談になりますが、スクリプトで使うチャンネルを決める際には、混線に注意して下さい。
例えば、髪の色を変えるスクリプトと、照明の色を変えるスクリプトがどちらも7チャンネルを使っているような場合、
「/7 red」
の発言で、髪と照明が両方とも赤になってしまうようなことが起こり得ます。
ダイアログを実装するのであれば、コマンドをチャット欄から入力する必要がなくなりますので、マイナスのチャンネルを使うのが効果的です。
その際にも、-1チャンネルなどは頻繁に使われていますので、-1237チャンネルとか、使われそうもないチャンネルを選ぶと混線を避けることができます。

以上、4つの引数を指定してダイアログを表示します。
具体的に例を書いておきましょう。

 llDialog(llGetOwner(), "Do you love me?", ["Yes", "No", "Never"], -7);

この例では、まずllGetOwner()でキーを指定しています。
llGetOwner()は初めて登場しますが、オブジェクトのオーナーのUUIDを取得する命令です。
ですのでダイアログはオーナーの画面に表示されます。

メッセージは"Do you love me?"です。
「愛してるかい?」ですね。

それに対して返答するボタンは、"Yes", "No", "Never"の3つ。
「はい」「いいえ」「ありえない」の3択です。

返事が公の場に聞こえてしまうのはこっぱずかしいので、チャンネルは-7にしています。
こうすることで、愛の結末がどうなるか他人には知られずに済むでしょう。

ダイアログ対応版照明スクリプト


さて、ダイアログを照明スクリプトに組み込んでみましょう。
listenを使って照明の色を変化させる部分ができていますので、組み込みは至って簡単です。
「タッチしたとき」に「色を選択するダイアログを表示する」処理を追加するだけで済みます。

……わずか一行ですね(^^;

integer handle;

light(vector color){
  if (color == ZERO_VECTOR) {
    llSetPrimitiveParams(
      [PRIM_POINT_LIGHT, FALSE, ZERO_VECTOR, 0.5, 3.0, 0.75]
    );
  }else{  
    llSetPrimitiveParams(
      [PRIM_POINT_LIGHT, TRUE, color, 0.5, 3.0, 0.75]
    );
  }
}

default {
  state_entry(){
    light(ZERO_VECTOR);
  }

  touch_start(integer detected){
    handle = llListen(7, "", llDetectedKey(0), "");
    llDialog(llDetectedKey(0), "select color", ["red", "green", "blue", "off"], 7);
    llSetTimerEvent(30.0);
  }

  listen(integer ch, string name, key id, string message){
    if (message == "red"){
      light(<1.0, 0.0, 0.0>);
    }else if(message == "green"){
      light(<0.0, 1.0, 0.0>);
    }else if(message == "blue"){
      light(<0.0, 0.0, 1.0>);
    }else if(message == "off"){
      light(ZERO_VECTOR);
    }else{
      llSay(0, "You can use red, green, blue, and off only.");
    }
    llListenRemove(handle);
    llSetTimerEvent(0.0);
  }

  timer(){
    llListenRemove(handle);
    llSetTimerEvent(0.0);
  }
}

追加したのは、

 llDialog(llDetectedKey(0), "select color", ["red", "green", "blue", "off"], 7);

これだけです。

ダイアログは「タッチした人」の画面に表示しますので、UUIDにはllDetectedKey(0)を指定しています。
llDetectedKey(0)は前回出てきた「タッチした人のUUID」を取得する関数ですね。

メッセージは単純に"select color"(色を選択して下さい)にしました。
ここはダイアログの目的がわかれば何でもかまいません。

ダイアログのボタンは、
 ["red", "green", "blue", "off"]
の4つです。
listen?イベントの中で処理されるコマンドを列記しただけですね。

llListen()は7チャンネルで「聞き耳を立て」ていますので、ダイアログのチャンネルも7にしています。

これでいちいちチャットで色を発言しなくても、容易に光の変更ができるようになりました。

リストを使いこなす


ここまでで完成としてもいいのですが、今回はほとんどコードをいじっていませんので、もう少し改良してみましょう。
list型の変数をうまく使うことで、スクリプトの拡張性を高くすることができるというところをお見せしたいと思います。

リストの良いところは、まさにデータの並びを利用できるところです。
"red", "green", "blue", "off"という4つの文字列が、順番に並んでいるというところに意味があるのです。

リストに入っているデータの順番を「インデックス」と言います。
インデックスは以下のように、0から始まります。

インデックス 0 1 2 3
red green blue off

さて、この並んだデータの、一体何が便利なのでしょう。
今回のスクリプトでは、vector型の引数に応じて照明の色を変えるユーザー関数を作っていますね。
listen?イベントの中でコマンドを判定し、それに応じたvector型の引数を使っています。
vector型の値が変わるだけで、色を変える部分は共通になっていることに着目して下さい。

listen?イベントの中でやっていることは、単にコマンドに対応するvector値を選んでいるだけだというのが見えるでしょうか。
だらだらと書いているif文は、コマンドに対応するvector値を選んでいるだけです。

コマンドとvector型の値とを並べてみましょう。

インデックス 0 1 2 3
red green blue off
vector <1.0, 0.0, 0.0> <0.0, 1.0, 0.0> <0.0, 0.0, 1.0> ZERO_VECTOR

このように並べると、「色」と「vector」の間に関連性が出てきます。
関連性とは、インデックスのことです。
インデックス0の「red」は、インデックス0の「<1.0, 0.0, 0.0>」
インデックス1の「green」は、インデックス1の「<0.0, 1.0, 0.0>」
インデックス2の「blue」は、インデックス2の「<0.0, 0.0, 1.0>」
インデックス3の「off」は、インデックス3の「ZERO_VECTOR」
コマンドとvector値のインデックスが一致しています。

……というか、一致するように並べているのですがw

勘の良い方はおわかりかもしれませんが、私がやろうとしているのはvector値をlist化することです。

 ["red", "green", "blue", "off"] // コマンドのリスト
 [<1.0, 0.0, 0.0>, <0.0, 1.0, 0.0>, <0.0, 0.0, 1.0>, ZERO_VECTOR] // vector値のリスト

このように並べてやると、コマンドとvectorのリストはインデックスで関連付けてやることができます。
つまり、コマンドのインデックスがわかれば、vecotr値を特定することができるようになります。

listen?イベントでコマンドを受け取ったときに、
(1)コマンドのインデックスを調べる
(2)インデックスに対応するvector値を使ってlight()ユーザー関数を実行する

例えば、"green"というコマンドが聴こえてきたとします。
"green"のインデックスは1です。
ですので、インデックス1のvector値、<0.0, 1.0, 0.0>を使って照明の色を変えます。

このような仕組みにすれば、色を増やしたいときにはリストにコマンドとvector値を追加するだけで済むことになります。

以下のように実際のコードを書いてみました。
リスト化だけでなく、他にも拡張性を考えて改良を加えています。

integer handle;

integer channel = -1;

float intensity = 0.5;
float radius = 3.0;
float falloff = 0.75;

list colors = [
  "red", "green","blue", 
  "yellow", "Cyan","Magenta", 
  "pink","orange", "purple",
  "grey","white","off"
];

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 
];


light(vector color){
  llSetPrimitiveParams(
    [PRIM_POINT_LIGHT, (color != ZERO_VECTOR), color, intensity, radius, falloff]
  );
}

default {
  state_entry(){
    light(ZERO_VECTOR);
  }
  
  touch_start(integer detected){
    handle = llListen(channel, "", llDetectedKey(0), "");
    llDialog(llDetectedKey(0), "select color", colors, channel);
    llSetTimerEvent(30.0);
  }
  
  listen(integer ch, string name, key id, string message){
    integer i = llListFindList(colors , [message]);
    if (i != -1){
      light(llList2Vector(rgb, i));
    }
    llListenRemove(handle);
    llSetTimerEvent(0.0);
  }
  
  timer(){
    llListenRemove(handle);
    llSetTimerEvent(0.0);
  }
}

リスト化以外の拡張性を高める改良として、チャンネルや照明の強さ、範囲、減衰率などを変数化しました。
使用するチャンネルを変えたいとか、光の強さを変えたいときは、最初に定義されている変数の値を変えるだけで済みます。

色を指定するコマンドとして、colorsという名前のlist型変数を用意しました。
3色だけでなく、ダイアログで使用可能な12種類まで増やしてあります。

そのコマンドに対応するvector値のリストが、rgbというリスト型の変数です。

listen?イベントの中身が大幅に変わってスッキリしているのがわかるかと思います。
if文がいっぱい書いてあったのが、4行におさまりました。

初めて出てくる命令が二つありますね。
まずllListFindList()ですが、これがインデックスを調べる関数です。

llListFindList関数


 integer llListFindList(list src , list find)

最初の引数srcは、インデックスを調べたいリストです。
今回はコマンドのインデックスを調べますので、コマンドのリストcolorsを指定しています。
二番目の引数findは、調べたいリスト内の値です。
今回はlisten?イベントで聴いたコマンドを調べるわけですから、messageを指定しています(引数の型はlist型なので[]で囲んでいます)。

llListFindList()関数は、最初の引数srcリストの中に、二番目の引数findがあるかどうかをまず調べます。
あった場合はそのインデックスを返してきます。
リスト内にない項目だった場合は、-1が返ってきます。

ですので次のif文で、返ってきたインデックスが-1じゃないことを判定しています。

 if (i != -1){

「!=」は「イコールではない場合」の意味です。
「i(調べたインデックス)」が「-1ではなかった場合」ということになります。
つまりコマンドがリスト内にあるものだった場合、です。

llList2Vector()関数も初登場です。
これは指定されたリストの中から、特定のインデックスの位置にあるデータをvector値として取り出す関数です。
取り出したいのは調べたインデックスに対応するvector値ですので、vector値が格納されているrgbリスト変数を指定しています。

(1)コマンドのインデックスを調べる・・・integer i = llListFindList(colors , [message]);
(2)インデックスに対応するvector値を使ってlight()ユーザー関数を実行する・・・light(llList2Vector(rgb, i));

改良したかった部分は以上のようにシンプルになりました。

もう一箇所改良しています。
lightユーザー関数をシンプルに書き換えてあります。
このような書き方は、シンプルではありますが、一見してわかりにくいかもしれません。
余談として説明しておきます。

 light(vector color){
   llSetPrimitiveParams(
     [PRIM_POINT_LIGHT, (color != ZERO_VECTOR), color, intensity, radius, falloff]
   );
 }

ポイントは照明の二番目のパラメータを、
  (color != ZERO_VECTOR)
にしているところです。

このパラメータは照明をONにするかOFFにするかでした。
値はTRUEかFALSEです。

(color != ZERO_VECTOR)のような書き方は、先ほどif文でも出てきましたが、
「colorの値がZERO_VECTORではない」
という意味になります。

「colorの値がZERO_VECTORではない」というのが正しい場合はTRUE、間違っている場合はFALSEです。
すなわち、colorがもしZERO_VECTORだったら、
「colorの値がZERO_VECTORではない」というのは間違いですので、FALSEとなり、証明はOFFになります。
それ以外の場合は、
「colorの値がZERO_VECTORではない」というのは正しいですので、TRUEとなり、証明はONになるわけです。

今回のポイント


  • ダイアログの使い方:

 llDialog(llGetOwner(), "Do you love me?", ["Yes", "No", "Never"], -7);

  • リストのインデックスを調べる:

 integer llListFindList(list src , list find)

  • インデックスで指定した値を取り出す:

 llList2Vector(list src, integer index) // vector型で取り出す

※他にも各型に対応した関数があります
 llList2String(list src, integer index) // string型で取り出す
 llList2Integer(list src, integer index) // integer型で取り出す
 llList2Rot(list src, integer index) // rotation型で取り出す
 llList2Key(list src, integer index) // key型で取り出す
など。

  • if文の条件判定:

 if (x == y) // xがyと等しい場合
 if (x != y) // xがyと等しくない場合

※他にも以下のような判定ができます
 if (x < y) // xがyより小さい場合
 if (x > y) // xがyより大きい場合
 if (x <= y) // xがy以下の場合(xのほうが小さい、もしくはイコール)
 if (x >= y) // xがy以上の場合(xのほうが大きい、もしくはイコール)

最重要はダイアログの使い方です。
ぜひ使いこなせるようになりましょう。

リストの使い方については、何をどうリスト化すると効率がよくなるか、ある程度スクリプト作成の経験を積んでいくと見えてきます。
今回のように、コマンドに対して値が変わるだけで処理は一緒というパターンの際には有効でしょう。

三回にわたって照明スクリプトを作ってきましたが、
「ユーザー関数で処理を統一する」
「listenを使ってコマンドを処理する」
「ダイアログを使ってコマンドを選択する」
この3つはセットで使うことが多いパターンです。

例えば、私の製作している楽器もまさにこれで、
「指定されたサウンドを鳴らす」ユーザー関数を作り、
「listenイベントでサウンド名を受け取る」仕組みにして、
「ダイアログでサウンド名を選択する」ようにしてあります。

典型的なスクリプト構成の一つと言えますので、しっかり身に着けておきましょう。

さて、次回は心機一転、また新たなスクリプトに取り組んでみたいと思います。

名前:
コメント: