ソラマメブログ

2008年08月07日

【スクリプト】ベクトルをノートカードから読み込む

ノートカードから設定値を読み込んで処理するのはよくある手法ですが、ノートカードに位置情報(ベクトル)を記述する場合どうしているかという話です。
(ベクトルだけではなくローテーションも)

《サンプル》

<ノートカードの記述>
Wet Hoof,<192.0, 192.0, 25.0>
Chouchou,<128.0, 128.0, 22.0>

<スクリプト>
//==============================================================
    key notecard_query = NULL_KEY; // データサーバー読み出しキー
    integer notecard_line = 0; // ノートカード行番号
    list sim_position = []; // 読み出したデータ

    default
    {
        state_entry()
        {
            notecard_query = llGetNotecardLine("NOTECARD", notecard_line); // ノートカード読み出し
        }

// Dataserver Event
        dataserver(key requested, string data)
        {
            if(notecard_query == requested) // 読み出しキーが一致
            {
                if(data != EOF) // EOFでない場合
                {

// データ抽出部分(あとの説明で変更する部分)↓
                    sim_position += llCSV2List(data); // 1行をカンマ区切りで分離
// ↑ここまで

                    notecard_line ++; // 行番号更新
                    notecard_query = llGetNotecardLine("NOTECARD", notecard_line); // ノートカード読み出し
                }
                else // EOFの場合
                {
                    llSay(PUBLIC_CHANNEL, "ノートカード読み込み終了");

// 確認のため表示する
                    llSay(PUBLIC_CHANNEL, "0s: " + llList2String(sim_position, 0));
                    llSay(PUBLIC_CHANNEL, "1s: " + llList2String(sim_position, 1));
                    llSay(PUBLIC_CHANNEL, "1v: " + (string)llList2Vector(sim_position, 1));
                    llSay(PUBLIC_CHANNEL, "2s: " + llList2String(sim_position, 2));
                    llSay(PUBLIC_CHANNEL, "3s: " + llList2String(sim_position, 3));
                    llSay(PUBLIC_CHANNEL, "3v: " + (string)llList2Vector(sim_position, 3));
                }
            }
        }
    }
//==============================================================

とした場合、分離後のsim_positionは
(文字列)Wet Hoof
(文字列)<192.0, 192.0, 25.0>
で分離されてしまいます。これでは使用できません。

対策としてノートカードの記述を変える方法があります。

Wet Hoof,192.0,192.0,25.0

のようにベクトルの記述をスクリプトのように<>で囲った形ではなく、値をカンマ区切りでそのまま書いてしまい、データを読み込んだときに

    list temp = llCSV2List(data);
    sim_position += [llList2String(temp, 0), <llList2Float(temp, 1), llList2Float(temp, 2), llList2Float(temp, 3)>];

とすればsim_positionは
(文字列)Wet Hoof
(ベクトル)<192.0,192.0,25.0>
としてデータ化できます。

別の手法として文字列操作をして、3つのフロート値部分を切り出す方法もあります。

    list temp1 = llCSV2List(data); // 第1段階の分離
    string temp2 = llList2String(temp1, 1); // ベクトル部分を取り出す
    integer index1 = llSubStringIndex(temp2, "<"); // <文字位置を取得
    integer index2 = llSubStringIndex(temp2, ">"); // >文字位置を取得
    temp2 = llGetSubString(temp2, index1 + 1, index2 - 1); // <>内の文字列を取り出す
    list temp3 = llCSV2List(temp2); // 第2段階の分離
    vector temp4 = <llList2Float(temp3, 0), llList2Float(temp3, 1), llList2Float(temp3, 2)>;
    sim_position += [llList2String(temp1, 0), temp4];

文字列操作のもっと効率のよい方法もあるかと思いますが、とにかく文字列から数字部分を取り出してベクトルにしてしまえばよいわけです。


(最初のサンプルだとllList2Vectorで読み出しstringにキャストして表示してみると<0.0,0.0,0.0>となっているのがわかります)

2008.08.07 追記
ごめんなさい、考えすぎてしまいました。

    list temp1 = llCSV2List(data);
    sim_position += [llList2String(temp1, 0), (vector)llList2String(temp1, 1)];

とすれば、ノートカードの記述も<>付きのままでよいですし、文字列操作の面倒なこともやらなくてもよかったわけです。
ポイントは文字列としてリストから取りだし、取り出した後でベクトルにキャストするということです。
  

Posted by ささぴ at 16:11Comments(3)TrackBack(3)スクリプト

2008年08月04日

【スクリプト】認証システム(不正利用防止)

8月2日のiNNXオフィスアワー(http://blog.innx.co.jp/)で話題になった認証システムについてです。
認証システムというか不正利用防止策というか・・・。

どういった状況を想定しているかというと、
Aさんが作成したスクリプトを、Bさんのオブジェクトに入れて販売しました。
それを買ったCさんがスクリプトだけを抜き出し、自分で作ったオブジェクトに入れました。(販売できるんだろうか?)
AさんはCさんにスクリプトを不正利用されたことになりますが、これを阻止するための仕組みをスクリプトでなんとかできないかということです。

で、その時に口走ってしまったのが、
「スクリプトのクリエーター」と「オブジェクトのクリエーター」と「オブジェクトのオーナー」を限定させれば何とかなるのではないかということだったのですが・・・。あとで考えたら、十分ではありませんでした(後述)。

とりあえず、最初の思いつきを実行するためのスクリプトを作ってみました。
方法としては、上に書いたような販売形態でCさんがそのオブジェクトを購入したときに認証コードをCさんにIMします。Cさんが初めて装着/REZしたタイミングで認証コードを入力(チャットから)すれば、以降は通常通り使えるようになります。
認証コードを入力しない/間違っている場合は、認証コードを催促するか、なんらかの処理(オブジェクト削除とか)を行います。

もう一つ気にしていたことは、ノートカードを使いたくなかったということです。
ノートカードに認証キーを記述すれば、いちいちチャットに発言しなくてもいいのですが、オブジェクトの中のノートカードを編集してもらう方法(オブジェクトはMOD可でなければならない)やノートカードをオブジェクトにドロップしてもらう方法(オブジェクトがNoMODの場合)は、間違った場合のノートカードをどうするかとか、ノートカードを複製できるということもあって、ノートカードを使わずになるべくスクリプトだけで処理したかったわけです。

認証コードの作成には、「スクリプトのクリエーター」と「オブジェクトのクリエーター」と「オブジェクトのオーナー」(それぞれのキー)から暗号化したコードを作成します。
(さらに謎のキーワードも埋め込みます)

この方法で作成者・使用者を限定してしまえば、スクリプトを他のオブジェクトに入れて使うということはできなくなりますし、さらにDさんに転売しても使えないことになります(ここが間違ってました)。

最初に認証キー発行機です。
ベンダーに組み入れてもいいのですが、今回はベンダーとは別に発行機を設置したということでスクリプトを書いています。購入者がタッチすれば認証キーをIMするものです。

//==============================================================
// send_activation_key
// 1.0.0
// 2008/08/04
// created by sasapy Beck

// Define
    key script_creator = NULL_KEY; // スクリプトのクリエーターキー
    key object_creator = NULL_KEY; // オブジェクトのクリエーターキー
    key avatar_key = NULL_KEY; // タッチしたアバターキー

    string SCRIPT_NAME = "send_activation_key.lsl"; // スクリプト名
    string secret_word = "sasapy"; // 秘密のキーワード
    integer rot_number = 20080804; // ロット番号

    string original_word = ""; // 暗号化する前の文字列
    string keyword = ""; // 暗号化した文字列

    string IM_message = "認証キーを送信します。\n商品を装着/REZした際に認証が必要になります。\n次の認証キーをコピーペーストしてチャットから発言してください。\nこの認証キーはあとで必要になる場合がありますので、ノートカード等に記録しておいてください。\n認証キー:";

//--------------------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            script_creator = llGetInventoryCreator(SCRIPT_NAME); // スクリプトクリエーターのキー取得
            object_creator = llGetCreator(); // オブジェクトクリエーターのキー取得

// 確認のため表示する
            llOwnerSay("Script Creator: " + llKey2Name(script_creator) + " (" + (string)script_creator + ")");
            llOwnerSay("Object Creator: " + llKey2Name(object_creator) + " (" + (string)object_creator + ")");
        }

// Rez Event
        on_rez(integer start_param)
        {
            llResetScript();
        }

// Touch Event
        touch_start(integer total_number)
        {
            avatar_key = llDetectedKey(0); // タッチしたアバターのキー取得

            original_word = secret_word + (string)script_creator + (string)object_creator + (string)avatar_key; // 文字列作成
            keyword = llMD5String(original_word, rot_number); // 暗号化
            llInstantMessage(avatar_key, IM_message + keyword); // IM送信
        }
    }
//==============================================================

次に商品に入れるスクリプトです。
認証部分と本来のスクリプト部分のファイルを分けて作ることも可能ですが、今回は1つにしてステートを分けるようにしました。
REZされたら(REZでも装着でもon_rezイベントは発生する)、認証キーの入力をうながし、入力されたらチェック、合っていれば本来の処理へ移行するものです。

//==============================================================
// activate_script
// 1.0.0
// 2008/08/05
// created by sasapy Beck

// Define
    key script_creator = NULL_KEY; // スクリプトのクリエーターキー
    key object_creator = NULL_KEY; // オブジェクトのクリエーターキー
    key avatar_key = NULL_KEY; // タッチしたアバターキー

    string SCRIPT_NAME = "activate_script.lsl"; // スクリプト名
    string secret_word = "sasapy"; // 秘密のキーワード
    integer rot_number = 20080804; // ロット番号

    string original_word = ""; // 暗号化する前の文字列
    string keyword = ""; // 暗号化した文字列

    integer listen_handle = 0; // チャット用ハンドル

//--------------------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            script_creator = llGetInventoryCreator(SCRIPT_NAME); // スクリプトクリエーターのキー取得
            object_creator = llGetCreator(); // オブジェクトクリエーターのキー取得
            avatar_key = llGetOwner(); // オブジェクトオーナーのキー取得
            original_word = secret_word + (string)script_creator + (string)object_creator + (string)avatar_key; // 文字列作成
            keyword = llMD5String(original_word, rot_number); // 暗号化

            llOwnerSay("認証コードをチャットに発言してください");
            listen_handle = llListen(PUBLIC_CHANNEL, "", avatar_key, "");
        }

// Rez Event
// このステートでREZされたらリセットでもよい
        on_rez(integer start_param)
        {
            llResetScript();
        }

// Listen Event
        listen(integer channel, string name, key id, string message)
        {
            if(message == keyword)
            {
                llListenRemove(listen_handle); // リッスン解放(ステートを変えるので不用)
                llOwnerSay("認証しました。ありがとうございます。");
                state active; // ステート遷移
            }
            else
            {
                llOwnerSay("認証コードが違います。再入力してください。");

// 認証できなかった場合の処理
// 例えばカウンターを持って、回数の制限を設ける
// 最終的に認証できなかった場合どうするか?(llDie?)

            }
        }
    }

//--------------------------------------------------------------
// State
// このステートでスクリプトのリセットをしてはならない
state active
    {
        state_entry()
        {
// 本来の処理開始
            llSay(PUBLIC_CHANNEL, "Good Morning Avatar!");
        }

// Change Event
// オーナーが変わったとき(TRANSした時)の処理
        changed(integer change)
        {
            if(change & CHANGED_OWNER) // オーナーが変わったとき
            {
                state default; // ステート遷移
            }
        }
    }
//==============================================================

となります。

で、勘違いというか考えが足りなかったことが一つ。
認証キー発行機はタッチした人にキーを発行してしまいますが、例えば購入者CさんがDさんにTRANSした場合、認証キーを再入力するようになりますが、Dさんが発行機にタッチすれば認証キーを発行してしまいます。NoMOD/NoCOPYであれば実害はない(と思います)ですが、COPY可であれば認証している意味が無くなってしまいます。
発行機側で購入者のキーを管理していれば、購入者以外には認証キーを発行しないようにできますが、購入者の数が多ければ管理しきれない(メモリー容量の問題)ことと、ベンダーとの通信が必要になってきます(面倒)。
ベンダーに認証キー発行システムを組み込んで購入した人にしか認証キーを発行しないようにすれば解決しそうですが、なにかのタイミングでスクリプトがリセットしてしまったときに、認証キーの再入力が必要になりますので、保存していない場合は再発行の手続きが必要になります。

ということで、まだまだ研究が必要です・・・。
  

Posted by ささぴ at 10:55Comments(3)TrackBack(0)スクリプト

2008年07月09日

【スクリプト】テクスチャー表示、タッチで切り替え

「おしえて!セカンドライフ!」に上がった
  「テクスチャーの変更(http://oshiete.slmame.com/e296168.html)」
  「長押しクリック(http://oshiete.slmame.com/e293031.html)」
にインスパイアーされて作ってみました。

同じコンテンツ内にあるテクスチャーを表示し、タッチするとテクスチャーが切り替わるという、ありがちなスクリプトですが、ちょっと機能的に寂しいので、「長押し(タッチし続ける)」すると表示テクスチャーを戻す機能を入れました。押しっぱなしにしているとドンドン戻ります。

プリムのコンテンツに表示したいテクスチャーとこのスクリプトを入れます。
先にスクリプトを入れると、コンテンツ内にテクスチャーがないのでエラーになりますが、テクスチャーを入れてからタッチするとリセットされますので、気にしないで作業を続けてください。

リスト変数を使ってテクスチャー名を『管理していない』ので、テクスチャー数が多かったり、テクスチャー名が長かったりしても、メモリーが足りなくなってエラーになったりしません。

だいたいコメントとして書いているので、それを参照してください。

//==================================================
// texture_changer_by_touch
// スクリプトでコンテンツ内のテクスチャー名を自動取得
// コンテンツに変化があればテクスチャー数をチェックし、テクスチャー数に変化があれば最初から表示し直します
// タッチでテクスチャー切り替え
// 「長押し」でテクスチャー切り替え(リバース)
// 「長押し」しっぱなしにしているとどんどん戻ります。
// コンテンツ内にテクスチャーが無い場合はエラーステートに入る
// オーナータッチのみに反応するか、誰にでも反応するか選択可(変数OWNER_ONLY)

// 1.0.0
// 2008/07/07
// created by sasapy Beck

// Define
    integer AVATAR_DROP = TRUE; // 他のアバターのドロップを許可するか
    integer OWNER_ONLY = FALSE; // オーナーのタッチにのみ反応する

    key owner_key = NULL_KEY; // オーナーキー
    key avatar_key = NULL_KEY; // アバターキー

    string texture_name = ""; // テクスチャー名
    integer texture_index = 0; // 表示テクスチャー番号
    integer texture_max = 0; // テクスチャー数

    integer FACE = ALL_SIDES; // 表示する面番号
    vector COLOR = <0.0, 1.0, 0.0>; // テキストの色
    float ALPHA = 1.0; // テキストの透明度

    integer touch_count = 0; // 長押し用カウンター
    integer LONG_PUSH = 20; // 長押し検出閾値(20で約1秒)

//--------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            owner_key = llGetOwner(); // オーナーのキー取得

            llAllowInventoryDrop(AVATAR_DROP); // ドロップの許可

            texture_max = llGetInventoryNumber(INVENTORY_TEXTURE); // テクスチャー数取得
            if(texture_max <= 0) // テクスチャー数が0以下の場合
            {
                llOwnerSay("テクスチャーが見つかりません"); // 表示
                state error; // エラーステートへ
            }

            texture_name = llGetInventoryName(INVENTORY_TEXTURE, texture_index); // テクスチャー名取得
            llSetTexture(texture_name, FACE); // テクスチャー表示
            llSetText((string)(texture_index + 1) + "/" + (string)texture_max + "\n" + texture_name, COLOR, ALPHA); // フローティングテキスト表示
        }

// Touch Event
// タッチ開始
        touch_start(integer total_number)
        {
            touch_count = 0; // カウンター初期化
        }

// タッチ最中
        touch(integer total_number)
        {
            avatar_key = llDetectedKey(0); // タッチしたアバターのキー取得

            if(!OWNER_ONLY || (avatar_key == owner_key)) // タッチしたアバターは有効か?
            {
                touch_count ++; // カウンター更新

                if((touch_count % LONG_PUSH) == 0) // 定数回の剰余が0の場合(毎回)
                {
                    texture_index --; // 番号の更新
                    if(texture_index < 0) // 0より小さくなったら
                    {
                        texture_index = texture_max - 1; // 最大値-1に戻す
                    }

                    texture_name = llGetInventoryName(INVENTORY_TEXTURE, texture_index); // テクスチャー名取得
                    llSetTexture(texture_name, FACE); // テクスチャー表示
                    llSetText((string)(texture_index + 1) + "/" + (string)texture_max + "\n" + texture_name, COLOR, ALPHA); // フローティングテキスト表示
                }
            }
        }

// タッチ終了
        touch_end(integer total_number)
        {
            avatar_key = llDetectedKey(0); // タッチしたアバターのキー取得

            if(!OWNER_ONLY || (avatar_key == owner_key)) // タッチしたアバターは有効か?
            {
                if(touch_count < LONG_PUSH) // 通常のタッチ
                {
                    texture_index ++; // 番号の更新
                    if(texture_index >= texture_max) // 最大値以上になった場合
                    {
                        texture_index = 0; // 0に戻す
                    }

                    texture_name = llGetInventoryName(INVENTORY_TEXTURE, texture_index); // テクスチャー名取得
                    llSetTexture(texture_name, FACE); // テクスチャー表示
                    llSetText((string)(texture_index + 1) + "/" + (string)texture_max + "\n" + texture_name, COLOR, ALPHA); // フローティングテキスト表示
                }
            }
        }

// Change Event
        changed(integer change)
        {
            integer temp_max;

            if((change & CHANGED_INVENTORY) || (change & CHANGED_ALLOWED_DROP)) // インベントリーに変化があった場合
            {
                temp_max = llGetInventoryNumber(INVENTORY_TEXTURE); // テクスチャー数の取得
                if(texture_max != temp_max) // テクスチャー数が変わった場合
                {
                    texture_max = temp_max; // テクスチャー数の更新

                    if(texture_max <= 0) // テクスチャー数が0以下の場合
                    {
                        llOwnerSay("テクスチャーが見つかりません"); // 表示
                        state error; // エラーステートへ
                    }

                    texture_index = 0; // 0に戻す
                    texture_name = llGetInventoryName(INVENTORY_TEXTURE, texture_index); // テクスチャー名取得
                    llSetTexture(texture_name, FACE); // テクスチャー表示
                    llSetText((string)(texture_index + 1) + "/" + (string)texture_max + "\n" + texture_name, COLOR, ALPHA); // フローティングテキスト表示
                }
            }
        }
    }

//--------------------------------------------------
// エラーが発生した場合
state error
    {
        state_entry()
        {
            llOwnerSay("エラーを解消してタッチしてください"); // 表示
        }

        touch_start(integer total_number) // タッチしたら
        {
            llResetScript(); // スクリプトのリセット
        }
    }
//==================================================
青文字:お好みで変更してください
  

Posted by ささぴ at 10:24Comments(5)TrackBack(0)スクリプト

2008年06月26日

【スクリプト】キャンプチェアーのスクリプト(その2)

以前にこのブログに書いたキャンプチェアーのスクリプト(http://sasapy.slmame.com/e147429.html)にリクエストがあったので、新たに記事にします。

リクエストというのは「座りポーズを変えたい」ということで、スクリプトで保持しているパーミッションの関係などがあり、元記事を直すよりは、新たに立てた方が分かりやすいかなと思ったわけです。

さらに、予想外の問題があることが分かりました。
というのは、キャンプしている座る部分に、無理矢理他のアバターが座ってきた場合、キャンプが中断されてしまうということです。

<パーミッションについて>
サンプルでは椅子本体(ルートプリム)と実際に座るシート(子プリム)で構成しているオブジェクトで、キャンプによる金銭の支払いは各シートで行っています。
ということはオーナーの支払パーミッションは各シートで保持しています。
1スクリプトで保持できるパーミッションは1アバターに限りますので、座っているアバターにポーズ(アニメーション)をさせるということはパーミッションが必要になってきますので、同じスクリプトでは不可能になってきます。
そこで現在の子プリム用のスクリプトとは別にアニメーションをさせるスクリプトを同じプリムに入れる必要が出てきます。

現在の子プリム用のスクリプト(以下、子プリムスクリプト)と新たなポーズ用スクリプト(以下、ポーズスクリプト)の間の通信はリンクメッセージを使い、子プリムスクリプトからアニメーションの開始・停止の指示だけだします。

<割り込みキャンプについて>
同じ場所に座ってくるというのは予想外の行動で、そんないじわるな人がいるのか・・・と思いますが、対応しなければならないでしょう。
スクリプト中にコメントを書いている部分に条件判定をひとつ追加しました。

<ポーズスクリプトについて>
ポーズが一つではないということでしたので、複数のポーズをキャンプをするたびに切り替えることにしました。
子プリムスクリプトからリンクメッセージを受け、開始の場合はメッセージ中のidからアバターのキーを取得し、アニメーションのパーミッション要求・取得を行います。
パーミッションを取得すればリスト番号を更新してリストからアニメーション名を取得して、そのアニメーションを実行します。
リンクメッセージが停止の場合は、単純にアニメーションを停止しています。

では、まず子プリム用のスクリプトです。

//==================================================
// sasa_camp_child
// 1.0.0 2008/02/15
// 2.0.0 2008/06/20 アニメーションをするように処理追加
// 2.1.0 2008/06/20 座っている場所に座ってきた場合の処理追加
// created by sasapy Beck

//--------------------------------------------------
// Mode (スクリプトの動作に関わります。)

    integer GROUP_ONLY = FALSE; // FALSE:誰でも
                                            // TRUE:同じグループのみ可
    float SECOND_MINI = 1200.0; // 最小時間1200.0秒 = 20分
    integer MONEY_MINI = 2; // 最小金額2L$
    integer MONEY_MAX = 10; // 最大金額10L$

//--------------------------------------------------
// Define
    integer LINK_MSG_RESET = 11; // メッセージ番号
    integer LINK_MSG_REQUEST = 21;
    integer LINK_MSG_ANSWER = 31;
    integer LINK_MSG_LIMIT_NG = 32;
    integer LINK_MSG_TIME_NG = 33;
    integer LINK_MSG_CONTINUE_NG = 34;
    integer LINK_MSG_STAND = 41;
    integer LINK_MSG_START_ANIME = 51;
    integer LINK_MSG_STOP_ANIME = 52;

    float now_time = 0.0; // 経過時間
    integer last_money = 0; // 前回までの金額(1日単位)
    integer now_money = 0; // 現在の金額

    vector SIT_OFFSET = <0.3, 0.0, 0.5>; // 座る位置
    vector SIT_ROTATION = <0.0, 0.0, 0.0>; // 座る回転

    integer same_group; // アクティブグループが同一か
    string GROUP_NAME = "group name"; // グループのみキャンプ可のグループ名

    key owner_key = NULL_KEY; // オーナーキー
    key avatar_key = NULL_KEY; // アバターキー
    key camper_key = NULL_KEY; // キャンプ対象アバターキー
// 座ったときはavatar_key、キャンプ開始したらcamper_key
// プリムをリンクしているので座ったとき/立ち上がったときのイベントが、座っていない場所でも発生する
// 立ち上がったときのイベントでcamper_keyを判定し、自プリムかどうか判定する

    vector COLOR_OFF = <1.0, 1.0, 1.0>; // 白
    vector COLOR_ON = <1.0, 0.0, 0.0>; // 赤

//--------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            owner_key = llGetOwner(); // オーナーキー取得

            llSitTarget(SIT_OFFSET, llEuler2Rot(SIT_ROTATION * DEG_TO_RAD)); // 座る位置指定

// 保持パーミッションがオーナーの場合
// 保持パーミッションが支払いの場合
// REZしたらllResetScriptしているので、パーミションは持っていない。条件判断しないでllRequestPermissionsするほうがよい。こういうパターンということでそのまま記述
            if((llGetPermissionsKey() == owner_key) && (llGetPermissions() & PERMISSION_DEBIT))
            {
                state operation;
            }
            else
            {
                llRequestPermissions(owner_key, PERMISSION_DEBIT);
            }
        }

// Rez Event
        on_rez(integer start_param)
        {
            llResetScript();
        }

// Permission Event
        run_time_permissions(integer perm)
        {
            if(perm & PERMISSION_DEBIT)
            {
                state operation;
            }
        }
    }

//--------------------------------------------------
state operation
    {
        state_entry()
        {
            llOwnerSay("Camp start");
        }

// Rez Event
        on_rez(integer start_param)
        {
            llResetScript();
        }

// Change Event
        changed(integer change)
        {
            if(change & CHANGED_LINK)
            {
                avatar_key = llAvatarOnSitTarget();

// 座った場合
                if(avatar_key != NULL_KEY)
                {
// 座っているのに同じ場所に座ってきた場合の処理追加
                    if(camper_key == NULL_KEY) // 自プリムが空いている場合
                    {
                        same_group = llSameGroup(avatar_key); // アクティブグループが同じ場合TRUE

// グループのみ可で、アクティブグループが違う場合
                        if(GROUP_ONLY && !same_group)
                        {
                            llUnSit(avatar_key); // 立たせる
                            llSay(PUBLIC_CHANNEL, "This camp is Group Only. Please active group : " + GROUP_NAME);
                        }

// グループ不問、またはグループのみ可の時のグループメンバーの場合
                        else
                        {
// ルートにリクエストを送信
                            llMessageLinked(LINK_ROOT, LINK_MSG_REQUEST, "", avatar_key);
                        }
                    }
                }

// 途中で立ち上がった場合
                else
                {
                    if(camper_key != NULL_KEY) // 自プリムでキャンプしていた
                    {
                        llMessageLinked(LINK_THIS, LINK_MSG_STOP_ANIME, "", camper_key); // アニメーションの停止
                        llSay(PUBLIC_CHANNEL, "You stopped camping.");

// 今回の金額が発生する場合
                        if(now_money > 0)
                        {
                            llSay(PUBLIC_CHANNEL, "I pay " + (string)now_money + "L$.");
                            llGiveMoney(camper_key, now_money); // 支払い
                            llMessageLinked(LINK_ROOT, LINK_MSG_STAND, (string)(last_money + now_money), camper_key); // ルートに情報送信
                        }
                        camper_key = NULL_KEY; // キーのクリア
                        llSetTimerEvent(0.0); // タイマー停止
                        llSetText("Camp chair\n" + (string)MONEY_MINI + " L$ / " + (string)llFloor(SECOND_MINI / 60.0) + " min", COLOR_OFF, 1.0); // テキスト表示
                    }
                }
            }
        }

// Link Message Event
        link_message(integer sender_num, integer num, string str, key id)
        {
// 許可メッセージの場合
            if(num == LINK_MSG_ANSWER)
            {
                last_money = (integer)str; // 前回までの金額
                camper_key = id; // キー取得

                now_time = 0.0; // キャンプ時間の初期化
                now_money = 0; // 今回金額の初期化

                llSetText("Your camp\n" + (string)now_money + " L$ / " + (string)llFloor(now_time / 60.0) + " min", COLOR_ON, 1.0); // テキスト表示
                llSetTimerEvent(SECOND_MINI); // 最小時間でタイマー起動

                llMessageLinked(LINK_THIS, LINK_MSG_START_ANIME, "", camper_key); // アニメーション開始
            }

// 金額上限の場合
            else if(num == LINK_MSG_LIMIT_NG)
            {
                llUnSit(avatar_key); // 立たせる
                llSay(PUBLIC_CHANNEL, "You reached the limit of 1 day. Please camp tomorrow.");
            }

// 前回キャンプから経過時間が少ない場合
            else if(num == LINK_MSG_TIME_NG)
            {
                llUnSit(avatar_key); // 立たせる
                llSay(PUBLIC_CHANNEL, "Your time does not pass from the last camping. Please camp after doing it for a while.");
            }

// 連続の場合
            else if(num == LINK_MSG_CONTINUE_NG)
            {
                llUnSit(avatar_key); // 立たせる
                llSay(PUBLIC_CHANNEL, "You cannot camp in succession. Please camp after doing it for a while.");
            }
        }

// Timer Event
// 最小時間でタイマーイベント発生
        timer()
        {
            now_time += SECOND_MINI; // キャンプ時間の更新
            now_money += MONEY_MINI; // 今回金額の更新

            llSetText("Your camp\n" + (string)now_money + " L$ / " + (string)llFloor(now_time / 60.0) + " min", COLOR_ON, 1.0); // テキスト表示

// 前回までの金額と今回の金額で上限となった場合
            if((last_money + now_money) >= MONEY_MAX)
            {
                llMessageLinked(LINK_THIS, LINK_MSG_STOP_ANIME, "", camper_key); // アニメーションの停止
                llUnSit(camper_key); // 立たせる
                llSay(PUBLIC_CHANNEL, "You reached the limit of 1 day. Please camp tomorrow.");
                llSay(PUBLIC_CHANNEL, "I pay " + (string)now_money + " L$.");
                llGiveMoney(camper_key, now_money); // 支払い

                llMessageLinked(LINK_ROOT, LINK_MSG_STAND, (string)(last_money + now_money), camper_key); // ルートに情報送信
                camper_key = NULL_KEY; // キークリア
                llSetTimerEvent(0.0); // タイマー停止
                llSetText("Camp chair\n" + (string)MONEY_MINI + " L$ / " + (string)llFloor(SECOND_MINI / 60.0) + " min", COLOR_OFF, 1.0); // テキスト表示
            }
        }
    }
//==================================================

次に新たに子プリムに追加するポーズ用のスクリプトです。

//==================================================
// sasa_camp_animation
// 1.1.0
// 2008/06/20
// created by sasapy Beck

//--------------------------------------------------
// Define
    list ANIMATION_NAME = [ "anime1",
                                            "anime2",
                                            "anime3"
                                        ]; // 座ったときのアニメーション名

    integer animation_number = 0; // アニメーション番号
    integer animation_max = 0; // アニメーション数

    integer LINK_MSG_START_ANIME = 51;
    integer LINK_MSG_STOP_ANIME = 52;

//--------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            animation_max = llGetListLength(ANIMATION_NAME);
        }

// Link Message Event
        link_message(integer sender_num, integer num, string str, key id)
        {
            if(num == LINK_MSG_START_ANIME) // アニメーション開始メッセージの場合
            {
                if((llGetPermissionsKey() == id) && (llGetPermissions() & PERMISSION_DEBIT)) // パーミッションがある場合
                {
                    animation_number ++;
                    if(animation_number >= animation_max) animation_number = 0;

                    llStartAnimation(llList2String(ANIMATION_NAME, animation_number)); // アニメーション開始
                }
                else // パーミッションがない場合
                {
                    llRequestPermissions(id, PERMISSION_TRIGGER_ANIMATION); // アニメーションのパーミッション要求
                }
            }
            else if(num == LINK_MSG_STOP_ANIME) // アニメーション停止メッセージの場合
            {
                llStopAnimation(llList2String(ANIMATION_NAME, animation_number)); // アニメーション停止
            }
        }

// Permission Event
        run_time_permissions(integer perm)
        {
            if(perm & PERMISSION_TRIGGER_ANIMATION) // アニメーション許可
            {
                animation_number ++;
                if(animation_number >= animation_max) animation_number = 0;

                llStartAnimation(llList2String(ANIMATION_NAME, animation_number)); // アニメーション開始
            }
        }
    }
//--------------------------------------------------

赤文字:変更してください、 青文字:お好みで変更してください)  

Posted by ささぴ at 10:06Comments(0)TrackBack(0)スクリプト

2008年06月20日

【スクリプト】llEmailとemailでのメール本文ついて

もしかしたらバグ?と思われる事があったので書いておきます。

まずなんでもいいのでオブジェクトを2つ用意し、「送信ボックス」と「返信ボックス」とします。
スクリプトは単純で送信ボックスをタッチすると返信ボックスにメールをします。返信ボックスはメールが着たら、そのアドレス宛に返信するというものです。

送信ボックスのスクリプト
//==================================================
// lottery machine
// 1.0.0
// 2008/06/18
// created by sasapy Beck

// Define
    string mail_address = "69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com"; // 送信先のメールアドレス
    string SUBJECT = "LOTTERY"; // メール題名
    string send_message = "sasapy test message"; // メール本文

    float WAIT_TIME = 5.0; // タイマー(sec)

//--------------------------------------------------
// Main
    default
    {

// Touch Event
        touch_start(integer total_number)
        {
            llOwnerSay("Send Email");
            llEmail(mail_address, SUBJECT, send_message); // メール送信
            llSetTimerEvent(WAIT_TIME); // メール受信チェックタイマー起動
        }

// Timer Event
// メール受信チェックタイマー
        timer()
        {
            llOwnerSay("Timer");
            llGetNextEmail("", SUBJECT); // メール読み出し
        }

// E-Mail Event
// 景品の受け渡し終了の確認
        email(string time, string address, string subj, string message, integer num_left)
        {
            llOwnerSay("time : " + time);
            llOwnerSay("address : " + address);
            llOwnerSay("subj : " + subj);
            llOwnerSay("message : " + message);
            llOwnerSay("num_left : " + (string)num_left);
            llSetTimerEvent(0.0); // タイマー停止
        }
    }
//==================================================

返信ボックスのスクリプト
//==================================================
// premium_box (email版)
// 1.0.0
// 2008/06/19
// created by sasapy Beck

// Define
    string SUBJECT = "LOTTERY"; // メール題名
    string send_message = "recieve sasapy test message"; // メール本文

    float WAIT_TIME = 5.0; // メール受信チェックタイマー用

//--------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            llOwnerSay(" My key : " + (string)llGetKey()); // 送信アドレスを取得するためにキーを表示
            llSetTimerEvent(WAIT_TIME); // メール受信チェックタイマー起動
        }

// Timer Event
        timer()
        {
            llGetNextEmail("", SUBJECT); // メール読み出し
        }

// E-Mail Event
        email(string time, string address, string subj, string message, integer num_left)
        {
            llOwnerSay("time : " + time);
            llOwnerSay("address : " + address);
            llOwnerSay("subj : " + subj);
            llOwnerSay("message : " + message);
            llOwnerSay("num_left : " + (string)num_left);

            llEmail(address, SUBJECT, send_message); // メール送信
        }
    }
//==================================================

この二つを並べて実験しているときは問題なかったのですが、送信ボックスを別のSIMに持っていきREZして試してみたところ、最初の1回だけ返信されてきたメールの本文が空になってしまいます。

スクリプト中でllOwnerSayして、emailイベントのパラメータを表示していますので、そのログも載せます。

//==================================================
[4:50] You: ここからテスト
[4:51] You: 返信ボックスと同じ土地にいます
[4:51] You: 送信ボックスをREZする
[4:51] You: 送信ボックスにタッチする
[4:51] mail send: Send Email
[4:51] mail recieve: time : 1213962696
[4:51] mail recieve: address : 57bbe59e-aa14-8d5f-678d-3d805cdf3b7c@lsl.secondlife.com
[4:51] mail recieve: subj : LOTTERY
[4:51] mail recieve: message : Object-Name: mail send
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 233, 20)

sasapy test message
[4:51] mail recieve: num_left : 0
[4:52] mail send: Timer
[4:52] mail send: time : 1213962701
[4:52] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[4:52] mail send: subj : LOTTERY
[4:52] mail send: message : Object-Name: mail recieve
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 231, 20)

recieve sasapy test message
[4:52] mail send: num_left : 0
[4:52] You: 送信→受信・返信→受信顔料
[4:52] You: 完了
[4:52] You: もう一度タッチする
[4:53] mail send: Send Email
[4:53] mail recieve: time : 1213962781
[4:53] mail recieve: address : 57bbe59e-aa14-8d5f-678d-3d805cdf3b7c@lsl.secondlife.com
[4:53] mail recieve: subj : LOTTERY
[4:53] mail recieve: message : Object-Name: mail send
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 233, 20)

sasapy test message
[4:53] mail recieve: num_left : 0
[4:53] mail send: Timer
[4:53] mail send: time : 1213962785
[4:53] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[4:53] mail send: subj : LOTTERY
[4:53] mail send: message : Object-Name: mail recieve
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 231, 20)

recieve sasapy test message
[4:53] mail send: num_left : 0

[4:53] You: 完了
[4:53] You: 送信ボックスをTAKEする
[4:54] You: 別SIMへ移動
[4:54] Teleport completed from http://slurl.com/secondlife/Wet%20Hoof/206/236/21
[4:54] Connecting to in-world Voice Chat...
[4:54] Connected
[4:54] You: サンドボックスにきました
[4:55] You: 送信ボックスをREZします
[4:55] You: タッチします
[4:55] mail send: Send Email
[4:55] mail send: Timer
[4:55] mail send: time : 1213962929
[4:55] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[4:55] mail send: subj : LOTTERY
[4:55] mail send: message :
[4:55] mail send: num_left : 0
[4:56] You: メールが戻ってきました
[4:56] You: 返信ボックスのOwnerSayが表示されない(のは気にしないで)
[4:56] You: 戻ってきた本文が空です
[4:56] You: もう一度タッチ
[4:56] mail send: Send Email
[4:57] mail send: Timer
[4:57] mail send: time : 1213963018
[4:57] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[4:57] mail send: subj : LOTTERY
[4:57] mail send: message : Object-Name: mail recieve
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 231, 20)

recieve sasapy test message
[4:57] mail send: num_left : 0

[4:57] You: 今度は本文があります
[4:57] You: 送信ボックスをTAKEします
[4:57] You: もう一度REZします
[4:58] You: タッチします
[4:58] mail send: Send Email
[4:58] mail send: Timer
[4:58] mail send: time : 1213963087
[4:58] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[4:58] mail send: subj : LOTTERY
[4:58] mail send: message :
[4:58] mail send: num_left : 0
[4:58] You: 本文がありません
[4:58] You: もう一度タッチします
[4:58] mail send: Send Email
[4:59] mail send: Timer
[4:59] mail send: time : 1213963126
[4:59] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[4:59] mail send: subj : LOTTERY
[4:59] mail send: message : Object-Name: mail recieve
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 231, 20)

recieve sasapy test message
[4:59] mail send: num_left : 0
[4:59] You: 今度は本文があります
[4:59] You: 送信ボックスをTAKEします
[4:59] You: 元の場所にテレポします
[4:59] Teleport completed from http://slurl.com/secondlife/Sandbox%20Island/128/18/27
[4:59] Connecting to in-world Voice Chat...
[5:00] Connected
[5:00] You: 元の場所に戻ってきました
[5:00] You: 送信ボックスをREZします
[5:00] You: タッチします
[5:00] mail send: Send Email
[5:00] mail recieve: time : 1213963244
[5:00] mail recieve: address : e6128ef6-1e7b-d240-4571-ce3a464f85f4@lsl.secondlife.com
[5:00] mail recieve: subj : LOTTERY
[5:00] mail recieve: message : Object-Name: mail send
Region: Wet Hoof (288512, 280832)
Local-Position: (205, 232, 20)

sasapy test message
[5:00] mail recieve: num_left : 0
[5:01] mail send: Timer
[5:01] mail send: time : 1213963245
[5:01] mail send: address : 69e2ef7b-6ca3-84a0-c9bb-21a5646fc445@lsl.secondlife.com
[5:01] mail send: subj : LOTTERY
[5:01] mail send: message : Object-Name: mail recieve
Region: Wet Hoof (288512, 280832)
Local-Position: (204, 231, 20)

recieve sasapy test message
[5:01] mail send: num_left : 0
[5:01] You: 本文はちゃんとあります
//==================================================

今のところ、違うSIMに行ってREZして1回目は100%起こっています。

  

Posted by ささぴ at 21:38Comments(4)TrackBack(0)スクリプト

2008年06月10日

【スクリプト】LSL Wikiについて

私はLSL Wikiといえば「LSL Portal (http://wiki.secondlife.com/wiki/LSL_Portal)」を指すものだと思いこんでいました。
別のWikiの存在は知っていました、それは古いもので、その古いWikiの情報はLSL Portalに記述されているものと思っていました。
ですが、この認識は誤りでした。

先日のiNNXオフィスアワー(http://blog.innx.co.jp/)での話ですが、前記事のパーミッションの関係の話になり、私のブログに載っている情報を検証していただきました。
検証動作が私が試した時とびみょーに違うこともあったのですが、結果としてはブログに記述した通りなのですが、このときは、それがWikiに書いていなかったということが話題になりました。

上にも書いているとおり、私のリファレンスとしてのLSL WikiはLSL Portalだと思いこんでおりましたので、そこには記述がなかったわけですが、他のLSL Wikiには記述がありました。

他のLSL Wikiというのは

LSL Wiki (http://www.lslwiki.net/lslwiki/wakka.php?wakka=HomePage)
LSL Wiki (http://rpgstats.com/wiki/index.php?title=Main_Page)

なわけですが、Wikiという性格上、正確で公式な仕様ではないということは分かっていましたが、「あっちにあって、こっちにない」記述があるわけです。

今後は気を付けてLSL Portal以外も見るようにします・・・。
(なんとか1箇所に集約して欲しいものですが・・・。というかリンデン公式LSLリファレンス(詳細版)ができればいい話ですが・・・。)

  

Posted by ささぴ at 12:46Comments(0)TrackBack(0)スクリプト

2008年06月03日

【スクリプト】で取得するパーミッションについて

スクリプト中でllRequestPermissionsでパーミッションを取得していろいろな事を可能にするわけですが、関数の使い方等は他のきちんと説明しているサイトを見てもらうとして、いくつか疑問に思っていたことを実験してみた結果を残しておきます。
(LSL Wikiに記述されている内容に不足がある・・・という理由もあります。)

《パーミッション取得に関する条件等》
・通常は青色のダイアログが表示されるが、PERMISSION_DEBITの場合は黄色いダイアログが表示される。

・一度取得したパーミッションを、再度要求して拒否された場合、そのパーミッションを失う。

・1つのスクリプトで保持できるパーミッションは1人のアバターに関するものだけで、複数のアバターのパーミッションを保持したければ、その人数分のスクリプトが必要になる。(モジュール化)

・同一スクリプト内でステートが変わっても、パーミッションは保持する。

・パーミッションの許可/拒否はrun_time_permissionsイベントで確認する。(拒否してもイベントは発生する。)

・sitまたはattachしている場合、いくつかのパーミッションはダイアログが表示されずに無条件に許可となる。(表参照)

(空白が出来てしまいましたが、この下に表があります。)
(タグを全部小文字にしたり、改行を抜いてみたりしましたが、表示確認すると戻ってしまう・・・)








































































































































































































パーミッションの種類 取得対象 sitしている場合は
無条件に許可する
attachしている場合は
無条件に許可する
(ルートプリムのみ)
備考
PERMISSION_DEBIT
オーナーからの支払を許可する
オーナー × ×
PERMISSION_TAKE_CONTROLS
キーコントロールを許可する
誰でも
PERMISSION_TRIGGER_ANIMATION
アニメーションを許可する
誰でも
(Wiki無記載)
PERMISSION_ATTACH
アタッチ/デタッチを許可する
オーナー ×
PERMISSION_CHANGE_LINKS
リンクの変更を許可する
オーナー × ×
PERMISSION_TRACK_CAMERA
カメラ位置の取得を許可する
誰でも
(Wiki無記載)
PERMISSION_CONTROL_CAMERA
カメラの制御を許可する
誰でも
(Wiki無記載)

(Wiki無記載)
sitまたはattachしている時のみ要求可
(Wiki無記載)



《複数のパーミッションを同時に要求する場合》
・通常は一つのダイアログに複数の要求が併記されていて、すべてに対して許可/拒否しかできない。(個別に許可/拒否出来ない)

・通常は青色のダイアログが表示されるが、複数のパーミッション要求の中にPERMISSION_DEBITが含まれている場合、ダイアログは黄色いダイアログとなる。(要求内容は併記される)

・sitまたはattachしている場合は、無条件に許可するパーミッションだけで組み合わせた場合は、ダイアログは表示しない

・sitまたはattachしている場合は、無条件に許可しないパーミッションを含んだ組合せの場合は、すべての条件が併記されているダイアログが表示される

・sitまたはattachしている状態で、PERMISSION_DEBITとPERMISSION_CONTROL_CAMERAを組み合わせた場合のパーミッション要求ではダイアログが出ない。(許可/拒否出来ない)
(これはバグか?)

<2008.06.10追記>

PERMISSION_DEBITとPERMISSION_CONTROL_CAMERAを組み合わせたパーミッションの要求については別のWikiに記述がありました。エラーとなるようです。
  

Posted by ささぴ at 13:21Comments(1)TrackBack(0)スクリプト

2008年05月30日

【スクリプト】ヴィークル(乗り物)の基本形の解説

サンプルスクリプトの解説です。
スクリプトと一つにして投稿すると長くなるので、分割しました。
なるべくスクリプト中にコメントを入れるようにしていますので、そちらも参照してください。

----------------------------------------------------
【オブジェクトの制限】
・物理オブジェクトでなければならない(非物理でもスクリプトで物理オブジェクトにステータスを変える)
・乗り物本体、乗員(1アバターが1プリム計算)の合計プリム数が31プリム以下
(複数のアバターが乗るなら、その分プリム数を少なくしなければならない)

----------------------------------------------------
【スクリプトの基本的な流れと各イベントの処理】
●state_entryイベント
・初期設定(乗り物のパラメーター設定)

●on_rezイベント
・スクリプトのリセット

●touch_startイベント(無くてもよい)
・情報表示

●changedイベント(座った/立った)
・座った場合
アニメーションとキーコントロールのパーミッションの確認
・立った場合
アニメーション停止
キーコントロール停止

●run_time_permissionsイベント(パーミッションの取得)
・アニメーション開始
・使用するキーの設定

●controlイベント(キー入力)
・キーに対応したパラメーターを設定

----------------------------------------------------
【乗り物のパラメーターについて】
乗り物の種類ごとに基本的なパラメーターがリンデンから示されています。
パソコンのスタートメニューから「すべてのプログラム」→「Second Life」→「SL Scripting Language Help」(普通にインストールするとc:\Program Files\SecondLife\lsl_guide.htmlです)を開くとLinden Scripting Language Guideというハイパーテキストが開きます(ホームページの形式)。
最後の方に「C.26. Vehicle Types」という項があり、その中に各種乗り物の基本的なパラメーター設定が記載されています。
これを自分のスクリプトに組み込んで、実際に操作してみてパラメーターを微調整していけば、自分好みの乗り物が作れるわけです。

サンプルではAIRPLANEを使用しています(一部パラメーターは調整しています)。

----------------------------------------------------
【複数アバターが乗る場合の注意点】
複数のアバターが乗れる場合、アバターが乗った/立った場合のイベント(changed)に気を付けなければなりません。

例えば、2人乗りの車があって、運転席と助手席があったとします。
運転席にAさんが座ったときにchangedイベントが発生し、無事座れたとします。次に助手席にBさんが座ったときに、またchangedイベントが発生します。
このときにAさん側の処理でこのchangedイベントは自分のアバターのものではないということをチェックしなければなりません。
また、2人が無事に乗っていたとして、Aさん(Bさんでもいいですが)が先に立った場合もchangedイベントが発生しますが、このときに残されている側は自分が立ったのではないと分からなければなりません。
この辺をきちんと把握していないとパーミッションが無くなったり(助手席の人が運転するような事態になる)、アニメーションが止まってしまったりします。

サンプルではsitting_keyという変数に座っているアバターのキーを保持し、changedイベントでllAvatarOnSitTargetで取得したキーと一緒に判定に使っています。

    sitting_key                 NULL_KEY    NULL_KEY    アバター        アバター
    llAvatarOnSitTarget   NULL_KEY    アバター         NULL_KEY    アバター
    状況                           ありえない       座った           立った            ありえない

----------------------------------------------------
【キーボード入力の判定の仕方】
(キーというとUUIDのキーと混同しそうですが、ここではキーボードのキーという意味で使います)
キー入力で操作に必要なキーはllTakeControlsで指定します(当然、パーミッションが必要です)。
指定したキーが押された場合(押している最中、放した時も)はcontrolイベントが発生します。このイベントの引数としてlevelとedgeがあり、ビット毎にキーの種別を表します。
例えばCONTROL_FWD(↑キーまたはWキー)という定数が用意されていますが、この値は0x01(16進数)です。
ビットで表現すると00000001(2進数)で一番左の桁が↑キー(またはWキー)の情報ですよという意味になります。

// 書き忘れていたので追記(2005.05.30) ここから
特定のビットだけに着目すると、
    level   edge    状態
      0       0       押していない
      1       1       押した時
      1       0       押している最中
      1       0       押している最中
      1       0       押している最中
      1       0       押している最中
      0       1       放したとき
      0       0       押していない
となります。
経験上、押しっぱなしにしているとこのイベントは1秒間に4~7回くらい発生します。
// ここまで

実際に入力されたキーによってllSetVehicleVectorParamでVEHICLE_LINEAR_MOTOR_DIRECTIONとVEHICLE_ANGULAR_MOTOR_DIRECTIONにパラメーターを指定します。
アバター操作と同じ感覚ですと↑↓キーは前後、←→は左右(向きを回転するか、軸を倒すかはパラメーター次第)、Up/Downは上下または平面しか動かない乗り物ならギアとなります。
VEHICLE_LINEAR_MOTOR_DIRECTIONで指定するとベクトルの方向に進み、VEHICLE_ANGULAR_MOTOR_DIRECTIONで指定すると、軸を基準に回転します。

サンプルで実際に設定しているパラメーターを例にすると、

・↑↓キーでそれぞれLINER_FORWARD = < 5.0, 0.0, 0.0>とLINER_BACK = <-3.0, 0.0, 0.0>を設定しています。これは乗り物を基準にして↑キーでX軸方向+5、↓キーでX軸方向-3の力を加えます。

・Upキー、Downキーの場合は、LINER_UP = < 0.0, 0.0, 5.0>とLINER_DOWN = < 0.0, 0.0, -3.0>を設定しています。Z軸方向に5.0(または-3.0)の力を加えます。

・←→の場合は、ROLL_RIGHT = < 0.0, 0.0, -1.0>とROLL_LEFT = < 0.0, 0.0, 1.0>を設定しています。これは乗り物を基準にしてZ軸を軸にして-1.0(または1.0)の回転の力をかけます。感覚としては平面的に回ります。

・Shift+←→の場合は、BANK_RIGHT = <-1.0, 0.0, 0.0>とBANK_LEFT = < 1.0, 0.0, 0.0>を設定しています。この場合はX軸を軸として回転の力をかけますので、感覚としては倒れ込む感じです。

このように各キー入力によってmotor_linearとmotor_angularという変数に値を設定し、最後にllSetVehicleVectorParamでパラメーターを設定しています。

  

Posted by ささぴ at 11:34Comments(0)TrackBack(0)スクリプト

2008年05月30日

【スクリプト】ヴィークル(乗り物)の基本形

ヴィークル系のスクリプトのサンプルは、たぶんいろんなところにあると思いますが、載せておきます。

※乗り物として動きますが、快適に動かすには各パラメーターの調整が必要です。

//==================================================
// sasa_vehicle
// ヴィークルの基本サンプル
// 1.0.0
// 2008/05/26
// created by sasapy Beck

//--------------------------------------------------
// Mode
    integer UP_DOWN_MODE = 1; // Up/Downキーの機能割り当て
                                                // 1: 上昇・下降する
                                                // 2: ギアチェンジ

//--------------------------------------------------
// Define
    key owner_key = NULL_KEY; // オーナーキー
    key sitting_key = NULL_KEY; // 座っているアバターのキー
    key avatar_key = NULL_KEY; // イベントを起こしたアバターのキー

    vector SIT_POSITION = <-0.1, 0.0, 0.85>; // 座る位置
    vector SIT_ROTATION = < 0.0, 0.0, 0.0>; // 座る回転
    vector CAMERA_OFFSET = <-2.0, 0.0, 1.0>; // カメラ位置
    vector CAMERA_AT = < 2.0, 0.0, 1.0>; // カメラの向き

    vector LINER_FORWARD = < 5.0, 0.0, 0.0>; // 直進時のパラメーター
    vector LINER_BACK = <-3.0, 0.0, 0.0>; // 後退時のパラメーター
    vector LINER_UP = < 0.0, 0.0, 5.0>; // 上昇時のパラメーター
    vector LINER_DOWN = < 0.0, 0.0, -3.0>; // 下降時のパラメーター
    vector ROLL_RIGHT = < 0.0, 0.0, -1.0>; // 回転時のパラメーター
    vector ROLL_LEFT = < 0.0, 0.0, 1.0>; // 回転時のパラメーター
    vector BANK_RIGHT = <-1.0, 0.0, 0.0>; // 傾き時のパラメーター
    vector BANK_LEFT = < 1.0, 0.0, 0.0>; // 傾き時のパラメーター

    float gear = 1.0; // ギア
    float GEAR_MAX = 4.0; // ギアの上限
    float GEAR_MINI = 1.0; // ギアの下限

    vector motor_linear = ZERO_VECTOR; // 直進方向のパラメーター
    vector motor_angular = ZERO_VECTOR; // 回転方向のパラメーター

    string ANIMATION_SIT = "sit"; // アニメーション名
    string ANIMATION_FORWARD = "forward"; // アニメーション名
    string ANIMATION_BACK = "back"; // アニメーション名
    string ANIMATION_LEFT = "left"; // アニメーション名
    string ANIMATION_RIGHT = "right"; // アニメーション名
    string ANIMATION_UP = "up"; // アニメーション名
    string ANIMATION_DOWN = "down"; // アニメーション名

//--------------------------------------------------
// Function
// ヴィークルのパラメーター設定
    ssSetVehicleParams()
    {
// タイプ
        llSetVehicleType(
//          VEHICLE_TYPE_NONE // なし
//          VEHICLE_TYPE_SLED // ソリ
//          VEHICLE_TYPE_CAR // 車
//          VEHICLE_TYPE_BOAT // ボート
            VEHICLE_TYPE_AIRPLANE // 飛行機
//          VEHICLE_TYPE_BALLOON // 気球
        );

// フラグ(一旦全フラグを解除してから設定する)
        llRemoveVehicleFlags(0
            | VEHICLE_FLAG_NO_DEFLECTION_UP // 垂直方向無効
            | VEHICLE_FLAG_LIMIT_ROLL_ONLY // 上下に向いても垂直方向の移動に影響しない
            | VEHICLE_FLAG_HOVER_WATER_ONLY // HOVERの水面を基準とする
            | VEHICLE_FLAG_HOVER_TERRAIN_ONLY // HOVERの地面を基準とする
            | VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT // HOVERの指定の高さを基準とする
            | VEHICLE_FLAG_HOVER_UP_ONLY // HOVERの上昇のみ有効(下降は重力)
            | VEHICLE_FLAG_LIMIT_MOTOR_UP // 空中にいるときはモーター無効
            | VEHICLE_FLAG_MOUSELOOK_STEER // マウスルックした向きへ方向転換
            | VEHICLE_FLAG_MOUSELOOK_BANK // マウスルックした向きへバンク
            | VEHICLE_FLAG_CAMERA_DECOUPLED // 乗り物の回転に視点を合わせない
        );

        llSetVehicleFlags(0
//          | VEHICLE_FLAG_NO_DEFLECTION_UP // 垂直方向無効
            | VEHICLE_FLAG_LIMIT_ROLL_ONLY // 上下に向いても垂直方向の移動に影響しない
//          | VEHICLE_FLAG_HOVER_WATER_ONLY // HOVERの水面を基準とする
//          | VEHICLE_FLAG_HOVER_TERRAIN_ONLY // HOVERの地面を基準とする
//          | VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT // HOVERの指定の高さを基準とする
//          | VEHICLE_FLAG_HOVER_UP_ONLY // HOVERの上昇のみ有効(下降は重力)
//          | VEHICLE_FLAG_LIMIT_MOTOR_UP // 空中にいるときはモーター無効
//          | VEHICLE_FLAG_MOUSELOOK_STEER // マウスルックした向きへ方向転換
//          | VEHICLE_FLAG_MOUSELOOK_BANK // マウスルックした向きへバンク
//          | VEHICLE_FLAG_CAMERA_DECOUPLED // 乗り物の回転に視点を合わせない
        );

// フロートパラメーター
        llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.5); // 力を前後移動に変換する0.0~1.0
        llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 0.5); // 変換が最大になる時間(秒)
        llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 10.0); // 静止するまでの時間(秒)
        llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 2.0); // 最高速度になる時間(秒)

        llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 1.0); // 力を向きに変換する0.0~1.0
        llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 2.0); // 変換が最大になる時間(秒)
        llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 5.0); // 角速度が静止するまでの時間
        llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 3.0); // 角速度が最大になるまでの時間

        llSetVehicleFloatParam(VEHICLE_BANKING_EFFICIENCY, 1.0); // バンクを回転に変換する-1.0(外側)~0(なし)~1.0(内側)
        llSetVehicleFloatParam(VEHICLE_BANKING_MIX, 0.7); // 速度が速いとカーブする0.0(しにくい)~1.0(しやすい)
        llSetVehicleFloatParam(VEHICLE_BANKING_TIMESCALE, 3.0); // バンクが最大になるまでの時間(秒)

        llSetVehicleFloatParam(VEHICLE_BUOYANCY, 0.0); // 浮力-1.0(重力2倍)~0.0(重力1倍)~1.0(無重力)

        llSetVehicleFloatParam(VEHICLE_HOVER_HEIGHT, 0.0); // 基準からHOVERする高度0.0~100.0(m)
        llSetVehicleFloatParam(VEHICLE_HOVER_EFFICIENCY, 0.5); // 高度変更のなめらかさ0.0~1.0
        llSetVehicleFloatParam(VEHICLE_HOVER_TIMESCALE, 50.0); // 目標高度に達するまでの時間(秒)

        llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.9); // 垂直を維持する強さ0.0(弱い)~1.0(強い)
        llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 2.0); // 垂直になるまでの時間(秒)

// ベクト