7.5 選択肢プラグイン(その1)

前回まででとりあえず選択肢が表示できるようになったから、 今回は選択肢を管理するプラグインを作っていくね。
選択肢を管理するって具体的にどんなことするの?
ん〜、とりあえずこの2つがメインかな。

  1. 選択肢の項目の1つがクリックされたら、全部の項目を非表示にしてから指定されたラベルにジャンプする
  2. (右クリックなどで)メッセージレイヤが非表示になった時に選択肢も非表示にする(メッセージレイヤが表示されたら選択肢も表示する)

まず 1. についてだけど、 前回は選択肢がクリックされたら、手動で選択肢を非表示にしてたよね。
うん、ジャンプ先のラベルの後に eval タグを書いて visiblefalse に設定してたね。
あれってプラグインを作ったら書かなくてもよくなるって言ってなかったっけ?
ん、ジャンプした後手動で選択肢の項目を非表示にしなくてもよくするってのがプラグインを使う1つ目の目的。
じゃあ、ジャンプする時にプラグインが自動的に選択肢の項目を非表示にしてくれるってこと?
そ。で、あとは 2. の方だけど、 これはシステムボタンを作った時とかにも同じことやったよね。
§5.9 参照。
メッセージレイヤが非表示になった時に選択肢も非表示にする、ってことは…
onMessageHiddenStateChanged メソッドを使うってこと?
そう。プラグインを使えば、onMessageHiddenStateChanged メソッドが呼び出された時に、 メッセージレイヤが非表示になったり表示されたりしたってわかるからね。
なるほどねぇ。
んじゃスクリプトの方を見ていこっか。
そだね。
これが選択肢を管理するプラグイン ButtonLinkPlugin クラスのスクリプト。

<選択肢を管理するプラグイン ButtonLinkPlugin クラス>

class ButtonLinkPlugin extends KAGPlugin
{
    var links = []; // 選択肢オブジェクト(ButtonLinkLayer クラスのオブジェクト)の配列

    // コンストラクタ
    function ButtonLinkPlugin()
    {
        // スーパークラスのコンストラクタを呼び出します
        super.KAGPlugin();
    }

    // 選択肢の項目を追加するメソッド
    function addLink(elm)
    {
        // 新しく選択肢オブジェクトを作って links に追加します
        links.add(new ButtonLinkLayer(kag, kag.fore.base, onLinkClicked, elm));
    }

    // 選択肢がクリックされた時に呼び出されるメソッド
    function onLinkClicked(link)
    {
        // 指定のシナリオファイル・ラベルにジャンプします
        kag.process(link.storage, link.target, link.countpage);

        // 選択肢をすべて破棄します
        clear();
    }

    // すべての選択肢を破棄するメソッド
    function clear()
    {
        for(var i=links.count-1;i>=0;i--)
            invalidate links[i]; // 選択肢オブジェクトを無効化します
        links.clear(); // 配列の要素をすべて消去します
    }

    // デストラクタ
    function finalize()
    {
        // すべての選択肢を破棄します
        clear();
        // スーパークラスのデストラクタを呼び出します
        super.finalize();
    }

    // array の各メンバのプロパティを設定するメソッド
    function setObjProp(array, member, value)
    {
        for(var i = array.count - 1; i >= 0; i--) array[i][member] = value;
    }

    // onMessageHiddenStateChanged メソッドをオーバーライドします
    function onMessageHiddenStateChanged(hidden)
    {
        // メッセージレイヤの表示状態にあわせて選択肢の表示状態を設定します
        setObjProp(links, 'visible', !hidden);
    }

    // onRestore メソッドをオーバーライドします
    function onRestore(f, clear, elm)
    {
        // 選択肢が表示された状態でデータがロードされた時、
        // ロード後に選択肢が残らないようにここで消去しておきます
        this.clear();
    }
}

うわ、結構長いね…
でもやってることはわりとシンプルだよ。
ふぅん、そーなんだ。
じゃまずはメンバ変数から見ていくね。
links っていうメンバ変数があるね。
えっと… links = []; ってことは、 links は配列になってるってこと?
※links = [];links = new Array(); と同じ意味です。
 Array クラス(配列)については §1.14 参照。
うん。links は選択肢オブジェクト、つまり ButtonLinkLayer クラスのオブジェクトの配列だよ。
じゃあ、例えば選択肢の項目が3つあったら、links の要素も3つになるの?
ん、そうなるね。
それじゃ、次はメソッドを1つずつ見ていくね。
はーい。
まずコンストラクタから。
まぁこれは特に問題ないと思うけど。
スーパークラスのコンストラクタを呼び出してるだけだね。
ん、そう。
デストラクタはちょっと後回しにして、次は選択肢の項目を追加する addLink メソッド
ButtonLinkLayer クラスのオブジェクトを作って links に追加してるみたいだね。
Array クラスの add メソッドについては §1.14 参照。
ButtonLinkLayer クラスのコンストラクタに指定する引数は覚えてる?
第1引数がこのレイヤが所属するウィンドウで、第2引数が親レイヤで、 第3引数がクリックされた時に呼び出されるメソッドで、 第4引数が setOptions メソッドの引数に指定する設定項目だったよね?
§7.2 参照。
ん、そうだね。
じゃ addLink メソッドで選択肢オブジェクトを作る時はそれぞれどうなってる?
所属するウィンドウは、まぁ kag オブジェクトになるよね。
あと、親レイヤは kag.fore.base だから、表画面の背景レイヤだね。
kag.fore.base しかないってことは、選択肢は表画面にしか作らないの?
もし選択肢を表示してる時にトランジションする必要があるんだったら裏画面にも作らなくちゃいけないけど、 選択肢表示中にトランジションすることってあんまりないかなーと思って。
ん〜、まー確かに選択肢を表示してる時にトランジションはしなさそうだよね。
えっと、あとクリックされた時に呼び出されるメソッドは onLinkClicked になってるけど、 これってこのクラスのメソッド?
そうだよ。onLinkClicked メソッドはこの後説明するね。
りょーかい。
第4引数の辞書配列は addLink メソッドの第1引数をそのまま指定してるね。
addLink メソッドは選択肢を追加するマクロ(blink マクロ)から呼び出すから、 この辞書配列は mp になるの。
そーなんだ。
それじゃ次は選択肢がクリックされた時に呼び出される onLinkClicked メソッドを見ていくね。
うん。
onLinkClicked メソッドが呼び出された時、 引数の link は何になってるかわかる?
え? え〜っと…何になるんだっけ…?
じゃ、ButtonLinkLayer クラスの onMouseUp メソッドをもっかい見直してみて。
えっと、onClickFunctiononLinkClicked メソッドへの参照になってるんだったよね…
onClickFunction(this); になってるから、this を引数にして onLinkClicked メソッドを呼び出してるのかな。
this が引数になってるってことは…?
ん〜、this は「このオブジェクト」って意味だから、それが引数に指定されてるってことは、 onLinkClicked メソッドの引数の link は、 クリックされた選択肢のオブジェクトになってる…ってことかな?
ん、そういうこと。
じゃ最初に実行してる process メソッドで何してるかはわかるよね?
kag.process って jump タグみたいにシナリオにジャンプするメソッドだったよね。
process メソッドについては §7.4 参照。
そうだよ。
第1引数と第2引数がジャンプ先のファイル名とラベル名で、それぞれ link.storagelink.target になってて、 第3引数は jump タグの countpage 属性とおんなじ意味で、これは link.countpage になってるね。
つまり?
つまり、link はクリックされた選択肢オブジェクトになってるから、 クリックされた選択肢の storagetarget に設定されてる場所にジャンプするってことかな。
あと、countpage が真だったら、選択肢がある場所のラベルは読んだとみなされるんだよね。
ん、そういうこと。
その次に呼び出されてる clear っていうメソッドはどんなメソッドなの?
それは clear メソッドの定義を見ればわかると思うよ。
for ループで links の要素を無効化してるみたいだね。
ilinks.count-1 から始まって 0 まで 1 ずつ減っていくから…全部の要素を無効化してるのかな?
ん、そう。
for ループの後は全部の選択肢オブジェクトが無効化されちゃってて使えなくなってるから、 links.clear(); で配列の要素を全部削除してるってこと?
そういうこと。
この clear メソッドを呼び出すことで、 選択肢がクリックされた後自動的に選択肢の全項目が非表示になるようにしてるの。 まぁ正確には非表示にしてるんじゃなくて削除しちゃってるんだけどね。
選択肢がクリックされたら、もう選択肢オブジェクトは必要なくなるから削除しても問題はないでしょ。
なるほどねぇ。
じゃこれで onLinkClicked メソッドは OK かな?
あ、ちょっといいかな?
ん?
process メソッドが呼び出された後に clear メソッドが呼び出されるようになってるけど、 process メソッドが呼び出されたらどこかのラベルにジャンプするから、 その後の clear メソッドが呼び出されなくなっちゃったりしないの?
あー、それなら大丈夫。
詳しい説明はパスさせてもらうけど、 onLinkClicked メソッドの途中で process メソッドを呼び出しても、 onLinkClicked メソッドはちゃんと最後まで実行されるようになってるんだ。
へぇ、そうなんだ。
あとは大丈夫?
うん、おっけーだよ。
じゃ次は デストラクタ ね。
あ、やっとデストラクタが出てくるんだ。
選択肢オブジェクトを無効化してから選択肢のプラグインを無効化するために、 デストラクタの中で clear メソッドを呼び出してるから、 clear メソッドを先に見とこうと思って。
なるほどね。それでデストラクタは後回しにしたんだ。
他には特別なことはやってないから、後は大丈夫だよね?
うん。
次は setObjProp メソッドだけど、 これはシステムボタンプラグインの setObjProp メソッドと全く同じだから、 特に説明の必要はないかな。
ホントだ、おんなじだね。
システムボタンプラグインの時は array がシステムボタンの配列になってたけど、 今回は選択肢オブジェクトの配列になってるんだよね?
ん、そうだよ。
それじゃ次は onMessageHiddenStateChanged メソッド
このメソッドで最初に言ってた「メッセージレイヤが非表示になったら選択肢も非表示にする」ってのをやるんだよね?
そうそう。
ま、基本的にはシステムボタンの時と同じようにすればいいんだけど、 今回はシステムボタンの時に使った foreSeen みたいな変数は使ってないよ。
foreSeen については §5.8 参照。
確かにそういう変数はないみたいだね。
setObjProp(links, 'visible', !hidden); ってことは、hidden が真だったら !hidden は偽になるから、 選択肢を非表示にして、hidden が偽だったら、逆に選択肢を表示するってことなのかな?
そうそう。!hidden はメッセージレイヤの visible と同じ値になるから、 要はメッセージレイヤの visible と 選択肢の visible を同じにしてるってこと。
メッセージレイヤが非表示になる前の選択肢の visible は記録しとかなくていいの?
もちろん記録しといても全然 OK だよ。
ただ、メッセージレイヤが非表示になる時以外は、選択肢を非表示にする必要はないかなと思ったからこうしただけだよ。 その方がスクリプトもシンプルになるしね。
ふぅん、そうなんだ。
あと残ってるのは onRestore メソッドだけだね。
onRestore メソッドってセーブデータをロードする時に呼び出されるメソッドだよね?
これって選択肢と何か関係あるの?
まぁ中身を見てみて。
this.clear(); って書いてあるけど、 これってどーいうこと?
clear メソッドを呼び出してるってことだよ。
え? それなら clear(); でいいんじゃない?
this をつけてるのは、 clear っていう引数があるから。
あ、そーいえば onRestore メソッドには clear って名前の引数があったよね。
onRestore メソッドの中で clear って書くと、 引数の方の clear だと見なされるんだ。
引数の clear はメソッドじゃないから、こんなふうに呼び出そうとするとエラーになるの。

function onRestore(f, clear, elm)
{
    // ↓こう書くと引数の clear だと見なされます
    clear(); // 引数の clear はメソッドの参照ではないのでエラーになります
}

onRestore メソッドの中で clear メソッドを呼び出したい時は、 this. をつけて this.clear(); って書かなくちゃいけないの。
へぇ、そーなんだ。
ところで、何でここで clear メソッドを呼び出してるの?
選択肢が表示されてる時にデータをロードしたらどうなると思う?
え? 別に普通にロードできるんじゃないの?
前にも言ったけど、選択肢用のレイヤみたいにプラグインで独自に作ってるレイヤは、 kag オブジェクトに管理してもらえないんだよ?
§4.6§4.7 参照。
えっと、それじゃあもしかして、ロードした後も選択肢が表示されたままになっちゃう、ってこと?
ん、そのとーり。
だからそうならないように、データをロードする時には clear メソッドで選択肢を全部消すってワケ。
なるほどねー。
じゃあ、後は選択肢の項目を追加するマクロを改めて作って完了だね。
それって blink マクロのこと?
そうそう。
今回見てきた ButtonLinkPlugin クラスの内容が解ってれば作れるはずだから、ちょっと作ってみて。
プラグインのオブジェクトはこんな感じで作っとくことにするね。

ButtonLinkPlugin クラスのオブジェクト>

kag.addPlugin(global.buttonlink_plugin = new ButtonLinkPlugin());

ええっと、確か addLink メソッドを使って選択肢の項目を追加するって言ってたよね?
ん、そうだよ。
あと、addLink メソッドの引数が mp になるって言ってたはずだから…

<選択肢を追加する blink マクロ(改)>

[macro name=blink]
[eval exp="buttonlink_plugin.addLink(mp)"]
[endmacro]

ん〜…こんな感じだと思うけど、ちょっと単純すぎるかな…?
ん、これで OK。
あ、これでいいんだ。
それじゃ実行してみよっか。
うん、そうだね。
first.ks はとりあえずこんな感じで。

<first.ks>

; プラグインを読み込みます
[call storage="ButtonLinkPlugin.ks"]

どこへ行く?

[blink text="商店街" graphic="buttonlink_bg" left=50 top=51 target=*mall]
[blink text="公園" graphic="buttonlink_bg" left=50 top=139 target=*park]
[blink text="家に帰る" graphic="buttonlink_bg" left=50 top=226 target=*home]
[s]

*mall
[cm]
買いたいものもあるし、商店街に寄って行こう。[p]
;以下略

*park
[cm]
公園にでも行ってみようかな。[p]
;以下略

*home
[cm]
今日はもう疲れたし、家に帰ろう。[p]
;以下略

じゃあ実行してみるね。
うん。

<実行結果>

表示されたウィンドウ

ちゃんと選択肢が表示されてるね。
ん、じゃあとりあえずどれかクリックしてみて。
わかった。

<選択肢をクリックした時に発生したエラー>

スクリプトで例外が発生しました オブジェクトはすでに無効化されています

あれっ!? エラーが起きちゃったよ?
えっ!?
えっと…これってどういうことなの?
ちょっと待ってね。
今原因を調べてみるから。
う、うん。わかった。
えーと…エラーが起きた場所は……
あー、そっか!
原因が判ったの?
うん。
ちょっと ButtonLinkLayer クラスの設計を間違えてたみたい。ゴメンね。
ButtonLinkPlugin クラスじゃなくって ButtonLinkLayer クラスが原因だったの?
ん、そう。
とりあえず、間違ってるとこは次回直すことにするね。
えっ、次回にするの?
今から直してもいいけど、ちょっと長くなるかもしれないよ?
え、そうなの?
えっと…それじゃあ次回ってことで…
今回はちょっと中途半端になっちゃったけど、 次回はちゃんと選択肢のプラグインを使えるようにするね。
それじゃ、また次回。


前へ | TOP | 次へ