9.19 レイヤ移動機能を拡張する(その5)

今回は ExtendedMover クラスの続きね。
前回はすっごい長かったよねぇ…
まぁコンストラクタに関係してるメソッドを全部見ていったからね。
一応今回からは前回ほどは長くならない予定だから。
そっか。ならいーんだけど。
今回はコンティニュアスハンドラから呼び出される move メソッドと、 move メソッドから呼び出されるメソッドを見ていくことにするね。
move メソッドについては §9.17 参照。
move メソッドってレイヤを動かすメソッドだったっけ?
LinearMover クラスと SplineMover クラスの move メソッドはレイヤを移動させたり不透明度を変えたりするメソッドだけど、 ExtendedMover クラスの move メソッドはレイヤを拡大・縮小・回転するメソッドだよ。
あ、そっか。
でも“move メソッド”ってゆーとなんかレイヤを動かすメソッドみたいだよね。
まぁ他の名前にしてもよかったんだけどね。
LinearMover クラスや SplineMover クラスの move メソッドに対応するメソッドだから同じ名前にしといたの。
ふぅん、そーなんだ。
じゃとりあえず ExtendedMover クラスの move メソッドのスクリプトを見ていくね。

move メソッド>

function move(tickCount)
{
    // 一時レイヤがある場合は拡大・縮小・回転処理します
    if(refLayer !== void && refLayer isvalid)
    {
        var index = tickCount \ time * 2; // 現在の区間を計算します
        var ratio = tickCount % time / time; // 現在の区間内の位置を計算します
        var ss = pathex[index]; // 現在の区間での初期拡大率
        var sa = pathex[index+1]; // 現在の区間での初期回転角
        var es = pathex[index+2]; // 現在の区間での最終拡大率
        var ea = pathex[index+3]; // 現在の区間での最終回転角

        // 線形補間法で現在の拡大率(scale)と回転角(angle)を計算します
        var scale = int(real(es - ss) * ratio + real ss);
        var angle = int(real(ea - sa) * ratio + real sa);

        // 画像を拡大/縮小/回転します
        moveFunc(angle, scale);

        // 現在の拡大率と回転角の値を更新します
        currentScale = scale;
        currentAngle = angle;
    }
}

まず最初の if の条件がどうなってるかわかる?
えっと、まず“refLayer !== void”の方は、 refLayer(一時レイヤのことだったよね)が void じゃなかったら真になるね。
…けど refLayer って void になることあるんだっけ?
前回、レイヤを拡大・縮小・回転する場合は一時レイヤが必要になるって言ったでしょ。
確か stretchCopy メソッドとか affineCopy メソッドを使う時に、 一時レイヤがコピー元のレイヤになるんだったよね?
stretchCopy メソッドについては §9.2affineCopy メソッドについては §9.4 参照。
そうそう。
逆に言うと、レイヤを拡大も縮小も回転もしない場合(つまり移動と不透明度の変更だけやる場合ね)は、 一時レイヤを作る必要がないから、refLayervoid になるの。
なるほど。
…でも拡大も縮小も回転もしないんだったら move タグでいいんじゃない?
まぁそうなんだけど、一応拡大も縮小も回転もしない場合でも exmove マクロが使えるようにしとこうと思ってね。
そっか。
え〜っと、あとは“refLayer isvalid”だけど…
isvalid ってレイヤが無効化されてるかどうかを調べる時に使うんだったかな?
そ。isvalid 演算子は、オブジェクトが無効化されてたら false、無効化されてなかったら true になるからね。
isvalid 演算子については §2.2 参照。
じゃあ、“refLayer !== void && refLayer isvalid”は、 一時レイヤが作られてて、しかも無効化されてなかったら真になるってことだね。
そういうことだね。
ちなみに一時レイヤが無効化されてないかどうかを調べるのは、§9.14 の時と同じで、 コンティニュアスハンドラ実行中にレイヤが無効化されても大丈夫なようにするためだから。
コンティニュアスハンドラを使う時って、確かそーゆーチェックをちゃんとしといた方が良いんだよね?
ん、LinearMover クラスとかでもちゃんとチェックしてるしね。
あ、そーなんだ。
あと、もし一時レイヤが作られてなかったり無効化されてたりしたら、 コンティニュアスハンドラが呼び出されても(ExtendedMover クラスでは)何もしないってとこに注意してね。
え、そなの?
だって move メソッドには if ブロックの中身以外スクリプトが無いでしょ。
あ、ホントだ。
ExtendedMover クラスはレイヤを拡大・縮小・回転するのが目的だから、 一時レイヤが無い場合(つまりレイヤを拡大も縮小も回転もさせる必要が無い場合)は何もすることないよね。
そー言われれば確かにそーだね。
んじゃ次は if ブロックの中身を見てくね。
なんか色々計算してるみたいだけど…これって何してるの?
画像を何%に拡大して何度に回転すればいいかを計算して、 それから実際にその拡大率に拡大/縮小したり、その回転角に回転したりしてるんだよ。
そーなんだ…でもこれってパッと見てもどんな計算してるのかよくわかんないよね…
ん〜、確かにちょっとわかりにくいかもねー。
それじゃ1つ例を見ていこっか。多分そっちのがわかりやすいと思うから。
はーい。
じゃこういうスクリプトを実行する場合を見てみるね。

exmove マクロを使ったスクリプトの例>

; 表画面の 0 番の前景レイヤに画像を読み込みます(初期拡大率 100%、初期回転角 0°)
[eximage layer=0 page=fore storage="krkr" scale=100 angle=0 left=200 top=200 visible=true]
; レイヤを拡大・縮小・回転します
[exmove layer=0 page=fore time=1000 path="(,,,165,20)(,,,120,52)(,,,200,120)"]

eximage マクロの方は普通に画像を読み込んでるだけで特に問題ないと思うから、 exmove マクロの方に注目してみて。
えと、この path 属性の書き方って、 レイヤの位置(left, top)と不透明度(opacity)を省略してるんだよね?
exmove マクロの path 属性の書き方については §9.15 参照。
そだよ。だから exmove マクロを実行しても位置と不透明度は変わらないよ。
※シンプル版 exmove マクロの場合は常に画像の左上の点を中心として拡大・縮小・回転しますので、“画像の左上の点の位置が変わらない”ということです。
ってことは、time 属性が 1000 (ミリ秒)だから、 1秒後にレイヤの拡大率が 165%、回転角が 20°になって、 2秒後に拡大率が 120%、回転角が 52°になって、 3秒後に拡大率が 200%、回転角が 120°になって終わり、 ってことだね。
そ。つまりこんな感じにレイヤの大きさと角度が変化するってことね。

<レイヤの大きさと角度の変化>

1秒後とか2秒後のレイヤの拡大率とか回転角はすぐにわかると思うけど、 例えば 1.3 秒後のレイヤの拡大率と回転角はどうなってると思う?
えっ、1.3 秒後??
そ、つまり 1300 ミリ秒後だね。
う〜ん…1秒後の拡大率が 165% で、 2秒後には 120% になるから、 拡大率は 120%〜165% の間だと思うけど…
具体的に何%になるのかはわかんないよー。
ま、それは if ブロックの中身のスクリプトを実行すれば計算できるんだけどね。
…だったらわざわざ訊かなくてもいーのに…
まぁまぁ。
ってワケで、実際にどんなふうに計算されるのか見ていくね。
は〜い。
まず、move メソッドtickCount っていう引数があるでしょ。
うん、あるね。
この tickCountexmove マクロが実行されてからの経過時間がミリ秒単位で代入されてるんだ。
だから、例えば tickCount1300 になってたら 1300 ミリ秒、 つまり 1.3 秒経過したってことが判るわけね。
へぇ、そーなんだ。
あと、拡大率と回転角の初期値がそれぞれ 1000 で、path 属性が "(,,,165,20)(,,,120,52)(,,,200,120)" だから、 pathex 配列の中身がこうなるってのは OK かな?
pathex 配列については §9.18 参照。

pathex 配列の中身>

pathex[0]pathex[1] がそれぞれ拡大率と回転角の初期値になって、 pathex[2]pathex[3] が第1区間の拡大率と回転角の最終値になって…って感じだったよね。
そうそう。
ところで、1.3 秒後って何番目の区間に入ってるかわかる?
う〜ん、time 属性が 1000 だから、 1つの区間の長さが1秒(1000ミリ秒)ってことだよね。
そだね。
じゃあ、exmove マクロを実行してから1秒後までが第1区間で、 1秒後から2秒後までが第2区間になるはずだから…
1.3 秒後は第2区間に入ってるんじゃない?
ん、そうなるね。
じゃ改めて if ブロックの中身を見てみるね。
まず indexratio っていう変数の値を計算してるでしょ。
うん。
1.3 秒後に move メソッドが呼び出された時、 つまり tickCount1300 になってる時は、 indexratio はそれぞれどんな値になるか計算してみて。
えっと、index の方は“tickCount \ time * 2”だから…
あ、この time って time 属性の値だよね?
そうだよ。
じゃあ tickCount1300time1000 になってるってことだから、 index には“1300 \ 1000 * 2”が代入されるってことだよね。
…えと、これってどーやって計算したらいいんだっけ?
先に“1300 \ 1000”を計算して、 その結果を2倍すれば OK だよ。
“\”演算子については §1.6 参照。
そっか。
“1300 \ 1000”13001000 で割って、小数点以下を切り捨てるから 1 だよね。
あと、それを2倍するから…2、かな?
ん、だから index には 2 が代入されるわけだね。
この 2 って第2区間の 2 ってこと?
んーん、そーじゃないよ。
index の値は1つ前の区間の pathex 配列の(拡大率の方の)添え字を表してるんだ。
第2区間の1つ前の区間、つまり第1区間の拡大率の最終値は pathex[2] だから index2 になるの。
なんで今の区間じゃなくって1つ前の区間になるの?
第1区間が終わった時には、拡大率は 165%(=pathex[2]) になってるよね。
うん。
で、その状態から第2区間が始まるわけだよね。
そーだね。
だから、第2区間の拡大率の初期値も 165%(=pathex[2]) になるよね 。
つまり、pathex[index](pathex[2]) は現在の区間(第2区間)の拡大率の初期値になってるわけ。
こう考えると何となく納得できない?
ん〜、まぁね。
じゃあと ratio の方も計算してみて。
ratio“tickCount % time / time”だから、 “1300 % 1000 / 1000”になるよね。
えっと、これも“1300 % 1000”を先に計算して、 それを 1000 で割ったらいーのかな?
ん、それでいいよ。
ただ、さっきの“\”演算子と今度の“/”演算子の違いには気をつけてね。
“\”は小数点以下を切り捨てるけど、 “/”は割り算した結果をそのまま小数で表すんだったよね?
そ。だから“/”で割り算すると、計算結果が実数型になるんだったよね。
※これらの演算子については §1.6 参照。
“1300 % 1000”13001000 で割った余りで 300 だから、 それをさらに 1000 で割って小数にすると…
0.3 になるのかな?
そう。つまり ratio には 0.3 が代入されるわけね。
この 0.3 ってなんなの?
1区間の長さは 1000 ミリ秒だから、 1300 ミリ秒経ったってことは、 第2区間に入ってから 300 ミリ秒経ったってことになるでしょ?
うん、そーだね。
つまり、第2区間に入ってから、 1区間の長さ(1000 ミリ秒)の3割の時間が経ったってことになるよね。
で、3割ってのは10分の3、つまり 0.3 なわけね。
えっと、じゃあ例えば 1.4 秒経ってたら ratio0.4 になって、1.5 秒経ってたら ratio0.5 になるってコト?
そういうこと。
ratio の値は後で 1.3 秒後の拡大率と回転角を計算する時に出てくるから、 その時にまた説明するね。
りょーかい。
それじゃ次は、ss, sa, es, ea っていう変数の値がどうなるか考えてみて?
それぞれ pathex[index], pathex[index+1], pathex[index+2], pathex[index+3] になってて、index2 だから…
ss には pathex[2]sa には pathex[3]es には pathex[4]ea には pathex[5] が代入されるよね。
ん、そうだね。
ちなみに図にするとこんな感じね。

if ブロック内の各変数の値>

で、次に ss, sa, es, earatio の値を使って、 拡大率(scale)と回転角(angle)を計算すると、 こんなふうに拡大率が 151% で、回転角が 29°になるってことがわかるわけね。
int 演算子と real 演算子については §1.6 参照。

<拡大率(scale)と回転角(angle)の計算>

どーゆー計算してるのかはよくわかんないけど、 拡大率は 120%〜165% の間になってるし、 回転角は 20°〜52° の間になってるみたいだね。
この計算のやり方は線形補間って言って、 こんなふうに拡大率も回転角もグラフで表した時に直線的に変化するって考えて計算してるんだ。

<拡大率(左)と回転角(右)の線形補間>

まず左側が拡大率の変化を表したグラフなんだけど、 経過時間が 1300 ミリ秒の時の拡大率は大体 151% になってるでしょ。
あと、右側が回転角の変化を表したグラフで、 こっちも経過時間が 1300 ミリ秒の時の拡大率を見ると、 大体 29° になってるよね。
なるほどねぇ…こーやって計算してるんだ。
じゃスクリプトの方に戻って…
これで拡大率と回転角がわかったから、 次にここでその拡大率と回転角に画像を拡大・縮小・回転してるんだ。
moveFunc って確か前回出てきたよね?
ん、画像を回転させる必要がある時は moveFuncrotate メソッドへの参照になってて、 拡大・縮小だけすればいい時は resize メソッドへの参照になってて、 回転も拡大も縮小もしなくていい時は void になってるから、 ここで moveFunc を呼び出すことで、 画像を回転したり拡大したり縮小したりできるわけね。
なるほど。
…あ、でも moveFuncvoid の時でも moveFunc を呼び出しちゃって大丈夫なの?
moveFuncvoid だと当然 moveFunc を呼び出すことはできないよ。
え、じゃあ moveFuncvoid じゃないかどうかチェックしなきゃいけないんじゃないの?
moveFuncvoid になってるってことは、 画像を回転も拡大も縮小もしないってことだから、一時レイヤを作る必要はないでしょ。
うん、そだね。
ってことは、refLayervoid になってるってことだから、この if の条件が偽になって moveFunc を呼び出してる if ブロックの中身は実行されないよね。
あ、そっか。
じゃあ moveFuncrotate メソッドへの参照か resize メソッドへの参照になってる時しか moveFunc は呼び出されないんだね?
そういうこと。
で、moveFunc が呼び出されたら画像が拡大・縮小・回転されるから、 その後にここでメンバ変数の currentScalecurrentAngle にそれぞれ現在の(拡大・縮小・回転後の)拡大率と回転角を代入するの。
それって何に使うの?
どっちも rotate メソッドとか resize メソッドの中で使ってるから、 詳しくはこの2つのメソッドを見ていく時に説明するね。
そーなんだ。りょーかい。
それじゃ次は rotate メソッドと resize メソッドを見てくね。
まずは簡単な resize メソッドの方から。

resize メソッド>

function resize(angle, scale)
{
    if(scale != currentScale)
    {
        if(scale != 100)
        {
            // scale が前回のハンドラ呼び出し時と違っていて
            // かつ 100% でなければ画像を拡大・縮小します
            scaleImage(scale / 100);
        }
        else
        {
            // scale が 100% であれば元々の画像をコピーします
            layer.assignImages(refLayer);
        }
    }
}

確かに結構シンプルそーだね。
でしょ。
じゃ最初の if のとこから見てくね。
“scale != currentScale”ってのが条件になってるけど、 この scalecurrentScale ってさっき move メソッドで出てきたののことだよね?
そうそう。
scale は今の拡大率だったよね。
じゃあ currentScale は?
move メソッドで moveFunc を呼び出してる辺りを見てもらうとわかると思うんだけど、 scale には moveFunc を呼び出す前に値を代入してて、 currentScale には moveFunc を呼び出した後に値を代入してるでしょ。
そーだね。
つまり、scale今の拡大率になってて、 currentScale前に move メソッドが呼び出された時の拡大率になってるの。
※初めて move メソッドが呼び出された時は、 currentScale は初期拡大率になっています。
えっと、それってつまり…どーいうコト?
つまり、“scale != currentScale”が真だったら、 前に move メソッドが呼び出された時から拡大率が変わったってことだし、 偽だったら前に move メソッドが呼び出された時から拡大率が変わってないってことだよ。
ってことは…前に move メソッドが呼び出された時から拡大率が変わってたら if ブロックの中身が実行されるってこと?
そういうこと。
逆に言えば、前に move メソッドが呼び出された時から拡大率が変わってなかったら、 if ブロックの中身が実行されないから、結局 resize メソッドでは何もしないワケ。
そっか。確かにそーなるね。
拡大率が変わってないってことは、画像を拡大したり縮小したりする必要はないわけだから、 resize メソッドでやることは何も無いよね。
あ、なるほど。
それじゃ次は2番目の if ブロックを見てくね。
条件が“scale != 100”だから…
今の拡大率が 100% じゃなかったら if ブロックの中身が実行されて、100% だったら else ブロックの中身が実行されるんだよね?
ん、そう。
拡大率が 100% じゃなかったら画像を拡大・縮小しなきゃいけないから、 if ブロックの中では scaleImage メソッドを呼び出してて、 拡大率が 100% だったら一時レイヤの画像をそのまんまレイヤにコピーすればいいだけだから、 else ブロックの中では assignImages メソッドを呼び出してるわけね。
assignImages メソッドについては §4.6 参照。
えーっと、まぁその辺は大体解るんだけど…
この scaleImage メソッドって前に出てきた時と引数が違ってない?
確かに ExtendedMover クラスの scaleImage メソッドの引数は、前に eximage マクロを作った時に出てきた ImageHandlerPlugin クラスの scaleImage メソッドの引数とは違ってるよ。
だよねぇ?
じゃあこの scaleImage メソッドって eximage マクロの時の scaleImage メソッドと違うってこと?
んー、まぁ引数とかメソッドの中身のスクリプトは多少違ってるんだけど、 どっちも一時レイヤをコピー元レイヤにして stretchCopy メソッドで画像を拡大・縮小コピーしてるから、基本的にやってることは全く同じだよ。
stretchCopy メソッドについては §9.2 参照。
そーなの?
うん。その辺は scaleImage メソッドのスクリプトを見比べてみてもらえばわかると思うよ。
ってワケで、今回の(ExtendedMover クラスの) scaleImage メソッドの説明は省略させてもらうね。
あ、省略しちゃうんだ?
どっちの scaleImage メソッドも似たようなスクリプトだし、今回の scaleImage メソッドの方がシンプルだからね。
ふぅん。
あ、一応今回の scaleImage メソッドの引数は説明しとくね。
この引数には拡大率をパーセント単位じゃなくて何倍かで指定するようになってるの。
じゃあ例えば拡大率が 200% だったら2倍だから、 引数は 2 になるってこと?
そ。だから scale(パーセント単位の拡大率)を 100 で割った値を指定してるわけね。
ちなみに倍率は実数値で指定するから、 割り算には“\”演算子じゃなくて“/”演算子を使ってるよ。
なるほどねぇ。
じゃ最後に resize メソッドの処理の流れをフローチャートにしとくね。

resize メソッドの処理の流れ>

…なんか最近フローチャートが良く出てくるね。
if ブロックがたくさん出てきたり、 if ブロックの中にまた if ブロックがあったりするスクリプトはフローチャートで表すとわかりやすくなるような気がしない?
う〜ん、どーなんだろ…?
じゃ次は rotate メソッドを見てくね。

rotate メソッド>

function rotate(angle, scale)
{
    // アフィン変換のパラメータを計算します
    calcRotateParams(rotateParams, refLayer, angle * radConst, scale / 100);
    // レイヤの位置を調整します
    layer.setPos(layer.left - rotateParams.dspX, layer.top - rotateParams.dspY);
    if(angle != 0)
    {
        // 回転角が 0°でなければアフィン変換します
        // (但し前に呼び出された時と回転角または拡大率が変わっている場合のみ)
        if(angle != currentAngle || scale != currentScale)
            rotateImage();
    }
    else if(scale != currentScale)
    {
        if(scale != 100)
        {
            // 回転角が 0°で拡大率が 100% (等倍)でなければ拡大/縮小します
            // (但し前に呼び出された時と拡大率が変わっている場合のみ)
            scaleImage(scale / 100);
        }
        else
        {
            // 回転角が 0°で拡大率が 100% の場合は一時レイヤの画像をコピーします
            layer.assignImages(refLayer);
        }
    }
}

こっちはだいぶフクザツそうだねぇ…
拡大・縮小だけじゃなくて回転も入ってるからね。
最初に呼び出してる calcRotateParams メソッドって確か前回も出てきたよね?
結局何やってるのかはよくわかんなかったけど。
画像を回転する時は affineCopy メソッドを使うからね。
アフィン変換のパラメータとかレイヤの表示位置とかを計算するために calcRotateParams メソッドを呼び出して、 んでその後に setPos メソッドでレイヤの位置を補正してるわけね。
affineCopy メソッドについては §9.4setPos メソッドについては §3.2 参照。
レイヤの位置の補正もあんまりよくわかんなかったけどねぇ。
まぁこの辺はちょっと面倒な話だからね。
じゃ次の if ブロックに行くね。
条件が“angle != 0”だから、 回転角が じゃなかったら if ブロックの中身が実行されるんだね。
そうそう。
if ブロックの中にまた if があって、 条件が“angle != currentAngle || scale != currentScale”になってるね。
“scale != currentScale”はさっきも出てきたからわかるよね。
あと、“angle != currentAngle”の方も同じように考えればわかるんじゃない?
“scale != currentScale”は、 前に move メソッドが呼び出された時と拡大率が変わってれば真になるから、 “angle != currentAngle”だと、 前に move メソッドが呼び出された時と回転角が変わってたら真になるってことかな?
そ。で、その2つの条件が“||”(論理 OR 演算子)でつながってるから…
前に move メソッドが呼び出された時と拡大率か回転角のどっちかが変わってたら、 条件式が真になるってことだよね?
ん。その時は rotateImage メソッドが呼び出されるわけだね。
rotateImage メソッドも eximage マクロを作った時に出てきたと思うけど…やっぱり引数が違ってるよね。
ってゆーか、今回の rotateImage メソッドは引数が無くなっちゃってるね。
ImageHandlerPlugin クラス(eximage マクロのためのクラス)の rotateImage メソッドについては §9.10 参照。
一時レイヤとかアフィン変換のパラメータとかの、 画像の回転に必要な情報を全部メンバ変数が持ってるからね。
特に引数が必要なくなったんだ。
ふぅん、そーなんだ。
rotateImage メソッドの方も、やってることは基本的に eximage マクロを作った時に出てきた(ImageHandlerPlugin クラスの) rotateImage メソッドとおんなじだから、 こっちも説明は省略させてもらうね。
りょ〜かい。
で、後は今の回転角が じゃなかった時、 つまり“angle != 0”が偽だった時に実行される else if ブロックの中身だけど、 これは説明の必要ないよね。
え、なんで?
だって scaleImage メソッドの if ブロックと全くおんなじになってるでしょ。
あ、ホントだ。
この else if ブロックの中身は回転角が の時、つまり画像が回転してない時に実行されるわけだから、 画像を拡大・縮小する必要はあるかもしれないけど、回転する必要はないよね。
そっか、だから resize メソッドとおんなじになるんだ。
じゃ最後に rotate メソッドの処理の流れもフローチャートにしとくね。

rotate メソッドの処理の流れ>

これはやっぱりフローチャートにしてもフクザツだよね…
う〜ん、わかりやすくフローチャート描くのって難しいもんだね…
だったら無理に描かなくてもいーのに。
スクリプトだけだと条件分岐がどうなってるのかわかりにくそうだったから、ね。
ホントにこれでわかりやすくなってるのかなぁ…?
まぁ、無いよりはある方がいいかなってコトで。
はい、じゃあ今回はここまでね。
確かに前回ほどじゃなかったけど、今回も結構長かったねぇ。
うーん、exmove マクロ関係のスクリプトは複雑だから、 どうしても長くなっちゃうんだよね。
まぁでもあともう少しだから。
あ、じゃあもうちょっとで終わりなんだ?
ん、あと2回くらいかな。
ってワケで、次回も ExtendedMover クラスの続きをやるね。
それじゃ、また次回!


前へ | TOP | 次へ