9.12 セーブデータの読み込みに対応する(その2)

さて、じゃ早速前回の問題を修正していこっか。
そーいえば、何で 200% で表示した時にはちゃんと画像が復元されたのに、 80% で表示すると画像が切れちゃったの?
実はね、見た目は問題なかったけど、200% の時もホントはちゃんと復元できてなかったんだよ。
えっ、そーなの?
ん。ちなみにこの問題の原因はココにあるんだ。

<問題の原因箇所>

「レイヤサイズの復元」ってトコ?
そ。
何でコレが問題なの?
んー、それは KAGLayer クラスの restore メソッドを見ながら説明した方がわかりやすいかな。
KAGLayer クラスの restore メソッドって、 レイヤサイズを復元してるメソッドだよね?
そう。
ちなみに KAGLayer クラスの定義は system フォルダにある KAGLayer.tjs に書いてあって、restore メソッドはこんな感じになってるよ。

KAGLayer クラスの restore メソッド(KAGLayer.tjs より抜粋)>

function restore(dic)
{
    // 辞書配列 dic(=f.foreCharacterLayers または f.backCharacterLayers の要素)から情報を読み出し、
    // このレイヤに設定することでセーブ時のレイヤの状態を復元します
    setImageSize(dic.imageWidth, dic.imageHeight); // レイヤの持っている画像のサイズを復元します
    setPos(dic.left, dic.top, dic.width, dic.height); // レイヤの位置・表示サイズを復元します
    setImagePos(dic.imageLeft, dic.imageTop); // レイヤ内の画像の位置を復元します
    opacity = dic.opacity; // 不透明度を復元します
    visible = dic.visible; // 表示状態を復元します
    absolute = dic.absolute if !isPrimary && dic.absolute !== void// 重ね合わせ順序を復元します
    type = dic.type if !isPrimary && dic.type !== void// レイヤタイプ(画像の表示形式)を復元します
}
※コメントを改変・追加しています。

最初に setImageSize メソッドを呼び出してるでしょ。
setImageSize メソッドについては §7.3 参照。
うん。
ここでセーブデータに保存されてるレイヤの画像サイズを読み込んで復元してるの。
setImageSize メソッドの引数の dic.imageWidthdic.imageHeight ってゆーのがセーブデータに保存されてるレイヤの画像サイズってコト?
そうだよ。
restore メソッドの引数の dic っていう辞書配列は、 例えば表画面の 0 番の前景レイヤの状態を復元する時は foreFlags[0](=f.foreCharacterLayers[0]) になってて、 裏画面の 0 番の前景レイヤを復元する時は backFlags[0](=f.backCharacterLayers[0]) になってるんだ。
foreFlags, backFlags 等については §9.11 参照。
じゃあ表画面の 0 番の前景レイヤだったら setImageSize(foreFlags[0].imageWidth, foreFlags[0].imageHeight); で、 裏画面の 0 番の前景レイヤだったら setImageSize(backFlags[0].imageWidth, backFlags[0].imageHeight); になるってことだね。
そういうこと。
で、例えば前回やったみたいに画像を 80% に縮小表示すると、 元々の画像(吉里吉里のアイコン画像 のことね)のサイズは 64×64 ピクセルだから…
64×0.8 は、え〜っと… 51.2 だから、51.2×51.2 ピクセルになるってこと?
小数点以下は切り捨てられるから、51×51 ピクセルになるね。
あそっか。画像サイズは整数なんだよね。
セーブデータには縮小後のサイズが記録されてるから、それを読み込んで setImageSize メソッドを呼び出すと setImageSize(51, 51); になって、 レイヤの画像サイズは 51×51 ピクセルに設定されるわけね。
うん、そうなるよね。
でも restore メソッドが呼び出された時にはまだ画像は縮小されてないから、 ここでレイヤのサイズを小さくすると、こんなふうにレイヤの画像の一部が無くなっちゃうんだ。

setImageSize メソッド実行後のレイヤの状態(80% 縮小時)>

あ、そーいえば前回 80% に縮小した後にセーブデータを読み込んだ時も、 こんなふうに画像の右下の部分が切れちゃってたよね。
それは画像を縮小する前にレイヤのサイズを小さくしてたのが原因だったってワケだね。
ちなみに 200% に拡大する場合は setImageSize(128, 128); が実行されるから…

setImageSize メソッド実行後のレイヤの状態(200% 拡大時)>

こんなふうにレイヤのサイズが 128×128 ピクセルに設定されるの。
この場合は画像が切れたりしないし、レイヤが拡張された部分は透明色になってて見えないから、 一見ちゃんと復元されてるように見えてたってワケ。
でも実際にはレイヤのサイズがセーブした時とは違っちゃうんだよね。
なるほどねぇ…
じゃあ、この問題を解決しようと思ったらどうすればいいと思う?
えっ?
えーっと…要するにレイヤの画像サイズが拡大とか縮小した後のサイズになっちゃうからいけないんだよね?
そうだね。
じゃあ setImageSize メソッドを呼び出さないようにすればいいんじゃない?
まぁそれはそうなんだけど、KAGLayer クラスの restore メソッドを勝手に書き換えちゃうと他の問題が起きるかもしれないから、 それはどうかと思うよ?
そっかぁ…
じゃあどーすればいいんだろ…?
さっき「レイヤの画像サイズが拡大とか縮小した後のサイズになっちゃうからいけない」って言ってたよね?
うん。
それってつまり、レイヤの画像サイズが拡大とか縮小する前のサイズにならなければ OK ってことだよね。
う〜ん、確かにそーだけど、setImageSize メソッドが呼び出されたらレイヤの画像サイズは変わっちゃうんだからどーしよーもないじゃない?
setImageSize メソッドが呼び出されてもレイヤの画像サイズが変わらないようにすれば大丈夫でしょ?
えっ、そんなコトできるの?
例えば、レイヤの画像サイズが 100×100 ピクセルの時に、 setImageSize(100, 100); を実行しても画像サイズは変わらないよね。
あ、そっか。
setImageSize メソッドを呼び出す前の画像サイズを引数に指定すればいいんだね。
そ。つまり、KAGLayer クラスの restore メソッドが呼び出される前に、予め imageWidthimageHeight の値を拡大/縮小する前の元々の画像サイズに設定しとけばいいってワケ。
なるほどね…って言いたいトコだけど、 どうやって imageWidthimageHeight の値を元々の画像サイズに設定するの?
セーブする時(厳密に言うとセーブ可能なラベルを通過する時だね)に loadParamsimageWidth 要素と imageHeight 要素(= dic.imageWidthdic.imageHeight)の値が元々の画像の幅と高さになるように設定しとけばいいの。
loadParams については §9.11 参照。
セーブする時に設定するの?
この引数 dic はセーブデータに書き込まれてる loadParams になってるから、セーブデータに元々の画像サイズを書き込んどけば、 setImageSize メソッドが呼び出されてもレイヤの画像サイズは変わらなくなるわけ。
あ、そっか。なるほどね。
じゃセーブデータに元々の画像のサイズを書き込もうと思ったら、 プラグインにどんなメソッドを追加すればいい?
んーと、セーブデータに書き込みたいわけだから、 onStore メソッドかな?
onStore メソッドについては §4.11 参照。
ん、そうそう。
onStore メソッドでこんな感じに書き込めば OK。

onStore メソッド>

function onStore(f, elm)
{
    // kag オブジェクトが持っている前景レイヤの情報を修正します
    var foreFlags = f.foreCharacterLayers; // 表画面の前景レイヤの情報が記録されている配列
    var backFlags = f.backCharacterLayers; // 裏画面の前景レイヤの情報が記録されている配列
    for(var i=0;i<foreFlags.count;i++)
    {
        var flags = foreFlags[i];
        if(flags.loadParams !== void)
        {
            // レイヤに画像が読み込まれている場合は画像の幅と高さの情報を修正します
            flags.width = flags.imageWidth = foreInfo[i].originalWidth;
            flags.height = flags.imageHeight = foreInfo[i].originalHeight;
        }
        flags = backFlags[i];
        if(flags.loadParams !== void)
        {
            // 裏画面のレイヤの情報も同様に修正します
            flags.width = flags.imageWidth = backInfo[i].originalWidth;
            flags.height = flags.imageHeight = backInfo[i].originalHeight;
        }
    }
}

最初の foreFlagsbackFlags のとこonRestore メソッドとおんなじだから大丈夫だよね?
うん。
じゃ for ブロックの中を見てくね。
まずここの if の条件で何をチェックしてるかはわかる?
えっと、flags.loadParamsvoid じゃないかどうかをチェックしてるみたいだね。
その前に flags = foreFlags[i]; って書いてあるから、 例えば i0 だったら flags.loadParamsforeFlags[0].loadParams になるから、 表画面の 0 番の前景レイヤに画像を読み込んだ時に指定されてた属性ってことだよね。
ん、そだね。
確か前回 loadParams はレイヤに画像が読み込まれてない時とか freeimage タグで画像を解放した時に void になるって言ってたから、 レイヤに画像が読み込まれてたら、if の条件が真になって、 if ブロックの中身が実行されるんだね。
そうそう。
で、その if ブロックの中で元々の画像サイズを loadParamsimageWidth 要素と imageHeight 要素に書き込んでるわけね。
あと一応レイヤの表示サイズも width 要素と height 要素に書き込んでるよ。
foreInfo[i].originalWidthforeInfo[i].originalHeight ってゆーのが画像の元々の幅と高さなの?
そうだよ。
これって前はなかったよね?
ん、これは今回新しく作る辞書配列で、 画像を読み込む時に originalWidth 要素と originalHeight 要素に画像の元々の幅と高さを代入しとくの。
代入する部分は別のメソッドに書かなくちゃいけないから、 とりあえず今はそれぞれ画像の元々の幅と高さが代入されてるって思っといて。
そーなんだ、りょーかい。
ちなみに foreInfobackInfo はどっちも ImageHandlerPlugin クラス(今作ってる画像読み込み機能拡張プラグインのことね)のメンバ変数で、 それぞれ表画面と裏画面の前景レイヤに読み込まれてる画像の元々の幅と高さを記録しとくための配列だよ。
※メンバ変数については §2.2 参照。
foreInfo[i]backInfo[i] にそれぞれ表画面と裏画面の i 番の前景レイヤに読み込まれてる画像の元々のサイズが記録されるんだよね?
そうだよ。
えっと、じゃあ for ブロックの中でやってることって、 表画面と裏画面のそれぞれの前景レイヤに画像が読み込まれてたら、 foreInfobackInfo に元々の画像サイズを記録して、 読み込まれてなかったら何もしないってことかな?
そういうこと。
じゃ次は originalWidth 要素と originalHeight 要素に画像の元々の幅を書き込まなくちゃいけないわけだけど、 これってどのメソッドでやればいいと思う?
さっき画像を読み込む時に書き込むって言ってたから、 image タグを実行する時に書き込むってコトだよね…?
だね。
…って言っても image タグの中身は変えられないから、その後ってことになるね。
ってことは… processImage メソッドってコト?
そ。
じゃあまた processImage メソッド書き換えるんだ?
うん、また書き換えるの。こんなふうにね。

processImage メソッド(さらに改)>

function processImage(elm, layer = void)
{
    if(layer === void)
        layer = kag.getLayerFromElm(elm);

    // layer の情報を取得します
    var info = getLayerInfo(layer);
    if(info === void)
        return// layer が前景レイヤでない場合は何もしません

    // layer の情報(画像の元々の幅と高さ)を更新します
    info.originalWidth = layer.imageWidth;
    info.originalHeight = layer.imageHeight;

    if(elm.x1 !== void && elm.y1 !== void && elm.x2 !== void && elm.y2 !== void && elm.x3 !== void && elm.y3 !== void)
        transformImage(layer, elm);

    if(elm.angle !== void)
        rotateImage(layer, elm);
    else if(elm.scale !== void || elm.xscale !== void || elm.yscale !== void)
        scaleImage(layer, elm);

    if(elm.xblur !== void && elm.yblur !== void)
        boxBlur(layer, elm);
}

今回もどこが変わったかわかりやすいように、前の processImage メソッドから変わったとこにだけコメント書いてるよ。
前は isCharacterLayer メソッドを呼び出して layer が前景レイヤかどうかチェックしてたけど、それが無くなって getLayerInfo っていうメソッドが呼び出されてるみたいだけど、これって新しいメソッドだよね?
ん、そだよ。
getLayerInfo メソッドは、引数に指定されてるレイヤに対応する、 元々の画像の幅と高さを記録するための辞書配列を返すメソッドなんだ。
えっと…それってつまりどーいうコト?
例えば layer が表画面の 0 番の前景レイヤへの参照になってたら、 foreInfo[0] を返すってことだよ。
あ、そーゆーコトね。
あと layer が前景レイヤ以外のレイヤへの参照になってたら void を返すようにしてるよ。
getLayerInfo メソッドの戻り値が void だったら return してるから何にもしないってことだよね?
そ。getLayerInfo メソッドを呼び出すことで layer が前景レイヤかどうか判るから、isCharacterLayer メソッドは呼び出さなくてもよくなるワケ。
なるほど、だから isCharacterLayer メソッドを呼び出してる部分が無くなってたんだね。
んで後はここinfooriginalWidth 要素と originalHeight 要素にそれぞれ layerimageWidthimageHeight の値(元々の画像の幅と高さ)を代入すれば OK。
え〜っと、ここ って image タグが実行された後だし scaleImage メソッドとかで画像が拡大/縮小される前だから、 こうすれば info.originalWidthinfo.originalHeight に画像の元々の幅と高さがちゃんと代入されるってことかな?
ん、その通りだよ。
じゃ次はさっき出てきた getLayerInfo メソッドを見ていこっか。
は〜い。
これが getLayerInfo メソッドのスクリプトだよ。

getLayerInfo メソッド>

function getLayerInfo(layer)
{
    // 表画面の前景レイヤ配列に layer が含まれているかどうかチェックします
    var index = kag.fore.layers.find(layer);
    if(index != -1)
    {
        // 含まれていればそのレイヤの情報を記録するための辞書配列を返します
        if(foreInfo[index] === void)
            foreInfo[index] = %[]; // 辞書配列がまだ作られていない場合は新しく作ります
        return foreInfo[index];
    }
    // 裏画面の前景レイヤ配列に layer が含まれているかどうかチェックします
    index = kag.back.layers.find(layer);
    if(index != -1)
    {
        // 含まれていればそのレイヤの情報を記録するための辞書配列を返します
        if(backInfo[index] === void)
            backInfo[index] = %[]; // 辞書配列がまだ作られていない場合は新しく作ります
        return backInfo[index];
    }
    // 該当するレイヤがなければ前景レイヤではないので void を返します
    return void;
}

isCharacterLayer メソッドと似てるから大体何やってるかはわかると思うんだけど、どうかな?
確かに find メソッドで前景レイヤかどうかを調べてるところとか似てるね。
isCharacterLayer メソッドは truefalse を返してたけど、こっちは辞書配列を返してるんだね。
あと、foreInfobackInfo はこんなふうに最初から配列になってるんだけど…

ImageHandlerPlugin クラスのメンバ変数宣言部>

class ImageHandlerPlugin extends KAGPlugin
{
    // 表画面と裏画面の前景レイヤに読み込まれている画像の
    // 元々のサイズを記録する配列を作っておきます
    var foreInfo = [], backInfo = [];

    function ImageHandlerPlugin()
    {
        super.KAGPlugin();
    }

    function finalize()
    {
        super.finalize();
    }

    (以下略)
}

配列の中身は空っぽだから、もしまだ配列の中身(元々の画像のサイズを記録する辞書配列のことね)が作られてなかったら、 こことかここで新しく辞書配列を作って返すんだ。
配列の中身って最初から作っとかなくてだいじょーぶなの?
配列の中身が作られてないってことはレイヤに画像が読み込まれてないってことだから、 セーブデータに何も記録する必要ないでしょ。だから中身がなくても大丈夫なの。
あ、そー言われればそーだね。
さて、これであと2つメソッドを追加すればプラグイン完成だね。
え、まだメソッド追加しなきゃいけないの?
foreInfobackInfo で表画面と裏画面のレイヤの情報を管理する以上、onCopyLayer メソッドと onExchangeForeBack メソッドはちゃんと作らなくちゃね。
onCopyLayer メソッドについては §4.6onExchangeForeBack メソッドについては §4.7 参照。
onCopyLayer メソッドと onExchangeForeBack メソッド…ってどんなメソッドだったっけ?
なんかトランジションとかの関係だったよーな気がするんだけど…?
onCopyLayer メソッドは backlay タグとか forelay タグを実行した時に呼び出されるね。
あとトランジションが終わった時にも呼び出されるよ。
あ、そーそー。onCopyLayer メソッドって確か表画面の内容が裏画面にコピーされたり、 裏画面の内容が表画面にコピーされたりすると呼び出されるんだったよね。
そ。じゃ、今回のプラグインの場合は onCopyLayer メソッドが呼び出されたらどうすればいいと思う?
えーっと… backInfoforeInfo の中身をコピーしたり、 foreInfobackInfo の中身をコピーしたり…とか?
ん、そうだね。
あ、それでいーんだ。
んじゃ onCopyLayer メソッド作ってみて。
ちなみに onCopyLayer メソッドには toback っていう引数があって、 表画面の内容が裏画面にコピーされる時には true になってて、 裏画面の内容が表画面にコピーされる時には false になってるよ。
配列の中身をコピーするのって assign メソッドだったっけ?
assign メソッドについては §1.14 参照。
うん。
じゃあ作ってみるね。

onCopyLayer メソッド>

function onCopyLayer(toback)
{
    if(toback)
    {
        // 表画面の情報を裏画面にコピーする場合は foreInfo の内容を backInfo にコピーします
        backInfo.assign(foreInfo);
    }
    else
    {
        // 裏画面の情報を表画面にコピーする場合は backInfo の内容を foreInfo にコピーします
        foreInfo.assign(backInfo);
    }
}

こんな感じかな?
ん、これで OK。
それじゃ次は onExchangeForeBack メソッドだね。
これは基本的にトランジションが終わった後に呼び出されるメソッドだよ。 Fore(表画面)と Back(裏画面)を Exchange(交換)するってことだから…
あ、表画面と裏画面のレイヤが入れ替わった時に呼び出されるんだ。
そう。じゃこれで onExchangeForeBack メソッドも作れるよね?
foreInfobackInfo を入れ替えるだけでいいの?
ん、それだけでいいよ。
そっか。じゃあ…

onExchangeForeBack メソッド>

function onExchangeForeBack()
{
    // 表画面と裏画面の情報を入れ替えます
    foreInfo <-> backInfo;
}
<-> 演算子については §4.7 参照。

こうなるよね。
これも OK だね。
それじゃ今度こそ完成だから、前回うまくいかなかったスクリプトを実行してみよっか。
必要なスクリプトはここにまとめとくね。
※09/03/01 追記:
eximage マクロと image タグを混在させるとエラー(例外)が発生するというご指摘を頂きましたので、 両者を混在させてもエラーが発生しないように改良した eximage2.ks を同梱しました。
はーい!
それじゃあ実行してみるね。

<実行結果(セーブデータ読み込み後の状態)>

あ、今度はちゃんと復元されてる!
ん、今回はうまくいったね!
あと回転とか拡大/縮小と回転を同時にやるのとかも試してみてね。
はーい。
は〜、やっと完成できた〜。
今回のプラグインはちょっと作るの大変だったかな。
すごいタイヘンだったよ〜。
スクリプトとかかなりややこしかったし。
まぁ確かにそうかもね。
あ、そうそう。
ん? なに?
この画像読み込み機能拡張プラグインなんだけどね、 いくつか制限があるから使う時には注意してね。
えっ、制限って?
結構色々あるんだけどね、まとめるとこんな感じ。

eximage マクロを使用して画像を拡大/縮小/回転した時の制限事項>

※画像を拡大・縮小・回転しない場合は eximage マクロを使用して画像を読み込んでもクリッカブルマップ・アニメーションは正しく動作します。

簡単に言うと、eximage マクロで画像を拡大・縮小・回転した時には、 クリッカブルマップとかアニメーションとか pimage タグとか ptext タグは使わないようにしてね、ってこと。
あ、そーいうのって使えなくなっちゃうんだ。
今回作ったプラグインで拡大とかしてるのはレイヤのメイン画像とマスク画像だけで、 領域画像とかアニメーション用の画像とか部分画像とかまでは処理してないからね。
そっかぁ…
まぁやろうと思えばこの制限をなくせるかもしれないけど、 かなり大変だと思うからそこまではやらないことにするね。
確かにタイヘンそーだよね…
ってワケで、これで画像読み込み機能拡張プラグインは完成ってことにするね。
うん、わかった。
じゃあこれで第9章は終わりになるの?
んー、そうしようかと思ったんだけど、 せっかく画像の拡大・縮小・回転ができるようになったから、 これをもうちょっと他の事にも利用しようかなーと思うんだ。
え、他の事って?
move タグは知ってるよね?
うん、もちろん知ってるよ。
レイヤを動かしたり不透明度を変えたりするタグだよね。
今度はその move タグを拡張してみようかなーって思ったの。
move タグを拡張するって…
もしかして画像を動かしながら拡大とか縮小とか回転できるようにするってコト?
ん、そのとーり。
そりゃ確かにそれが出来れば便利になりそーだけど…
なんかそれってすごい難しそーじゃない?
んー、画像読み込み機能拡張プラグインよりはちょっと難しいかもね。
今回のプラグインだけでも十分難しかったのに、 これ以上難しくなったらついてけなくなりそーなんだけど…
まぁ次はついてけなかったとしても大丈夫だから、とりあえず作るだけ作ってみよ?
move タグが拡張できると色々使えそうでしょ?
ん〜…まーそれなら作ってみてもいーかもね。
んじゃ次回からは move タグを拡張して、 レイヤを動かしたり不透明度を変えながら拡大・縮小・回転ができるようにしていくね。
は〜い!
それじゃ、また次回ね!


前へ | TOP | 次へ