2005/07/16上梓

編成スクリプト標準化試案

はじめに。

本稿は、VRM4スクリプト、特に編成スクリプトの書き方について論じるものです。非常に長くなるので読み通すのは大変かと思いますが、「スクリプト、何それ?」な方にもご理解いただけるよう、可能な限り注釈を添えますので、通読する価値はあるような気がします。

なお、以下の点は予めお断り申し上げます。

2006/02/26 追記

本稿で論じたことをベースにした、具体的な標準案と、それを組み込むスクロールの配布を開始しました。詳しくはこちらを参照。

 

- レイアウター/ビュワーのバージョン4.0.1.2をベースに書いています。今後のアップデートによって、ここに書かれた内容はその意味を失う可能性があります。

- サンプルとして収録しているVRM4レイアウトファイルには、ビュワーを異常終了させるコードが故意に含まれています。ghostはこのレイアウトを利用することによって生じる如何なる現象についてもその責を負いかねますので、自己責任で利用してください。

- 本稿は、あくまでも「ひとつの考え方」を示すものであり、「こうでなければならない」という主張ではありません。ご意見・反論はありがたくいただきますが、不毛な議論をふっかけるのはご遠慮ください。

 

2005/08/22 追記

バージョン4.0.2.0からSetEventFocusIn/Out命令が実装されます。これにより本稿の使命は完了しました。

加えて、VRM侍/VRMスクリプト禅問答にて詳述していますが、本稿執筆時のghostの認識不足により、編成スクリプト内でGetDirection命令が不適切な使われ方をしています。

手前味噌ながら、それらを勘案しても本稿はVRMスクリプトのコーディングを考えていく上での示唆を多分に含んでいると思いますので、当面このまま公開しておくことにします。

何を目指すのか

由羽氏の提案は非常に優れた洞察に基づくものです。彼の問題提起がなければ、ghostもこの問題を深く考えようとはしなかったでしょう。この場をお借りして由羽氏の功績をたたえると共に、その功績に報いるべく本稿を書いたのだ、ということを宣言しておきます。

本稿は、I.MAGiC HOBBY WORLDVRMスクリプト会議室に由羽氏によって書き込まれたNo.38の投稿に含まれるアイデアを元に展開しました。要約すると以下のようなものです。

 

(1) レイアウト上に存在する複数の編成に共通する操作キーを割り当てたい。たとえば、どの編成もLキーを押すとライトが点灯する等。

(2) 編成に組み込むギミックは、可能な限り編成側で定義したい。これにより、最小限の手間で編成ファイルを他のレイアウトへ持ち込むことを可能にする。

(3) 編成に組み込むギミックには、可能な限りの自由度を確保したい。すべての編成のギミックを共通化しなければならない、とは考えない。

 

なぜ左記3点が編成ファイルのポーティング性確保に必要なのか、ご理解いただけますね。念のため少し補足しましょう。

(1)は、たとえばある編成が z, x, c の3つのキーをギミックに割り当てていたとします。この編成が含まれるレイアウトに、a, s, d を使う編成を持ってくることは可能ですが、q, a, zを使う編成については、zキーが衝突して問題になります。キーの衝突を回避するには、原理上にはレイアウトに含まれるすべてのスクリプトを調べて適切に修正する必要があり、現実的ではありません。

(2)、(3)については、編成のギミック(ライト消点灯・方向幕変更)などの制御について、その一部でもレイアウト側のスクリプトに記述してしまうと、その編成は他のレイアウトに持ち込んだ際に意図したギミックを発動できないことによります。

この問題を解決するには、編成スクリプトの記述にあたって、ある流儀に従ってコーディングすることが必要となります。今回示す「VRM4編成スクリプト標準化試案」がそれで、このルールに従う限り、編成ファイルを(若干の制約はありますが)自由に他のレイアウトに持ち込むことが可能になります。

 

総じては、VRM3における、編成ファイルのポーティング性(あるレイアウトで走行可能な編成は、パッケージさえ充足していれば他のレイアウトにも持ち込むことができる)をVRM4においても実現しよう、というものです。

由羽氏の投稿は、編成間で同じキーに対するイベントを定義できない点において、上記アイデアの実装が困難であることを指摘して終わっています。今回、これを実現する方法を考えてみました。結論から言うと、条件付で実現は可能です。また、この研究を通じて、VRM4スクリプトに求めるべき改善点がいくつか浮かび上がってきました。

以下、VRMスクリプトの基本的な考え方にも触れながら、上記アイデア実現の方法と、それによって提起される「VRM4編成スクリプト標準化試案」、及び、残課題解決のために必要となるVRM4スクリプト実装への改善提案を述べていきます。

何故、実現が困難だったのか

最初に、由羽氏が上掲アイデアの実現が困難だと考えた理由について追体験してみましょう。それを通して、VRM4スクリプトのキー判定の仕組みを理解します。

VRM4では「あるキーを押したときに何かが起こる」という仕組みを実現するために「キーイベント」という考え方を使います。イベントとは、出来事、の意ですが、キーを押すという出来事と、それによって起こって欲しい何か、の関係を宣言しておくと、後はVRMビュワーが勝手に処理をしてくれる、というものです。具体的には以下のように書きます。

イベントの概念を使わない場合、ユーザーは一定間隔で「キーが押されたか」「押されたのならどのキーか」を確認するプログラムを書かなければなりません。VRMビュワーはその面倒な部分を代行してくれているワケです。

//
//イベント管理用の変数を宣言
//
Var [変数]
//
//キーイベントを設定
//
SetEventKey [オブジェクト] [メソッド] [変数] [キー] 

無理矢理日本語に翻訳して読むと、「キーを押したときにオブジェクトのメソッドが動くことにして、この関係は変数で管理します」という意味になります。たとえば・・・

何故、イベントの設定に変数が必要なのでしょうか。それは、出来事(この場合はキー)と動作(メソッド)それぞれに固有の名前がついているのと同様に、出来事とそれによって起こる動作の「関係」にも名前が必要だからです。

具体的には、この関係を無効化する(キーを押しても動作しなくなる)際に、変数名を使ってその処理をおこなうワケです。

Var Keyz
SetEventKey this LIGHTON Keyz z

は、「zキーを押すと(this = このスクリプトが書かれている編成)のLIGHTONメソッドが動くことにして、この関係は変数Keyzで管理します」という意味です。この命令が実行された以降は、zキーを押すことで、thisに含まれるLIGHTONメソッド(たとえば「この」編成の「ライトが点灯する」んでしょうね)が実行されます。そして、

KillEvent Keyz

を実行することにより、この関係を無効にすることができます。つまり、KillEvent実行後はzキーを押しても何も起こらないし、新たにzキーに他の機能を割り当てることが可能になります。

さて。

ここで問題になるのが、SetEventKeyで使用する変数に課せられている制約です。それは「同じスコープ内で宣言されたグローバル変数でなければならない」というものです。まず「スコープ」というVRM4スクリプトのマニュアルには登場しない言葉を使っているので、その説明からしておきましょう。

VRM4では、まったく同じスクリプトであっても、それを「何処に書くか」によって意味が変わってきます。最もわかりやすいのは先ほども登場したthisです。これは、文字通り、thisが書かれたオブジェクト(レイアウト、編成、ポイントetc.)を意味しますから、「常にthisは〜である」とは言えません。本稿では、この「あるスクリプトが何処に書かれたか」を指す言葉として「スコープ」を使います。

ここで改めて「同じスコープ内で宣言されたグローバル変数でなければならない」の意味を考えると、たとえば編成スクリプトにおいてキーイベントを設定する際は、その編成スクリプトの中で宣言されたグローバル変数(つまりメソッドの外に書かれた変数)でなければならない、という意味になります。そして、この制約が由羽さんの提起された課題の解決を困難にするのです。

スコープは、本来オブジェクト指向系の言語で、グローバル/ローカル変数の有効範囲を示す用語です。一般のコンピュータ言語では、プログラム全体(VRMではレイアウトに相当)に対してグローバル宣言された変数は、プログラム内の何処からでも利用できますが、VRM4では、必ずしもそうではなく、そのスクリプトが「レイアウトスクリプト」に書かれたか、「編成スクリプト」に書かれたか、によって異なります。この仕様は将来的に改善されそうな気もしますが、少なくとも現時点(4.0.1.2)ではそうなっています。

ここであらためて、由羽氏の提案を実現するために、具体的にどんな動きをするスクリプトを書かなければならないか、を考えてみましょう。

まず、レイアウト上のすべての編成のキー操作を揃えるためには、現在アクティブな(選択されていて運転できる)編成に対してのみ有効なキーイベントを設定する必要があります。次に、編成のギミックは可能な限り編成側で設定するというポリシーに従う限り、そのスクリプトは編成スクリプトに書かれなければなりません。

この結果、以下のような問題が発生します。TRAIN1とTRAIN2という編成があり、zキーを選択中に限って共通して使いたい場合を想定し、何が起こるかをまとめてみましょう。

 

[1] TRAIN1のスクリプトでzキーにイベントが設定される。

[2] zキーを押すとTRAIN1で何かが起こる。

[3] TRAIN2をアクティブに切り替える。

[4] TRAIN2のスクリプトでzキーにイベントを設定しようとしても、既に[1]で使われているので設定できない。

 

本当は次節で述べるような「無理矢理」な解決方法は、電脳哲学的には正しくありません。このような処理が可能となる実装をVRM4側でおこなうべきです。その具体的な改善案は最後にまとめます。

ちなみに「なんでそこまでせなアカンねん」とお思いになる読者もおられると思いますが、その理由は簡単です。なぜなら、由羽氏が提案し、本稿で実現を目指しているソレこそが、VRM4が自称する「オブジェクト指向」だからです。

なお、オブジェクト指向という言葉自体、極めて多義的で、必ずしも一意の定義を有する用語ではありませんが、読者に理解されずとも、私と由羽氏、そしてI.MAGiC社の開発者の間に限定すれば、おそらく共通した理念として存在するだろう、と信じて本稿を書きました。

[4]でしようとしていることを達成するには、[1]で設定したキーイベントを無効化(つまりKillEvent)する必要があります。ところが、KillEventにも実は「同じスコープ内で宣言されたイベントに対してのみ有効」という制約があります。従って、[1]で設定したキーイベントを無効化できるのはTRAIN1のみです。

しかし、処理が[3]に至った時点で、既に実行されるスクリプトはTRAIN2に移っており、TRAIN1は制御を失って放置状態になっています。

これが、由羽氏が実装が困難だと指摘した理由の本質です。しかし、氏の提案はVRM4の編成ファイルのポーティング性実現という意味で非常に魅力的なものです。そこで、若干の制約を含みつつも、氏の提案を実現する方法を考えてみました。

どうやって実現するか−レイアウトスクリプト編ー

実際のスクリプトコードをご覧いただく前に、問題解決の戦略について説明しておくことにしましょう。

前段の最後で問題となったのは、突き詰めれば「新たな編成に制御が移った際、直前にアクティブであった編成が設定したキーイベントを無効化する手段がない」でした。そこで、これを実現するために少し妥協することにします。ここで言う「妥協」というのは、処理のすべてを編成側でおこなうことを諦め、少しだけレイアウトスクリプトのお世話になる、という意味です。

// 選択中の編成識別変数
Var VActTrainNo
set VActTrainNo 0

//
// 直前のアクティブ編成の
// 制御イベント削除をコール
//
BeginFunc KILLEVENT
    VarTrain ObjT
    // 定数1
    Var Con1
    set Con1 1
    // 1編成目判定
    sub VActTrainNo Con1
    ifzero VActTrainNo
        get ObjT "VRM-1"
        call ObjT LOST
    endif
    // 2編成目判定
    sub VActTrainNo Con1
    ifzero VActTrainNo
        get ObjT "VRM-2"
        call ObjT LOST
    endif
    // 3編成目移行
    // sub VActTrainNo Con1
    // ifzero VActTrainNo
    //     get ObjT "編成名"
    //     call ObjT LOST
    // endif
EndFunc

ハイ、そこ。パニックにならないで落ち着いて。冷静に読むとそんなに難しいことなんかしてないんだから。

 

なお、本稿ではスクリプトコード中、メソッド内やif系命令が作る節を、一般的なコーディング記法に則って「字下げ(インデント)」していますが、これはVRMへの入力時には必ずしも必要ありません。ただ、入れておいた方が見やすいと思います。

これは今回本稿のために用意したサンプルレイアウトのレイアウトスクリプト全文です。本当は完璧なポーティング性を確保するためにすべての処理を編成スクリプト内で完結したいのですが、それが不可能(に思える)なので、ここだけ止む無くレイアウトスクリプトに書くことにしました。

この結果、どういうことになるかと言うと、今回提案する仕組みを組み込んだ編成を配置するレイアウトにはかならず上掲のスクリプトが必要で、かつ、編成を追加する際は上掲のスクリプトの一部分だけには少しだけ修正を加える必要が生じる、ということです。

編成の追加については、後で演習問題が出てきます。

閑話休題。上掲スクリプトが具体的にどのような役割を果たすのか、見ていくことにしましょう。鍵となるのは冒頭で宣言されているグローバル変数 VActTrainNo です。この変数には、現時点でアクティブな編成を見分けるための便宜上の数字を保存することにします。

この数字を使うのがメソッド KILLEVENT です。このメソッドは長く見えるかも知れませんが、コメントを除いて5ステップ(行)でひとまとまりの同じ処理が続いているだけのものです。つまり・・・

 

[1] sub VActTrainNo Con1 で、VActTrainNoから1引く

[2] ifzero VActTrainNo で0になったか(つまりこの場合は、元の値が1であったことになる)をチェックする

[3] 0になった(つまりVActTrainNoが1の状態でこのメソッドが呼ばれた)のであれば、それは便宜上の1番目の編成が現時点でアクティブであることを意味している。

[4] そこで、get ObjT "VRM-1" で編成を特定する。つまり、このレイアウトでは便宜上の1番目の編成に ”VRM-1" という編成名が設定されている。

[5] call ObjT LOST で、編成名"VRM-1"のメソッドLOSTを実行する。もちろんここには、編成VRM-1がこの時点で占有しているキーイベントを無効化する処理が書かれている。

[6] endif に続く同じ処理で、2番目、3番目と同じことを繰り返す。これにより、VActTrainNoが示す編成を特定し、その編成のメソッドを実行することが可能になる。

 

編成側のスクリプトについては(こっちの方がもっと長いですよー)このあとで。

といった感じです。従ってメソッドKILLEVENTの役割を敢えて日本語で書くとすると「変数VActTrainNoが示す編成に設定されたキーイベントを無効化する」ということになります。

どうやって実現するか−編成スクリプト編−

前段のメソッドKILLEVENTの中で、編成スクリプト側にメソッドLOSTの存在が示されていました。当然これは編成スクリプト側になければなりません。それも含めて、より汎用性のある雛形として書いてみた編成スクリプトを以下に示します。

// 編成切替イベント変数
Var EvtID
// 編成制御キーイベント変数
Var KeyIDZ
Var KeyIDX
Var KeyIDC

//リソース参照モード設定
Var VMode
set VMode 1
SelectTrainResource VMode
CallCar SET
//編成切替イベント設定
SetEventKey this ACTIVE EvtID 1

//
//キーイベント削除メソッド
//

BeginFunc LOST
    KillEvent KeyIDZ
    KillEvent KeyIDX
    KillEvent KeyIDC
EndFunc

//
//編成アクティブ化メソッド
//

BeginFunc ACTIVE
    VarLayout ObjL
    getlayout ObjL

    // 編成制御取得
    VarTrain ObjT
    get ObjT this
    SetActiveTrain ObjT
    //既存イベント削除
    call ObjL KILLEVENT
    set ObjL VActTrainNo 1
    //編成制御イベント設定
    SetEventKey this TON KeyIDZ Z
    SetEventKey this TOFF KeyIDX X
    SetEventKey this TSET KeyIDC C
EndFunc

//
// 起動メソッド
//
BeginFunc TON
    Var Chk1
    set Chk1 1
    //編成方向取得
    Var TmpDir
    GetDirection TmpDir
    //編成向きに応じたメソッドをコール
    ifeq TmpDir Chk1
        CallCar ON
    else
        CallCar ONR
    endif
EndFunc

//
// 停止メソッド
//
BeginFunc TOFF
    CallCar OFF
EndFunc

//
// リソース設定メソッド
//
BeginFunc TSET
    Var Chk1
    set Chk1 1
    //編成方向取得
    Var TmpDir
    GetDirection TmpDir
    //編成向きに応じたメソッドをコール
    ifeq TmpDir Chk1
        CallCar SET
    else
        CallCar SETR
    endif
EndFunc

ハイ、そこ。目を逸らさない、弱音を吐かない。1行、1行、ゆっくりと意味を噛み締めるように読んでいけば、どーってことないんだから。スクリプトは友達、怖くないよ。

 

本筋から逸れる部分については、ここで説明しておきましょう。

今回、サンプルレイアウトに含まれる編成には3つのギミックが組み込まれており、それぞれSHIFT+z, x, c で発動します(SHIFT押しを選んだのは、それ以外はレイアウト側でポイント変更に自由に使うため)。

zが起動=ライト点灯・パンタ上げ、xが停止=ライト消灯・パンタ下げ、cがリソース再設定になっています。それぞれ、後述する車輌スクリプトに標準化して仕込んであるメソッドを CallCar で呼ぶ、というたわいもない仕掛けですが、zとcについては、編成の向きに応じて別のメソッドを呼ぶようにしています。

これは、ライト点灯についてはヘッド・テールライトの相違、リソース再設定については方向幕の切替を目的としています。

なお、これらを方向転換と連動させていないのは、例えば方向幕で言うと、方転は必ずしも行先の変更を意味しない(スイッチバックを想起せよ)からです。

本稿の主題に関わる部分は紫色で示しています。

冒頭の3つのVarは、キーイベント管理用の変数の宣言です。3つある、ということは、3つのキー操作(ギミック)を設定することを意味しています。

順序が前後しますが、先にメソッドLOSTを見ましょう。これも一目瞭然ですが、先のレイアウトスクリプトのメソッドKILLEVENTは「変数VActTrainNoが示す編成に設定されたキーイベントを無効化する」ことを目指していました。これを実現するために、KillEventが3つ並んでいます。

KillEventがあるということは、これが無効化するイベントの設定がどこかにあるはずです。これはメソッドACTIVEの中にあります。メソッドACTIVE自身は「この編成をアクティブにし、然るべきキーイベントを設定する」ことを目的としたメソッドです。このACTIVEを機能させるためのイベントの設定は、SetEventKey this ACTIVE EvtID 1によっておこなわれます。これは「この(this)オブジェクト(編成)のACTIVEというメソッドを、キー[1]が押されたときに実行します、この関係は変数EvtIDで管理します」という意味ですね。

メソッドACTIVEの中も見てみましょう。冒頭の着色部ではレイアウトを指すオブジェクト変数を取得しています。これは、ここからレイアウトスクリプトに含まれるメソッドKILLEVENTを呼び出すために必要だからです。続いてアクティブな編成を切り替え(SetActiveTrain)ます。

本稿では、ギミックの中身(すなわち左で言うところの3つのキーイベントから呼び出されるメソッド)については雑多になるので説明しません。

なお、今回の実装では車輌スクリプトの呼び出しに CallCar を使っています。これは、編成に所属するすべての車輌に、すべき処理があろうがなかろうが同名のメソッドをすべて記述していることを意味します。

これは必ずしも必須ではありません。たとえば、特定の車輌にだけ call でメソッドを呼んでも問題ないです。が、標準化という観点では、CallCarの方が良いように思います。CallCarを使う限りは、もちろん一部例外もありますが、編成の組み換えにも柔軟に対応できるからです。

ここらへんの考え方もリクエストがあれば改めてまとめます。本稿はとりあえず由羽氏の提案に応えることに主軸をおいたため、やや片手落ちになっています、スマソ。

そして今回の核心部ですが、call ObjL KILLEVENT でレイアウトスクリプトのメソッドKILLEVENTを実行します。これにより、直前のSetActiveTrain以前にアクティブであった編成のキーイベントが無効化されます。そして、おもむろに set ObjL VActTrainNo 1 で、次回の同様の処理にそなえて自分がアクティブになったことをグローバル変数VActTrainNo に記録します。

これにより、次に、例えば VActTrainNo = 2 に該当する編成に制御が移っても、set ObjL VActTrainNo 2 が実行されるまでその値は1です。よって、次回はここで設定された VActTrainNo = 1 の編成のキーイベントが無効化されることになります。

こうして、キーの割り当てが解除されました。あとは、続く3つの SetEventKey でそれぞれにギミックを実現するメソッドを設定してやれば、目的が達成されます。

ちなみに、レイアウト起動時はVActTrainNoの値は0です。従って、この時点でいずれかの編成のメソッドACTIVEが実行され、KILLEVENTが呼ばれても、条件に合致するものがない(0にいくら減算しても再び0にはならない=ifzeroに該当しない)ので、キーイベントの無効化もおこなわれません。レイアウト起動時は編成ギミック用のキーイベントは設定されていないので、これでOKなのです。 

 

この方法の利点は?

この方法にはいくつかの利点があります(無論、回避しがたい欠点もありますが、それは後ほど)。

第一に、この編成スクリプトには編成名が一切含まれていません。よって二箇所の例外を除き、このままコピー&ペーストしてどんな編成にも組み込むことが出来ます。すなわち、標準化です。

二箇所の例外、は上掲スクリプトコード中、赤太字で示してあります。1つは、この編成をアクティブにするためのキー(ここでは[1])、もう1つはメソッドKILLEVENTで編成を識別するために、グローバル変数 VActTrainNo に記録される数値(ここでは1)です。どちらも1ですが、意味が異なる点に注意してください。ただ、応用に際しては選択キーと編成番号を揃えておいた方が分かりやすいとは思います。

ただし、編成キーと識別番号を揃えるとすると、最大9編成までになっちゃいますが。

第二に、一部の例外を除き、すべての処理が編成スクリプト内で完結しているため、このスクリプトの根幹の部分以外は好き勝手に改造することが可能です。ここで言う好き勝手とは、たとえば、ある編成では z, x に消点灯を割り当てるのみだが、ある編成ではそれにくわえて v に編成解放を割り当てる、といった自由度がある点です。

一部の例外=レイアウトスクリプトのメソッドKILLEVENTにおいて、編成番号と編成の対応関係を書かなければならないこと。

もちろん、自由ながらも守らなければならない決まりごとがあります。それが紫色で示した部分であり、具体的には・・・

 

- ギミック用のキーイベント設定前に必ずレイアウト側のメソッドKILLEVENTを実行すること。

- KILLEVENT実行後に、グローバル変数VActTrainNoに自分を示す番号を入れること。

- 必ずメソッドLOSTを用意し、その中で自分が設定するキーイベントの無効化をおこなうこと。

 

になります。

これが意味するところは、すなわち、このルールに従う限りにおいて「独自の機能を満載した編成を自由に作ることが可能で、かつ、その編成は他のレイアウトに持っていっても最小限の手直しですぐに走行できる」ということに他なりません。

実際に試していただきましょう

VRM4第1号用のサンプルレイアウト及び編成ファイルをご用意しました。ただし、詳しくは後述しますが、このサンプルレイアウトはあることをすると、確実にVRMビュワーを異常終了させることが出来る危険なものです。ghostはこのレイアウトファイルの利用によって貴方が被るいかなる損害に対してもその責を負いかねます。ダウンロードされた方は、私の免責を承諾したものとみなします。本稿でここまでに論じた内容が理解できなかった方には、以降に書かれている内容を試すことをお奨めしません

0号しか持ってない人は・・・まぁ、本稿を読めば似たようなモノが作れるはずなので頑張ってください。ボクは作る気ないです、面倒臭いし不毛なので。

 

[Download] 編成スクリプト標準化試案サンプル(640KB)

<アーカイブ内容>

レイアウト本体     ghost-sample00.vrx

追加編成サンプル(1) ghost-sample-485.trn

追加編成サンプル(2) ghost-sample-ef81.trn

 

LZH圧縮しているので適当に解凍してください。

レイアウトには以下の操作キーが設定されています。

 

a  内回り線入側ポイント

s  内回り線出側ポイント

z  外回り線出側ポイント

x  外回り線入側ポイント

 

いずれもトグル式(押す都度、正位と反位が入れ換わる)です。

1で外回り線の、2で内回り線の485系「VRMトレイン」がアクティブになります。これらの編成には以下の操作キーが共通して設定されています。もちろん、アクティブな編成に対してのみ、これらの操作が有効になります。

 

SHIFT + z  起動(ライト点灯・パンタ上げ)

SHIFT + x  停止(ライト消灯・パンタ下げ)

SHIFT + c  方向幕変更

 

起動・方向幕変更は、HOMEキーによる方転に連動して変化します。たとえば、HOMEキーで向きを変えてからSHIFT+cを押すと、方向幕が「回送」になります。

以上が基本です。続いて、編成の追加を試してみましょう。

パーツウィンドウから[編成]を空いている側線にドロップし、編成エディタの[ファイル再生]で ghost-sample-485.trn を読み込んでください。おっと、ビュワーを起動するのはまだですよ。ここまでの話を思い出してください。組み込みには一手順必要でしたね。

 

[1] 編成エディタで、既存の編成と重ならない名前を入力する。たとえば「VRM-3」。

[2] 編成スクリプトを開き、編成選択用のイベントキー設定(SetEventKeyでACTIVEメソッドを呼ぶステップ)の末尾に、この編成を選択するためのキーを設定する。たとえば「3」。

[3] 同じく編成スクリプト、ACTIVEメソッド内のset VActTrainNoの末尾に編成番号を入れる。ここでは3つ目の編成なので必ず3。

[4] レイアウトスクリプトを開き(レイアウトメニュー内、[スクリプト編集])、KILLEVENTメソッド内の「//3編成目移行」に続く5ステップの頭のコメント記号(//)を取る。

[5] get ObjT "編成名" の編成名を、[1]で入力した名前に書き換える。ここでは「VRM-3」。

 

これだけの手順で既にギブアップ、という方もいそうな気はしますが・・・。ですが、この方法もしくは類似する標準化をおこなわない場合、ギミックを含む編成ファイルを他のレイアウトに綺麗に運ぶのは存外骨が折れそうです。

異なるタイプの編成も組み込めることを確認してください。同様の手順で ghost-sample-ef81.trn を4編成目に追加することが出来ます。コイツには、ライト消点灯に加え、SHIFT + v で機関車を客車から切り離す機能が加わっています。

このレイアウトの危険性

察しの良い方は既に想像がついていると思うのですが、このレイアウトには未解決の問題が残されており、以下のことをすると、確実にVRMビュワーを異常終了させてしまいます。問題の鍵になるのは編成の解放・連結です。

理屈はこうです。編成の解放・連結をおこなうと編成オブジェクトの名前の関係に変化が生じます。特に今回の実装に際しては「ある編成をアクティブにする時点で、直前までアクティブであった編成が(LOSTメソッドを呼ぶべく)存在していること」が前提になっており、編成の解放・連結によりこの関係に狂いが生じます。結果的に、キーイベントが既になくなってしまった編成にあったはずのメソッドの呼び出しを試み、これがWindowsから見ると一般保護違反になってしまうようです。

この「消滅済みオブジェクトのメソッドコールがイベントリガが可能」という仕様は、脆弱性になるような気もするので、早めに対処して欲しいところです。

余談ながら、VRM4スクリプトの実装は、VRMが悪意のあるコードの実行に利用されないよう、細心の注意を払っているように思うんですが・・・気のせい?

仮にそうだとすると、ちょっと編成の解放・連結の実装は焦り過ぎのようにも思いますが。

この問題を回避する方法をいろいろ試してみたのですが、現時点では私は答えに至れませんでした。この問題を解決するまで公開を見送るべきか、とも思ったのですが、このような実装があり得ることを示すことには意味があるように思えますし、何より、こうしたコンセプト立証的なコードがないと開発側もデバッグや改善がしにくいのではないか、と思ったので公開に踏み切りました。

まとめとVRM4への改善案

本稿が目指したのは、ある一定の規約の元、レイアウトスクリプト側の記述に依存せず、ポーティング性を保持した編成スクリプトを記述することです。これについては、完璧ではないにせよ、一定の成果を得たように思います。

特に、ネットVRMユーザー界隈を見るにつけ、編成ファイル配布は、全体のアクティビティの中でそれなりのウェイトを占めていました。が、VRM4以降は、スクリプト実装の問題からこれが困難になっています。本稿で示したような標準化をおこなうことにより、オリジナルのリソースやギミックを含み、かつ、他者の作ったレイアウトとの親和性の高い編成ファイルの作成が可能となり、これはネットVRM界隈の活性化への効果が期待されます。

一方で、完璧でない、すなわちレイアウトスクリプト側にある仕掛けをすることが必須となる点は、当初の目的に反するため、満足のいく結果とはなりませんでした。さらに、編成の解放・連結に伴う「宙に浮いたキーイベント」の危険性についても、何某かのケアが必要かと思われます。

 

よもや、編成解放・連結時、各自のロジックで消滅オブジェクトを割り出して事前にKillAllEventしない限りは安定動作を保障しません、なんてふざけたことは言わないよね。

以下、ユーザー向けの記述ではないので書き飛ばしますが、取り合えず以下の事柄は急務ではないかと思いますので、提案しておきます。

開発者さん、よろしく。

 

SetEventLostActive の実装

 要するに、編成オブジェクトが自分がアクティブでなくなる直前に、自分が握っていたイベントを解放出来れば問題がないワケで。対でSetEventGetActiveがあれば、ビュワーメニューからの選択にも対応できる上、これらを対で使って自動/手動運転の切り替えも出来ていいんじゃないかと。

SetCouplePolicy の実装

 解放(Uncouple)がコーディングした人間にその意図がない限り発生しないのに対し、連結は一般ユーザーが容易に起こすことが出来ます。これに対する対策をコーディング者に求めるのはナンセンスなので、ビュワー操作による連結を禁止したり一定の条件を課したりするプロパティが必要なんじゃないかと。

SetUncouplePolicy の実装

 解放の実行、すなわち編成オブジェクトのコピー発生時に、現行のような杓子定規な変数・イベント継承をするのではなく、「全破棄」「イベントのみ破棄」「すべてそのまま維持」「名前空間変換をおこなって維持」などのポリシーを選択するプロパティが、切り離す側、切り離される側のそれぞれに設定できると、いろいろ便利なんじゃないかと。

 

命令名は適当。ま、わかるよね。

以上、由羽さんの興味深い提言をベースにまとめてみました。改めて由羽さんに御礼を申し上げます。また、本稿の参考資料としてKZ氏のWebのスクリプトに関する記述が強い助けになりました。ありがとうございます。

論考、及び、サンプルレイアウトには細心の注意を払い誤謬のないよう努めたつもりではありますが、私自身、目下研究中のため、いろいろ予期せぬ問題が隠れている可能性も高いです。どんなことでも結構ですので、お気づきの点があれば、VRMスクリプト会議室か、VRMovies BBSへフィードバックをお願いします。このアイデア自体に関するご意見・提案は前者へ、拙稿の誤謬指摘やサンプルレイアウトのバグ報告は後者へいただけると幸いかと。

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

 


Webmaster: ghost@nodus.ne.jp