ソラマメブログ

2007年04月24日

HUDを作ろう(スクリプト初級第十六回)

prim間通信をもう少し練習するという意味で、今回はHUDを作ってみましょう。
HUDというのはヘッドアップディスプレイ (Head-Up Display)のことで、SLでは画面上に表示されるインターフェースのことを言います。
ミニマップなども広義の意味ではHUDになるのでしょうが、SLでは特にユーザーが独自に作ったインターフェースのことを指します。
日本人にとって馴染みのあるものは翻訳機などでしょうか。
チャットの自動英語翻訳を行ってくれるJ2Eは画面左中央に表示される半円型のHUDですね。

何のHUDを作ってもいいのですが、あんまり出回ってなくて、それでいて便利そうなものが良いですね・・・。
テレポート用HUDなどはどうでしょうか。
HUDのボタンにテレポート先を登録しておいて、ボタンを押すとマップが開き、すぐにテレポートができるようなHUDです。
まずはbuildから

HUDもオブジェクトです。
従ってまずはbuildしなければなりません。
凝った形のHUDを作るにはそれなりに工夫が必要ですが、ここはスクリプトの勉強ですので、簡単な形から試してみることにしましょう。
以下のようなオブジェクトを作ってみてください。
もちろんbuildに慣れている人は自由な形でも構いません。



ボタンの数はいくつでもいいですが、それぞれにテレポート先を登録しますので、複数あったほうが便利でしょう。

buildができたら、これをHUDとしてアタッチしてみます。
オブジェクトを一度インベントリにしまい、インベントリ内で右クリックすると「HUDとして添付」のような項目があるかと思います。
慣れない人は「HUDとして添付」する際に「Center(中央)」に添付するとわかりやすいかと思います。
右端や左端にすると、オブジェクトが画面外に装着されてしまい、どこにいったのかわからなくなる場合があります。

画面上に貼り付けることができたら、貼り付けたHUDを右クリックし、「編集」を選びます。
するとbuildツールが開き、位置や回転の設定ができますので、お好みの位置に調整します。
このときマウスのホイールを回すとHUDのスクリーン全体を拡大・縮小できます。
うっかり画面外にHUDが張り付いてしまったときなどはスクリーン全体を縮小すれば画面外のHUDを確認できます。

ここまではbuildの知識ですね。

ボタンのスクリプト

ではスクリプトに入っていきましょう。
HUDでは各ボタンにスクリプトを仕込むのが一般的です。
以前ベンダーを作ったときのllDetectedLinkNumber()関数を使うとスクリプトを一つにまとめることもできますが、その場合は各ボタンのリンクナンバーを把握しておく必要がでてきます。
llDetectedLinkNumber()関数は「タッチされたprimのリンクナンバーを返す」関数だからです。
ボタンが一つや二つしかなくて、今後拡張もされないというのであればそれでも構わないのですが、今回のようなHUDではあとからボタンを増やしてテレポート先をもっと多く記憶できるようにしたくなるかもしれません。
なるべく拡張性の高いスクリプトにしておいたほうがあとあと楽になります。

ボタンの機能は二つです。
タッチされたとき、記憶していたテレポート先をルートプリムに送信するのが一つ。
それからテレポート先を記憶できるような仕組みがあったほうがいいでしょう。
どんな方法でもいいのですが、前回やった「一定時間タッチしつづけたとき」にテレポート先を記録できるようにしようと思います。
「この場所を記録しておきたい」と思ったときにはHUDのボタンをしばらく押しておけばよいだけになりますので、非常に便利なはずです。

それほど複雑ではありませんので、コードから入ります。

string teleport_pos = "";
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){
        string sim_name = llGetRegionName();
        vector pos = llGetPos();
        teleport_pos = sim_name + "/" + (string)pos.x + "/" +(string)pos.y + "/" + (string)pos.z + "/";
        llOwnerSay("Memorized a teleport position.");
        counter ++;
      }
    }
  }
  
  touch_end(integer detected){
    if (llDetectedKey(0) == llGetOwner()){
      if (counter < 50){
        if (teleport_pos != ""){
          llMessageLinked(LINK_ROOT, 0, teleport_pos, NULL_KEY);
        }else{
          llOwnerSay("Not memorized a teleport position yet.");
        }
      }
    }
  }
}

文字列型変数teleport_posはテレポート先を管理するための変数です。
SLのランドマーク表記でお馴染みの形式「SIM名/X/Y/Z/」でテレポート先を格納しておきます。
初期状態ではテレポート先の登録がないので空文字です。

counterはタッチし続けている間増加するカウンターです。
このカウンターが50に達すると、テレポート先の記録を行います。

llGetRegionName()関数は初登場ですが、現在居るSIMの名前を返す関数です。
llGetPos()は同様に現在の位置(X,Y,Z)を返します。
この二つの関数で取得した位置情報を文字列型変数teleport_posに格納するわけですが、そこで見慣れない表現が出てきてますので説明します。

  teleport_pos = sim_name
        + "/" + (string)pos.x
        + "/" + (string)pos.y
        + "/" + (string)pos.z + "/";

vector型変数posに、.xとか.yとか付けているのは、vector型データに含まれるxのデータ、yのデータを取り出しているのです。
例えば、<1.0, 2.0, 3.0>というvector型変数pがあったとすると、
  p.x は 1.0
  p.y は 2.0
  p.z は 3.0
です。

つまり上記のスクリプトは、HogehogeというSIMの<10, 45, 20>で動かしたとすると、
  "Hogehoge/10/45/20/"
という文字列を作ってteleport_posに格納することになります。

touch_endイベントが発生したとき(マウスボタンを離したとき)にカウンターが50に達していなかったら、ルートプリムにテレポート先データを送信します。
ただしteleport_posにまだテレポート先データが格納されていない場合は「まだテレポート先を記憶してないよ」というメッセージの表示のみ行います。

ルートプリムへのテレポート先データ送信には、前回から扱っているllMessageLinked()関数を使います。

  llMessageLinked(LINK_ROOT, 0, teleport_pos, NULL_KEY);

LINK_ROOTはルートプリムにのみメッセージを送信するということです。
送信したいのは文字列型データのteleport_posのみです。
数値型及びキー型のデータは使用しないので、0とNULL_KEYに固定しています。

ルートのスクリプト

続いてルートプリムに仕込むスクリプトです。
こちらも非常に簡単です。

default {
  link_message(integer sender, integer num, string str, key id){
    list telepos = llParseString2List(str, ["/"], []);
    string sim_name = llList2String(telepos ,0);
    vector pos = <llList2Float(telepos ,1), llList2Float(telepos ,2), llList2Float(telepos ,3)>;
    llMapDestination(sim_name, pos, ZERO_VECTOR);
  }
}


ルートのスクリプトは、ボタンから送られてくるメッセージを受け取り、マップを表示するだけです。
llParseString2List()関数は前に登場しました。
文字列を指定した区切り文字で分割してリストに変える関数です。
今回のテレポートデータは"/"で区切られていますので、
  list telepos = llParseString2List(str, ["/"], []);
こうですね。
文字列strが例えば、
  "Hogehoge/10/45/20/"
だとしたら、
リストteleposは、
  ["Hogehoge", "10", "45", "20"]
のようになります。

このリストteleposからSIM名(先頭の文字列)と位置(後ろ3つの数値)を取り出し、llMapDestination()関数に渡しています。
llMapDestination()関数も看板を作ったときに登場しました。
指定したSIMの指定した位置をマップ上に表示する関数です。

今回のポイント

・SIM名の取得:
  llGetRegionName()

・現在位置の取得:
  llGetPos()

新しく登場する要素がどんどん少なくなってきました(^^;
それだけいろいろなことを覚えてきているということですね。

HUDは使い方次第でいろいろと便利なものを作れると思います。
今回の例は非常に簡単なものでしたが、応用して面白いものを作ってみてください。

さて、次回は何をしましょうw
書くべきことがほとんど無くなってきましたが・・・音でも鳴らしてみましょうかね。
ではまた。

この記事へのトラックバックURL

http://miz.slmame.com/t3173
この記事へのトラックバック
ちょっと執筆&スクリプトを練ってる時に疲れたのでINしてみて、行きつけでもないんですがカフェに行って只今AFK。^^;まあ、なんつーか話題がつまらないので音楽だけ聴いてるって感...
ちょっと息抜きにINしてみると・・【BLACK WIDOW】at 2008年06月23日 02:24
この記事へのコメント
ルートプリムのスクリプトで

string sim_name = llList2String(str,0);
vector pos = <llList2Float(str,1), llList2Float(str,2), llList2Float(str,3)>;

に出てくる四ヶ所の str は telepos の間違いでしょうか?
Posted by Tak Nishi at 2007年04月24日 13:19
>Tak Nishiさん

仰るとおりです(><;
直しておきます・・・。
Posted by Miz at 2007年04月24日 13:53
ボタンがいくつかあるHUDを作成し、そのどれかをクリックすることで
アバターの飛行速度を変更する・・・といったようなことは可能でしょうか?
乗り物じゃないと無理・・・なのでしょうか。
Posted by herohero at 2007年09月27日 11:31
>heroheroさん

可能かどうかで言うと、可能です。

ただ、飛行速度を変える仕組みはそういう関数があるわけではないです。
「加速装置」を装備する、とでも言うか・・・移動のキー操作に合わせて、llSetForceで進行方向に推進力を加えています。
進行方向はアタッチメントでllGetRot関数を使うとアバターの向きが取れます。

どのくらいの推進力を加えるかによって飛行速度は変わってきます。
そのコントロールにHUDを使えば、heroheroさんのお望みのものは実現可能です。
Posted by MizMiz at 2007年09月28日 09:59
こんにちは。

HUDを作成しようと思っているのですが、
HUDでアバタの動きを制御する(アニメ再生)を行うことは可能なのでしょうか?

AOのようなものではなく、TouchしたらおじぎとかTouchしたら踊るとかそういうものなのですが。
(ダンスに関してはもう一度Touchで止まるようにしたいと考えています。)

地面に置いた物をTouchで動かすことは出来るのにHUDに装着すると動かなくて;

アニメ再生は基本SITでしか造ったことが無くて色々試行錯誤しています。
HUDだと命令が行かないのは何でだろうと思いながら^^;

もし可能であればどのような方法が有るのかをお教えいただければ幸いです。

※体に命令を受けるものを装着だと命令の飛ばす方法がわからなくて。。
Posted by めりる at 2007年12月20日 15:33
>めりるさん

もちろんHUDからアニメ再生は可能です。
アニメ再生のやり方については、HUDでもAOでもポーズボールでも全て同じです。

SITでアニメするスクリプトを組んだことがあるなら、そのときのコードと各関数・イベントの意味を復習してみてください。
そのスクリプトでは、どんな方法でアニメーションを実行させていましたか?
アニメーション対象(誰をアニメさせるか)の指定方法は?
アニメーション再生の前に処理していることは?
それらがわかれば、HUDからもアニメーション再生のさせ方は一緒です。
唯一の違いは、スクリプトが動き出すきっかけだけです。
SITか、それともtouchか、ですね。

アバターをアニメーションさせる仕組みについては、
http://miz.slmame.com/e2514.html
こちらの記事が参考になるかもしれません。

>地面に置いた物をTouchで動かすことは出来るのにHUDに装着すると動かなくて;

これは地面に置いてある状態だとTouchでアニメーションするのに、HUDだとアニメーションしないということでしょうか?
もしそうであれば不可解ではありますが・・・。
Posted by MizMiz at 2007年12月21日 10:04
こんにちは。
ご解答有難うございます。
ドタバタしていてお返事が遅くなってしまい申し訳ございません。

試行錯誤してTouchしてアニメするようになりました。
ですが、やっぱりREZしてTouchでは動くのに装着してTouchだと動かないのです。。

もう少し頑張ってみようと思います。

有難うございました。
Posted by めりる at 2008年01月06日 14:34
的外れだったらごめんなさい...
attachイベントで一度reset scriptしてみてはどうでしょう。

私はstate_entryが実行されていると思ったタイミングでstate_entry(のところ)が実行されておらず、はまったことがあります。
Posted by 通りすがり at 2008年01月10日 00:43
はじめまして、以前より大変参考にさせていただいておりますが
スクリプトに関しては全くの初心者・・・というより・・ほとんど理解できておりません。
複数の自作アニメをHUDで順番に動かすことはできたのですが、一つを停止させると
その前のアニメが再生されます、思ったとおりの展開になってしまたんですが・・・
いっせいに全てのアニメを停止させるオブジェクトを作れば良いだろうとは思うんですが、スクリプトがわかりません。
他力本願で申し訳ありません、お時間があればご教示お願いしたいと思います。
Posted by tomo at 2008年01月18日 01:30