ソラマメブログ

2007年05月01日

センサーを使おう(スクリプト初級第二十回)

今回はセンサーについて見てみましょう。
センサーというのは、スクリプトによって近くのアバターやオブジェクトを検知する仕組みのことを言います。
最も単純な使い方としては、自動ドアなどが考えられます。

また、翻訳機などでは近くにいる人をダイアログに出力し、誰の言葉を翻訳するか指定できるようにしているものもあります。
このとき近くにいる人を特定するのにセンサーが使われています。
センサーの仕組み

センサーは、探知を開始する関数と、探知結果を受け取るイベントで成り立っています。
探知を実行する関数は二種類あり、一度だけ探知を行うものと一定間隔で繰り返し探知し続けるものがあります。
それから繰り返し探知を行っているセンサーを止めるための関数もあります。

センサー関数説明
llSensor一度だけ探知を実行する
llSensorRepeat繰り返し探知を実行する
llSensorRemove探知を止める


一度のみの探知、繰り返しの探知のどちらの関数を使っても、結果を受け取るイベントには同じものを使います。
イベントにも二種類あり、一つは「何か見つかったとき」のイベント、もう一つは「何も見つからなかったとき」のイベントです。

センサーイベント説明
sensor何か見つかったときのイベント
no_sensor何も見つからなかったときのイベント


センサーを使おう(スクリプト初級第二十回)

センサー関数

まずは探知を実行する関数から具体的に見てみましょう。

  llSensor(string name, key id, integer type, float range, float arc)

この関数は指定した条件での探知を一度だけ実行します。
探知条件を指定する引数について順番に説明します。

string name

探知対象の名前です。
例えば「Miz」というアバターやオブジェクトを探したいときは、ここに"Miz"を指定します。
大抵の場合、探知対象の名前が分かっていることは少なく、名前を指定することはあまりありません。
空文字""を指定すると、どんな名前のアバターやオブジェクトであろうと探知対象になります。

key id

探知対象のUUIDです。
名前と同様、UUIDがわかってて探知する機会はそれほど多くはありません。
使うとしたら、机の中やカバンの中を探しても見つからなかったものを探すときくらいでしょう。
NULL_KEYを指定すると、全てのUUIDが探知対象になります。

integer type

探知対象のタイプです。
どんなタイプのものを探すのか、ここで指定します。
と言っても、髪の長い子、とか、性格の穏やかな子、などの詳細な指定は残念ながらできません。
指定できるのは以下の4タイプのみです。

タイプ説明
AGENTアバター
ACTIVE移動している物理オブジェクト
またはアクティブなスクリプト(*1)を含むオブジェクト
PASSIVEパッシブなスクリプト(*2)を含むオブジェクト
または移動していない物理オブジェクト
またはその他の全オブジェクト
SCRIPTEDなんらかのスクリプトを含むオブジェクト

*1:アクティブなスクリプトとは、listenのように比較的高負荷な常に動作を続けているスクリプトのこと
*2:パッシブなスクリプトとは、listenなどを含まない比較的低負荷なスクリプトのこと

このあたりのタイプ分けがイマイチどういう意図に基づいているのかわからないのですが、lslWikiによれば上記のようになっております。
上記4つのタイプですが、組み合わせて使うことも可能です。
組み合わせるときには「AGENT | ACTIVE」のように、|でつなぎます。

余談ですが、タイプSCRIPTEDを絡めて使った場合、どうもセンサーの動きが不可解であることは識者の間では有名な話です。
例えば「AGENT | SCRIPTED」という組み合わせは、アバターとスクリプトを含むオブジェクトの双方を探知してくれそうなものですが、どういうわけかアバターが探知されないそうです。
組み合わせ時のセンサーの動きについては、学会でも諸説あり、明確な答えが出ていない状態です。
余裕のある方は自分で研究してみるのも良いでしょう。
余裕のない方は、物好きな人が検証結果を発表してくれるのを待ちましょう。


float range

探知の範囲です。
半径を指定します。
例えば5.0であれば半径5m以内が探知対象になります。

float arc

探知角度です。X軸に対するラジアン角度になります。
最大はπ(=180度)です。
わかりにくいのでlslWikiから引っ張ってきた以下の図を参考にして下さい。

arc=π/4(45度)の場合:
センサーを使おう(スクリプト初級第二十回)

arc=π/2(90度)の場合:
センサーを使おう(スクリプト初級第二十回)

arc=π(180度)の場合:
センサーを使おう(スクリプト初級第二十回)

センサーの具体的な使い方を示しておきます。
例えば全方位半径5m以内のアバターを探知する場合は、

  llSensor("", NULL_KEY, AGENT, 5.0, PI);

このようになります。

次に、繰り返し探知を行う関数llSensorRepeat()ですが、llSensor()関数に引数が一つ増えているだけです。

  llSensorRepeat(string name, key id, integer type, float range, float arc, float rate)

増えているfloat型の引数rateは、探知を繰り返す周期を秒数で指定するものです。
例えば1.0を指定すると、1秒ごとに探知を繰り返します。

  llSensorRepeat("", NULL_KEY, AGENT, 5.0, PI, 1.0);

上記は全方位半径5m以内のアバターを1秒ごとに探知します。

llSensorRepeat()による探知を止めるには、llSensorRemove()を使います。

  llSensorRemove();

引数はありません。

センサーイベント

センサーが何かを探知すると、sensorイベントが起きます。

  sensor(integer num_detected){
    // 処理
  }

引数num_detectedには、探知したものの数が入ります。
例えば3人のアバターが探知された場合は3です。

「それだけじゃ何が何やらわからんじゃないか!」と思った方、ごもっともです。

探知した結果、名前や位置、UUIDなどが知りたいのは素直な気持ちでしょう。
探知された対象の情報を取得するためには、Detected系と呼ばれる関数を使います。
実は今までにもコレ使ってるんですよね。
タッチイベントの中でよく使っているllDetectedKey()関数などがそうです。

いくつか挙げてみましょう。
これらの関数はいずれも引数にはinteger型の数値を取ります。
「何番目の対象か」を指定する引数です。

Detected系関数説明
llDetectedKeyUUIDを取得
llDetectedName名前を取得
llDetectedPos位置を取得
llDetectedRot回転角度を取得
llDetectedGroupグループが同じかどうか確認
llDetectedTypeタイプ(AGENT/ACTIVE/PASSIVE/SCRIPTED)を取得


他にもありますが、使用頻度の高いものは名前とUUIDでしょう。
例えば以下のようにすると、センサーで探知したもの全ての名前を連呼します。

  sensor(integer num_detected){
    integer i;
    for (i = 0; i < num_detected; i++){
      llSay(0, "I found " + llDetectedName(i) + "!");
    }
  }

一方、何も見つからなかった場合のno_sensorイベントは非常にシンプルです。

  no_sensor(){
    // 処理
  }

引数はありません。

自動ドアスクリプト

さて、センサーを使ってスクリプトを作ってみましょう。
安直に自動ドアです(^^;

前に作ったドアと異なり、横にスライドして開くタイプのドアにします。
どんなものでもいいのですが、テストの際にはドアオブジェクトを用意して下さい。

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

default{
  state_entry(){
    close_pos = llGetPos();
    llSensorRepeat("", NULL_KEY, AGENT, 5.0, PI, 3.0);
  }
  
  sensor(integer total_number) {
    if (!opened){
      llSetPos(close_pos + open_pos);
      opened = TRUE;
    }
  }

  no_sensor() {
    if (opened){
      llSetPos(close_pos);
      opened = FALSE;
    }
  }
}

グローバル変数として、ドアの閉じたときの位置をclose_posに保持しています。
開いたときの位置は、close_posにopen_posを加算したものです。
この例ではY方向に1mスライドするようになっています。
opendはboolian型として使う「開いてるか/閉じてるか」を判定する変数です。

スクリプトが開始されると同時に、llSensorRepeat()関数を使い、全方位5m以内のアバターを3秒ごとに探知しています。
誰か見つかった場合は、llSetPos()関数を使い、ドアをスライドさせます。
誰も見つからなかった場合は、llSetPos()関数を使って、ドアを元の位置に戻します。

なお、スクリプトを組み込んだあとにドアの位置を手動で調整した場合は、一度スクリプトをリセットしないとまた元の位置に戻ってしまいますので注意して下さい。

今回のポイント

・センサー関数:
  llSensor(string name, key id, integer type, float range, float arc); // 一度だけ探知
  llSensorRepeat(string name, key id, integer type, float range, float arc, float rate); // 繰り返し探知

・センサーの停止:
  llSensorRemove();

・何か見つかったときのイベント:
  sensor(integer num_detected){
    // 処理
  }

・何も見つからなかったときのイベント:
  no_sensor(){
    // 処理
  }

センサーは面白い機能ですが、サーバーには負荷がかかります。
例に出した自動ドアスクリプトでは、センサーの繰り返し間隔を3秒と少々長めに設定していますが、なるべく負荷を減らすための処置です。
とは言っても、常にセンサーが動き続けることになりますので、多用するとラグを引き起こす原因になります。

正しいセンサーの使い方としては、常に探知をするのではなく、必要なときにのみセンサーON、使わなくなったらすぐにOFFするべきです。

・・・そうなると自動ドアとしては役に立たなくなってしまいますが(^^;


同じカテゴリー(初級スクリプト)の記事画像
衝突判定(スクリプト初級第二十三回)
カメラ制御(スクリプト初級第二十二回)
HUDを作ろう(スクリプト初級第十六回)
prim間通信(スクリプト初級第十五回)
アニメさせよう(スクリプト初級第十三回)
お金を扱う(初級スクリプト第十一回)
同じカテゴリー(初級スクリプト)の記事
 衝突判定(スクリプト初級第二十三回) (2007-05-08 12:15)
 カメラ制御(スクリプト初級第二十二回) (2007-05-07 14:36)
 デモ商品を作ろう(スクリプト初級第二十一回) (2007-05-02 12:15)
 ステートのこと(スクリプト初級第十九回) (2007-04-27 12:15)
 rez!(スクリプト初級第十八回) (2007-04-26 12:15)
 音を鳴らそう(スクリプト初級第十七回) (2007-04-25 13:35)
この記事へのコメント
llSensorでは探知角度はarcですが、探知方向はどうなるのでしょうか?。
Posted by もに at 2007年05月09日 00:26
>もにさん

探知方向はXプラスの方向だったと思います。
方向性のあるセンサーを使うときにはオブジェクトの向きも考える必要がありますね。
Posted by Miz at 2007年05月09日 16:35
こんにちはw
センサーでこう書いてエラーも出ないのですが
IMが来ません(T_T)

センサーでアバターを探知して
そのアバターの名前、UUID、訪問時間をIMで
受信しようと思うのですが。。。
お時間ありましたらご教授下さいm(__)m



default
{
state_entry()
{
llSensor("","",AGENT,5.0,PI);
}
sensor(integer total_number)
{
llInstantMessage(llGetOwner(), "1");
}
sensor(integer num_detected){
integer i;
for (i = 0; i < num_detected; i++){
llSay(0, "I found " + llDetectedName(i) + "!");
}
}
Posted by プクスケ at 2007年07月25日 18:25
>プクスケさん

sensorイベントが二つありますね。
同じイベントを二つ以上書いてしまった場合、一番最後のイベントだけが有効です。
つまりIMを送る部分は実行されません。

また、センサー発動のタイミングがstate_entryイベントになっていますが、これだとスクリプトを起動した時点で一回センサーが動き、以後スクリプトは何もしません。

常にセンサーによる探知を行いたい場合は
 llSensorRepeat("","",AGENT,5.0,PI,10.0);
のようにllSensorRepeat関数を使ってください。
一番最後の「10.0」がセンサーを繰り返し動かす間隔(秒)です。
この例であれば10秒ごとにセンサーが動きます。
Posted by Miz at 2007年07月25日 18:41
>プクスケさん

書き忘れました(^^;

仮に10秒ごとに動くセンサーにしたとして、探知結果をIMで送るとなると、オブジェクトの近くに誰かがずーーーっと立っていた場合、その人のUUIDが延々と10秒ごとに送られてくることになります。

かなりウザイ上、サーバー泣かせのスクリプトになりそうです。
一度試してみるとどんな具合なのかわかるかと思いますが・・・いろいろと工夫しないと実用には耐えられないかと・・・(^^;
Posted by Miz at 2007年07月25日 18:46
Mizさん
即レス感激です(T_T)
ありがとうございますm(__)m

>同じイベントを二つ以上書いてしまった場合、一番最後のイベントだけが有効です。

そうでしたか。。。無知でした^^;

この内容に似た商品があったのですが、どうやってるのか気になりました^^
その商品は、アバター名、訪問時間、帰った時間が分かるみたいです。
どういう仕組みなんだろ?

>その人のUUIDが延々と10秒ごとに送られてくることになります。

かなりウザいですね^^;
自分に嫌がらせしてる感じですねw
Posted by プクスケ at 2007年07月25日 18:57
>プクスケさん

アバター名、訪問時間、帰った時間が分かるようなものとなると、やはり工夫が必要になるかと思います。
逆に、工夫しているからこそ商品として成り立っているのでしょう。

うーん、具体的な商品の仕組みをこの場で解明するのはちょっとはばかられますので、どうしても仕組みが知りたいという場合は別途こっそりお尋ね下さい(^^;
Posted by Miz at 2007年07月26日 11:23
>正しいセンサーの使い方としては、常に探知をするのではなく、
>必要なときにのみセンサーON、使わなくなったらすぐに
>OFFするべきです。
>
>・・・そうなると自動ドアとしては役に立たなくなってしまいますが(^^;

そうですね。ただ、探索範囲が常に1m以内である必要もまたないはずです。


探知対象のと距離を考えていくと、必要な探知時間が変わってくると思います。

デフコン3:10m以内の探知を15秒に1回
デフコン2:デフコン3を探知後に即座にシフト。5m以内の探知を7秒に1回行う。ただし、デフコン2に移行後、探知出来なかった場合はデフコン3に移行する。
デフコン1:デフコン2探知後に即座にシフト。1m以内の探知を0.1秒に一回行う。ただし、デフコン1に移行後、探知できなかった場合、デフコン2へ戻す。

デフコン0:ドア動作後3秒間、1秒に一回の探知動作を行う。


といった、段階的な警戒レベルを設定すると負荷に関しては最適な頻度を設定できるのではないかと思います。
Posted by すくりぷたX at 2007年08月11日 21:49
>すくりぷたXさん

負荷軽減のためのアドバイスありがとうございます。
参考になります(^^
Posted by MizMiz at 2007年08月13日 11:24
はじめまして
参考にさせていただいています。
時に
観音開きの自動ドアを作りたいのですが
どうしたらいいのでしょうか?
Posted by 木にハッパ at 2007年08月15日 02:55
>木にハッパさん

この記事のドアはスライド式ですが、第五回で回転式のドアを解説しています。
http://miz.slmame.com/e1714.html

また、観音開きということは複数のprimを同時に動かすことになるので、動くprimごとに回転のスクリプトをいれてやる必要があります。

回転の動きを同時に行うには、リンクメッセージというprim間通信の手法を利用します。
http://miz.slmame.com/e3076.html

つまり、以下のような二段階の仕組みになりますね。

(1)センサーで人を探知したらリンクメッセージを送信
(2)リンクメッセージを受信したら回転して開く

2のスクリプトのほうは開く扉ごとに用意することになります。


まずはヒントレベルの回答ですが、仕組みさえわかれば基本的な関数だけで実現できるかと思います。
Posted by MizMiz at 2007年08月16日 09:20
Mizさん
回答ありがとうございました。
がんばってみます。
Posted by 木にハッパ at 2007年08月18日 01:02
以下のスクリプト、なぜかコンパイルされません。
なにぶんにも経験不足ため原因がわかりません。
どうかお助けください(涙


list nameList;
list pointList;
integer handle;
vector target;
vector offset;

default
{

state_entry()
{
llSay(0, "ready.");
}

touch_start(integer total_number)
{
llSensor("", NULL_KEY, AGENT, 500, PI);
}

sensor(integer num_detected)
{
handle = llListen(7, "", llGetOwner(), "");
integer i;
for (i = 0; i < num_detected; i++)
{
nameList = llListInsertList(nameList, llDetectedName(i), 0);
pointList = llListInsertList(pointList, llDetectedPos(i), 0);
}

llDialog(llGetOwner(), "Who do you wanna see?", llDetectedName(i), 7);

}



listen(integer ch, string name, key id, string message)
{
integer findIndex = llListFindList(nameList, message);
target = pointList(findIndex);
offset = (target- llGetPos()) *
(ZERO_ROTATION / llGetRot());
llSetSitText("Teleport");
llSitTarget(offset, ZERO_ROTATION);
}


}

}
Posted by Lsoleil Tokyoska at 2007年08月20日 01:21
こんにちは。
単純に一番最後の}が多くないですか。
インデント(字下げ)して記述すれば、わかりやすくなりますよ。
Posted by sasapy at 2007年08月20日 11:52
そうすると

listen(integer ch, string name, key id, string message)
{
integer findIndex = llListFindList(nameList, message);
target = pointList(findIndex);//<<<<<<<<<<<<ここ
offset = (target- llGetPos()) *
(ZERO_ROTATION / llGetRot());
llSetSitText("Teleport");
llSitTarget(offset, ZERO_ROTATION);
}

でエラーになるんです。
Posted by Lsoleil Tokyoska at 2007年08月20日 17:16
解決しました。
全体的にリストの使い方が間違ってました。
Mr.sasapy,ご協力ありがとうございました。
Posted by Lsoleil Tokyoska at 2007年08月20日 17:52
センサーの距離が測定対象によって変わることってあります?

AGENTだと普通に使えていたんですが
(PASSIVE | ACTIVE)だと
10mぐらいの半径が限界なんですよね。
仕様なんですかね?

なにか知っていたら教えてください。
Posted by ねこおやじ at 2008年01月18日 00:56
たしかセンサーは対象が近いところから64個までしか検知できない(そこで検知するのをやめてしまう)と思います。

今回の場合、「PASSIVE | ACTIVE」の指定だとすべてのオブジェクト(でしたっけ?)に反応してしまいますので、AGENT指定よりも近隣までしか検知しないと思いますよ^^;

あと、range指定をPI_BY_TWO とかにして2回に分けたりするともう少し多く検知できますが、限界があるのは一緒ですね...

※最大値の64はもう少し多かったかも知れません。。。
Posted by 通りすがり at 2008年01月20日 14:33
どんなタイプのものを探すのか、ここで指定します。
と言っても、髪の長い子、とか、性格の穏やかな子、などの詳細な指定は残念ながらできません。
Posted by imitation jewelry at 2011年01月11日 16:39
センサーで探知したもの全ての名前を連呼します
Posted by louis vuitton handbags at 2011年06月17日 17:57
たしかセンサーは対象が近いところから64個までしか検知できない(そこで検知するのをやめてしまう)と思います。

今回の場合、「PASSIVE | ACTIVE」の指定だとすべてのオブジェクト(でしたっけ?)に反応してしまいますので、AGENT指定よりも近隣までしか検知しないと思いますよ^^;

あと、range指定をPI_BY_TWO とかにして2回に分けたりするともう少し多く検知できますが、限界があるのは一緒ですね...

※最大値の64はもう少し多かったかも知れません。。。
Posted by Authentic Jordans For Sale at 2011年07月23日 12:58
 
<ご注意>
書き込まれた内容は公開され、ブログの持ち主だけが削除できます。