目次へ戻るにはこちら

ポータブル編成テクニカルガイド

2007/04/24

β3Lite、m4g/1.0を受けて一部修正

2006/08/22

β3版公開に伴う改定

本稿は「ポータブル編成ギミック一発設定」スクロールに関するテクニカルガイドです。一般的なユーザーは、このページに書かれた内容を理解しなくても問題ありません。

 

ghostは「ポータブル編成」をVRM4ユーザー界隈での事実上の「標準規格化」することを目論んでいます。この規格化プロジェクトに参加したい方、独自機能の追加に挑戦したい方には、この資料が役に立つでしょう。

本稿は、読者にVRM4スクリプトの知識があることを前提に書かれています。本稿内容に関する質問は歓迎しますが、スクリプトの基本に関する質問を本稿に事寄せて私に投げないでください。そういう方は、まずは「鉄道模型シミュレーター4エキスパートガイド」付録CD-ROM所収の「スクリプトの教科書」をお読みになることをお奨めします。

以下、字数を減らし文章を簡便にするため、敢えて敬語体を使いません。

 

基本戦略

多くのユーザーに利用されることを期待するスクロールを設計するに当たり、最も重要なことは、可能な限りユーザーが指定しなければならない項目を減らすことである。こうすることで、ユーザー自身にその仕組みに関する深い理解がなくとも、容易にスクロールを活用することが可能になる。逆に、設定すべき項目が多く、その意味が複雑なスクロールは、大半のユーザーから敬遠されるだろう。

つまり、手間を利用者/スクロール作者のどちらが負うか、という問題。個人的な主義としては、自ら手間を背負い込むことが出来ないスキルレベルの人間が、見栄だけでスクロールを配布することはお奨めしない。それはビギナーユーザーを混乱させ、VRM4の評判を貶めるだろう。

「ポータブル編成」に限って言えば、車両スクリプトの扱いがこれに当たる。編成の組み方には事実上無限の可能性があるが、このそれぞれについてパラメータの指定をおこなわなければならないとすれば、スクロール化する意味のほとんどは失われる。理想を言えば、すべての車両オブジェクトに同一のスクリプトを記述し、そのスクリプトが自身の編成内での位置などを元に、自律的に自らがどうあるべきかを判断して然るべく動作することが望ましい。こうすることで、ユーザーは冗長なパラメータ指定の作業から開放される。

「自律的に自らがどうあるべきかを判断して然るべく動作する」の好例はヘッド/テールライト灯火。このスクロールでは、先頭/終端車両にヘッド/テールライトを点灯させる命令を書くのではなく、すべての車両に自分自身が先頭/中間/終端のいずれであるかを調べさせ、その結果に応じて灯火制御をおこなっている。スクリプトは全車両で共通だが、動作はそれぞれに異なる。

一方で、現行VRM4スクリプトでは、編成オブジェクトと車両オブジェクトの間で数値を受け渡しする方法がない(あるいはghostが知らない)ため、スクリプトの書き方はやや冗長気味になっている。具体例を挙げると、編成の現時点での進行方向を記録するグローバル変数が、編成/車両の双方に存在し、個別に制御されているのがこれに当たる。

 

β版はアップデータ4.0.3.2ベースで開発された。

今回、編成ギミック制御スクリプトの標準化を目論むに当たり、敢えてgws/1.0規格への編入を見送った(つまり、変数/メソッド名頭にgws10の接頭辞は付与されていない)。これは、gws/1.0規格が複数オブジェクトの相互連携を前提に構築されているのに対し、ポータブル編成については必要とされないからである。

これは、個々のユーザーが独自に機能拡張をおこなっても、互換性に問題が発生しにくいことを意味している。基本的な制御構造は、今回示したものが(VRMスクリプトに劇的な仕様変更がおこらない限りは)事実上の決定版であろうと考えているが、個々の機能については検討の余地を残している。願わくは、VRM4スクリプト職人諸兄の参戦を受けて、より洗練されたスクロールへ進化させていきたいと考えているので、積極的なコミットをよろしく。

当面は一般ユーザーの混乱を防ぐため、特に改変されたスクロールについては公開前に個別にご相談いただけるとありがたいです。

予約語一覧

以下に「ポータブル編成ギミック一発設定」によって組み込まれる編成/車両スクリプトに含まれる予約語(グローバル変数、メソッドの名前として使用されており、衝突を回避すべきもの)を、それぞれの意味・用途を添えて一覧する。独自機能の追加やスクロールの拡張を目論む場合の参考にされたし。

なお、β3Liteではグレーで着色した部分が総行数削減のために削除されている。

<接頭辞凡例>

 Event...イベントハンドラ変数

 Var...グローバル変数(公開プロパティ)

 Mtd...メソッド

オブジェクト 名称 用途 備考
編成 EventIDFocusIn 編成アクティブ化イベントハンドラ  
EventIDFocusOut 編成非アクティブ化イベントハンドラ  
EventIDHome キー[Home]イベントハンドラ  
EventIDKeyZ キー[Shift]+[z]イベントハンドラ  
EventIDKeyX キー[Shift]+[x]イベントハンドラ  
EventIDKeyC キー[Shift]+[c]イベントハンドラ  
EventIDKeyV キー[Shift]+[v]イベントハンドラ  
EventIDKeyB キー[Shift]+[b]イベントハンドラ 拡張用予約
EventIDKeyS キー[Shift]+[s]イベントハンドラ 拡張用予約
EventIDKeyD キー[Shift]+[d]イベントハンドラ 拡張用予約
EventIDBlink LED交互表示用イベントハンドラ  
EventIDPower パンタグラフ・照明消点灯遅延
イベントハンドラ
 
EventIDInitialize  車両初期化メソッド遅延実行用
イベントハンドラ
 
VarDirection 編成向き管理変数 0=起動時向き
1=逆進
VarOnOff 編成電源状態管理変数 0=電源オフ
1=電源オン
VarBlinkMode 画像リソース交互切替管理変数 0=交互表示しない
1以上=する
MtdCallInitialize 車両初期化メソッド駆動メソッド  
MtdFocusIn 編成アクティブ化イベントメソッド
 キーイベントを設定する
 
MtdFocusOut 編成非アクティブ化イベントメソッド
 キーイベントを解除する
 
MtdHome 進行方向反転メソッド  
MtdPowerOn パンタグラフ上げメソッド  
MtdPowerOff ライト消灯メソッド  
MtdPowerOnAfter ライト点灯メソッド  
MtdPowerOffAfter パンタグラフ下げメソッド  
MtdChangeResource 画像リソース更新メソッド 車両側メソッドを
CallCarするのみ
MtdSignBlink 画像リソース交互切替メソッド  
MtdUncouple 編成分割メソッド  
MtdAddB 機能拡張用メソッド  
MtdAddS 機能拡張用メソッド  
MtdAddD 機能拡張用メソッド  
車両 VarResourceNo 選択中画像リソース管理変数 パターン番号に相当
VarDirection 編成向き管理変数 0=起動時向き
1=逆進
VarOffResource 電源オフ時画像リソース番号 0の場合、
交互表示機能無効
VarBlink  交互表示画像リソース番号管理  
MtdInitialize 車両初期化メソッド  
MtdTurn 進行方向反転メソッド  
MtdLightOn ライト点灯メソッド  
MtdLightOff ライト消灯メソッド  
MtdPantoUp パンタグラフ上げメソッド  
MtdPantoDown パンタグラフ下げメソッド  
MtdSetResourceNo 方向幕パターン番号更新メソッド  
MtdSetResource 画像リソース割り当てメソッド  
MtdBlink 画像リソース交互切替メソッド  

メソッド連携図

以下に「ポータブル編成ギミック一発設定」によって組み込まれる編成/車両スクリプトのメソッドの連携を図示する。独自機能の追加やスクロールの拡張を目論む場合の参考にされたし。

クリックで拡大表示

<メソッド連携図/クリックで拡大表示>

車両初期化メソッド

β版では、ビュワー起動時に編成オブジェクトの宣言部(メソッドの外側)から、CallCar命令によって車両オブジェクトののメソッドMtdInitializeが実行していた。

β2では、さらにもう1つ中継メソッド(MtdCallInitialize)を設け、これをビュワー起動時にSetEventAfterで100ミリ秒後に実行されるように仕掛けた上で、これを介して車両オブジェクトのMtdInitializeをCallCarするように改めている。上掲のメソッド連携図を参照されたし。

これは、β版時に欄外注に記していた「MtdInitilizeで車両のグローバル変数に初期値をsetしても正しく処理されないことがあるように見える」問題に対する対処としておこなったものである。なぜこうしなければならないのか(非常に面倒だ)定かではないのだが、VRM4ビュワー起動時の処理を以下のように仮定するとなんとなく説明はつく。

 

(1) ビュワーを起動すると、まず各オブジェクトの宣言部(BeginFunc〜EndFuncに囲まれていない部分)が解釈される。これは既知。

(2) この処理が、どのオブジェクトから順におこなわれるかは不確定である。つまり、編成オブジェクトの宣言部が処理される時点で、車両オブジェクトの宣言部処理が終わっているとは限らない。

(3) 編成オブジェクトの宣言部が先に処理された場合、CallCar命令で実行すべきMtdInitializeが未コンパイルであるか、あるいは、グローバル変数の宣言がなされておらず、その効果が無効化される。

(4) そこで、SetEventAfetrを使って、編成オブジェクトの宣言部の処理開始からいくらか時間が経ってからMtdInitializeがCallCarされるようにしてやると、この待ち時間の間に車両スクリプトの宣言部処理が終わる。

 

この仮説が正しいとすると、編成の宣言部でSetEventAfterに与える値は、この仕組みが実行されるPCの性能によってはかなり長く取らなければならないケースがあることも考えられる。とりあえず、今回公開するβ2では恣意的に100ミリ秒を設定しているが、これで十分かどうかについては正直言って自信が無い。万が一、うまく動作しない場合は、その報告に利用者各位の動作レポートに各位のPCのCPUスペックと実装メモリを添えてもらえるとありがたい。誰も読んでないとは思うけど、この文章を。

 

BeginFunc MtdInitialize
//
//ビュワー起動時の初期化
//
Var Tmp
GetCarPos Tmp
//ヘッドマーク設定
ifeq Tmp 1
  SetHeadmark WizHeadmark
endif
//パンタグラフ初期化
SetPantograph 0 0
SetPantograph 1 0
//テクスチャ初期化
set VarResourceNo 1
GetCarNumber Tmp
SetSignTexture Tmp
//電源オフ時のリソース番号取得
set VarOffResource WizBlinkInterval
if VarOffResource
  set VarOffResource WizResources
  mul VarOffResource 2
  mul VarOffResource WizNumberOfCar
  set VarBlink 0
endif

EndFuncBeginFunc

 

気分を変えてβ2版以降の初期化処理を概説しておく。

まず、GetCarPos命令で車両位置(先頭車/中間車/終端車)を取得し、先頭車の場合(値が1)、SCRIPTウィザードのヘッドマークの項目で入力された定数WizHeadmarkをSetHeadmark命令に与えている。つまり、ヘッドマークは編成先頭車のみに有効であり、2両目以降の機関車に対しては無効となる。

 

極めて恣意的な仕様だが、これについては実害はないと思う。第2号所収のコンテナ車の末尾反射板については現在サポート外。

第2号用練習素材では、実はサンプルの編成ファイルで末尾のコンテナ車に明示的に SetHeadmark命令がハナから仕込んである。これはスクロールの機能ではない。

次に、パンタグラフを初期化している。β版では[Shift]+[z]キーを繰り返して押すことで上がったり下がったりするパンタグラフが選べるようになったが、パンタグラフの総数を2と仮定する仕様については改めていない。決め打ちで0番と1番のパンタグラフに一旦 0 を設定している。

ビュワー起動時、何もしなければパンタグラフは上がっているが、このときGetPantographしても、その値が1であるとは限らない。一般的にこのような状態を「起動時の値は不定である」と呼ぶ。敢えて0をセットしているのは、片上げ下げが、GetPantographで得られた値に依存する(これをxor 1 して上げ/下げをトグルしている)ため、少なくとも一度は値を確定させる必要があるためだ。

 

 

この仕様は現時点では問題ないものの、今後のアップデータによって何か問題が生じる可能性はある。

困ったことに、SCRIPTウィザードはパンタグラフ装備車/非装備車を見分ける機能をもっているが、スクリプト命令にこれに相当するものがない。理屈の上では各車両オブジェクトは自身がどんな機能を持っているか知っているはずなので、GetCarType命令(仮称)なんかが増設されるとありがたいのだが。

GetCarType命令の実装がないままに、何らかの副作用が出るようなアップデータがリリースされた日にゃぁ・・・(ありそうだから怖い)。

 

次に、GetCarNumber命令で車両の号車番号を取得し、これをリソースIDとしてSetSignTexture命令に渡している。ポータブル編成において各車両に割り当てられるリソースIDが(パターン番号−1)×編成長+号車番号であることは別項に示した通りだが、ビュワー起動時はパターン番号が1であり、従って割り当てるべきリソースIDは号車番号に等しくなる。

 

続く赤字部分は、β2でのLED行先表示交互切替機能の実装に伴って追加された部分である。ここでは、まず同機能が有効であるかをスクリプトウィザードで指定された切替間隔パラメータWizBlinkIntervalから判断している。

この値が0であれば、同機能は無効化される。この値が1以上である場合は、号車番号とパターン番号からだけでは決定できない「電源オフ時の画像リソースID」を計算によって求め、これを変数VarOffResourceに収めている。以降、この値が0か否かが交互切替をおこなうかいなかの判断材料となる。

 

音声リソースの割振りなど、独自にビュワー起動時の初期設定として追加したい処理がある場合、このメソッドの末尾、SetSignTexture命令以降に追記すると良い。

この部分に、個々人でロジックを追加するのは自由におこなっていただいて良いし、驚くべきことに既に実例があるが、その機能の汎用スクロール化は、私の印象としては無謀だと思われる。

自分で「無謀だ」と書いておきながら、結局作った

直感的には、音声リソースの割り当てを画像リソースのそれと同じポリシーでスクロールに組み込むと、極めて使いにくくなりそうな気がする・・・

編成進行方向の考慮

Uncouple命令、GetCarNumber命令は、車両の位置に関して、編成エディタで組成した際の車両位置ではなく、現在の進行方向先頭車両からの相対位置を扱う。つまり、[Home]キー操作によって反転される進行方向によって、値が変わる。「ポータブル編成ギミック一発設定」では、以下の二点がこの仕様に関連して問題となる。

 

・編成分割位置。たとえば機関車を切り離すつもりで「1両目で切り離し」を指定していても、編成の向きによっては終端車両を切り離すことになってしまう。

・車両へのリソースIDの割り当て。別項で解説しているように、車両に割り当てるIDはすべて計算によって求めているが、編成の向きによってこの結果が異なってしまう。

 

この問題を回避するために、「ポータブル編成ギミック一発設定」では編成/車両オブジェクトそれぞれにグローバル変数VarDirectionを設け、編成の向きを管理している。この値は、[Home]キーイベントからトリガされるメソッドMtdHomeと、そこからCallCarされるメソッドMtdTurnによって更新され、0または1で編成の向きを表す。

これを使って、編成の進行方向に依存しない、絶対的な車両位置を算出している。以下は編成分割メソッドからの当該ロジックの抜粋。

 

//編成向きによって切り離し位置を変更
GetNumberOfCar Tmp
ifzero VarDirection
  Uncouple WizUncouple ObjTrain
else
  sub Tmp WizUncouple
  Uncouple Tmp ObjTrain
endif

 

以下、スクリプトコード中、赤字表記になっている部分は、スクリプトウィザードで設定されるパラメータです。組み込み後のスクリプトでは、具体的な定数に置き換わっています。

左例の場合、切り離し位置が3両目であれば、赤字部分は「WizUncouple」ではなく「3」になっています。

 

 

左スクリプトは変数宣言部を省略して抜粋しています。実際にはこの部分よりも前に、ローカル変数Tmp、ObjTrainを宣言する記述があります。

まず、ローカル変数TmpにGetNumberOfCar命令で編成長を得ている。

次に、変数VarDirectionを評価し、これが0であれば、定数WizUncoupleで示される位置でUncouple命令による編成分割をおこなう。

逆に、VarDirectionが1の場合(else以降)は、WizUncoupleの示す分割位置はユーザーが意図したものとは異なるはずですあり、これを正しい値に変換している。具体的には、編成長=TmpからWizUncoupleを引けば、適切な分割位置になる。これを受けて、Tmpの示す位置で編成分割をおこなっている。

 

よりベターな解決方法があれば、誰か教えてください。

本節の意味するところは、諸兄が編成内での車両の絶対位置に関わる処理の拡張を試みるに際し、同様の配慮が必要であるという点である。車両スクリプトについても同様で、GetCarNumber命令から得られる値を、そのまま絶対位置として扱ってはならない。

なお、現行アップデータ3.0.2.4では、車両オブジェクト内でGetNumberOfCar命令で編成長を得ようとしても、エラーにはならないが正しい値が取得できない(常に0が返るようだ)。これを回避すべく、遺憾ながらSCRIPTウィザードでユーザーに「編成長」の入力を強いている。諸兄がスクロールの拡張をおこなうに際しては、この値が定数WizNumberOfCarとなるので、これを利用されたい。

 

//車両スクリプトの該当部抜粋
GetCarNumber TmpCarPosition
if VarDirection 
  Var Tmp
  mov Tmp TmpCarPosition
  set TmpCarPosition WizNumberOfCar
  add TmpCarPosition 1
  sub TmpCarPosition Tmp
endif

 

ここで言っているのは、スクロールの拡張に関する注意事項です。スクロールを組み込んだ編成に独自のスクリプトを追記して配布する分には、さほど大きな問題にはならないでしょう。知っておくに越したことはないですが。

これに関する既知問題としては、編成の分割/併合に伴うVarDirectionの値の信用性の欠如がある。特に編成併合に際し、編成の向きがどうなるかはユーザーの操作次第であり(それ以前に、この仕組みを組み込んでいない編成と併合するとスクリプトが失われる)分割、併合以降はVarDirectionの値に依存する動作は整合性を維持できない。これをどう解決するかは目下研究中であり、そういう意味でβ版における編成分割機能は、あくまでも「オマケ」であると諒せられよ。

加えて。

この機能はHomeキーのイベントのみに連動しているので、編成スクリプト内でTurn命令を使って方向転換した場合も不整合がおこる。

一般的なユーザーにおいては気にすることもないと思うが、自動運転を組み込んだレイアウトでポータブル編成を使うのであれば、Turn実行と同時にMtdTurnをCallCarしてくれ。

キー操作拡張

予約語一覧、メソッド連携図にも示したが、ユーザーが「ポータブル編成ギミック一発設定」組み込み済みの編成に独自のキー操作を拡張する際の便を図るため、3つのフックが組み込み済みである。

 

※β3Liteではこの機能は省略されている。

 

具体的には、編成のアクティブ化/非アクティブ化に連動して有効/無効となるキー操作に[Shift]+[b]/[s]/[d]が予約されており、それぞれスクロール組み込み時点では中身のないメソッドを実行している。この中身のないメソッドに独自のスクリプトを追記すれば、他の機能同様に編成アクティブ時にのみ有効なギミックを容易に追加することができる。

 

なお、イベントハンドラの無駄使いを避けるため、この部分はすべてコメントアウトされている。独自ロジックを組み込む場合、下例の赤字部の//を削除すること。

 

//イベント管理変数
   ・
   ・
//Var EventIDKeyB
//Var EventIDKeyS
//Var EventIDKeyD
   ・
   ・

BeginFunc MtdFocusOut
   ・
   ・
//独自機能追加用フック解除
// KillEvent EventIDKeyB
// KillEvent EventIDKeyS
// KillEvent EventIDKeyD
EndFunc

//BeginFunc MtdAddB
//
//独自機能追加用メソッド(Shift+B)
//

//ここに独自スクリプトを追記する

//EndFunc

 

ここに、ポータブル編成が標準でサポートしていない独自ギミックを組み込んだ編成ファイルを公開したりすると面白いと思うのだが、やはり誰もこの文章は読んではいないのだろう(苦笑)。

諸兄の作例の中に普遍的に流用可能な何かが登場した場合、それを正式採用し「ポータブル編成ギミック一発設定」の標準機能として取り込む所存。

[Shift]+[a]はgws/1.0規格において自動運転の設定/解除キーとして予約されているため故意に外しています。逆に言うと、ポータブル編成とgws/1.0規格自動運転スクロール群は機能衝突しません。

VRM開発者向け課題リスト

このスクロール開発を通して明らかになった、ユーザー側の工夫ではどうしようもない、VRMシステム側の改善として期待される点を以下に列記する(順不同)。SetEventFocusIn/Outが実装された経緯も踏まえ、I.MAGiCの中の人の善処を期待したい。

 

まぁ、FocusIn/Outは、私が要望しなくても実装する予定だったんだろうと思ってますが。

[1] 編成オブジェクトと車両オブジェクトの間で値をやり取りする方法の確立。

[2] 編成/車両オブジェクトから、現時点の進行方向がビュワー起動時に等しいのか、逆転しているのかを取得する命令の増設。

[3] 編成分割/併合時の処理ポリシーの明確化と、可能であればユーザーによるポリシー制御。

詳細なアイデアはこちらにまとめた。

[4] 編成分割時に、新たな名前を与えられた方の編成に、リソースが引き継がれていないように見える問題への対処。

[5] スクロールobjectセクションでtype CAR指定する際、選択済みの編成オブジェクトを参照する機能の増設。対象編成を選択済みであるのに、車両オブジェクトを含む編成をもう1度選ばなければならないのはナンセンスかつリスキーである。

[6] ビュワー起動時のデフォルトアクティブ編成における、FocusInイベントの発動。

アップデータ4.0.5.0にて解決済み。

[7] 編成スクリプト宣言部から車両スクリプトの初期化用のメソッドをCallCarする場合に結果が不安定になる問題への対処。やはり、SetEventAfterで恣意的な待ち時間を設けるというのは、正解ではありえないだろう。

VRMoviesは、ここに紹介した技術情報について、読者のお手元の環境での再現性をいかなる場合においても保証いたしかねます。ご承知おきください。

 


Webmaster: ghost@nodus.ne.jp