2008/3/6

編成スクリプト中のthisの話  VRMスクリプト禅問答
結論から言うと、厳密には編成スクリプト内でcall/SetEvent〜命令のパラメータにthisは使わない方がいい、という話。まぁ、言っているボクもそこまで徹底してませんが、来るべき次期自動運転スクロール/フレームワークでは、以下に紹介する方法論を採用したいと思ってます。

*     *     *

まず、基本的なところをおさらいしておきましょう。たとえば“TRAIN1”と命名された編成があるとします。ここに以下の編成スクリプトを書いたとします。
<編成スクリプト>

BeginFunc MtdA
  call this MtdB //thisのMtdBをcallする
EndFunc

BeginFunc MtdB
  SetTimerVoltage 0.0 10000 //10秒かけて停車する
EndFunc
まぁ、普通は書かないスクリプトですが、説明の便宜上こうしておきます。要するに、メソッドMtdAを何らかの方法で実行すると、メソッドMtdBがcallされ、編成が停車する、という流れです。

さて、メソッドMtdAを実行すると常に編成は停車する、と言えるでしょうか。答えはNoです。これは特別な識別子であるthisの挙動によるものです。thisは、ビュワー起動時に「thisが書かれた部品の名前に読み替えてコンパイルされる」という性質を持っています。従って、編成以外の部品であれば、上に示したサンプルスクリプトは常に意図通り動作します(SetTimerVoltageは編成部品以外に書いても意味がないですけども)。

しかし、編成部品についてはこれが成り立ちません。問題になるのは、編成の分割と併合に際してです。


Uncouple命令を使って編成を分割すると、その時点での進行方向側(機関車側)が元の編成名を引き継ぎ、逆側(客車側)は、ビュワー起動時には存在しなかった「新しい編成」として生成されます。元の編成に含まれていたスクリプト(コンパイル済みのプログラム)は、新しい編成にもコピーされ、その大半はこれまで通りに動作します。

一方、一旦分割した編成を併合した場合・・・たとえば機関車を切り離し、いわゆる機回しをおこなって逆側につけかえるとします。編成の併合にスクリプト命令は不要で、ビュワー上で編成を一定距離に接近させる(加えて、その他のいくつかの細々した条件を満たす)と2つの編成が1つになります。このとき、ぶつけた側(機関車側)がぶつけられた側(客車側)に吸収されます。

今、仮に編成TRAIN1があったとして、ここから機関車を切り離し、機回しして連結をおこなうと、ビュワー上からは起動時に存在した編成TRAIN1はなくなり、機関車が逆側に付け替えられた編成TRAIN1_1が残ることになります。

ところが、スクリプトに書かれたthisは、この時点でも元の“TRAIN1”(厳密にはそこから導かれる内部部品/オブジェクト番号)を指し示しています。従って、編成TRAIN1に冒頭に示したスクリプトが書かれていたとして、分割・機回し・再併合後に生まれる編成TRAIN1_1のメソッドMtdAをセンサー等から実行しても、メソッドMtdBは実行されません。既に消滅したオブジェクトTRAIN1のメソッドMtdBの実行が試みられ、その処理は空振り(内容によってはビュワーが異常終了)します。

これを避けるには、以下のような書き方をすることになります。
<編成スクリプト>

VarTrain ObjMe //自分自身を指す編成オブジェクト変数
get ObjMe this //ビュワー起動時は自分自身を指す

BeginFunc MtdA
  call ObjMe MtdB //変数ObjMeの示す編成のMtdBをcallする
EndFunc

BeginFunc MtdB
  SetTimerVoltage 0.0 10000 //10秒かけて停車する
EndFunc
そして、編成の分割・併合が生じた際に、適当な方法でObjMeの内容を書き換えてやります。一番簡単で確実なのは、分割・併合した編成を、以下のスクリプトを含むセンサー上を通過させる方法です。
<センサースクリプト>

Var SensorID
SetEventSensor MtdUpdateObjMe SensorID

BeginFunc MtdUpdateObjMe
  VarTrain TmpTrain
  GetSenseTrain TmpTrain //検知した編成を取得
  get TmpTrain.ObjMe TmpTrain //ObjMeを更新
EndFunc
GetSenseTrain命令は、検知した編成がビュワー起動時に存在したか否かに関わらず、常にその時点でセンサーが検知した編成(のオブジェクト番号)を取得しますから、これを編成のグローバル変数ObjMeに書き戻すことで、それ以降の編成スクリプト内のObjMeを使ったcall/SetEvent〜命令で、確実に編成自身のメソッドを実行できるようになります。

*     *     *

現在流通しているほとんどのVRM編成スクリプトのサンプルコード、そしてスクロールは、拙作も含めてこの配慮がおこなわれていないので、編成の分割・併合に際してそこまで動作していた一部のギミックが無効になります。ポータブル編成が、その典型です。

逆に言うと、分割・併合さえしなければ、ここで書いたことは問題になりませんし、併合後は一部のギミックが無効になるものだ、と割り切って使えば実害はありません。ただ、文中で例に挙げた「機回し」などは、それを割り切っていては実現が難しい課題なので、ここで述べたような知識を持っておかないと、実現が難しいか、あるいは、非効率的なスクリプトを書かねばならなくなります。

後日、汎用的に使える「機回しスクロール」を書こうか、と思っています。が、如何せん、考慮せねばならんことが多過ぎるというか、どこまでの範囲をスクロールに含むか(片上げパンタグラフの向きを入れ替えるか、とか、連結前に一旦停車するか、とか、入換信号機をアドオンサポートするか、とか、そういうの)でボクの中で揺れがあって、やや優先度が低いです。もし「それ、すぐに欲しい!」とお考えの方がおられたら、適当な手段でその旨を訴えてください。適当に対応します。
1

コメントを書く

この記事にはコメントを投稿できません




teacup.ブログ “AutoPage”
AutoPage最新お知らせ