8.7 パズルを作る(その1)

今回からパズルの本体を作っていくね。
はーい!
前回もちょっと言ったけど、 パズルのシステムは Puzzle クラスっていうクラスを作って管理することにするね。
えっと、今回はプラグインにはしないんだったよね?
うん、プラグインの機能は使わないからね。
でも KAGPlugin クラスを継承して、 プラグインとしてパズルのシステムを作っても全然 OK だよ。
そーなんだ。
それじゃ、まずはコンストラクタとメンバ変数を見ていくことにするね。
うん。

Puzzle クラスのメンバ変数とコンストラクタ>

class Puzzle
{
    // メンバ変数
    var horizontalBlocks; // 横方向画像分割数
    var verticalBlocks; // 縦方向画像分割数
    var pieceWidth; // ピースの幅
    var pieceHeight; // ピースの高さ
    var remainingPieces; // 残っている(正しい位置に置かれていない)ピースの数
    var baseLayer; // 背景用レイヤ(グリッドを表示するためのレイヤ)
    var imageLayer; // 完成画像を読み込んでおくレイヤ
    var pieceLayers; // ピース用レイヤの配列(2次元配列)
    var storageOnCompletion; // パズルが完成した時にジャンプするシナリオファイル名
    var targetOnCompletion; // パズルが完成した時にジャンプするラベル名

    // コンストラクタ
    function Puzzle(elm)
    {
        // 背景用レイヤを作ります
        baseLayer = new Layer(kag, kag.fore.base);
        with(baseLayer)
        {
            .setImageSize(kag.fore.base.width, kag.fore.base.height); // 画像サイズを表画面の背景レイヤと同じサイズにします
            .setSizeToImageSize(); // レイヤの表示サイズを画像サイズに合わせます
            .fillRect(0, 0, .width, .height, 0x00000000); // 全体を透明色で塗りつぶします
            .hitThreshold = 256; // マウスメッセージを受け取らないようにします
            .visible = true// 表示状態にします
        }

        // 完成画像を読み込んでおくためのレイヤを作ります
        imageLayer = new Layer(kag, baseLayer);
        imageLayer.loadImages(elm.image); // 完成画像を読み込みます

        // マクロの属性に指定されている設定項目をメンバ変数に保存しておきます
        horizontalBlocks = elm.hblock !== void ? +elm.hblock : 5;
        verticalBlocks = elm.vblock !== void ? +elm.vblock : 5;
        storageOnCompletion = elm.storage !== void ? elm.storage : "";
        targetOnCompletion = elm.target !== void ? elm.target : "";

        // 縦/横方向の分割数からピースの幅・高さ・総数を計算します
        pieceWidth = imageLayer.imageWidth \ horizontalBlocks;
        pieceHeight = imageLayer.imageHeight \ verticalBlocks;
        remainingPieces = horizontalBlocks * verticalBlocks;

        // 完成画像の左端・上端の位置を left, top にセットします
        var left = elm.left !== void ? +elm.left : (baseLayer.width - imageLayer.imageWidth) \ 2;
        var top = elm.top !== void ? +elm.top : (baseLayer.height - imageLayer.imageHeight) \ 2;

        // グリッドの色を gridColor にセットします
        var gridColor = elm.gridcolor !== void ? +elm.gridcolor : 0xFF000000;

        pieceLayers = new Array(); // ピース用レイヤの配列を作ります
        for(var y=0;y<verticalBlocks;y++)
        {
            pieceLayers[y] = new Array(); // 2次元配列を作ります
            for(var x=0;x<horizontalBlocks;x++)
            {
                // 左から x 番目、上から y 番目のピース(pieceLayers[x][y])の位置を計算して
                var pieceLeft = left + x * pieceWidth;
                var pieceTop = top + y * pieceHeight;
                // ピース用レイヤを作ります
                pieceLayers[y][x] = new PieceLayer(kag, baseLayer, pieceLeft, pieceTop, onDragStarted, onDragFinished);
                with(pieceLayers[y][x])
                {
                    .setImageSize(pieceWidth, pieceHeight); // レイヤの画像サイズを pieceWidth×pieceHeight に設定します
                    .setSizeToImageSize(); // レイヤの表示サイズを画像サイズに合わせます
                    // このピースに表示する画像を完成画像からコピーします
                    .copyRect(0, 0, imageLayer, x * pieceWidth, y * pieceHeight, pieceWidth, pieceHeight);
                    // 位置をランダムに設定します
                    .setPos(int (Math.random() * (kag.fore.base.width - pieceWidth)), int (Math.random() * (kag.fore.base.height - pieceHeight)));
                    .absolute = x + y * horizontalBlocks + 1; // 重ね合わせ順序を設定します
                    .visible = true// 表示状態にします
                }
                // このピースの正しい位置を gridColor に設定されている色の枠で囲みます
                baseLayer.fillRect(pieceLeft, pieceTop, pieceWidth, 1, gridColor);
                baseLayer.fillRect(pieceLeft, pieceTop, 1, pieceHeight, gridColor);
                baseLayer.fillRect(pieceLeft + pieceWidth - 1, pieceTop, 1, pieceHeight, gridColor);
                baseLayer.fillRect(pieceLeft, pieceTop + pieceHeight - 1, pieceWidth, 1, gridColor);
            }
        }

        // 完成画像の領域を gridColor に設定されている色の枠で囲みます
        var width = pieceWidth * horizontalBlocks;
        var height = pieceHeight * verticalBlocks;
        baseLayer.fillRect(left - 1, top - 1, width + 2, 1, gridColor);
        baseLayer.fillRect(left - 1, top - 1, 1, height + 2, gridColor);
        baseLayer.fillRect(left + width, top - 1, 1, height + 2, gridColor);
        baseLayer.fillRect(left - 1, top + height, width + 2, 1, gridColor);

        // 右クリックでギブアップできるように右クリックフックを登録します
        kag.rightClickHook.add(onGiveUp);
    }
}

うわ、長っ!
しかもコンストラクタがすごいフクザツ…
ん〜、Puzzle クラスはコンストラクタでやることが多いから、 コンストラクタが一番長いし複雑になってるんだよね。
それにしてもちょっとややこし過ぎない?
まぁそうなんだけどね。
ま、とりあえずいつものようにスクリプトを見ていこ。
う、うん。
メンバ変数はちょっと置いといて、先にコンストラクタから見ていくね。
引数が elm になってるってことは、コンストラクタってもしかしてマクロから呼び出されるの?
ん、そうだよ。
マクロを実行すると Puzzle クラスのオブジェクトを作るようにするからね。
で、マクロの属性でパズルの設定を指定するの。
パズルの設定って?
どんな画像を使うかとか、画像を何分割してピースを作るかとか、 パズルが完成した時にどのラベルにジャンプするかとか、そういうの。
あ、なるほど。
じゃコンストラクタの中身を見てくね。
まず最初にやるのが、背景用のレイヤを作る処理
背景用のレイヤ…って背景レイヤを使えばいいんじゃないの?
んー、まぁそれでもいいんだけどね。
ピースを置く枠を表示したいから、背景用のレイヤを作ることにしたんだ。
ピースを置く枠って、あの赤い枠のこと?
そうそう。
前もちょっと言ったけど、背景レイヤに直接文字とか図形とかを書き込むのはあんまりやらない方がいいからね。
だから専用の背景レイヤを作って、そこに枠を書き込むってこと?
ん、そういうこと。
じゃ改めて背景用レイヤを作るスクリプトを見ていくね。
…って言っても、別に新しいことは何もやってないから、何をやってるかはわかるよね?
えっと、最初に baseLayer っていうレイヤオブジェクトを作ってるね。
kag オブジェクトがこのレイヤが所属するウィンドウで、 表画面の背景レイヤが親レイヤだね。
ん、そうだね。
ちなみに baseLayerPuzzle クラスのメンバ変数だよ。
それから、setImageSize メソッドで、レイヤが持ってる画像の幅と高さをそれぞれ kag.fore.base.widthkag.fore.base.height にしてるね。
これってレイヤが持ってる画像のサイズを背景レイヤのサイズとおんなじにしてるってことだよね。
setImageSize メソッドについては §7.3 参照。
うん。パズルは画面全体を使ってやるから、背景レイヤのサイズに合わせてるの。
あとは setSizeToImageSize メソッドを使ってレイヤの表示サイズを画像のサイズをおんなじにして、 fillRect メソッドでレイヤ全体を…えと、 0x00000000 だから、透明色で塗りつぶしてるのかな?
setSizeToImageSize メソッドについては §3.3fillRect メソッドについては §3.2 参照。
このスクリプトでは透明色で塗りつぶしてるから、 元々背景レイヤに表示されてた画像の上に枠が表示されるように見えるわけだけど、 元々背景レイヤに表示されてた画像を見えなくしたかったら別に他の色で塗りつぶしてもいいし、 loadImages メソッドを使って好きな画像を読み込んでも OK だよ。
loadImages メソッドについては §3.3 参照。
あ、そーなんだ。
次は hitThreshold プロパティを設定してるわけだけど、 hitThreshold256 にするとどうなるんだったか覚えてる?
んーっと、確か hitThreshold256 にすると、 onClick とかのマウス関係のメソッドが呼び出されなくなるんじゃなかったっけ?
hitThreshold プロパティについては §7.2 参照。
そうそう。
hitThreshold256 にしてる理由はちょっと後で説明するとして、 次の visible は大丈夫だよね?
true にしてるから、レイヤが表示されるようにしてるんだよね。
ん、じゃ次はパズルに使う画像、 つまりパズルが完成した時の画像を読み込んどくレイヤを作ってる部分ね。
Layer クラスのコンストラクタの第2引数が baseLayer になってるってことは、 このレイヤって baseLayer の子レイヤってことだよね?
パズルに使うレイヤは、背景用のレイヤ(baseLayer) だけを表画面の背景レイヤ(kag.fore.base)の子レイヤにして、 あとは全部背景用のレイヤの子レイヤにするからね。
つまり、パズルに使う画像を読み込んどくレイヤとかピース用のレイヤは、 パズルの背景用のレイヤから見ると前景レイヤみたいなものになるわけだね。
ふぅん、なるほどね。
あと loadImages メソッドの引数を見ればわかると思うけど、 パズルに使う画像はマクロの方で指定するの。
elm.image になってるから、 マクロの image っていう属性にパズルに使う画像のファイル名を指定すればいいってことだよね?
ん、そういうこと。
それじゃ次はマクロの属性に指定されてる設定項目をメンバ変数に保存しとく部分ね。
最初の2つのメンバ変数 horizontalBlocksverticalBlocks には、 それぞれ画像を横方向と縦方向に何分割するかっていう値を保存しとくんだ。
えーと、horizontalBlocks の方には、 マクロに hblock っていう属性が指定されてたら、 それを数値にした値を代入してて、指定されてなかったら 5 を代入してるね。
つまり、画像を横方向に何分割するかをマクロの hblock 属性に指定するわけだね。
で、hblock 属性が省略されてたら、デフォルトで5分割するの。
verticalBlocks の方も基本的におんなじみたいだから、 画像を縦方向に何分割するかを vblock 属性に指定するんだね。 で、こっちも省略するとデフォルトで5分割になるんだね。
ん、そう。
あとは storageOnCompletiontargetOnCompletion っていうメンバ変数だけど、 この2つはパズルが完成した時にジャンプするシナリオファイル名とラベル名を記録しとくメンバ変数なんだ。
えっと、マクロに storage 属性が指定されてたら storageOnCompletion にその値を代入してて、 target 属性が指定されてたら targetOnCompletion にその値を代入してるね。 あと、属性の値が指定されてなかったらどっちも空文字列を代入してるね。
こうすると、属性の値が指定されてない時の動作は jump タグとかの storage, target 属性と同じになるの。
それってつまり storage 属性を省略したらジャンプ先が今実行してるシナリオファイルになって、 target 属性を省略したらジャンプ先がシナリオファイルの先頭になるってことだよね?
そういうこと。
次はピースの幅と高さ、 あとピースの数を計算してメンバ変数に代入してるんだけど、どうやって計算してるかわかる?
pieceWidth っていうメンバ変数がピースの幅で、 pieceHeight っていうメンバ変数がピースの高さになってるんだよね?
そうだよ。
ん〜、pieceWidthimageLayer.imageWidth \ horizontalBlocks だから、パズルに使う画像の幅を横方向の分割数で割った値になってるってことだよね。
ピースは横方向に horizontalBlocks 個並んでるわけだから、 横方向のピースの個数(horizontalBlocks)×ピースの幅がパズルに使う画像の幅になるよね。
つまり、horizontalBlocks × pieceWidth = imageLayer.imageWidth が成り立ってるってことだから、この式を変形すると…?
pieceWidth = imageLayer.imageWidth ÷ horizontalBlocks っていう式が成り立つから、 pieceWidthimageLayer.imageWidth \ horizontalBlocks になるんだね。
そ。あとピースの高さも同じようにして計算できるよね。
幅が高さに変わるから、pieceHeight はパズルに使う画像の高さ(imageLayer.imageHeight)を縦方向の分割数(verticalBlocks)で割ってるんだね。
ただ、ここでちょっと注意しなくちゃいけないのが、 割り算に \ 演算子を使ってるってトコ。
え、どういうこと?
\ 演算子ってどんな演算子だった?
割り算した結果が整数になるんでしょ?
§1.6 参照。
そ。つまり \ 演算子を使うと余りが切り捨てられちゃうから、 パズルに使う画像の幅が horizontalBlocks の倍数になってないと、 こんなふうに画像の端が切れちゃうんだ。

<画像の端が切れる例>

あ、ホントだ。右端がちょっと切れちゃってるね。
…ってことは、高さも verticalBlocks の倍数になってないと、 下の方が切れちゃうってことだよね?
ん、そうなるね。
まぁ画像の端が切れてもパズルの動作には影響ないんだけど、 パズルが完成した時の見た目が悪くなっちゃうかもしれないから、 できるだけパズルに使う画像の幅は horizontalBlocks の倍数にして、 高さは verticalBlocks の倍数にした方がいいんじゃないかな。
そーだね。
じゃスクリプトの方に戻るね。
あと remainingPieces っていうメンバ変数にパズルのピースの数を代入してるんだけど、 この計算はわかるよね?
ピースは横方向に horizontalBlocks 個並んでて、 縦方向に verticalBlocks 個並んでるから、 horizontalBlocks * verticalBlocks でピースの数になるんだよね。
ん。ちなみに remainingPieces は、 正しい位置に置かれてないピースがあと何個残ってるかってのを表す変数で、 これが 0 になったらパズルが完成したってことになるの。
そーなんだ。
じゃあ、最初は正しい位置に置かれてるピースが1つもないから remainingPieces は ピースの数になるってこと?
そういうこと。
んじゃ今回は一旦ここで終わりにしとこっか。
あ、もう終わっちゃうんだ。
なんかちょっと中途半端な気もするけど。
んー、まだコンストラクタでやることも結構残ってるし、 この後ちょっと新しい事とかも出てくるしね。
そっか。それじゃ続きは次回にした方がよさそーだね。
ん。それじゃ、また次回ね!


前へ | TOP | 次へ