前回はラリーXでつかわれる青い車とコースの作成をしました。矢印キーをおすことで移動させることができるようになりました。では次に敵の車(レッドカー)をつくり、マイカーを追尾させます。
追尾のアルゴリズムはどうするかは後で考えます。まずはレッドカーが移動できるようにしましょう。
レッドカーは1台だけではないので、リストに格納して管理します。
レッドカーも移動方向をもっています。移動しようとしている方向に壁があって動けない場合は移動できる方向を求めてその方向に移動します。
これはレッドカーのクラスです。そしてこのインスタンスを単数または複数生成してフォームクラス内にあるリスト List<RedCar> RedCars で管理します。
レッドカーにも位置情報があります。そして現在の位置情報を調べて現在の進行方向のままで進行できるかどうか調べます。進行できない場合は方向転換です。また交差点やT字路では曲がることもできます。レッドカーは基本的にUターンはしません。
Form1クラス内
1 2 3 4 5 |
public partial class Form1 : Form { // Form1クラス内にRedCarのリストを作成 List<RedCar> RedCars = new List<RedCar>(); } |
RedCarクラス
1 2 3 4 |
public class RedCar { // さあ、どう書くか? } |
まずレッドカーも道路の上のみを移動するので、マイカーと同様にRoadBlocksプロパティが必要です。そこでコンストラクタ内で渡すようにします。その他にもレッドカーの初期位置、初期の方向を渡しています。また曲がり角では直進するのか方向転換をするのかを決める乱数の初期化もしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class RedCar { public double X = 0; public double Y = 0; Random random = null; List<Block> RoadBlocks = null; public Direct CurDirect = Direct.North; public RedCar(List<Block> roadBlocks, double x, double y, Direct direct) { random = new Random((int)DateTime.Now.Ticks); RoadBlocks = roadBlocks; X = x; Y = y; CurDirect = direct; } } |
マイカーと同じようにレッドカーも道路の上だけを移動します。そこでCanNorth(double x1, double y1)、CanSouth(double x1, double y1)、CanEast(double x1, double y1)、CanWest(double x1, double y1)の各メソッドを用意します(赤であっても青であっても同じ車なのだからCarクラスをつくって継承すればよかったな …… 今更)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class RedCar { bool CanNorth(double x1, double y1) { return RoadBlocks.Any(x => (x.IndexY == Math.Ceiling(y1 + 0.001)) && x.IndexX == Math.Round(x1, 4)); } bool CanSouth(double x1, double y1) { return RoadBlocks.Any(x => x.IndexY == Math.Floor(y1 - 0.001) && x.IndexX == Math.Round(x1, 4)); } bool CanEast(double x1, double y1) { return (RoadBlocks.Any(x => x.IndexX == Math.Ceiling(x1 + 0.001) && x.IndexY == Math.Round(y1, 4))); } bool CanWest(double x1, double y1) { return RoadBlocks.Any(x => x.IndexX == Math.Floor(x1 - 0.001) && x.IndexY == Math.Round(y1, 4)); } } |
次にレッドカーを動かします。進行方向に移動させて、そのあと方向転換をさせることができるのであれば、IfCanChengeDirect()メソッドで方向転換をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class RedCar { public void MoveCar() { if(CurDirect == Direct.North) Y += 0.20; if(CurDirect == Direct.South) Y -= 0.20; if(CurDirect == Direct.West) X -= 0.20; if(CurDirect == Direct.East) X += 0.20; IfCanChengeDirect(); } } |
IfCanChengeDirect()メソッドは変更することができる方向を取得して、そのなかからランダムに新しい進行方向を設定するメソッドです。
1 2 3 4 5 6 7 8 9 |
public class RedCar { void IfCanChengeDirect() { List<Direct> directs = GetCanChengeDirects(); int i = random.Next(0, directs.Count); CurDirect = directs[i]; } } |
GetCanChengeDirects()メソッドは前述のCanNorth(double x1, double y1)、CanSouth(double x1, double y1)、CanEast(double x1, double y1)、CanWest(double x1, double y1)の各メソッドを利用して進行可能な方向を取得するメソッドです。ただし元来た道を引き返すことは原則としてできないこととします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class RedCar { List<Direct> GetCanChengeDirects() { List<Direct> directs = new List<Direct>(); if(CanNorth(X, Y)) { if(CurDirect != Direct.South) directs.Add(Direct.North); } if(CanSouth(X, Y)) { if(CurDirect != Direct.North) directs.Add(Direct.South); } if(CanEast(X, Y)) { if(CurDirect != Direct.West) directs.Add(Direct.East); } if(CanWest(X, Y)) { if(CurDirect != Direct.East) directs.Add(Direct.West); } return directs; } } |
あとはForm1クラスにレッドカーを表示するための記述を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); glControl1.Load += GlControlEx1_Load; glControl1.Paint += GlControlEx1_Paint; glControl1.Resize += GlControl1_Resize; timer.Interval = 60; timer.Tick += Timer_Tick; timer.Start(); // 最初はレッドカーを3台 RedCars.Add(new RedCar(RoadBlocks, 12, 1, Direct.North)); RedCars.Add(new RedCar(RoadBlocks, 10, 1, Direct.North)); RedCars.Add(new RedCar(RoadBlocks, 14, 1, Direct.North)); } } |
Timer.Tickのイベントハンドラに直接マイカーをどう動作させるかを記述していましたが、あれこれ書き加えることが増えるかもしれないので、マイカーの移動とレッドカーの移動を処理するメソッドを作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public partial class Form1 : Form { private void Timer_Tick(object sender, EventArgs e) { MoveMyCar(); MoveRedCars(); glControl1.Refresh(); } void MoveMyCar() { if((CurDirect == Direct.North || NextDirect == Direct.North) && CanNorth(EyeX, EyeY)) { CurDirect = Direct.North; EyeY += 0.20; } if((CurDirect == Direct.East || NextDirect == Direct.East) && CanEast(EyeX, EyeY)) { CurDirect = Direct.East; EyeX += 0.20; } if((CurDirect == Direct.South || NextDirect == Direct.South) && CanSouth(EyeX, EyeY)) { CurDirect = Direct.South; EyeY -= 0.20; } if((CurDirect == Direct.West || NextDirect == Direct.West) && CanWest(EyeX, EyeY)) { CurDirect = Direct.West; EyeX -= 0.20; } } void MoveRedCars() { foreach(var red in RedCars) { red.MoveCar(); } } } |
実際に描画する処理です。レッドカーも進行方向で車体の向きを変える必要があります。これで一応、動くことは動くのですが、レッドカーの動きは乱数で適当に決めているだけなので、なかなか捕まることがありません。あまり最適化された追尾アルゴリズムを採用するとすぐにゲームオーバーになってしまうし、なかなか難しい。追尾に関するアルゴリズムは後日ということで・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public partial class Form1 : Form { private void GlControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); DrawRoad(); DrawMyCar(); DrawRedCars(); glControl1.SwapBuffers(); } void DrawRedCars() { foreach(RedCar redCar in RedCars) { GL.PushMatrix(); { GL.Translate(redCar.X, redCar.Y, 0); if(redCar.CurDirect == Direct.East) GL.Rotate(-90, 0, 0, 1); if(redCar.CurDirect == Direct.South) GL.Rotate(180, 0, 0, 1); if(redCar.CurDirect == Direct.West) GL.Rotate(90, 0, 0, 1); DrawCarZero(Color.Red); } GL.PopMatrix(); } } } |