8.11 パズルを作る(その5)

前回Puzzle クラスが完成したから、 今回は Puzzle クラスを使って簡単にパズルを始められるようにマクロを作っていくね。
りょ〜かい。
まず、パズルを始める時に実行する puzzle マクロはこんな感じ。

<パズルを開始する puzzle マクロ>

[macro name=puzzle]
[eval exp="kag.add(global.puzzle = new Puzzle(mp))" cond="typeof global.puzzle == 'undefined'"]
[endmacro]

意外とシンプルなんだね。
まぁ基本的に Puzzle クラスのオブジェクトを作るだけだからね。
じゃ、このマクロを実行するとどうなるか説明してみて。
おっけー。
えっと、eval タグの exp 属性が kag.add(global.puzzle = new Puzzle(mp)) になってるから、 まず Puzzle クラスのオブジェクトを作って global.puzzle っていう変数にその参照を代入してて、 それから kag.add メソッドでこのオブジェクトを KAG オブジェクトに管理してもらってるんだよね。
global キーワードについては §4.5add メソッドについては §3.2 参照。
ん、そうだね。
けど cond 属性があるから、この条件が真じゃないと Puzzle クラスのオブジェクトは作られないよね。
cond 属性は typeof global.puzzle == 'undefined' だから…これって global.puzzle っていう変数が存在しない時に真になるんだったかな?
ん、そだよ。§4.5 でやったよね。
えっと、じゃあ puzzle マクロを実行すると、 global.puzzle っていう変数が存在しなかったら Puzzle クラスのオブジェクトを作って、 global.puzzle に参照を代入するんだね。
そういうこと。
つまり、global.puzzle が存在しない時だけ Puzzle クラスのオブジェクトを作るようにすることで、 パズルが二重に実行されないようにしてるわけね。
なるほどね。
んじゃ次はパズルを終了する時に実行する endpuzzle マクロね。
パズルを終了する時に実行するってどーいうこと?
パズルが完成したら予め指定しといたラベルにジャンプするようにしてたでしょ?
うん、そだね。
パズルが完成した時点ではまだ Puzzle クラスのオブジェクト(global.puzzle)は無効化してないから、 パズルが終わってこのオブジェクトが要らなくなった時点で無効化する必要があるの。
あとギブアップした時にどこかのラベルにジャンプするようにしてたりする場合も、ジャンプ先で無効化しないといけないしね。
そーなの?
Puzzle クラスのオブジェクト(global.puzzle)を無効化しないと、 パズルの画面がずっと残り続けちゃうでしょ?
あ、そっか。
うーん、でもそれならパズルが完成してラベルにジャンプする前にオブジェクトを無効化しちゃえばいいんじゃないの?
オブジェクトを無効化したら、その後いつパズルの画面(ピースとか背景の枠とかの事ね)が全部消えちゃうかわからなくなるから、 パズルが完成してラベルにジャンプする前にオブジェクトを無効化すると、 パズルが完成した瞬間にパズルの画像が全部消えちゃったりすることもあるわけ。
あ、そーなんだ。それはちょっと困るね…
だから、ちょっと面倒だけどマクロを実行することでオブジェクトを無効化してパズルの画面を消すようにしてるの。
そうすれば好きな時にパズルの画面を消すことができるからね。
なるほどねぇ…
じゃマクロの方を見てくね。

<パズルを終了する endpuzzle マクロ>

[macro name=endpuzzle]
[eval exp="kag.remove(global.puzzle), invalidate global.puzzle, delete global.puzzle" cond="typeof global.puzzle != 'undefined'"]
[endmacro]

このマクロも eval タグだけなんだね。
まぁオブジェクトを無効化するだけだしね。
どんな処理してるかわかる?
んーと、cond 属性は puzzle マクロの時と逆で typeof global.puzzle != 'undefined' だから、 global.puzzle が存在する時に真になるよね。
Puzzle クラスのオブジェクトが無かったら無効化できないから、 これは当然チェックする必要があるよね。
だね。えっと、global.puzzle が存在する時は exp 属性の kag.remove(global.puzzle), invalidate global.puzzle, delete global.puzzle ってゆーのが実行されるよね。
これって確か kag.remove(global.puzzle)invalidate global.puzzledelete global.puzzle の順番に実行されるんだったよね?
そうだよ。
順次演算子(,)はクリッカブルマップを作った時に使ったよね。
※順次演算子については §5.9§6.6 参照。
最初の kag.remove(global.puzzle) ってゆーのは?
Puzzle クラスのオブジェクトを作る時に add メソッドを呼び出してオブジェクトを管理してもらってるよね。
うん、そだね。
kag.remove(global.puzzle) を実行すると、 もう global.puzzle を管理してくれなくてもいいですよー、 って KAG オブジェクトに伝えることになるの。
じゃあ kag.remove(global.puzzle) を実行したら global.puzzle は管理してもらえなくなっちゃうんだ?
そういうこと。
無効化されたオブジェクトを管理してもらうことはできないから、 オブジェクトを無効化する前に remove メソッドを呼び出しとく必要があるの。
そっか、なるほど。
あとは…次の invalidate global.puzzle でオブジェクトを無効化してるのはわかるんだけど、 その次の delete global.puzzle って何してるの?
delete ってのは演算子の一種で、右側にあるオブジェクトのメンバを削除するんだ。
削除するって…無効化するのとは違うの?
ん、もちろん。
配列の要素を削除する erase メソッドって覚えてる?
えっと、それって前に出てきたことあったっけ…?
まぁ §1.14 で初めて配列が出てきた時にちょっと説明しただけだから、 忘れちゃってるのも無理ないかな。
そーなんだ。配列関係のメソッドってなんかいっぱいあったもんね…
簡単に説明しとくと、例えば var array = [1, 2, 3]; って感じで配列を作って、 array.erase(1); を実行すると、1番目(配列の要素は0番目から始まることに注意してね)の要素が削除されて、 array[1, 3] っていう配列になるの。
引数に指定した番号の要素が削除されるんだね。
配列の要素はこんな感じで削除できるんだけど、辞書配列の要素はこうやって削除することはできないよね?
※辞書配列については §1.15 参照。
えっ、そーなの?
だって辞書配列の要素には順番がないでしょ?
あ、そーか。
辞書配列の要素は数字じゃなくて文字列で表すから、順番がつけられないんだったよね。
だから Dictionary クラスには特定の要素を削除するメソッドがないんだ。
※辞書配列の“すべての”要素を削除するメソッドはあります(clear メソッド)→ §1.15 参照。
じゃあ辞書配列のどれか1つの要素だけ削除するってことはできないの?
んーん、できるよ。
それをやるのが delete 演算子なんだ。
なんか delete 演算子と関係なさそーな話になってると思ってたけど、ここで出てくるんだ。
確かにちょっと説明が遠回りになっちゃったね。
ま、それはさておき、ちょっとこのスクリプトを first.ks に書き込んで実行してみて。

delete 演算子の使用例>

[iscript]
var dic = %["a" => 1, "b" => 2]; // 辞書配列を作ります
System.inform("要素 a の型は " + typeof dic.a + "、要素 b の型は " + typeof dic.b + " です。");
delete dic.a; // dic の要素 a を削除します
System.inform("要素 a の型は " + typeof dic.a + "、要素 b の型は " + typeof dic.b + " です。");
[endscript]

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

<1回目に表示されたメッセージ>

えと、dic っていう辞書配列の要素の ab にはどっちも整数が代入されてるから、 どっちも「Integer」(「整数型」っていう意味だよね)って表示されてるね。
typeof 演算子の値については §1.6 参照。
そうだね。

<2回目に表示されたメッセージ>

あ、今度は a の型のとこが「undefined」になってる。
つまり、delete dic.a; を実行したことで、 dic から要素 a が削除されてなくなっちゃったってことだね。
へぇ、こうすると辞書配列の要素を削除できるんだね。
あ、でも endpuzzle マクロで削除してるのって、辞書配列の要素じゃなくて global.puzzle っていう変数だったよね?
なかなか鋭いねー。
まぁね〜。
実は delete 演算子で削除できるのは、辞書配列の要素だけじゃないんだ。
そーなの?
delete 演算子は、 右側に書かれてる「オブジェクトのメンバ」または「ローカル変数」を削除できるの。
まぁ今はローカル変数(ブロックの中で宣言されてる変数のことね)は置いとくとして、 オブジェクトのメンバが削除できるってトコがポイント。
オブジェクトのメンバって?
クラスのメンバ変数とかメソッドとかプロパティのことだよ。
あと、global オブジェクトのメンバ変数とメソッドとプロパティも削除できるよ。
※メンバ変数・メソッド(メンバ関数とも呼ばれます)・プロパティ(メンバプロパティとも呼ばれます)については §2.3§2.4§2.5 参照。
メソッドとかプロパティも削除できちゃうの?
ん、できるよ。こんな感じで。

delete 演算子の使用例>

// square メソッドを定義します(このメソッドはすべてのブロックの外で定義されているので global.square で表されます)
function square(x)
{
    return x * x;
}
// two プロパティを定義します(このプロパティもすべてのブロックの外で定義されているので global.two で表されます)
property two
{
    getter(){return 2;}
}

System.inform(square(two)); // 「4」と表示されます

delete global.square; // square メソッドを削除します(delete square; と書くこともできます)
delete global.two; // two プロパティを削除します(delete two; と書くこともできます)

System.inform(two); // もう two プロパティは無いのでこれはエラーになります
System.inform(square(3)); // もう square メソッドも無いのでこれもエラーになります

この square っていうメソッドと two っていうプロパティは、 すべてのブロックの外で定義されてるから、それぞれ global.squareglobal.two になるんだ。
で、delete global.square; を実行すると square っていうメソッドが削除されて、 delete global.two; を実行すると two っていうプロパティが削除されるわけね。
えっと、削除されちゃうとメソッドやプロパティが使えなくなるから、 その後の System.inform(two); とか System.inform(square(3)); を実行しようとするとエラーになっちゃうってこと?
そういうこと。
なるほどねぇ…
でもせっかく作ったメソッドとかをわざわざ削除する意味ってあるの?
まぁ普通はあんまり削除したりしないとは思うけどね。
例えば何かの初期化処理の時だけ使って、その後は全然使わないっていうメソッドなんかは、 初期化処理が終わったら削除しちゃった方が無駄がないってのはあるかもね。
ふぅん、そーなんだ。
また話がそれちゃったから、この辺で endpuzzle マクロに話を戻すね。
exp 属性は kag.remove(global.puzzle), invalidate global.puzzle, delete global.puzzle だから…
global.puzzlePuzzle クラスのオブジェクトだよね)をもう管理しなくてもいいですよって KAG オブジェクトに伝えてから global.puzzle を無効化して、 それから global.puzzle を削除してるってことだよね?
そう。
ねぇ、delete 演算子で global.puzzle を削除できるんだったら、 もしかして invalidate global.puzzle って実行しなくてもいーんじゃない?
delete 演算子は直接オブジェクトを無効化するわけじゃないから、 無効化は invalidate 演算子でやる必要があるの。
なんかよくわかんないんだけど、そーなの?
この辺はちょっとややこしい話だからね。
ま、とりあえず無効化はちゃんとやるようにして、ってことで。
う、うん。わかった。
ちょっと話がごちゃごちゃしちゃったから一旦まとめることにするね。
まず、パズルを始める puzzle マクロの方は、 typeof(global.puzzle)"undefined" の時、 つまりまだパズルを始めてない時に実行すると Puzzle クラスのオブジェクトを作ってパズルを始めるわけね。
パズルを終了する endpuzzle マクロの方は、 typeof(global.puzzle)"undefined" じゃない時、 つまりパズルを始めた後に実行すると、global.puzzle を無効化してから削除するんだよね。
そうすると endpuzzle マクロを実行した後は typeof(global.puzzle)"undefined" になって、また puzzle マクロを実行すればパズルを始められるようになるからね。
うん。マクロでやってることは大体解ったよ。
それじゃ実行してみよっか。
そうだね。
first.ks は…とりあえずこんな感じにしとくね。

<first.ks の中身>

; Puzzle クラスとマクロの定義を読み込みます
[call storage="Puzzle.ks"]

; 表画面に適当な背景画像(puzzlebg)を読み込みます
[image layer=base page=fore storage="puzzlebg"]
; パズルを開始します ※パズル用の画像には適当な画像(puzzleimg)を使用します
[puzzle image="puzzleimg" target=*completed gridcolor=0xFFFF0000]
; パズルをやっている間は s タグで待機しておきます
[s]

; パズルが完成した時にジャンプするラベル
*completed
; 暗転します ※"black"は全体が真っ黒な画像です
[image layer=base page=back storage="black"]
[trans method=crossfade time=1000]
[wt]

; パズルを終了します
[endpuzzle]

; パズル完成後に何か画像を表示する場合は、以下のスクリプトを実行します
[image layer=base page=back storage="puzzlecompleted"]
[trans method=crossfade time=1000]
[wt]

意外と長いんだね。
でもそんな大したコトはやってないよ。
そーなんだ。
まず最初の call タグはわかるよね?
Puzzle.ks を読み込んで、 Puzzle クラスとマクロが使えるようにするんだよね。
そ。次の image タグと puzzle マクロもわかるでしょ?
image タグのとこで puzzlebg ってゆー画像を表画面に表示してるね。
puzzlebg はパズルの背景に表示する画像だよ。
実行する時は適当な画像を用意してね。
えっと、puzzle マクロの方は…
image 属性が "puzzleimg" だから、 これがパズル用の画像なんだね。
これも実行する時は適当な画像を用意してね。
それから target 属性が *completed になってて、 storage 属性は省略されてるから、 パズルが完成した時は first.ks の *completed っていうラベルにジャンプするんだね。
ん、そう。
あと gridcolor 属性が 0xFFFF0000 になってて…
確かこれってパズルのピースの正しい位置を表す枠の色を指定する属性で、0xAARRGGBB 形式で色を指定するんだったと思うから、 枠は不透明度が 255 の赤色で表示されるってことかな?
そういうこと。この色も適当に指定してね。
なんか今回は適当ってのが多いね…
まぁね。パズルの画像とか枠の色は決まってるわけじゃないから。
で、その次に s タグを実行して、パズルをやってる間は待機しとくわけね。
s タグの次は *completed っていうラベルになってるから、 ここからはパズルが完成した後に実行されるんだね。
そうそう。
最初の img, trans, wt タグで何やってるかはわかるよね?
ちなみに "black" っていう画像は全体が真っ黒な画像だよ。
えっと、その真っ黒な画像を裏画面に読み込んでトランジションしてるから、 画面をフェードアウトさせてるみたいだね。
ん、でその後 endpuzzle マクロを実行してパズルを終了するわけね。
ねぇ、なんで endpuzzle マクロを実行する前に画面をフェードアウトさせてるの?
さっき invalidate global.puzzle を実行するとパズルの画面が全部消えちゃうって言ったよね。
うん、global.puzzle が無効化された瞬間に消えちゃったりするんだよね。
ってことは、画面をフェードアウトさせる前に endpuzzle マクロを実行すると、 パズルの画面が一瞬でぱっと消えちゃうわけ。
それってあんまり見栄えが良くないでしょ?
うーん、確かにそー言われればそうかも。
だから、画面をフェードアウトさせてパズルの画面が見えなくなってから endpuzzle マクロを実行することで、 見た目をちょっと良くしてるの。
ふぅん、なるほどねぇ…
で、最後の img, trans, wt タグは、 パズルが完成した後に何か画像を表示したい時に実行するの。
この画像も必要に応じて適当に用意してね。
あ、やっぱりこれも適当なんだ…
んじゃ first.ks も一通りチェック出来たから、実行してみよ。
必要なファイルはここに置いとくから。
あ、今回はさっき言った「適当な画像」は入れてないから、各自で用意してから実行してね。
はーい。
じゃあ実行してみるね。


<実行結果>

  省略。

えっ、省略って…
いやホラ、パズルをやってるプロセスって表現するのがムズカシイから…ね。
まぁそりゃ確かにそーだけど、なんか今回は色んな意味でテキトーだね…
えっと、そーいうワケで、悪いんだけど今回は実行結果は各自で確認してね、ってコトで。
…まぁいっか。
りょーかい。
それじゃこれでパズルも一応完成したし、今回はこれくらいに…
あ、ちょっといいかな?
ん? 何?
パズルのピースがある所で右クリックしてもギブアップできないみたいなんだけど…?

ピース上で右クリックしてもギブアップできない

あー、ホントだね。
これってなんでなの?
うーん……あ、そうか。
えっと、ギブアップ処理って右クリックフックにしてたでしょ。
うん、そーだね。
右クリックフックは KAG のレイヤの上で右クリックすると呼び出されるけど、 パズルのピース用のレイヤみたいに独自に作ったレイヤの上で右クリックしても、 基本的に呼び出されないようになってるんだ。
え、そーなの?
…あ、でも確かあの赤い枠を表示してるパズル用の背景レイヤって画面とおんなじ大きさだったよね?
だとしたら、あれも独自に作ったレイヤなんだから、 画面のどこをクリックしても右クリックフックが呼び出されないことになるんじゃない?
確かにパズル用の背景レイヤは画面と同じ大きさなんだけど、 hitThreshold プロパティを 256 に設定してるから大丈夫なの。
hitThreshold プロパティについては §7.2 参照。
hitThreshold プロパティ…って確か 256 にすると onMouseDown メソッドとかが呼び出されなくなるんだったよね?
そうそう。だから、パズルの背景レイヤ上で右クリックしても、パズルの背景レイヤ上でマウスイベント(onMouseDown とかマウス系のイベントハンドラが呼び出されるってことね)が起きるんじゃなくて、 その1つ奥にある KAG の背景レイヤの方でマウスイベントが起きるんだ。
う〜ん、なんかややこしーね。
図にした方がわかりやすいかな?

<右クリックマウスイベントの発生>

これはこれでややこしいよね…
まぁそうなんだけどね。
この図は KAG の背景レイヤとパズル用のレイヤが重なってる状態を表してるんだけど、 とりあえず最初は左側にあるピンク色の矢印に注目してみて。
これって何を表してるの?
ピンク色の矢印はパズルのピースがない場所で右クリックした時を表してるの。
パズルのピースがない場所で右クリックするってことは、パズルの背景レイヤの上で右クリックするってことになるわけね。
うん、まぁそうなるよね。
パズルの背景レイヤは hitThreshold256 に設定してあるから、 パズルの背景レイヤ上ではマウスイベントは起こらなくて、 その奥にある KAG の背景レイヤ(kag.fore.base)でマウスイベントが起きるんだ。
つまり、パズルの背景レイヤ上で右クリックすると、KAG の背景レイヤ上で右クリックしたことになるの。
なるほど、だから右クリックフックが呼び出されるんだね。
じゃ今度は右側にある水色の矢印に注目してみて。
これってパズルのピースがあるとこで右クリックした時を表してるの?
ん、そう。
パズルのピース用レイヤは hitThreshold256 に設定してないから、 パズルのピースがある所で右クリックすると、ピース用レイヤでマウスイベントが起きるわけね。
うん。
マウスイベントは基本的に一度に1つのレイヤでしか起こらないから、 ピース用レイヤでマウスイベントが起きたら、それより奥にあるレイヤではマウスイベントは起こらないんだ。
それってつまり、ピースがある所で右クリックすると、 KAG の背景レイヤではマウスイベントは起こらない、ってこと?
そういうこと。
だから右クリックフックも呼び出されないワケ。
なるほどねぇ…
じゃあ、ピースがあるとこで右クリックしてもギブアップできないのはしょうがないってことなの?
んーん、ピースがあるとこで右クリックしてもギブアップできるようにしようと思えばできるよ。
え、そーなの?
PieceLayer クラスの onMouseUp メソッドをこんな感じに書き換えれば、 ピースがあるとこで右クリックしてもギブアップできるようになるよ。

onMouseUp メソッド>

function onMouseUp(x, y, button, shift)
{
    // ドラッグが終わった時に呼び出すメソッドを呼び出します
    funcOnMouseUp(this);
    // 右クリックされた場合は右クリックフックを呼び出すために onRightClickMenuItemClick メソッドを呼び出します
    kag.onRightClickMenuItemClick() if button == mbRight;
    // スーパークラスのコンストラクタを呼び出します
    super.onMouseUp(...);
}

kag.onRightClickMenuItemClick メソッドを呼び出す部分が増えてるね。
onRightClickMenuItemClick メソッドってどんなメソッドだったか覚えてる?
確か右クリックしたのとおんなじ動作をするんじゃなかったっけ?
onRightClickMenuItemClick メソッドについては §5.10§6.16 参照。
ん、そうそう。
このメソッドを呼び出すと、KAG のレイヤ上で右クリックしたのと同じことになるから、 右クリックフックも呼び出されるの。
そーなんだ。
えっと、if button == mbRight ってのは、 マウスの右ボタンが押された時に真になるの?
そ。つまり、右クリックした時だけ onRightClickMenuItemClick メソッドを呼び出して、 右クリックフックを呼び出してるわけ。
なるほどね。
さてと、だいぶ長くなっちゃったけど、こんな感じで OK かな?
うん、おっけーだよ。
それじゃ、パズルの話は今回でおしまい。
次回から動かせるシステムボタンを作っていくことにするね。
あ、そーいえばそんなのも作るって言ってたね。
ってことは第8章はまだまだ続くってことだよね?
んー、動かせるシステムボタンはパズルよりは簡単だから、 そんなに長くはならないと思うけどね。
あ、そーなんだ。
ん、多分ね。
それじゃ、また次回!


前へ | TOP | 次へ