ソラマメブログ

2008年01月23日

ダイアログ表示のサンプル

たまには役立つスクリプトを・・・。
(長くなってしまった)

ダイアログを表示して入力させるインターフェイスは分かりやすいですし、選択肢を並べることが出来るのでとても有用ですが、ダイアログに並ぶボタンの順番が分かりにくいですよね。
例えば、
llDialog(key, "Dialog Button Test", ["1","2","3","4","5","6","7","8","9","10","11","12"], -2);
と記述すると



のように表示され、下から順番になります。
選択肢の並び順がリストで定義した順番に上から並んで欲しいため、上の例でいうと
llDialog(key, "Dialog Button Test", ["10","11","12","7","8","9","4","5","6","1","2","3"], -2);
と並べ直したりしなければなりません。
また、1ページに収まる選択肢の数(12個以内)であればいいのですが、選択肢がそれ以上あって、ページの切り替えなどを行うと、非常に面倒になります。

そこで、ダイアログ表示と、入力された内容(文字列)から処理をするサンプルを作ってみました。
最大のポイントは、選択肢のリストを上から並べたい順番に記述できることと、ボタン数によってページ管理をしていることです。
12個あるボタンの最下段の3つのボタンを「機能」ボタンとして使用していますので、1ページ内の選択肢は9個となってしまいますが、ページを戻したり進めたりすることができます。
ボタン数が端数(9個に満たない)の場合は"-"のボタンを追加します。そのボタンがクリックされた場合は同じダイアログの再表示を行います。

//==================================================
// sasa_dialog
// ver 1.0.0
// 2008.01.22
// created by sasapy Beck

// Define
    integer owner_only = FALSE; // TRUE : オーナーのみ操作可
                                        // FALSE : 誰でも操作可

    key owner_key; // オーナーUUID
    key avatar_key; // アバターUUID
    string text_message = "Touch to Dialog"; //
    vector text_color = <1.0, 0.0, 0.0>; // 赤
    float text_alpha = 1.0; // 1.0 : 透過しない

    integer dialog_handle; // ハンドル
    integer dialog_channel = -080122; // ダイアログ用チャンネル番号
    float wait_time = 20.0; // ダイアログ入力待ち時間(20秒)
    integer button_max = 9; // 表示ボタン数

// 機能ボタンを変更した場合、リッスン内の各処理も相当の処理に変更すること
    string reserve_1 = "Back"; // 機能ボタン1
    string reserve_2 = "Next"; // 機能ボタン2
    string reserve_3 = "Stop"; // 機能ボタン3
    string null_button = "-"; // 空ボタン

// ダイアログボタン名
    list button_name = [ "A01", "B02", "C03",
                                "D04", "E05", "F06",
                                "G07", "H08", "I09",
                                "J10", "K11", "L12",
                                "M13", "N14"];
// ダイアログ機能
    list button_string = [ "a01", "b02", "c03",
                                "d04", "e05", "f06",
                                "g07", "h08", "i09",
                                "j10", "k11", "l12",
                                "m13", "n14"];
    list button_vector = [ <0.0, 0.0, 0.0>, <0.0, 0.0, 1.0>, <0.0, 1.0, 0.0>,
                                <0.0, 1.0, 1.0>, <0.5, 0.0, 0.0>, <0.5, 0.0, 1.0>,
                                <0.5, 1.0, 0.0>, <0.5, 1.0, 1.0>, <1.0, 0.0, 0.0>,
                                <1.0, 0.0, 1.0>, <1.0, 0.5, 0.0>, <1.0, 0.5, 0.0>,
                                <1.0, 1.0, 0.0>, <1.0, 1.0, 1.0>];

    integer button_number; // ボタン総数

    integer menu_number = 0; // メニュー番号
    integer menu_max = 0; // メニュー総数
    integer now_select = FALSE; // 入力中フラグ

//--------------------------------------------------
// Function
// Display Dialog
// ダイアログ作成と表示
    ssDisplayDialog()
    {
        integer start; // 表示ボタン開始位置
        list now_menu = []; // 表示メニュー作成用

        if(menu_max == 0) // メニューが1ページしかない場合
        {
            now_menu += [null_button, null_button, reserve_3]; // 固定ボタン
        }
        else // メニューが複数ページある場合
        {
            now_menu += [reserve_1, reserve_2, reserve_3]; // 固定ボタン
        }

        start = menu_number * button_max; // ボタン名取得開始位置

        string check_button;
        integer i;

        for(i = 6; i <= 8; i++) // 3段目
        {
            check_button = llList2String(button_name, start + i);
            if(check_button == "") check_button = null_button;
            now_menu += [check_button];
        }
        for(i = 3; i <= 5; i++) // 2段目
        {
            check_button = llList2String(button_name, start + i);
            if(check_button == "") check_button = null_button;
            now_menu += [check_button];
        }
        for(i = 0; i <= 2; i++) // 1段目
        {
            check_button = llList2String(button_name, start + i);
            if(check_button == "") check_button = null_button;
            now_menu += [check_button];
        }

        llDialog(avatar_key, "Please select (within 20 sec)", now_menu, dialog_channel);
        llSetTimerEvent(wait_time);
    }

//--------------------------------------------------
// Main
    default
    {
        state_entry()
        {
            owner_key = llGetOwner(); // オーナーUUID取得
            llSetText(text_message, text_color, text_alpha); // フローティングテキスト表示

            button_number = llGetListLength(button_name); // ボタン総数取得
// ボタン名のリスト、関連リストは正常に存在するとする(数、定義等)

            menu_number = 0; // メニュー番号初期化
            menu_max = (button_number - 1) / button_max; // メニュー数算出
        }

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

// Touch Event
        touch_start(integer num_detected)
        {
            avatar_key = llDetectedKey(0); // UUID取得

            if(owner_only && (avatar_key != owner_key)) // オーナーのみの時のオーナー以外がタッチした場合
            {
                llSay(PUBLIC_CHANNEL, "You are NOT OWNER.");
            }
            else
            {
                if(!now_select) // 選択中でない場合
                {
                    dialog_handle = llListen(dialog_channel, "", avatar_key, ""); // リッスンハンドル保持
                    now_select = TRUE; // フラグオン
                    ssDisplayDialog(); // ダイアログ表示
                }
                else // 誰かが選択中の場合
                {
                    llSay(PUBLIC_CHANNEL, "Somebody is the whole selecting. Please wait.");
                }
            }
        }

// Listen Event
        listen(integer chan, string name, key id, string msg)
        {
// 機能ボタン1の処理
            if(msg == reserve_1) // "Back"
            {
                menu_number --; // メニュー番号更新
                if(menu_number < 0) // メニュー番号が0より小さくなった場合
                {
                    menu_number = menu_max; // メニュー番号を最大値にする
                }
                ssDisplayDialog(); // ダイアログ表示
            }
// 機能ボタン2の処理
            else if(msg == reserve_2) // "Next"
            {
                menu_number ++; // メニュー番号更新
                if(menu_number > menu_max) // メニュー番号が最大値を超えたとき
                {
                    menu_number = 0; // メニュー番号を0に戻す
                }
                ssDisplayDialog(); // ダイアログ表示
            }
// 機能ボタン3の処理
            else if(msg == reserve_3) // "Stop"
            {
                llListenRemove(dialog_handle); // リッスン停止
                now_select = FALSE; // フラグオフ
                llSetTimerEvent(0.0); // タイマー停止
                menu_number = 0; // メニュー番号初期化
            }
// 空ボタンの処理
            else if(msg == null_button) // "-"
            {
                ssDisplayDialog(); // ダイアログ表示
            }

            else
            {
                llListenRemove(dialog_handle); // リッスン停止
                now_select = FALSE; // フラグオフ
                llSetTimerEvent(0.0); // タイマー停止
                menu_number = 0; // メニュー番号初期化

// 表示用のリストではなく、元のリストから番号を取得する
                integer select_number = llListFindList(button_name, [msg]); // ボタン名からリスト上の何番目かを取得

// ボタン番号により別のリストからパラメータを取得して、いろいろな処理をする

// 例1)文字列を取得し、チャットに発言
                string set_string = llList2String(button_string, select_number); // 番号から機能リストから該当するものを取得
                llSay(PUBLIC_CHANNEL, set_string);

// 例2)ベクターを取得し、色を変える
                vector set_color = llList2Vector(button_vector, select_number); // 番号から機能リストから該当するものを取得
                llSetColor(set_color, ALL_SIDES);
            }
        }

// Timer Event
        timer()
        {
            llListenRemove(dialog_handle); // リッスン停止
            now_select = FALSE; // フラグオフ
            llSetTimerEvent(0.0); // タイマー停止
            menu_number = 0; // メニュー番号初期化
            llSay(PUBLIC_CHANNEL, "Dialog timeout. Try again.");
        }
    }
//==================================================

(このスクリプトで表示したダイアログ)

まずメイン部分ですが、初期設定として、
・フローティングテキストの表示
・ボタン数の取得
・ボタン数からメニューのページ数の算出
を行っています。
次にお約束のREZされたらスクリプトのリセットを行う部分があります。

タッチ部分ですが、変数owner_onlyによってオーナーのタッチのみで動作するかを定義しています。owner_onlyがTRUEの時にオーナー以外がタッチした場合はメッセージを出力して、ダイアログ表示はしません。
タッチで動作する条件を満たしている場合には、変数now_selectによって、誰かがダイアログから選択中であるかを判断します。誰かが選択中の場合は、多重にダイアログが開かないようにしています。
この条件も満たしている場合にダイアログを表示します。表示する前にハンドルを取得し選択中フラグ(now_select)をセットします。実際のダイアログを表示するユーザー関数ssDisplayDialog()(後述)を呼びます。

次にリッスン部分です。ここがダイアログから選択された内容によって処理をしているところです。
まず機能ボタンが選択された場合ですが、この例では"Back"、"Next"、"Stop"があります。
"Back"と"Next"が選択された場合メニュー番号(変数menu_number)を更新してユーザー関数ssDisplayDialog()を呼びます(最大値、最小値のチェックもしています)。
"Stop"が選択された場合はダイアログからの入力を止めるので、リッスン停止等の設定処理をします(コメント参照)。

空ボタンの"-"が押された場合は、同じダイアログを再度表示します。

実際の選択肢が選択された場合は、まず停止等の処理をし、ボタン名リスト(リスト変数button_name)から、入力されたmsgが何番目かを取得します(select_number)。
ここで、表示しているダイアログのリストではなく、その元となっているリストから参照しているところがポイントとなります。元のリストを参照することで、現在のダイアログに表示されている選択肢が何かということを考慮する必要が無くなります。

このスクリプトの例では、ボタン名リストに対応した文字列リスト(button_string)とベクターリスト(button_vector)を使って、ボタンに対応した内容の発言をしたり色を変えたりしていますが、ボタン毎に違う処理を入れる事も可能です。
その辺はダイアログを使う内容によって変えてください。

タイマー処理はダイアログからの入力が指定時間内に無い場合に起動され、ダイアログ入力を無効にしています。これは、ダイアログを開きっぱなしにされると、他の人が何も出来なくなるためということと、「無視」された場合にはスクリプトからは認識できないための保険のようなものです。

最後にユーザー関数ssDisplayDialog()ですが、ここで実際のダイアログに表示するボタン名を取得し、並び替えを行っている部分です。
ダイアログに表示するボタン用のリスト(now_menu)を作成します。リストの順番はボタンの内容が上から順に並ぶようにするため、ダイアログ下段のボタンから追加していきます。
まずメニュー総数(menu_max:処理を複雑にしないため0始まりです)が0の場合(1ページしかない)は"Back"、"Next"は必要ないので空ボタンにしています。2ページ以上にまたがる場合は、"Back"、"Next"を含めた機能ボタンをリストに追加します。
次に選択肢のボタンですがメニュー番号(menu_number)とボタン数(button_max:9個固定)から、全体のリストの何番目からが今回表示するボタンの分なのかを計算します(変数start)。
例えば、メニュー番号が1の場合(2ページ目)、1*9=9で、全体リストの(0始まりの)9番目(実際は10個目)から始まります。
その位置から9個分のボタンを表示するわけですが、やはり登録する順番があるので下のボタンから順に登録します。このときにボタンが無い場合は空ボタンを追加します。
ダイアログに表示するボタンのリストが作成し終わったら表示して、タイムアウト用のタイマーを起動します。

//==================================================

まともに解説したら非常に長くなってしまいました。
かなり汎用的にしているつもりですが、具体的な処理をしていないのでちょっと適当なところもあります。
これを参考にしていただければ、ダイアログの選択肢からいろいろ入力させて処理を分けるようなことも簡単になると思います。
  


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

http://sasapy.slmame.com/t121995
この記事へのコメント
勉強になりました。
ささぴさんすごいですね☆
またお勉強しにきま〜す!
宜しくお願いします。
Posted by Treva SladeTreva Slade at 2008年01月30日 23:11