2007年12月1日土曜日

llDialog での Listen 処理 (その1)かな?

image 
Massive Link Dance Unit 作成で、もうしばらくスクリプトは勘弁~、、、なんて思っていたのですが、気になることがあって、、、また "Hallo, Avatar!" の文字を出してしまったり。

これもわかっている人には当然~なんでしょうけど、llDialog を使った操作スクリプトを書いたときに、ダイアログの結果を渡すチャンネルの「混線」で悩まれた方・悩んでいる方も多いのではないでしょうか。(私だけか・・・な?)

英語では Crosstalk っていうらしいですが。

結論から言えば、、、llDialog を使うときの llListen は touch_start イベントの中にいれて特定の Avatar からのメッセージをとる、、という鉄則を守らなかったり、それをちゃんとやっているにもかかわらず llDetectedKey(0) をグローバル変数にいれて処理して、、、処理終了後にその変数を NULL_KEY に戻していないために、、、、とか。

ダンス玉や Yaz Mania SP Music 玉、、、オブジェクトが1つの場合はいいのですが、同一 SIM 内に複数 Rez すると、ダイアログの結果を渡す同じチャンネルに聞き耳をたてているスクリプトが複数存在します。なので、処理をきちんとしないと、他の玉が表示したダイアログの返信を受け取ってしまう、、、、ということになります。もちろん、他のスクリプトで同じチャンネルを「たまたま」使っていたなんて場合も変な動きになります。

実際、、、混線回避処理をいれたつもりだったのに、ダイアログ表示で変な動きをして、、、理由は一部で llDetectedKey(0) でとった key の変数を、処理後 NULL_KEY に戻していなかったわけですが、それに気が付くまで、、、時間がかかって。 あ~、まだまだ修行足りません、、、

そこで、このダイアログの混線の仕組みと回避する方法を考えてみた、、、その備忘録みたいなエントリです。

llDialog を使ったダイアログの処理の考え方は、、、私個人としてはこう思っています。

1) 「個人使用」と「多人数使用」のどちらで多く使われるかを明確にしてみましょう
個人使用場合は、同じオブジェクトを Rez したときの混線回避をもっとも重要な項目として考えればいいと思います。多人数同時使用の場合は、混線処理にプラスして llDetectedKey(i) が複数存在することを意識したスクリプトにしなくてはなりません。

2) 以下のスクリプト処理をするときはスクリプトをわける必要があるので気をつけましょう
    a) timer処理
    b) Permission Request
特に、複数の Avatar が使う、、、みたいな場合は Listen を解放する timer 処理で悩むことになります。
複数同時使用が十分想定される場合は、1) と 2) が深く関係しあいます。
スタートの基本スクリプトは以下です。これを元にいろいろいぢってみます。
(注!これはダメなスクリプトですから、くれぐれも使用しないように、、、)

integer dHandle;
default
{
    state_entry()
    {
        llSay(0, "Hello, Avatar!");
        dHandle = llListen(3000,"",NULL_KEY,"");
        llListenControl(dHandle,TRUE);
    }
    touch_start(integer total_number)
    {
        llDialog(llDetectedKey(0),"TEST",["button 1","button 2"],3000);
    }
   
    listen(integer chan,string name,key id,string message) {
        llSay(0,llGetObjectName()+
": channel:"+(string)chan+
" name:"+name+
" id:"+(string)id+
" message:"+message);
    }
}

ちょっと説明すると、state_entry() ではダイアログが結果を返信するために使用する channel の 3000 に聞き耳をたてる処理を入れています。これ、、、しばらく チャンネルでの通信処理をやっていると、最初に宣言するクセがついてしまうことがあります。llListen/llListenControl の組み合わせで、聞き耳を立てたくない場合は、llListenControl で TRUE のところを FALSE にするだけでいいので、チャンネルを閉じたり、空けたりする記述が簡素化されるので重宝するのですが、後述するように Avatar を特定して Listen をするには llListen の中に key をいれる必要があるので、、、ダイアログを表示させる場合の llListen は注意しなくてはいけません。

touch_start ではオブジェクトをタッチした Avatar にダイアログを表示させています。タッチした Avatar の Key を取得するために llDetectedKey(0) を使い、"TEST" というメッセージをダイアログにいれ、"button 1" と "button 2" という2つのボタンを設定して、channel 3000 を使います~、と宣言しているわけです。

これ、、、通常の使用状況では llDetectedKey(0) つまり、最初に触った Avatar の Key を取得する、でほとんど問題ないのですが、lslwiki によると一斉に多くの Avatar が同時にタッチするような状況(ゲームみたいな状況)では llDetectedKey(1) とか llDetectedKey(2) とかが存在するので、それらの Avatar にもダイアログを表示するためには追加の処理もいれたほうがいい、、、なんて記述がありました。ダンス玉はその処理をいれていますw  touch_start(integer num_detected) の num_detected でとった値(タッチされた数) 分だけ for 文をまわす、、、(あ、、、脱線脱線w)

listen では 3000 チャンネルの内容を受け取って、渡されたすべての情報(チャンネル番号、ダイアログを押した Avatar 名、その Key、そしてどのボタンをタッチしたかを判断するボタンに貼り付けたテクスト)を llSay で表示する、、、というものです。

このスクリプトが入った2つのオブジェクト DialogTestBall 1 と DialogTestBall 2 を Rez して、一方の玉をタッチしてダイアログを表示させて、ボタンを押すと以下のようになります。
image
タッチしていないほうの玉まで、、、llSay で 3000 チャンネルの内容を表示しています。これが、、、混線 (Crosstalk) 状態なんです、、、
その理由は チャンネル 3000 のメッセージをすべてとってしまうスクリプトになっているからです。
これでは、、、だめですよね。
やり方はいくつかありそうですが、、、、あげてみると

チャンネルを変える

lslwiki によるとチャンネルは -2,147,483,648 から 2,147,483,647 (!!) まで使用可能なので、使用するチャンネルをこのソースみたいに固定にしないで、ランダムで生成して利用する、、、みたいなサンプルもあります。llFrand を使いますが、0 と DEBUG_CHANNEL (2,147,483,647) を外す処理をしています。これだけレンジが広いとバッティングすることはなさそうです。ただし、絶対混線しない、、、ということではないですね。通常の利用では問題ないかもしれませんが、、、。

必要なとき以外はチャンネルを聞かない
llDialog を行った直後に llListenControl を TRUE にし、timer() などで数秒~数十秒後に llListenControl をFALSE もしくは Listen でメッセージを受け取ったら FALSE  にします。
これだと、タッチされたときだけ聞き耳を 3000 に対してたてるのでよさそうですが、たとえば、複数の AV 利用するような使い方だと、最後にさわった AV の条件で timer がセットされます。また、同じタイミングで他のオブジェクトをタッチされちゃうと、、、だめですねぇ~、、、他のオブジェクトからの返答でも Listen を FALSE するので、、、込み入った処理にしたときに潜在的なバグになりそうですよね、、、

つまり、、、オブジェクトの気持ちになれば(笑)、タッチされたときだけ、 Listen でタッチした Avatar から受け取ったメッセージを処理したい、、、と考えているはず。。。複数の Avatar が、このような同じチャンネルをもつ複数のオブジェクトのなかのひとつのオブジェクトをタッチしたとしても、自分にタッチした Avatar は「ひとつ」しかないわけだから、、、そう考えると、、、

タッチした Avatar からの返答だけ明示的に処理する
これが一番簡単・確実ですね。
タッチした Avatar を特定する方法は、、、、2つあります。
ひとつは llDetectedKey(0) の KEY を変数としてもっておいて、Linsten 時に if で比較する方法。
基本ソースに6行 (実質5行) 加えるだけです。(赤にした部分です)

integer dHandle;
key avatarCheck;
default
{
    state_entry()
    {
        llSay(0, "Hello, Avatar!");
        dHandle = llListen(3000,"",NULL_KEY,"");
        avatarCheck = NULL_KEY;
    }
    touch_start(integer total_number)
    {
        avatarCheck = llDetectedKey(0);
        llListenControl(dHandle,TRUE);
        llDialog(llDetectedKey(0),"TEST",["button 1","button 2"],3000);
    }
   
    listen(integer chan,string name,key id,string message) {
        if (id == avatarCheck) {
            llSay(0,llGetObjectName()+
": channel:"+(string)chan+
" name:"+name+
" id:"+(string)id+
" message:"+message);
            llListenControl(dHandle,FALSE);

            avatarCheck = NULL_KEY; //これはとても重要な一行です。
        }

    }
}

最後の一行の NULL_KEY に戻す処理がないと、、、一度触った玉に Key が残っているので、その状態で違うオブジェクトの、同じチャンネルを使用しているダイアログを操作すると、、、、いきなり前に触った玉が動き出すみたいな・・・・そんな感じになります。複数の Avatars でテストしていたり、ひとつだけのオブジェクトでさわっていると気が付きにくいのですが。。。

もっと確実にやるには、、、ある意味スタンダードでこれで覚えて~のやり方なのですが、聞き耳を立てるときに、llDetectedKey(0) で取得した KEY の Avatar だけからのメッセージをとる、と llListen で指定する方法。
これだと、、、Listen を state_entry() から touch_start に移して、llListen に llDetectedKey(0) を入れるだけです。

default
{
    state_entry()
    {
        llSay(0, "Hello, Avatar!");
    }
    touch_start(integer total_number)
    {
        integer dHandle = llListen(3000,"",llDetectedKey(0),"");
        llListenControl(dHandle, TRUE);
        llDialog(llDetectedKey(0),"TEST",["button 1","button 2"],3000);
    }
   
    listen(integer chan,string name,key id,string message) {
        llSay(0,llGetObjectName()+
": channel:"+(string)chan+
" name:"+name+
" id:"+(string)id+
" message:"+message);
integer dHandle = llListen(chan, "", id, "");
        llListenControl(dHandle,FALSE);
        llListenRemove(dHandle); //<- Listen が残るよ~という指摘で追加しました。
    }
}

あたりまえ、、、といえば当たり前なのですが、、、上記のやり方にもかかわらず、llDetectedKey(0) を何度も使うため、グローバル変数にいれて、、、(ある意味、これが元凶、、、)処理を終えたあとにその変数を NULL_KEY に戻すのを忘れちゃい、、、言い訳ですが(笑)、/11 show data みたいなチャットからのコマンド処理をいれると、listen が結構複雑になってくるんですよね。

あと、最初にさわった人が長い時間ダイアログを出したままの状態(=その人の返答をListenしている)で、他の人がタッチすると別のダイアログを表示して、その人の返答をListenしようとします。この時点で2人の Avatar へのListen が発生している、、、わけですよね。。。

また、上記の方法では表示したダイアログで [ignore/無視] を押された場合は、llListenControl の FALSE 処理は、、、、されていないわけです。
なので、、、llSetTimerEvent を使っての FALSE 処理が必要、、、となるわけです。

でも、、、

Yaz Mania の連続再生モードなんかもそうなんですが、すでにメインの処理として timer を使っているような場合は、、、かなり苦労します、、(しました)

と、、、かなり長くなったので今日はここまでw

次回は私の「だめだめスクリプト」をご紹介しながら、回避方法なんかも書いてみますね。

4 件のコメント:

  1. ControlでFalseにした場合、そのハンドルに対応するlistenイベントを抑制するだけだから…
    タッチした人数分、listenの残骸がどんどん残っていくような…
    気がしないでもない。

    返信削除
  2. jukebox 用のスクリプトを探していたら、ここのことを思い出しました♪ はらゆきさんとか、いろいろな人がいろいろな分野の制作をしているようですが、とりあえず、すーさん サンクス! だい

    返信削除
  3. おおお!って、ぜんぜん意味わからないですけど。・・・とりあえず、がんばってください(><

    返信削除
  4. ぬるぽ~、やっぱり、llListenRemove か State Change 必要なのかもね~。ちょっと調べてみよw

    返信削除