9.14 レイヤの自動移動(その2)

じゃ今回は前回の続きってことで、handler メソッドから見ていくね。
えっと、コンティニュアスハンドラ…だっけ?
そ。レイヤを動かすためにできるだけ頻繁に呼び出されるメソッドね。
なんか try ってゆーブロックがあるんだけど、これ何?
try ブロックは前にも使ったことあるよ。
え、そーだっけ?
try ブロックと catch ブロックは例外処理をやるために作ってるの。
例外処理は §3.12§3.13 とかでやったでしょ?
あ〜…
思い出した?
…ゴメン、忘れちゃった。
…。
ま、まぁ例外処理をやったのはだいぶ前だからね。
じゃ簡単に例外処理をおさらいしとくね。
うん。
例外ってのは、例えばスクリプトの書き方が間違ってたり、 無効化された後のオブジェクトを使おうとしたりして、 スクリプトの実行がそれ以上続けられなくなっちゃった時に起きるってことはわかるよね?
うん、それはわかるよ。
普通は例外が起きると例外の内容が書いてあるメッセージボックスが表示されて、そこでスクリプトの実行が止まっちゃうわけだけど、 例外処理をしとくと、例外が起きてもスクリプトの実行を続けられるようになるんだ。
あ、なんかそーいえばそんな感じだったね。
で、try ブロックを書いとくと、 try ブロックの中で例外が起きたら catch ブロックの中身が実行されるようになるの。
catch ブロックって例外が起きたときだけ実行されるんだっけ?
そうだよ。
try ブロックの中で例外が起きたら、その後のスクリプトは実行されずに、 すぐに catch ブロックの中のスクリプトが実行されるんだ。
try ブロックの中で例外が起きなかったら、 catch ブロックは飛ばして、その次のスクリプトが実行されるの。
なるほどね。
どう? 少しは思い出せた?
ん〜…何となく思い出せたかな、って感じ。
まぁホントはもっとちゃんと例外処理をやってくれば良かったんだろうけどね。
そー言えば、なんで今回は例外処理をしてるの?
コンティニュアスハンドラを使う時は例外処理をちゃんとやってないと厄介なことになるかもしれないからね。
どーいうコト?
コンティニュアスハンドラは繰り返し呼び出されるから、 コンティニュアスハンドラの中で例外が起こったときに、 例外処理してないと繰り返しエラーが起こり続けることがあるんだ。
うわ、そーなんだ…
ってワケだから、コンティニュアスハンドラを使う時には必ず例外処理をやるようにしてね。
はーい。
じゃ try ブロックの中身を見てくね。
最初の if の条件になってる layer isvalid って?
これも一応前に出てきたんだけどね。
isvalid 演算子については §2.2 参照。
う、そーなんだ…
isvalid 演算子はオブジェクトが無効化されてないかどうかをチェックする演算子だよ。
つまり layer が無効化されてなければ true になって、 無効化されてれば false になるの。
ってことは、layer が無効化されてなかったら if ブロックの中身が実行されるってコト?
ん、そういうこと。
でもなんでそんなチェックしてるの?
コンティニュアスハンドラ実行中にレイヤが無効化されても大丈夫なようにだよ。
え、そんなことあるの?
レイヤが動いてる時にウィンドウの×ボタンを押してウィンドウを閉じたりすると、 SimpleMover クラスのオブジェクトが無効化される前に前景レイヤが無効化されることがあるんだ。
で、そうなると layer が無効化された状態で handler メソッドが呼び出されるワケ。
あ、じゃあチェックしなかったらその時にエラーになっちゃうってこと?
ん、その時はウィンドウが閉じる時に「オブジェクトはすでに無効化されています」っていう例外が発生するの。
なるほどね…
ただ、SimpleMover クラスのオブジェクトを作る時に…

// SimpleMover クラスのオブジェクトを kag オブジェクトに管理してもらいます
kag.add(global.mover = new SimpleMover(kag.fore.layers[0]));

こんなふうに add メソッドを使って kag オブジェクトが無効化される時(つまりウィンドウが閉じる時だね)に SimpleMover クラスのオブジェクトを先に無効化してもらうようにすれば、 チェックしなくても例外は起きないんだけどね。
※(Window クラスの)add メソッドについては §3.2 参照。
へぇ、そーなんだ。
まぁでもちゃんとチェックしとけば、前景レイヤ以外にも自分で作ったレイヤとかにも使えるからね。
そっか。
じゃ次は if ブロックの中身を見てくね。
最初に dist っていう変数にレイヤが移動した距離を計算して代入してるんだけど、 この計算のやり方はわかる?
tickCount ってどんな値なの?
handler メソッドの引数になってるみたいだけど?
前回 getTickCount メソッドを使って経過時間を計算したよね。
うん、したね。
tickCount には handler メソッドが呼び出されたときの時間がセットされてるんだ。
だから handler メソッドの中で getTickCount メソッドを呼び出さなくても、 tickCount の値からレイヤが動き始めたときの時間を引けば経過時間がわかるの。
えっと、それじゃあ tickCount - startTime が経過時間になるってコト?
ん、そういうこと。
あとはその経過時間にレイヤが動く速さ(speed)をかければレイヤの移動距離がわかるよね。
その後 1000 で割ってあるけど?
経過時間はミリ秒単位だけど、speed は1秒間に何ピクセル移動するかを表してるから、 1000 で割る必要があるの。
あ、そっか。
で、次にレイヤを表示する位置を計算してるんだけど…
これはちょっとわかりにくいかな?
う〜ん…確かに最初の dist %= moveWidth * 2; ってゆーのがまず何やってるのかよくわかんないね…
じゃまず dist %= moveWidth * 2; の部分は置いといて、 その次の if のとこから見てみて。
if(dist <= moveWidth) ってトコ?
そ。この条件はどんな時に真になるかわかる?
移動した距離が moveWidth 以下だったら真になるよね?
ん。つまり、レイヤは最初左から右に向かって moveWidth ピクセル動くから、 移動した距離(dist)が moveWidth 以下ってことは、 左から右に向かって移動してるってことになるの。
えっと、じゃあレイヤが左から右に向かって動いてたら layer.left = lLimit + dist; が実行されて、 右から左に向かって動いてたら layer.left = rLimit - (dist - moveWidth); が実行されるってこと?
そういうこと。
式だけだとわかりにくいと思うから図にしてみるね。
…って言っても今回は図にしてもわかりにくいかもしれないけどね。

<レイヤの位置設定>

まず、レイヤの左端の位置(left のことね)は最初 lLimit になってるから、左から右に向かって dist ピクセル移動すると、 leftlLimit + dist になるよね。
※上図のオレンジ色の矢印参照。
確かにそーなるね。
で、右方向に moveWidth ピクセル移動すると、 レイヤの左端の位置(left)が rLimit になるから、 ここで移動の向きが変わるわけね。
今度は右から左に移動するんだよね。
そうそう。
で、レイヤが右から左に移動してる時は、dist は上の図の緑色の矢印@の長さとAの長さを足した値になってるの。
え〜っと…そーなるのかな…?
上の図だと、一旦右端まで移動して、それから折り返して真ん中あたりまで移動してるから、 右端まで移動した分の@の長さと、折り返して真ん中あたりまで移動した分のAの長さを足した長さが移動距離になるよね。
あ、そっか。
@の長さが moveWidth になるのはわかるよね?
うん、@の長さは moveWidth って書いてある矢印の長さとおんなじ長さになってるもんね。
ってことは、Aの長さは移動距離から@の長さを引けばわかるから、 dist - moveWidth になるよね。
そだね。
で、レイヤの左端の位置は rLimit からAの長さ(dist - moveWidth)だけ左側にあるから、 右から左へ移動してる時の leftrLimit - (dist - moveWidth) になるの。
なんかややこしーよね…
まーね。
あと何で dist %= moveWidth * 2; が必要かって言うと…
うん?
レイヤは画面を左右に行ったり来たりしてたよね。
うん、そーだね。
だから、2往復目からは1往復目と同じように位置の計算ができるの。
? どーゆーコト?
例えば、こんなふうに画面の幅が 640 ピクセルで、レイヤ(画像)の幅が 64 ピクセルの場合を考えてみるね。

<レイヤの位置計算>

この場合だと、moveWidth640 + 64704 ピクセルになるから、 400 ピクセル移動した時の位置と 1808 ピクセル移動した時の位置は同じになるんだ。
そーなの?
moveWidth704 ピクセルだから、 レイヤが左から右に移動して、その後折り返して元の位置に戻ってくるまでに移動する距離は 704 + 7041408 ピクセルになるよね。
ん〜…そーだね。
1808 ピクセル移動したってことは、 元の位置に戻ってきてからさらに 400 ピクセル移動したってことだから、 元々 400 ピクセルしか移動してない時と同じ位置になるでしょ。
あー、なるほど。
で、この時 dist %= moveWidth * 2; つまり dist = dist % (moveWidth * 2);dist = 1808 % (704 * 2); ってことになって、1808 % 1408400 だから、 dist400 になるの。
…結局、元々 dist400 の時とおんなじ位置になるってこと?
そういうこと。
これもややこしー計算だよねぇ…
まぁ今回はスクリプトをできるだけ短く書くようにしたから、その分わかりにくくなっちゃってるかもね。
さて、じゃこれで try ブロックの中身は一通りチェックしたから、 次は例外が起きた時に実行される catch ブロックの中身を見てくね。
stopMove メソッドを呼び出してレイヤの移動を止めてるのはわかるんだけど、 その次の throw e; って何やってるの?
throw については §3.13 参照。
普通例外が起きたらスクリプトの実行がそこで止まっちゃうでしょ。
うん。
でもここで throw e; を実行しなかったら、 レイヤの移動が止まるだけで、スクリプトの実行は止まらないんだ。
あ、そーなんだ。
ここで throw e; を実行すると、 その後の例外処理は吉里吉里のシステムに任されるから、 結局 stopMove メソッドが呼び出された後にスクリプトの実行が止まるってワケ。
※但し handler メソッドを呼び出しているメソッド(呼び出し元のメソッド)の中に catch ブロックがある場合は、呼び出し元メソッドの catch ブロックが実行されます。
へぇ…
あ、でも catch ブロックでちゃんと例外処理してるんだったら、 わざわざ吉里吉里のシステムに例外処理を任せなくてもいいんじゃないの?
んー、まぁそーなんだけど、 今回の場合は例外が起きるとそれ以上スクリプトがちゃんと実行できなくなるからね。
handler メソッドの中で例外が起きるってことは、 レイヤを動かせないってことになるでしょ?
そーだね。
レイヤが動かせないんじゃそれ以上スクリプトの実行を続ける意味がないから、 今回は例外が起きたら吉里吉里のシステムに例外処理を投げて、スクリプトの実行を止めるようにしたんだ。
ふぅん…そっか。
じゃこれで SimpleMover クラスの中身は一通り見たから、 あとレイヤを移動したり止めたりするための KAG スクリプトの方もチェックしとくね。
はーい。

<レイヤの移動・停止を繰り返す KAG スクリプト>

; 前処理
[position layer=message0 page=fore visible=false]
[image layer=0 page=fore storage="krkr" visible=true]

*loop
; レイヤの移動を開始します
[eval exp="mover.beginMove()"]
; クリックするとレイヤの移動を停止します
[waitclick]
[eval exp="mover.stopMove()"]
; もう一度クリックすると再びレイヤの移動を開始します
[waitclick]
; 後はこれを繰り返します
[jump target=*loop]

最初にメッセージレイヤを非表示にして、画像を読み込んでるとこは大丈夫だよね。
うん。
loop ラベルから先は、まず1つ目の eval タグで beginMove メソッドを呼び出してレイヤの移動を始めて、waitclick タグでクリック待ちしてるの。
クリックされたら2つ目の eval タグで stopMove メソッドが呼び出されて、 レイヤの移動が止まるんだね。
そ。んでまた waitclick タグでクリック待ちして、 クリックされたら今度は jump タグで loop ラベルにジャンプするから、また1つ目の eval タグが実行されて、レイヤが動き始めるわけね。
後はそれがずっと繰り返されるから、クリックするたびにレイヤが動いたり止まったりするってことだよね。
そういうこと。
さて、これでスクリプトは一通りチェックできたね。
TJS スクリプトでレイヤを動かすのって結構タイヘンだよねぇ…
move タグを使うとカンタンなのに。
面倒なことは move タグが内部で処理してくれてるからね。
まぁこれからその move タグを拡張してくワケだけど。
そーだった…
そうそう、ちょっと余談なんだけど、handler メソッドってどれくらい呼び出されてると思う?
え? どれくらいって?
前にタイマーを作った時は interval プロパティを 1000 に設定して、1秒(1000ミリ秒)に1回メソッドが呼び出されるようにしたよね。
§3.15 参照。
あー、1秒に何回くらいメソッドが呼び出されるかってコト?
そう。タイマーは決まった時間ごとにメソッドを呼び出すわけだけど、 コンティニュアスハンドラは出来るだけ頻繁に呼び出されるから、 大抵の場合はタイマーのメソッドより呼び出される回数が多くなるんだ。
う〜ん…じゃあ1秒に10回とか20回くらい呼び出されるのかなぁ…?
じゃ実際にどれくらい呼び出されるか数えてみよっか。
え、それって数えられるの?
スクリプトを作ってみたから、 これを実行すれば数えられるよ。
※このスクリプトの説明は割愛させて頂きます。
えっと、この first.ks を実行すればいーの?
ん、ちょっと実行してみて。

<実行結果>

あ、画像が回ってる!
このスクリプトは画像を回転するようにしてみたんだ。
へぇ…
move タグを拡張するとこーゆーのもできるようになるんだよね?
まぁね。
じゃ次はちょっとタイトルバーを見てみて。
タイトルバー…ってウィンドウの上の方にある部分のことだよね?
そ。そこに1秒間に何回コンティニュアスハンドラが呼び出されてるか表示するようにしてるから。

<コンティニュアスハンドラの呼び出し回数表示>

※呼び出し回数は実行環境によって異なります。

えっ、に…2000回も呼び出されてるの!?
みたいだねー。
ちなみに最近の高性能なパソコンとか使うともっとたくさん呼び出されるんじゃないかな。
そ、そーなんだ…
色んな環境でこのスクリプトを実行してみるとわかると思うけど、 コンティニュアスハンドラの呼び出し回数はパソコンの処理能力によって変わってくるし、 あと吉里吉里以外に別のアプリケーションを同時に実行してたりすると、 その分だけ呼び出し回数が減っちゃうから、 例えば確実に1秒に10回呼び出されてくれないと困るっていう場合とかはタイマーを使った方がいいと思うよ。
レイヤを動かす時とかはタイマーじゃなくていーの?
handler メソッドtickCount の値と speed の値を使ってレイヤの位置を計算したよね。
うん、したね。
つまり、tickCount の値と speed の値さえわかればレイヤの位置は計算できるから、 別に handler メソッドが一定の時間ごとに呼び出されなくても、 レイヤの位置はちゃんと計算できるの。
あ、だからタイマーじゃなくてもいいんだ?
それに、出来るだけ頻繁にメソッドを呼び出して欲しい場合には、 メソッドの呼び出し間隔を確実に指定しなくちゃいけないタイマーはあんまり向いてないと思うしね。
なるほどねぇ。
それじゃ次は first.ks の中身をちょっと書き換えて実行してみて。
え、書き換えるってドコを?
8行目の rotate マクロのとこなんだけど、 今はこうなってるよね。

<first.ks の7〜8行目(書き換え前)>

; 回転する画像と画像の個数を指定します
[rotate storage="krkr"]

この rotate マクロに、 こんなふうに「num=2」っていうのを書き足してくれる?

<first.ks の7〜8行目(書き換え後)>

; 回転する画像と画像の個数を指定します
[rotate storage="krkr" num=2]

この num 属性ってどーゆー意味なの?
さっきは 吉里吉里のアイコン画像 が1つだけ表示されてたでしょ。
うん…あ、ってコトは、num=2 って書くと 吉里吉里のアイコン画像 が2つ表示されるようになるの?
ん、そのとーり。ちなみに num 属性は省略すると 1 とみなすってことにしてるんだ。
じゃちょっと実行してみて。
は〜い。

<実行結果>

確かに今度は画像が2個になったね。
んじゃ今回もタイトルバーを見てみて。
りょーかい。

<コンティニュアスハンドラの呼び出し回数表示>

※呼び出し回数は実行環境によって異なります。

あ、今度は 1000 回ぐらいになってるね。
こんなふうに、画像の数が増えるとコンティニュアスハンドラの呼び出し回数が減るんだ。
それって処理しなくちゃいけない画像が増えたから?
そうそう。
今回の場合は処理しなくちゃいけない画像が2倍に増えたから、 コンティニュアスハンドラでの処理に2倍時間がかかるようになったの。
えっと、それってつまり2倍時間がかかるから呼び出せる回数が半分に減っちゃったってこと?
そういうこと。ま、ちょうど半分になるわけじゃないんだけどね。
画像の数が2個、3個…って増えていくと、 コンティニュアスハンドラの呼び出し回数は大体2分の1、3分の1…って感じで減っていくね。
ちなみに、実際に測ってみたらこんな感じになったよ。

<画像の数とコンティニュアスハンドラ呼び出し回数の関係>

※呼び出し回数は環境によって異なりますが、比例関係はどの環境でも同程度になると思われます。

ホントだ。画像の数が増えると呼び出し回数がだんだん減っていってるね。
でしょ。
あと、画像の数だけじゃなくって、 画像の大きさが変わってもコンティニュアスハンドラの呼び出し回数が変わるんだ。
画像が大きくなればなるほど呼び出し回数が減るってコト?
そ。画像が大きくなるとその分処理しなくちゃいけないことが多くなるからね。
さっきまでで使ってた画像の大きさは64×64ピクセルだったんだけど、 例えば128×128ピクセルの画像を使うと、呼び出し回数は半分以下に減っちゃうんだ。
それって画像の縦と横の長さが2倍になったから呼び出し回数が半分になったってコト?
ううん、画像のサイズの場合は縦横の長さが2倍、3倍になったら呼び出し回数が2分の1、 3分の1になるってわけじゃないみたいだね。あと面積が4倍、9倍になるから呼び出し回数が4分の1、 9分の1になるってわけでもないみたい。
ただ、画像のサイズが大きくなると、こんなふうにだんだん呼び出し回数が減ってくのは確かだよ。

<画像の一辺の長さとコンティニュアスハンドラ呼び出し回数の関係>

※こちらも呼び出し回数は環境によって異なりますが、比例関係はどの環境でも同程度になると思われます。

実際測ってみたら、画像サイズが320×320ピクセルの時の呼び出し回数は、 64×64ピクセルの時の14分の1くらいになったよ。
へぇ、そんなに減っちゃうんだ…
ってワケだから、コンティニュアスハンドラの呼び出し回数は、 実行環境とか、処理する画像の数や大きさによってかなり変わっちゃうってことは覚えといてね。
はーい。
じゃあ move タグを拡張する時もこの辺に気をつけなくちゃいけないってことだよね。
あー、別にこの話は move タグの拡張とは関係ないから。
えっ?
move タグを拡張してもしなくても、 実行環境や動かす画像によってコンティニュアスハンドラの呼び出し回数が変わるってのは同じなんだ。
ただコンティニュアスハンドラにはこういう性質があって、 大きい画像を動かしたり一度にたくさんの画像を動かしたりするとコンティニュアスハンドラの呼び出し回数が減って、 あんまりキレイに動いてるように見えなくなっちゃうから、使うときには注意してねって言いたかっただけ。
そ、そーなんだ…
それじゃ次回から本格的に move タグの拡張をやっていくね。
じゃ、また次回!


前へ | TOP | 次へ