「VRM侍」に連載されていた「スクリプト禅問答」を一から辿っているのだが、これがま〜、見事なまでにはまるはまる。
はまってくださるのは嬉しい限りなのだが、油断している間に「連載されていた」と
過去形で語られてしまったので、慌てて書いてみるテスト(笑)。
【怎麼生】
I.MAGiCスクリプト会議室メッセージNo.220で言及している「メソッドの中でそのメソッド自身をcallする」ことの危うさとは、実のところどういうことですか?
【説破】 4.0.3.2
危ないと言っても、それでPCが壊れたりレイアウトデータが消えてしまったりするような話ではありません。
一方で、この手法は注意して使わないと、心臓に悪いトラブルの元になったり、VRM4の評判を落とすことにもなり兼ねないので、少し説明しておくことにします。
なお、以下の説明は、一般的なVRM4ユーザーはあまり気にする必要はありません。アクロバティックにVRM4スクリプトを使いこなすことに喜びを感じる方のみ、気をつければ済む話です。
まず「メソッド自身をcallする」ことの意味と意図をおさらいしておきましょう。以下のサンプルコードを読んでください。
[編成スクリプト]
Var VarResourceNo
//何かキーを押すと以下のメソッドが実行されると思ってね
BeginFunc MtdChangeTexture
add VarResourceNo 1 //VarReourceNoを1つ増やす
//VarReourceNoが1だったら
ifeq VarResourceNo 1
CallCar MtdResourceNo1
endif
//VarReourceNoが2だったら
ifeq VarResourceNo 2
CallCar MtdResourceNo2
endif
//VarReourceNoが3だったら
ifeq VarResourceNo 3
CallCar MtdResourceNo3
endif
//VarReourceNoが4になったら、0に戻してメソッドを再実行
ifeq VarResourceNo 4
set VarResourceNo 0
call this MtdChangeTexture
endif
EndFunc
上掲のサンプルコードでメソッドMtdChangeTextureがキーイベント等から実行されると、変数VarResourceNoが1ずつ増えます。これをifeq命令でチェックして、変数の値に応じた車両スクリプトのメソッドを実行します。車両スクリプト側にはSetSignTexture命令があると思えば、これが順に方向幕を切り替えるスクリプトの制御部に当たることがわかります。
問題の部分は ifeq VarResourceNo 4、つまり、メソッド冒頭のadd命令で変数の中身が4に至った場合(直前まで3だった場合)の動作です。ここで、変数に0をセットして、自分自身(メソッドMtdChangeTexture)をcallしています。
この結果、再び冒頭のaddによって変数の値は1になります。つまり、このメソッドを実行する都度、VarResourceNoの中身が1,2,3,1,2,3・・・と変化を繰り返し、その値に応じた車両スクリプトのメソッドMtdResourceNo1〜3がCallCarされることになります。
実は、ifeq VarResourceNo 4以降を・・・
//自分自身をcallしない場合の例
ifeq VarResourceNo 4
set VarResourceNo 1
CallCar MtdResourceNo1
endif
と書いても、動作はまったく同じになります。敢えて上の例のような書き方をしているのは、読みやすさを優先してのことです。つまり、ifeq VarResourceNo 1〜3を数字の順番に並べて書いた方が、後で読んだときに意図がわかりやすい、と思ってこういう書き方をしているワケです。下の書き方だと、値が1に戻る場合のみ書き方が異なるので、スクリプトを書いた直後ならばともかく、しばらく経ってから読み直すと、何を意図して書いたコードなのかわかりにくくなりがちです。
ただし、この書き方には特有の怖さがあります。
上の例ではcall this MtdChangeTextureする直前にVarResourceNoの中身を0にしていますから、次にメソッドが実行されたとき、冒頭のaddで値が1になり、繰り返してcall this MtdChangeTextureが実行されないことが保障されます。これなら大丈夫です(厳密には言い切れません、後述)。
が、これが保障されない場合、例えば冒頭のaddが無条件に実行されるのではなく、何らかの条件(if系命令)を伴っていて、その条件が満たされない場合がありえたり、あるいは他のオブジェクトからVarResourceNoの値が変更されることがある場合、call this MtdChangeTextureが何度も繰り返して実行されてしまう「無限ループ」に陥る可能性が生じます。
で、このときのVRM内部での動作が・・・
こうであれば、さして害はなくて(いや、これも害はありますが)なんかビュワーが重いな、で済むはずなのですが、実際には、
メモリ上でメソッドの構造(少なくともローカル変数については間違いなく)が複製されて、そちらに制御が移り、その実行が終わってから元のメソッドに戻ってくるんですな。
したがって、これが無限ループすると・・・
こうなります。マニュアルの記載を信じると「メソッドの呼び出しの深さ(上図における積み重ねの数)」に最大15の制約があるとされていますが、おそらくそれに達するか、VRMビュワーが確保しているスクリプト実行用の領域を食い尽くした時点で、何か嫌なコトが起きます。私自身は敢えて試していません、
この実験がトラウマになっているので(笑)。試してみたい方は自己責任でどうぞ。
で。
何が危ういかと言うと、一見して無限ループするスクリプトならばまだいいんですが、ある稀な条件が成立した場合に限り無限ループするスクリプトを含むレイアウトをネット等を介して他人に提供してしまうと、これを遊ぶ人にとっては「VRM4のバグ」にしか見えないんですね、どう考えても。普通の人は、ユーザーが書いたスクリプトでこういう現象を起こせることを知りませんから。これはPCでは何事にも通じる話ではありますが。
一方で、前述したようにこの書き方特有の「読みやすさ」というメリットもあるので、この書き方は絶対駄目!!というものでもないワケです。そういう意味で気をつけて使わないとね、という、有り体に言えば
どうでもいいネタでした。