Try   HackMD

ATSプラグイン 制作メモ

tags:BveTs

ATSプラグインを初めて作る方に少しでも参考になればと思います。
->DXDynamicTexture(C#)の使い方
->C#で作るATSプラグイン
->AtsEXを使ったプラグインを作成する方法 new!(23/02/23)

目次

解説してほしい所があればDM等でご連絡ください
随時更新していきます
twitter:@Tn_E235


準備

  1. Visual Studio 2019をインストールします
  2. 公式サイトからプラグインのソースをダウンロードします
  3. ats1-2.zipを解凍して、Ats.vcprojをダブルクリックして起動します
  4. そのままビルドして(ctl+B)成功すれば完了です

プロジェクトの設定

こちらのサイトにて画像付きで解説されています。
以下、自分用メモです。

モジュール定義ファイルの作成と参照の設定

  1. 上部メニューバー「プロジェクト」から「プロパティ」を選択します。
  2. ページ左上、構成©:[すべての構成]にします。
  3. 左サイドメニュー「構成プロパティ」->「リンカー」->「入力」まで展開します。
  4. メインメニュー内の「モジュール定義ファイル」を「Ats.def」に設定します。
  5. 設定後、画面右下の「適用」をクリックします。

dllexportの設定

  1. 上部メニューバー「プロジェクト」から「プロパティ」を選択します。
  2. ページ左上、構成©:[すべての構成]にします。
  3. 左サイドメニュー「構成プロパティ」->「C/C++」->「プリプロセッサ」まで展開します。
  4. メインメニュー内の「プリプロセッサの定義」を「ATS_EXPORTS」に設定します。
  5. 設定後、画面右下の「適用」をクリックします。

デバッガーの設定

  1. 上部メニューバー「プロジェクト」から「プロパティ」を選択します。
  2. ページ左上、構成©:[すべての構成]にします。
  3. 左サイドメニュー「構成プロパティ」->「デバッグ」まで展開します。
  4. メインメニュー内の「コマンド」を”BveTs.exeまでのパス”に設定します。
    入力例:E:\Users\Tn-E235\Games\mackoy\BveTs6.0\BveTs.exe
  5. 設定後、画面右下の「適用」をクリックします。

Release用の設定

  1. 上部メニューバー「プロジェクト」から「プロパティ」を選択します。
  2. ページ左上、構成©:[Release]にします。
  3. 左サイドメニュー「構成プロパティ」->「リンカー」->「デバッグ」まで展開します。
  4. メインメニュー内の「デバッグ情報の生成」を「いいえ」に設定します。
  5. 設定後、画面右下の「適用」をクリックします。

64bitでビルドする設定

  1. 上部メニューバー「プロジェクト」から「プロパティ」を選択します。
  2. ページ右上、構成マネージャーをクリックします。
  3. ページ右上、「アクティブ ソリューション プラットフォーム」の下のプルダウンメニューを展開し、新規作成をクリックします。
  4. 「新しいプラットフォームを入力または選択してください」の下のプルダウンメニューを展開し、「x64」を選択します
  5. 設定元のコピー元を「x86」にします。
  6. 画面下部の「OK」をクリックします。
  7. 「構成マネージャー」ウィンドウのプロジェクトのコンテキスト表内の、プラットフォームを「Win32」から「x64」に変更します。
  8. 画面右下「閉じる」、「OK」をクリックします。

ほぼ空のプログラムにする

以下のファイルを削除します。

  • AtsP.h
  • AtsSn.h
  • Spp.h
  • AtsP.cpp
  • AtsSn.cpp
  • Spp.cpp

ファイルを削除した状態でビルドするとエラーが出るので、エラーを解消していきます。
基本的に赤線が表示されている箇所を削除すればOKです。

  1. Ats.hファイル内、32~34行目を削除します。
CAtssn g_atssn; // ATS-SN CAtsp g_atsp; // ATS-P CSpp g_spp; // 停車駅通過防止装置
  1. Ats.cppファイル内、6~8行目を削除します。
#include "Atssn.h" #include "Atsp.h" #include "Spp.h"
  1. Ats.cppファイル内、13~33行目を削除します。
switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_atssn.Time = &g_time; g_atssn.Notch = &g_brakeNotch; g_atsp.TrainSpeed = &g_speed; g_atsp.DeltaT = &g_deltaT; g_atsp.BrakeNotch = &g_brakeNotch; g_atsp.Reverser = &g_reverser; g_spp.TrainSpeed = &g_speed; g_spp.DeltaT = &g_deltaT; g_spp.BrakeNotch = &g_brakeNotch; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; }
  1. Ats.cppファイル内、25~29行目を削除します。
g_atssn.EmgNotch = g_emgBrake; g_atssn.CancelNotch = vehicleSpec.AtsNotch; g_atsp.EmergencyNotch = g_emgBrake; g_atsp.ServiceNotch = vehicleSpec.AtsNotch; g_spp.ServiceNotch = vehicleSpec.AtsNotch;
  1. Ats.cppファイル内、29~31行目を削除します。
g_atssn.InitSn(); g_atsp.InitP(); g_spp.InitSpp();
  1. Ats.cppファイル内、38~40行目を削除します。
g_atssn.RunSn(); // ATS-SN g_atsp.RunP(); // ATS-P g_spp.RunSpp(); // 停車駅通過防止装置
  1. Ats.cppファイル内、38~58行目を次のように変更します
/* ハンドル出力 たぶんここ要らないです。 if(g_brakeNotch != g_emgBrake) { g_output.Brake = g_svcBrake; } else { g_output.Brake = g_brakeNotch; } */ if(g_pilotlamp) { g_output.Reverser = g_reverser; } else { g_output.Reverser = 0; }

ここの部分を削除するとマスコンの状態が反映されません。

  1. Ats.cppファイル内、58~73行目を次のように変更します
// パネル出力 panel[0] = 0; // サウンド出力 sound[0] = 0;
  1. Ats.cppファイル内、75~76行目を削除します。
g_atssn.CheckAts(); g_spp.NotchChanged();
  1. Ats.cppファイル内、84~96行目を削除します。
switch(atsKeyCode) { case ATS_KEY_S: // ATS 確認 g_atssn.DownButtom(); break; case ATS_KEY_A1: // 警報持続 g_atssn.CancelChime(); break; case ATS_KEY_B1: // 復帰 g_atssn.Reset(); g_atsp.Reset(); break; }
  1. Ats.cppファイル内、88行目を削除します。
if(hornType == ATS_KEY_S){g_atssn.UpButton();}
  1. Ats.cppファイル内、97行目を削除します。
g_spp.StopChime(); // 停通のチャイムを止める
  1. Ats.cppファイル内、110~142行目を削除します。
switch(beaconData.Type) { case ATS_BEACON_S: // Sロング g_atsp.Exit(); if(g_speed != 0){g_atssn.PassedLong(beaconData.Signal);} // 駅ジャンプを除外する break; case ATS_BEACON_SN: // SN直下 g_atsp.Exit(); g_atssn.PassedShort(beaconData.Signal); break; case ATS_BEACON_P: // 停止信号 g_atssn.TurnOff(); g_atsp.PassedSig(beaconData.Distance); break; case ATS_BEACON_EMG: // 即停(非常) g_atssn.TurnOff(); g_atsp.PassedStopEmg(beaconData.Distance); break; case ATS_BEACON_SVC: // 即停(常用) g_atssn.TurnOff(); g_atsp.PassedStopSvc(beaconData.Distance); break; case ATS_BEACON_SPDLIM: // 分岐器速度制限 g_atssn.TurnOff(); g_atsp.PassedSpeedLim(beaconData.Optional % 1000, beaconData.Optional / 1000); break; case ATS_BEACON_SPDMAX: // 最高速度 g_atssn.TurnOff(); g_atsp.PassedSpeedMax(beaconData.Optional); break; case ATS_BEACON_SPP: // 停車駅通過防止装置 g_spp.Receive(beaconData.Optional); }

お疲れ様です。これでビルドして成功すれば完了です。

不足している関数を追加する

64bitでビルドしたプラグインを単体で使用する際に、Bve本体が落ちる現象が発生します。
今回使用しているテンプレートは、とてもふるーいので、以下の関数を追加します。

  • Load
  • Dispose

atsplugin.hの最下部に追加

// このプラグインがBVEによって読み込まれた時に呼び出される。 ATS_API void WINAPI Load(); // このプラグインがBVEから解放された時に呼び出される。 ATS_API void WINAPI Dispose();

Ats.cppの最下部に追加

ATS_API void WINAPI Load() {} ATS_API void WINAPI Dispose() {}

Ats.defの最下部に追加

Load Dispose

パネル出力をしてみる

Elaps関数内のpanel配列に値(整数型)を入れることでパネル出力されます。
panel配列の要素数は

256(0255)になっていて、要素番号がパネルインデックスに対応しています。
例えば、ats0の画像を表示させたい場合、

panel[0] = 1;

とすることで、出力

1に対応した画像が表示されます。
これができれば何でもできます(何でもできるとは言っていない)


音声出力をしてみる

Elaps関数内のsound配列に値(整数型)を入れることでパネル出力されます。
sound配列の要素数は

256(0255)になっていて、要素番号がサウンドインデックスに対応しています。
例えば、ats0の音声を再生させたい場合、

sound[0] = ATS_SOUND_PLAY;

とします。再生開始の次フレーム以降は、

sound[0] = ATS_SOUND_CONTINUE;

とします。わからんと思うけど、がんばれ。イメージとしてはこんな感じ。

sound[0] = ATS_SOUND_CONTINUE if (再生しますか()) Sound[0] = ATS_SOUND_PLAY;

地上子から情報を受け取る

地上子(Beacon)を通過すると、SetBeaconData関数が呼ばれます。
ATS_BEACONDATAから以下の情報がとれます。

取れる情報 説明
Type int 地上子番号
Signal int セクションに対応する信号番号
Distance float 対応閉塞までの閉塞距離
Optional int 地上子の第3引数(sendData)

地上子構文:distance;Beacon.put(type,section,sendData);
Typeで条件分岐させて処理すればOKです。かんたんですね。
地上子で小数の値を入れるとintになる?(切り捨て?)

ATS_BEACONDATAは構造体なので

int type = beaconData.type;

な感じで情報がとれます
※beaconDataはsetBeaconData関数の引数

地上子を踏んだ順番(記述順)に読み込まれます


キー入力を受け取る

保安装置キーで設定されたキーが押下されたときKeyUp関数、離されたときKeyDown関数が呼ばれます。引数で押されたキー番号が入ってくるので、条件分岐させて処理してください。
一定時間押下すると長押しとして処理されます。(毎フレーム呼ばれる、だぶん)

種類 番号 保安装置キー キーボード
ATS_KEY_S 0 S Key Space
ATS_KEY_A1 1 A1 Key Insert
ATS_KEY_A2 2 A2 Key Delete
ATS_KEY_B1 3 B1 Key Home
ATS_KEY_B2 4 B2 Key End
ATS_KEY_C1 5 C1 Key PageUp
ATS_KEY_C2 6 C2 Key PageDown
ATS_KEY_D 7 D Key 2
ATS_KEY_E 8 E Key 3
ATS_KEY_F 9 F Key 4
ATS_KEY_G 10 G Key 5
ATS_KEY_H 11 H Key 6
ATS_KEY_I 12 I Key 7
ATS_KEY_J 13 J Key 8
ATS_KEY_K 14 K Key 9
ATS_KEY_L 15 L Key 0

外部ファイルを読み込む

1. 手っ取り早くiniファイルを読み込ませたい

まだini使ってんのかって誰かに言われた記憶

事前準備

  1. inimoniをダウンロードする
  2. 取説道理にソースを生成する
    ※ .iniのファイル名はプラグインと同じ名前にしておきましょう
    ※ デフォルト値が.iniで設定した値になります

テンプレに組み込む

インポートかなんか生成された.hファイルを取り込む
ソリューションなんたらで新しいファイル作ってコピペしても良い(ボクはこっち派)

Ats.hに変数定義

inimoniで生成されたヘッダファイルがiniRead.hの場合

iniRead ini;
Ats.cppに組み込む
1. ヘッダファイルをinclude
#include "iniRead.h" // #include "Ats.h"の上に書く
2. DllMainにおまじないを書く

Ats.dllまでのパスを取得してゴニョゴニョ

//ファイルパス格納 char filePath[_MAX_PATH + 1] = _T(""); //検索文字列へのポインタ char* posIni; //Ats.dllのファイルパスを取得 ::GetModuleFileName((HMODULE)hModule, filePath, _MAX_PATH); //パスから.dllの位置を検索 posIni = strstr(filePath, ".dll"); //.dllを.iniに置換 memmove(posIni, ".ini", 4);

セクションに対する各項目の値を取得する

iniファイルの設定例

[PANEL] index0 = 200 index1 = 201 [SOUND] index0 = 1 index1 = 2

PANELセクションのindex0の値を取得する

int index0 = ini.PANEL.index0;

double型の扱いのバグ

inimoniでdouble型を指定すると,ただしく扱われないらしいので以下の方法で修正する.
修正方法はこちら

2. csvを読み込ませたい

神的な解説はこちら

3. csvを読み込ませた

時間があったら実装してみる
した


サンプルプログラム

とりあえず簡単なソースを必要な部分だけ載せておきます。

現在の速度を一桁ずつ出力する

ATS_API ATS_HANDLES WINAPI Elapse ( ATS_VEHICLESTATE vehicleState, int *panel, int *sound){ ~略~ float speed = vehicleState.Speed; panel[0] = (int)speed % 100; panel[1] = (int)speed / 10 % 10; panel[2] = (int)speed % 10; ~略~ }

現在の時刻を時分秒で出力する

ATS_API ATS_HANDLES WINAPI Elapse ( ATS_VEHICLESTATE vehicleState, int *panel, int *sound){ ~略~ // 現在時刻はミリ秒で取得されるので秒に直します int time = vehicleState.Time % 86400000 / 1000 % (60*60*24); // 秒を時分秒に直します int time_h = time / 3600; int time_m = (time - time_h * 3600) / 60; int time_s = time % 60; panel[3] = time_h; panel[4] = time_m; panel[5] = time_s; ~略~ }
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

24h=606024=86400s=86400000ms

128番地上子を踏んだら音を鳴らす

ATS_API ATS_HANDLES WINAPI Elapse ( ATS_VEHICLESTATE vehicleState, int *panel, int *sound){ ~略~ sound[0] = (ATS_BELL) ? ATS_SOUND_PLAY : ATS_SOUND_CONTINUE; ATS_BELL = false; ~略~ } ATS_API ATS_HANDLES WINAPI setBeaconData ( ATS_BEACONDATA beaconData){ // 地上子の番号を取得 int type = beaconData.type; // 地上子のsendDataを取得 int sendData = beaconData.optional; if (type == 128) { // 再生フラグ(グローバル変数) ATS_BELL = true; } }

スペースキーを押している間,音を鳴らす

ATS_API ATS_HANDLES WINAPI Elapse ( ATS_VEHICLESTATE vehicleState, int *panel, int *sound){ ~略~ // no debug if (ATS_BUZZER) { if (sound[1] == ATS_SOUND_PLAYLOOPING) { sound[1] = ATS_SOUND_CONTINUE; } else { sound[1] = ATS_SOUND_PLAYLOOPING; } } else { sound[1] = ATS_SOUND_STOP; } ~略~ } ATS_API ATS_HANDLES WINAPI keyDown (int key){ if (ATS_KEY_S == key) { // 再生フラグ(グローバル変数) ATS_BUZZER = true; } } ATS_API ATS_HANDLES WINAPI keyUp (int key){ if (ATS_KEY_S == key) { // 再生フラグ(グローバル変数) ATS_BUZZER = false; } }

0.5秒毎に点滅させる(簡易)

ATS_API ATS_HANDLES WINAPI Elapse ( ATS_VEHICLESTATE vehicleState, int *panel, int *sound){ ~略~ deltaT = vehicleState.Time - time; time = vehicleState.Time; int INTERVAL = 500; // 500[ms] if (INTERVAl <= totalTime) { panel[3] = Math.abs(panel[3]-1); totalTime = 0; } else { totalTime += delta_T; } ~略~ }

0.5秒毎に点滅させる

ATS_API ATS_HANDLES WINAPI Elapse ( ATS_VEHICLESTATE vehicleState, int *panel, int *sound){ ~略~ deltaT = vehicleState.Time - time; time = vehicleState.Time; int INTERVAL = 500; // 500[ms] if (time >= switchTime + INTERVAL) { panel[3] = Math.abs(panel[3]-1); switchTime += INTERVAL; } ~略~ }

指定した桁の数値を取得する関数

int getDigitOfNumber(int num, int digit, int rt) { int n = 0; for (int i = 0; i < 10; ++i) { if (num / pow(10, i) < 1) { n = i; break; } } if (digit > n) return rt; int result = (int)(num / pow(10, digit - 1)) % 10; if (digit >= n && result == 0) return rt; return result; }

引数

  • num:入力値
  • digit:取得したい桁
  • rt:指定してた数字以上の桁が存在しないときの返り値

デバッグの方法

まずはじめに,デバッガーの設定を行ってください.

  1. 「Debug」状態でビルドします.
  2. ビルドして生成されたAts.dllをデバッグ用の車両に組み込みます
  3. コードの任意の箇所にブレークポイントを設定します(行番号の横をクリック)
  4. 「ローカルWindowsデバッガー」をクリックするとデバッグが開始し,Bveが起動します
  5. デバッグ用の車両が指定されたシナリオをロードします
  6. ブレークポイントにした行が呼ばれたとき,現在の変数とかが見れます

デバッグは大変がんばろう

私の環境ではBveTs5.7ではデバッグができませんでした(謎)
Bvets6.0RCではデバッグができました


各関数の説明

DllMain(HANDLE, DWORD, LPVOID)

メイン関数

GetPluginVersion()

Atsプラグインのバージョンを返す関数。何に使うのかは知らない。

SetVehicleSpec(ATS_VEHICLESPEC)

車両データが読み込まれたときに呼ばれる関数。

  • 第1引数:車両パラメータが入った構造体

Initialize(int)

初期化時に呼ばれる関数。シナリオの読み込み、始発駅に戻る際に呼ばれるが、駅ジャンプでは呼ばれない。

  • 第1引数:ブレーキ段数

Elapse(ATS_VEHICLESTATE, int*, int*)

毎フレーム呼ばれる関数。

  • 第1引数:車両の状態が入った構造体
  • 第2引数:パネル出力用配列(ポインタ)
  • 第3引数:サウンド出力用配列(ポインタ)

SetPower(int)

力行段数が変化したときに呼ばれる関数。

  • 第1引数:Pノッチ段数

SetBrake(int)

制動段数が変化したときに呼ばれる関数。

  • 第1引数:Bノッチ段数

SetReverser(int)

レバーサーが変化したときに呼ばれる関数。

  • 第1引数:レバーサーの状態

KeyDown(int)

キーが押されたときに呼ばれる関数。

  • 第1引数:キー番号

KeyUp(int)

キーを離したときに呼ばれる関数。

  • 第1引数:キー番号

HornBlow(int)

警笛を押したときに呼ばれる関数。

  • 第1引数:キー番号

DoorOpen()

ドアが開いたときに呼ばれる関数。

DoorClose()

ドアが閉まったときに呼ばれる関数。

SetSignal(int)

現在の閉塞の信号現示が変化したときに呼ばれる関数。

  • 第1引数:信号番号

SetBeaconData(ATS_BEACONDATA)

地上子を踏んだ時に呼ばれる関数。

  • 第1引数:地上子の情報が入った構造体

Load()

プラグインが本体に読み込まれたときに呼ばれる関数。

Dispose()

プラグインが本体から解放されたときに呼ばれる関数。