ソラマメブログ

2008年07月14日

【雑】REZ DAYらしい

1年前の7月14日、その頃にSecond Lifeが日本語ベータになったということで、んじゃぁやってみようと登録した日。
(厳密にはPDTで14日なのでJSTでは明日でしょうけど)

その前からSecond Lifeの事は知っていましたが、どーにもあのアバターと洋服のザックリした感じが好きになれずに、これは無いなぁと思っていたんですが、やはり1年前ぐらいに週刊アスキーのSecond Life特集の増刊号が出版され、掲載されているアバターがキレイなことと、いろいろ自分で物が作れるんだぁということで、興味は湧いてきたところでした。

いざ登録したら、「あの」オリエンテーション・アイランドに放り込まれ、何かの情報で、ちゃんとクリアしないとダメらしいという「間違った」情報が頭の中にあって、着々とクリアーしていったのですが、クリアしたはずの1つのオリエンテーションが未クリア状態のままで、何度やってもダメ。結局、オリエンテーション・アイランドに1週間ほどいるハメに・・・。
その後の情報で看板タッチで抜けられることを知り、「なんだよぉ」と思いながら世界に飛び出していったわけです。

で、週間アスキーに載っていたフリーのお店や、ステキ(?)なスポットをてきとーに回ったりしていたわけですが、当然、リンデン$も持っていないわけで、これはキャンプしかないのか・・・と某ビーチで転がってたりして、数十リンデン$を稼いでちょっとした服などを買っていたわけですが、時間の割に稼ぎが少ないということを悟り、あっというまにカード登録して、リアル・マネーを投資するにいたったわけです。

そんな初期の頃にラジオ放送があるということで、Sapporo SIMに降り立ちました。ラジオ局の前の空き地で音を出しながらギターの練習をしているshu Haxさん(現総合ミュージックショップ「Surreality」クラフトマン)に出会うこととなりました。ギターの話とか音楽の話などをし、お友達になってもらいました。
また、Sapporo SIMの公共部分(通路など)でラジオの過去放送分が流れていて、じゅりちゃん(Chouchouのシンガー・ライター)の声を初めて聴いたのもこのころでした。

そんなこんなでSapporo SIMに居着いてウロウロする日々を送り、ラジオ局のスタッフやDJの方々とお友達になり、ますますSapporo SIMに居着いてしまったわけです。

とある日のじゅりちゃんのラジオ番組「Little Tiara」の中で、恒例の占いのコーナーで、なんとその週末にもっとも幸運な星座になりました。たしか「積極的になにかすると成功する」みたいな占いでした。
冗談で「んじゃぁ、Chouchouのライヴでじゅりちゃんと一緒に歌うか?」みたいな事をいっていたら、「Chouchouのライヴでギターを弾いてみない?」というお誘いがあり、そりゃぁもう喜んでお受けして、現在もChouchou Bandでギターを担当しているわけです。
じゅりちゃんの占いは当たっていたのでした。

そんな日々を送りながら物作りをしたいなぁと思い始め、たまたま空いた土地をレンタルしてSapporo SIMの住人となったわけです。
土地を持ってからは、びみょーなオブジェクトと、画期的なスクリプト(自画自賛)を作る毎日。
基本的には売るようなモノではなく自分が欲しいモノしか作っていないんですけどね。

「ソラマメ」もSLを始めた頃から見ていましたが、私は面倒くさがりなのでブログを書くつもりはぜんぜんなかったんです。
お友達のブログが多かったので、ブックマークとして使うためにソラマメに登録して「お気に入り」だけ使っていたのですが、去年の11月くらいにせっかく作ったスクリプトなどが、私の「持ち物」の中で眠っているのももったいないなぁということで、ぼちぼちとスクリプト中心にブログを書き始めたわけです。

最近はメインランドの自分の土地で、なにか作ったり、音楽を聴きながらまったりとしていたり、お友達から「スクリプトで分からないことがぁ」といわれればお手伝いしたり、Chouchouのライヴがあればバンドメンバーとして参加したり(バンド無しライヴの時は聴きにいったり)しています。

ということで、飽きやすい私が1年も続けているということは、その間に出会った人たちのおかげかな。感謝。
2年目は逆の立場で、出会う人に貢献できればいいかなぁと思っています。
特にスクリプト関係で分からないことがあったら、面識が無くてもお気軽に訊いてください。
(答えられる範囲で・・・ですけどね)
  

Posted by ささぴ at 13:30Comments(7)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(1)TrackBack(0)スクリプト

2008年07月03日

【雑】AIFF形式からWAV形式に変換する

Chouchou(http://littletiara.slmame.com/)の新曲「ererna」が玉光道(http://slurl.com/secondlife/sapporo/207/76/26)さんで販売が開始されましたが、今まで通りのMP3とは別に高音質のAIFFフォーマットでも楽曲を購入することが出来るようになりました。

早速AIFFフォーマットを購入したわけですが、元々AIFFフォーマットというのはMacintoshでのリニアPCM音源で、さらに販売されているChouchouの楽曲が、これでもかってほど高音質のサンプリングレート48KHz、24ビットでした。

私のパソコンはWindowsXPパソコンで、通常のWindows Media Playerでは24ビットに対応していませんですし、WINAMPでも聞けませんでした。iTunesはどうなんでしょう?(Mac版は聞けました)

2008.07.04追記
WINAMPをインストールしなおしたらAIFFは普通に聞くことができました。
さらに、ファイル拡張子がaifだとWINAMPでは再生できないみたいです。拡張子をaiffにリネームすれば再生できました。
今回販売された高音質バージョンのファイル名の拡張子がaifだったんですねぇ・・・。

そこでAIFFフォーマットをWAVEフォーマットに変換しなければならなかったのですが、他にも同様の環境で高音質バージョンが聞けない方もいるかと思い、変換方法を残しておきます。

必要なのは、フォーマット変換ソフトなのですが、私の場合はフリーソフトの
YAMAHA Wave Editor TWE V2.3.1 (http://www.yamaha.co.jp/product/syndtm/dl/utility/twe/index.html
を使いました。


(こんな感じです)

1.サイトからダウンロードし、解凍後、新しくできたフォルダー内のTWE.exeを実行します。
2.起動すると「フォルダの参照」というウインドウが開くので、そのままOKします。
3.File→Openでファイルを参照するウインドウが開くので、AIFFフォーマットのデータが入っているフォルダを開き、AIFFデータを開きます。
4.データを開くと波形が表示されます。
5.File→Convert Bit Sizeでウインドウが開くので、「24 bit >>>> 16 bit」にチェックを入れOKをクリックします
6.変換が始まり、波形がジワジワと変わっていきます
7.変換が終了したらFile→Save Asを選択すると「名前を付けて保存」ウインドウが開きます
8.下段のファイルの種類を「WAVE PCM(*.wav;)」に変更し、必要であればファイル名を変更して「保存」ボタンを押します

これでWindows Media Palyer等で聞くことが出来る、WAVEフォーマットのデータが出来ます。

あとは、お好みでWAVからMP3などに変換してください(CDex等を使うと出来ます)。

  

Posted by ささぴ at 15:06Comments(3)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(2)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); // 垂直になるまでの時間(秒)

// ベクトルパラメーター
        llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_OFFSET, <0.0, 0.0, 0.0>); // プリム中心からのモーター位置
        llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0.0, 0.0, 0.0>); // モーターの最高速度(m/s)
        llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <90.0, 10.0, 5.0>); // 停止までの時間(秒)
        llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, <0.0, 0.0, 0.0>); // 最大角速度(rad/sec)
        llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <5.0, 5.0, 5.0>); // 回転が停止するまでの時間(秒)

// ローテーションパラメーター
        llSetVehicleRotationParam(VEHICLE_REFERENCE_FRAME, <0.0, 0.0, 0.0, 1.0>); // 乗り物の向きがデフォルトと違う場合の回転の向き
    }

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

// 初期設定
        state_entry()
        {
            owner_key = llGetOwner(); // オーナーキー取得
            llSetSitText("Ride"); // パイメニュー設定

// 座る位置と視点位置の設定
            llSitTarget(SIT_POSITION, llEuler2Rot(SIT_ROTATION * DEG_TO_RAD)); // 座る位置と回転
            llSetCameraEyeOffset(CAMERA_OFFSET); // カメラ位置
            llSetCameraAtOffset(CAMERA_AT); // カメラの向き

            ssSetVehicleParams(); // ヴィークルパラメーターの設定
        }

// REZ Event
// REZされたらリセットする
        on_rez(integer start_param)
        {
            llResetScript();
        }

// Touch Event
// タッチされたらインフォメーションを表示
// ヴィークルの動作に影響しないので、お好みで処理を抜くか、宣伝(LM配布、Notecard配布)等を入れてください
        touch_start(integer total_number)
        {
            avatar_key = llDetectedKey(0); // アバターキー取得

            if(avatar_key == owner_key) // オーナーの場合
            {
                llOwnerSay("This vehicle is yours");
            }
            else // オーナー以外の場合
            {
                llSay(PUBLIC_CHANNEL, "This vehicle is not yours");
            }
        }

// Change Event
// オブジェクトにアバターが座った/立ったときのイベント
        changed(integer change)
        {
            if(change & CHANGED_LINK) // LINKが変わる=座る/立つ
            {
                avatar_key = llAvatarOnSitTarget(); // 座ったアバターキー取得

                if((sitting_key == NULL_KEY) && (avatar_key != NULL_KEY)) // 今まで座ってなくて座られた
                {
                    sitting_key = avatar_key; // 座っているアバターキー更新
                    llSetStatus(STATUS_PHYSICS, TRUE); // 物理属性にする
                    llRequestPermissions(sitting_key, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS); // パーミッション要求
                }
                else if((sitting_key != NULL_KEY) && (avatar_key == NULL_KEY)) // 今まで座っていたが居なくなった
                {
                    sitting_key = NULL_KEY; // 座っているアバターキークリア
                    llSetStatus(STATUS_PHYSICS, FALSE); // 物理属性解除
                    llReleaseControls(); // キーコントロールの解放
                    llStopAnimation(ANIMATION_SIT); // アニメーションの停止
                    gear = 1.0; // ギアの初期化
                }
            }
        }

// Permission Event
// パーミッションの許可
        run_time_permissions(integer permissions)
        {
            if(permissions & PERMISSION_TRIGGER_ANIMATION) // アニメーション許可
            {
                llStartAnimation(ANIMATION_SIT); // 基本ポーズ
            }
            if(permissions & PERMISSION_TAKE_CONTROLS) // キーコントロール許可
            {
                llTakeControls( CONTROL_FWD | CONTROL_BACK |
                                        CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT |
                                        CONTROL_RIGHT | CONTROL_LEFT |
                                        CONTROL_UP | CONTROL_DOWN,
                                        TRUE, FALSE); // 使用するキーの設定
            }
        }

// Control Event
// キー入力による処理
// 同時に2つ以上のキーが押された場合を考慮していない
        control(key name, integer levels, integer edges)
        {
            motor_linear = ZERO_VECTOR;
            motor_angular = ZERO_VECTOR;

// ↑キー、Wキー
            if((levels & CONTROL_FWD) && (edges & CONTROL_FWD)) // 押したとき
            {
                llStartAnimation(ANIMATION_FORWARD);
            }
            if(levels & CONTROL_FWD) // 押している最中
            {
                motor_linear = LINER_FORWARD * gear;
            }
            if(!(levels & CONTROL_FWD) && (edges & CONTROL_FWD)) // 放したとき
            {
                motor_linear = ZERO_VECTOR;
                llStopAnimation(ANIMATION_FORWARD);
            }

// ↓キー、Sキー
            if((levels & CONTROL_BACK) && (edges & CONTROL_BACK)) // 押したとき
            {
                llStartAnimation(ANIMATION_BACK);
            }
            if(levels & CONTROL_BACK) // 押している最中
            {
                motor_linear = LINER_BACK * gear;
            }
            if(!(levels & CONTROL_BACK) && (edges & CONTROL_BACK)) // 放したとき
            {
                motor_linear = ZERO_VECTOR;
                llStopAnimation(ANIMATION_BACK);
            }

// →キー、Dキー
            if((levels & CONTRO