ドナドナされるプログラマのメモ

Windows用アプリのプログラミングメモ

列車モデルの表現方法と操作

車両の連結状態を表す列車モデルについて、どのような構成として、連結や切り離し操作をどのように表現するか整理を兼ねてメモ。

まず、車両は以下のようなインターフェースで表現されているものとする。

public interface IVehicle{
public ICoupler couplers{get;}
public float Length {get;}
//  その他インターフェース
}

public interface ICoupler{
public Vector3 KnucklePos;
// その他インターフェース
}

 車両の連結状態を表す列車モデルに最低限必要なものは、連結器を表すオブジェクトである。一方、どのような車両がつながっているのかが分からないと不便なため、車両を表すオブジェクトも必要である。これを先頭から順番にリスト化すれば列車モデルになるように思われるが、IVehicleのcouplersはグローバル系における車両の前後を考慮した順番となっていない。そのため、車両の連結・切り離し判定をする際には総当たり判定が必要となってしまい、計算量が理想値の4倍に増えてしまう。これを解決するには、どのcouplerがどのcouplerと連結しているかを記録すればよい。そこで、下記のような構造体を新たに定義する。

struct Vehicle{
public ICoupler coupler; // a coupler linked to vehicle.couplers. coupler[0] to vehicle.couplers[0], coupler[1] to vehicle.couplers[1]
public IVehicle vehicle;
}

coupler[0]はvehicle.couplers[0]に連結されたcouplerを、coupler[1]は同couplers[1]に連結されたcouplerを参照している。これにより、計算量を理想値に等しくできる。ただし、連結されたcouplerが無い場合はnullとなるので、連結判定の前にnullチェックが必要となる。

列車は、上記構造体のListとなる。

List<Vehicle> train;

車両の切り離し判定をするには、Listの先頭から順次coupler間距離のチェックをすればよい。先頭車両において一方の連結器は何にもつながっていないことが保証されているので、先頭車両で切り離しが検知されたならばそれは先頭と2番目の間であることが保証される。そして、2番目の車両で切り離しが検知されたならば、それは2番目と3番目の間であることが保証される。以下同様にして、n番目の車両で切り離しが検知されたならば、それはn番目とn+1番目の間である。・・・昔、数学でこんな感じの証明をやったなあ。数学的帰納法だっけ?

この判定は下記関数にやらせる。

bool CheckDisconnection(Vehicle vehicle, int couplerNum){省略} // 切り離されたらtrue

ここまでをまとめると、以下のようになる。

foreach(Vehicle vehicle in train){
if(CheckDisconnection(vehicle, 0) || CheckDisconnection(vehicle, 1)){//切り離し処理};
}

次の問題は、「切り離し処理」をどうするかだ。切り離しが検知されたならば、列車を動力車側とそれ以外に分割し、動力車側を新たに列車とする。このためには、列車における動力車の位置を別途記憶する必要がある。

private int playerPos;  // train中の動力車の位置(=プレイヤーの位置)

困ったことに、ここでリスト中の位置情報が必要となってしまった。for文にすることも考えたが、List型の場合[]演算子での参照はちょっぴり遅そうだ。foreachを使い続けることにする。foreachを使いつつリストを操作することはできないので、foreach文中では残すべきtrainの範囲をチェックし、ループ外にて不要部分を削除することにする。

実装はまたこんど。