こんな感じのゲームをつくります。
戦車から砲弾を発射してブロックを破壊します。それから前回のままでは戦車がブロックを通り抜けることができてしまうので、この問題を解消します。
まず戦車がブロックを通り抜けてしまう問題は
戦車の位置先の座標を取得して四捨五入する
その位置にブロックが存在するかどうか調べる
ブロックが存在する場合は移動できなくする
これでできそうです。
そのためにTankクラスのコンストラクタを変更します(Form1クラスのメンバーにアクセスできるようにする)。そしてその方向に動くことができるかどうかを判定するメソッドを作成します。
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 |
public class Tank { Form1 MainForm = null; public Tank(float x, float z, Color color, int angle, Form1 form1) { X = x; Z = z; Color = color; RotateY = angle; MainForm = form1; } public bool CanMove() { double x = Math.Round(X); double z = Math.Round(Z); List<Block> blocks = MainForm.GetNoBrokenBlocks(); Console.WriteLine(x); if(RotateY == 0) z += 2; else if(RotateY == 180) z -= 2; else if(RotateY == 90) x += 2; else //if(RotateY == 270) x -= 2; return !blocks.Any(x1 => x1.PositionX == x && x1.PositionZ == z); } } |
Tankクラスのコンストラクタを変更したため、Form1クラスも一部変更します。また壊れていないブロックを取得するためのメソッド GetNoBrokenBlocks()を追加します。
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 |
public partial class Form1 : Form { // 自分の戦車 public Tank Tank = null; public Form1() { InitializeComponent(); // 自分の戦車を生成する Tank = new Tank(0, 0, Color.Red, 0, this); ShowLabel(); InitTimer(); CreateStage(1); } /// <summary> /// 壊れていないブロックを取得する /// </summary> /// <returns></returns> public List<Block> GetNoBrokenBlocks() { List<Block> ret = new List<Block>(); ret.AddRange(BorderBlocks); ret.AddRange(Blocks.Where(x => !x.IsBroken)); return ret; } void Go() { if(turnCounter != -1 || goCounter != -1) return; // 移動できない場合はなにもしない if(!Tank.CanMove()) return; goCounter = 0; Timer goTimer = new Timer(); goTimer.Interval = 30; goTimer.Tick += GoTimer_Tick; goTimer.Start(); void GoTimer_Tick(object sender, EventArgs e) { Go0(); goCounter++; if(goCounter >= 20) { Timer t = (Timer)sender; t.Stop(); t.Dispose(); goCounter = -1; return; } } } } |
次に戦車から砲弾を発射してブロックを破壊する処理を考えます。
砲弾は処理を簡単にするため、移動中や方向転換中には発射できないことにします。すると砲弾はブロックの中心に向かって東西南北いずれかの方向から飛んでくることになります。また砲弾の速度は0.3です。
1 2 3 4 5 6 7 |
public class Bullet { public float Speed { get { return 0.3f; } } } |
またこれまで砲弾の高度も考えてきましたが、砲弾はなにかに当たるまでまっすぐに飛びつづけるので考えないことにします。
砲弾は東西南北いずれかの方向にしか飛ばないので書き直すことにしました。砲弾の方向をしめすDirectプロパティを追加しました。
1 2 3 4 5 6 7 |
public enum Direct { North, East, South, West, } |
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
public class Bullet { public Bullet(Tank tank) { Y = 0.6f; if(tank.RotateY == 0) { X = tank.X; Z = 0.5f + tank.Z; SpeedX = 0; SpeedZ = Speed; Direct = Direct.South; } else if(tank.RotateY == 180) { X = tank.X; Z = -0.5f + tank.Z; SpeedX = 0; SpeedZ = -Speed; Direct = Direct.North; } else if(tank.RotateY == 90) { X = 0.5f + tank.X; Z = tank.Z; SpeedX = Speed; SpeedZ = 0; Direct = Direct.East; } else if(tank.RotateY == 270) { X = -0.5f + tank.X; Z = tank.Z; SpeedX = -Speed; SpeedZ = 0; Direct = Direct.West; } } public float Speed { get { return 0.3f; } } public float X { get; private set; } public float Y { get; private set; } public float Z { get; private set; } public float SpeedX { get; private set; } public float SpeedZ { get; private set; } public Direct Direct { get; private set; } public bool isDead { get; set; } = false; public void Move() { if(isDead) return; X += SpeedX; Z += SpeedZ; } public void Draw() { // X,Y,Zを中心とする立方体 大きさ0.1 GL.PushMatrix(); { GL.Translate(X, Y, Z); GL.Begin(BeginMode.Quads); { GL.Vertex3(0.05, 0.05, 0.05); GL.Vertex3(0.05, -0.05, 0.05); GL.Vertex3(-0.05, -0.05, 0.05); GL.Vertex3(-0.05, 0.05, 0.05); GL.Vertex3(-0.05, 0.05, 0.05); GL.Vertex3(-0.05, -0.05, 0.05); GL.Vertex3(-0.05, -0.05, -0.05); GL.Vertex3(-0.05, 0.05, -0.05); GL.Vertex3(0.05, 0.05, 0.05); GL.Vertex3(0.05, -0.05, 0.05); GL.Vertex3(0.05, -0.05, -0.05); GL.Vertex3(0.05, 0.05, -0.05); GL.Vertex3(0.05, 0.05, 0.05); GL.Vertex3(-0.05, 0.05, 0.05); GL.Vertex3(-0.05, 0.05, -0.05); GL.Vertex3(0.05, 0.05, -0.05); GL.Vertex3(0.05, -0.05, 0.05); GL.Vertex3(-0.05, -0.05, 0.05); GL.Vertex3(-0.05, -0.05, -0.05); GL.Vertex3(0.05, -0.05, -0.05); } GL.End(); } GL.PopMatrix(); } } |
またBullet.isDeadプロパティを公開したので、戦車から発射された砲弾のリストを以下のようにプロパティに変更します(Bullet.isDead == trueであれば取得しない)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Tank { public List<Bullet> _bullets = new List<Bullet>(); public List<Bullet> Bullets { get { _bullets = _bullets.Where(x => !x.isDead).ToList(); return _bullets; } set{ _bullets = value; } } } |
ブロックに砲弾が命中したかどうかを判定するメソッドを作成します。砲弾が命中した方向で命中した側を消滅させます。同じ方向から2回命中させると完全に破壊することができます。90度ずれた方向からだと完全に破壊するために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 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 71 72 73 74 75 76 77 78 79 80 81 |
public class Block { public bool IsBombed(Bullet bullet) { // すでに破壊されているブロックに命中するはずはないのでfalseを返す if(IsBroken) return false; // 命中していない(砲弾の座標がブロックの内部であれば命中とする) if( !(PositionX - 1 < bullet.X && bullet.X < PositionX + 1) || !(PositionZ - 1 < bullet.Z && bullet.Z < PositionZ + 1) ) return false; // 北側から命中 if(bullet.Direct == Direct.South) { if(!IsBrokenNE || !IsBrokenNW) { IsBrokenNE = true; IsBrokenNW = true; } else { IsBrokenSE = true; IsBrokenSW = true; } return true; } // 南側から命中 if(bullet.Direct == Direct.North) { if(!IsBrokenSE || !IsBrokenSW) { IsBrokenSE = true; IsBrokenSW = true; } else { IsBrokenNE = true; IsBrokenNW = true; } return true; } // 西側から命中 if(bullet.Direct == Direct.East) { if(!IsBrokenSW || !IsBrokenNW) { IsBrokenSW = true; IsBrokenNW = true; } else { IsBrokenSE = true; IsBrokenNE = true; } return true; } // 東側から命中 if(bullet.Direct == Direct.West) { if(!IsBrokenSE || !IsBrokenNE) { IsBrokenSE = true; IsBrokenNE = true; } else { IsBrokenSW = true; IsBrokenNW = true; } return true; } return false; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class BorderBlock : Block { new public bool IsBombed(Bullet bullet) { // 命中していない(砲弾の座標がブロックの内部であれば命中とする) if( !(PositionX - 1 < bullet.X && bullet.X < PositionX + 1) || !(PositionZ - 1 < bullet.Z && bullet.Z < PositionZ + 1) ) return false; // 破壊されないブロックなので砲弾の死亡フラグだけtrueにする bullet.isDead = true; return true; } } |
そしてUpdateメソッドのなかで砲弾を移動させるとともに当たり判定をおこないます。
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 |
public partial class Form1 : Form { new void Update() { // 砲弾を移動させる foreach(Bullet bullet in Tank.Bullets) bullet.Move(); // 砲弾がブロックに命中したら爆発するとともにブロックを消滅させる foreach(Bullet bullet in Tank.Bullets) { if(Blocks.Any(x => x.IsBombed(bullet))) { bullet.isDead = true; Explosions.Add(new Explosion(bullet.X, bullet.Z)); } } // 砲弾が壊れることがないブロックに命中したら爆発の処理をする foreach(Bullet bullet in Tank.Bullets) { if(BorderBlocks.Any(x => x.IsBombed(bullet))) { bullet.isDead = true; Explosions.Add(new Explosion(bullet.X, bullet.Z)); } } // 描画する必要がないものはここで取り除く Explosions = Explosions.Where(x => !x.isDead).ToList(); // レーダーを表示させる ShowRadar(); } } |
最後のレーダーを表示させる処理は次回行ないます。