Rally-Xは敵の追尾を振り切るために煙幕(スモークスクリーン)が使えます。スモークスクリーンがある場所をレッドカーは通過することができません。
ラリーX – Wikipediaによると、
マイカーは、基本的に攻撃方法は無く、レッドカーから逃げるしかないが、ボタンで煙幕(スモークスクリーン)を発生させ、レッドカーを一定時間足止めすることができる。ただし足止めされたレッドカーにも当たり判定は残っているため、使い方を誤ると逆に追い込まれることもある。
とあります。今回はスモークスクリーンを発生させ追尾を妨害します。
最初にスモークスクリーンを描画する位置を管理するクラスを作成します。スペースキーが押されたときにマイカーが存在する位置に生成すればいいのですが、マイカーの後ろではなく前に発生するのはおかしいので、マイカーの方向で場所を調整しています。
スモークスクリーンは時間が経過すると消滅します。3秒後に消滅するようにしています(実際はもっと長かったような気が・・・)。
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 |
public class Smoke { public readonly int X = 0; public readonly int Y = 0; DateTime Time = DateTime.Now; public Smoke(double x, double y, Direct direct) { X = (int)(Math.Round(x, 4)); Y = (int)(Math.Round(y, 4)); if(direct == Direct.North || direct == Direct.East) { X = (int)(Math.Round(x, 4)); Y = (int)(Math.Round(y, 4)); } if(direct == Direct.West) { X = (int)(Math.Round(x, 4)) + 1; Y = (int)(Math.Round(y, 4)); } if(direct == Direct.South) { X = (int)(Math.Round(x, 4)); Y = (int)(Math.Round(y, 4)) + 1; } } public bool IsLife() { DateTime deadTime = Time.Add(new TimeSpan(0, 0, 3)); if(deadTime > DateTime.Now) return true; else return false; } } |
スモークスクリーンはスペースキーを押すと発生します。スモークスクリーンを発生させるメソッドとそれらを格納するリストを示します。
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 43 44 45 46 |
public partial class Form1 : Form { List<Smoke> Smokes = new List<Smoke>(); void SetSmoke() { Smokes.Add(new Smoke(EyeX, EyeY, CurDirect)); } [System.Security.Permissions.UIPermission( System.Security.Permissions.SecurityAction.Demand, Window = System.Security.Permissions.UIPermissionWindow.AllWindows)] protected override bool ProcessDialogKey(Keys keyData) { if(keyData == Keys.Up) { NextDirect = Direct.North; if(CanNorth(EyeX, EyeY)) CurDirect = Direct.North; } if(keyData == Keys.Down) { NextDirect = Direct.South; if(CanSouth(EyeX, EyeY)) CurDirect = Direct.South; } if(keyData == Keys.Left) { NextDirect = Direct.West; if(CanWest(EyeX, EyeY)) CurDirect = Direct.West; } if(keyData == Keys.Right) { NextDirect = Direct.East; if(CanEast(EyeX, EyeY)) CurDirect = Direct.East; } if(keyData == Keys.Space) { SetSmoke(); } return base.ProcessDialogKey(keyData); } } |
発生させたスモークスクリーンのなかで寿命が尽きていないものを描画するのであればDrawSmokes()メソッドを呼び出せばいいのですが、実際に描画するためにはテクスチャをつくる必要があります。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { void DrawSmokes() { Smokes = Smokes.Where(x => x.IsLife()).ToList(); foreach(Smoke smoke in Smokes) { DrawSmoke(smoke.X, smoke.Y); } } } |
以下はスモークスクリーンのテクスチャを生成するためのメソッドです。スモークスクリーンのビットマップイメージは背景を赤で塗りつぶしていますが、この部分は道路と同じ色にしなければなりません。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
public partial class Form1 : Form { // 道路の色 Color RoadColor = Color.Tan; void DrawRoad() { GL.Color3(RoadColor); foreach(Block block in RoadBlocks) DrawBlock(block.IndexX, block.IndexY); } int SmokeTexture = 0; void GetSmokeTexture() { Bitmap bitmap = Properties.Resources.smoke; bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); int width = bitmap.Width; int height = bitmap.Height; for(int y = 0; y< height; y++) { for(int x = 0; x < width; x++) { if(bitmap.GetPixel(x, y) == Color.FromArgb(255, 0, 0)) bitmap.SetPixel(x, y, Color.Tan); } } SmokeTexture = CreateTexture(bitmap); } int CreateTexture(Bitmap bmp) { // テクスチャ有効化 GL.Enable(EnableCap.Texture2D); // 領域確保 int texture; GL.GenTextures(1, out texture); // アクティブ設定 GL.BindTexture(TextureTarget.Texture2D, texture); System.Drawing.Imaging.BitmapData data = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), // 画像全体をロック System.Drawing.Imaging.ImageLockMode.ReadOnly, // 読み取り専用 System.Drawing.Imaging.PixelFormat.Format24bppRgb); // ロックをしたら必ずアンロックする bmp.UnlockBits(data); // テクスチャ領域にピクセルデータを貼り付ける GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, data.Width, data.Height, 0, PixelFormat.Bgr, // Rgbaにすると赤と青が反転するので注意 PixelType.UnsignedByte, data.Scan0); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); GL.BindTexture(TextureTarget.Texture2D, 0); return texture; } } |
GetSmokeTexture()メソッドはGlControlがロードされたときに呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { private void GlControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl1.BackColor); // Projection の設定 SetProjection(); // 視界の設定 SetSight(); GetSmokeTexture(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); } } |
実際にスモークスクリーンを描画します。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
public partial class Form1 : Form { private void GlControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); DrawSmokes(); DrawRoad(); DrawMyCar(); DrawRedCars(); JudgeContactCheckPoint(); DrawCheckPoints(); bool ret = JudgeContactRedCar(); if(ret) DrawBang(); glControl1.SwapBuffers(); if(ret) Miss(); if(!CheckPoints.Any(x => !x.Cleared)) StageClear(); } void DrawSmokes() { Smokes = Smokes.Where(x => x.IsLife()).ToList(); foreach(Smoke smoke in Smokes) { DrawSmoke(smoke.X, smoke.Y); } } void DrawSmoke(int x, int y) { GL.PushMatrix(); { GL.Translate(x, y, 0); GL.Color3(Color.White); GL.BindTexture(TextureTarget.Texture2D, SmokeTexture); GL.Begin(BeginMode.Quads); { double side = 1.0; GL.TexCoord2(0, 0); GL.Vertex3(-side / 2, -side / 2, 0.005); GL.TexCoord2(1, 0); GL.Vertex3(side / 2, -side / 2, 0.005); GL.TexCoord2(1, 1); GL.Vertex3(side / 2, side / 2, 0.005); GL.TexCoord2(0, 1); GL.Vertex3(-side / 2, side / 2, 0.005); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } |
さてスモークスクリーンを生成するとその場所をレッドカーは通ることはできません。スモークスクリーンがある場所を通れなくするためにはRedCarクラスからSmokesが見えるようにする必要があります。そこで静的メソッド GetSmoles()を作成しました。Smokesの内容が改ざんされないようにコピーを返します。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { // 既存の Smokes に staticをつける static List<Smoke> Smokes = new List<Smoke>(); public List<Smoke> GetSmoles() { return Smokes.ToList(); } } |
レッドカーはスモークスクリーンに接触するとスピンします。そこでスピンしている時間とそのときの回転角度を管理するプロパティを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class RedCar { public int SpinTime { get; private set; } = 0; int _spinAngle = 0; public int SpinAngle { get { return _spinAngle; } set { _spinAngle = value; if(_spinAngle > 360) _spinAngle -= 360; } } } |
既存のMoveCar()メソッドにスモークスクリーンに接触したときの処理を追加します。移動しようとした場所にスモークスクリーンがあるときは0.5秒間、その場でスピンさせます。そしてスピンが終わったときの進行方向を逆方向にセットします。
次回の呼び出しでSpinTimeプロパティが0よりも大きいときは、Timer.Tickイベント1回につき60度回転させ、SpinTimeプロパティを60ミリ秒分減らします。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
public class RedCar { public void MoveCar() { if(StopInterval > 0) { StopInterval -= 60; return; } if(SpinTime > 0) { SpinTime -= 60; SpinAngle += 60; return; } int x = (int)Math.Round(X, 1); int y = (int)Math.Round(Y, 1); if(CurDirect == Direct.North) { if(Form1.GetSmoles().Any(x1 => x1.X == x && x1.Y == y + 1)) { SpinTime = 500; CurDirect = Direct.South; } else Y += 0.20; } else if(CurDirect == Direct.South) { if(Form1.GetSmoles().Any(x1 => x1.X == x && x1.Y == y)) { SpinTime = 500; CurDirect = Direct.North; } else Y -= 0.20; } else if(CurDirect == Direct.West) { if(Form1.GetSmoles().Any(x1 => x1.X == x && x1.Y == y)) { SpinTime = 500; CurDirect = Direct.East; } else X -= 0.20; } else if(CurDirect == Direct.East) { if(Form1.GetSmoles().Any(x1 => x1.X == x + 1 && x1.Y == y)) { SpinTime = 500; CurDirect = Direct.West; } else X += 0.20; } IfCanChengeDirect(); } } |
レッドカーが進行できる方向を取得するメソッドです。原則的にこれまで来た道を引き返す動きはしませんが、袋小路に入ったときに出られるようにしています。
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 |
public class RedCar { void IfCanChengeDirect() { List<Direct> directs = GetCanChengeDirects(); if(directs.Count > 0) { int i = random.Next(0, directs.Count); CurDirect = directs[i]; } else { // 袋小路に入った場合は引き返す if(CurDirect == Direct.North) CurDirect = Direct.South; else if(CurDirect == Direct.South) CurDirect = Direct.North; else if(CurDirect == Direct.East) CurDirect = Direct.West; else if(CurDirect == Direct.West) CurDirect = Direct.East; } } } |
レッドカーを描画する既存のメソッドDrawRedCars()を修正しています。スピンしているときの処理を追加しています。
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 |
public partial class Form1 : Form { void DrawRedCars() { foreach(RedCar redCar in RedCars) { GL.PushMatrix(); { GL.Translate(redCar.X, redCar.Y, 0); if(redCar.SpinTime > 0) GL.Rotate(redCar.SpinAngle, 0, 0, 1); 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(); } } } |
ミス時とクリア時はスモークスクリーンをクリアします。
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 |
public partial class Form1 : Form { void Miss() { timer.Stop(); GetCount = 0; // スモークスクリーンをクリア Smokes.Clear(); Rest--; if(Rest == 0) { GameOver(); return; } Timer timer2 = new Timer(); timer2.Interval = 3000; timer2.Tick += Timer2_Tick; timer2.Start(); } void StageClear() { if(!isGameOver) { timer.Stop(); GetCount = 0; // スモークスクリーンをクリア Smokes.Clear(); MessageBox.Show("クリア"); } } } |