岡オカ営業所 ホームへ
Quickstart

BveEXプラグイン開発 クイックスタート

お急ぎの方向けに、BveEXプラグインの開発に必要な情報を最小限に絞って説明しています。
現時点ではBveEXプラグインの文法のうち最も根幹となる部分のみの解説になります。より込み入った部分の仕様についてはSDKを参考にしてください。

前提として、C#の言語仕様をある程度理解している必要があります。

INDEX

1. BveEXの仕組み

2. プラグインアセンブリ(DLL)の開発①:基本文法

3. プラグインアセンブリ(DLL)の開発②:各種機能の概要と呼び出し方

4. 開発したプラグインを実行させる方法

執筆中……

1. BveEXの仕組み

1-1. BveEXの動作原理

「BveEXは、BVE上であらゆる演出を実現可能にする」と言いますが、その原理を見ていきます。

基本原理はリフレクション

BVE本体に定義されているクラスや関数、変数を外部から直接参照することは可能ではあるのですが、 実際にやろうとすると、BVE本体(BveTs.exe)の構造上の理由で困難を極めます(想定されていない使い方なのですから当然です)。
そこで、BveEXはBVE本体のほぼ全てのクラスやそのメンバーをリフレクションによって参照し、BveTypes.dllを介して使いやすい形で公開しています。 これをブリッジなどと呼んでいます。

BVE本体の機能を、BveEXを経由してデータ作者の皆様に参照していただく。このようにしてBveEXは成り立っているわけです。

え、それだけ?と思いませんか

実際、BveEXはそこまで特殊なことをやっているわけではありません。
ではなぜBveEXが必要なのか。それは、とにかくBVE本体に実装されている機能が多すぎるからです。

原理は至って(ソフトウェア開発に精通している人にとっては)単純なのですが、データ作者の皆様が各々で実装するにはあまりにも複雑でかつ個数が多すぎるのです。 それをBveEXは全て肩代わりしてくれるわけですから、強力なツールと言うことができるでしょう。

強力なプラグイン機能

BVE本体のブリッジをデータ作者の皆様から呼び出すための手段として提供しているのが、プラグイン機能です。詳細は1-2にて説明しています。

同等の機能を自前で実装すれば、BveEXを使うメリットはない?

いいえ、そんなことはありません。詳しい理由はQ&Aにて説明しています。

1-2. BveEXプラグインの種類とその特徴

BveEXプラグインは、その実行形態によって

これら3種類に分けることができます。

車両プラグイン

車両プラグインは、車両データに同梱するプラグインです。
形としては通常のBVEにおけるATSプラグインに近いと言えます。

マッププラグイン

マッププラグインは、路線データに同梱するプラグインです。
通常のBVEでは提供されていない、新しい形のプラグインになります。

拡張機能

拡張機能は、他の2種類のプラグインとは異なり読み込まれている車両、路線に関わらず常に読み込まれる仕様のプラグインです。
形としては入力デバイスプラグインに近いと言えます。

どの種類のBveEXプラグインでも、BveEXの一部機能が制限されるようなことはありません
車両プラグインでも、マッププラグインでも、拡張機能でも、変わらずBveEXの機能をフル活用することができます。

2. プラグインアセンブリ(DLL)の開発①:基本文法

2-1. 車両・マッププラグイン:メインクラスの定義例

以下に示すのが、最小構成のBveEX車両・マッププラグインのコードです。

以下のコードをビルドするには、NuGetから BveEx.PluginHost をインストールしておく必要があります。

車両プラグイン

マッププラグイン

2-2. 車両・マッププラグイン:クラス、属性などの解説

PluginBase抽象クラス

PluginBase 抽象クラスを継承したクラスがBveEXプラグインのメインクラスとして認識される仕様ですが、 基本的には PluginBase の派生である AssemblyPluginBase 抽象クラスを継承することになります。

PluginBase抽象クラス、AssemblyPluginBase抽象クラスの違い

BveEXプラグインとして認識されるのはどちらの抽象クラスを継承した場合も同様なのですが、 AssemblyPluginBase では、BveEXプラグインの名前や説明、著作権表示等が AssemblyInfo.cs で定義したアセンブリ情報を参照して自動で設定されます。

Plugin属性(PluginAttributeクラス)

BveEXプラグインのメインクラスは、PluginBase 抽象クラスを継承することに加えて、Plugin 属性を付加することによってプラグインの種類を指定する必要があります。

2-3. 拡張機能:メインクラスの定義例

以下に示すのが、最小構成のBveEX拡張機能のコードです。
車両・マッププラグインでも必要だった要素に加えて、拡張機能の場合のみ必要となる要素が存在します。

2-4. 拡張機能:クラス、属性などの解説

PluginBase抽象クラス、Plugin属性

車両・マッププラグインでの解説を参照してください。

IExtensionインターフェイス

BveEX拡張機能のメインクラスは、車両・マッププラグインの場合と同様に PluginBase 抽象クラスを継承し、Plugin 属性を付加することに加えて、 IExtension インターフェイスを実装する必要があります。

「実装」とは言っても、IExtension インターフェイスにはメンバーは何も定義されていません(ver2.0時点)。 あくまでもBveEX本体の処理上の都合になります。

ITogglableExtensionインターフェイス(任意)

IExtension インターフェイスの派生である ITogglableExtension インターフェイスを実装し、Togglable 属性を付加した拡張機能は、 「BveEX バージョン情報・プラグイン一覧」画面(右クリックメニューから表示できます)で有効・無効を切り替えることができます。

ExtensionMainDisplayType属性(任意)

拡張機能のメインクラスのインスタンスは他のプラグインから取得することができるのですが (詳しくは後述)、 その際に見える型を実際とは異なるクラス・インターフェイスへ置き換えることのできる機能です。

BveEX拡張機能のメインクラスに ExtensionMainDisplayType 属性を付加し、そのパラメーターとして

ようなインターフェイスまたはクラスを指定します。
例えば以下のコードでは、本来のメインクラスが PluginMain クラスであるところを IHogeExtension インターフェイスに置き換えています。

HideExtensionMain属性(任意)

BveEX拡張機能のメインクラスに HideExtensionMain 属性を付加すると、 この拡張機能のメインクラスのインスタンスについて、他のプラグインからの取得が禁止されます

3. プラグインアセンブリ(DLL)の開発②:各種機能の概要と呼び出し方

3-1. BveHacker

1-1で説明した「BVE本体の機能を使いやすい形で公開する」機能を、 PluginBase クラスの BveHacker プロパティから参照することができます。
BveHacker の中に入っている機能を組み合わせることで、BveEXプラグインを作成していくことになります。

BveHacker の機能は、大きく以下の2種類に分けることができます。

クラスラッパー

BVE本体が管理するクラスのインスタンスを、BveEXを介して分かりやすい形で取得することができます。
ここで、元のインスタンスを「分かりやすい形」に加工するのがクラスラッパーです。 その機能は ClassWrapperBase 抽象クラスから派生する諸クラスに定義されています。

ClassWrapperBase 抽象クラスの概形を以下に示します。 BVE本体が管理する元のインスタンス(オリジナルオブジェクトと呼びます)を、Src プロパティに格納します。

参考:ClassWrapperBase 抽象クラスの概形(覚える必要はありません)

実際のコードをGitHubで確認する

これを基に、Scenario クラス、Vehicle クラス、Map クラス、Station クラスなど、 様々なラッパーが派生クラスとして定義されています。
一例として ScenarioInfo クラスの概形を示します。説明していない属性などが所々見られますが、先述した Src プロパティを使って、 Path プロパティからオリジナルオブジェクトの対応する値を参照できるようになっていることが分かるかと思います。

参考:ScenarioInfo クラスの概形(覚える必要はありません)

実際のコードをGitHubで確認する

さて、このようにして実装されたクラスラッパーを BveHacker プロパティから取得することができます。 BveHacker プロパティから直接取得できるクラスラッパー(ver2.0.5現在)のうち、よく使うものを示しました。

プロパティ名 クラス名 説明
MainForm MainForm BVEのメインフォーム
LoadingProgressForm LoadingProgressForm BVEの「シナリオを読み込んでいます...」フォーム
DirectSound DirectSound BVE本体が音声再生に使用するDirectSoundデバイス※
Assistants AssistantSet 補助表示のセット
InputManager InputManager キー入力を管理するオブジェクト
Preferences Preferences BVE本体の設定※
MapLoader MapLoader マップ読込ロジック
ScenarioInfo ScenarioInfo シナリオファイルの情報
Scenario Scenario 実行中のシナリオ全体を管理するオブジェクト
※……オリジナルオブジェクトをラップすることなく直接操作するため、厳密にはクラスラッパーではありません。

これらのオブジェクトの中にも更に沢山のクラスラッパーが格納されています。 特に、実行中のシナリオ全体を管理する Scenario クラスはとても大きく、何度も参照することになるでしょう。
例えば自列車の現在の速度は、以下のように記述すると取得・変更できます。

リフレクション情報

オリジナルオブジェクトのプロパティの値を取得・変更したり、メソッドを呼び出したりする他に、 ObjectiveHarmonyPatch を使ってBveEXプラグインからBVE本体に定義されているメソッドを上書きすることもできます。

BveHacker.BveTypes プロパティから上書きするメソッドのリフレクション情報(MethodInfo など)を取得して実装します。 難易度が高く使用頻度も低い機能なので詳細は割愛しますが、 RichLoadでの実装などが参考になるでしょう。

3-2. 外部の拡張機能を参照する

理論上は、BveHackerから取得できるオブジェクトを組み合わせることで、BveEXが提供するBVE本体への介入機能を全て使うことができます。 しかしながら、新しくBveEXプラグインを開発する度にその全てを自前で実装するのは手間がかかるものです。
そこで、BveEX拡張機能として実装された外部のプラグインを、ライブラリとして参照することができるようになっています。

例えば、2-4ExtensionMainDisplayType 属性の例として提示した拡張機能を参照する場合は、以下のように記述します。

4. 開発したプラグインを実行させる方法

執筆中……