自機から弾丸を発射してミサイルや燃料タンクを破壊します。Zキーを押すと弾丸が発射されます。実際の処理はStageクラスのJikiShot()メソッド(後述)にやらせます。
Form1.Shot()は自機から弾丸を発射するためのメソッドです。
1 2 3 4 5 6 7 |
public partial class Form1 : Form { void Shot() { Stage.JikiShot(); } } |
Bulletクラスは弾丸を描画の位置を記憶したり移動したり描画するためのクラスです。弾丸の飛行速度はフィールド変数で、位置はプロパティで管理します。
フィールド変数 IsDeadは敵や地形に命中したらtrueにします。これは弾丸としては存在しないものなので移動や描画対象から外します。
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 |
public class Bullet { static public float JikiBulletSpeed = 0.8f; public bool isDead = false; public Bullet(float x, float y) { X = x; Y = y; } public float X { get; private set; } = 0; public float Y { get; private set; } = 0; public void Move() { if(IsDead) return; X += JikiBulletSpeed; } public void Draw() { if(IsDead) return; GL.PushMatrix(); { GL.Translate(X, Y, 0); GL.Color3(Color.White); GL.Begin(BeginMode.Quads); { GL.Vertex3(0.25, 0.02, 0); GL.Vertex3(0.25, -0.02, 0); GL.Vertex3(-0.25, -0.02, 0); GL.Vertex3(-0.25, 0.02, 0); } GL.End(); } GL.PopMatrix(); } } |
StageクラスのJikiShot()メソッドが呼び出されたら弾丸の初期位置を決めてBulletオブジェクトを生成します。弾丸の初期位置はJikiクラスのGetBuletStartPosition()メソッドで求めます。この部分は自機の先端部分になります。
生成されたBulletクラスのインスタンスはBulletのリスト Bulletsに格納します。
1 2 3 4 5 6 7 8 9 10 11 |
public class Stage { List<Bullet> Bullets = new List<Bullet>(); public void JikiShot() { Position position = Jiki.GetBuletStartPosition(); Bullet bullet = new Bullet(position.X, position.Y); Bullets.Add(bullet); } } |
1 2 3 4 5 6 7 |
public class Jiki { public Position GetBuletStartPosition() { return new Position(X + 1f, Y); } } |
1 2 3 4 5 6 7 8 9 10 11 |
public class Position { public Position(float x, float y) { X = x; Y = y; } public float X = 0f; public float Y = 0f; } |
StageクラスのUpdate()内で自機から発射された弾丸を前方へ移動させます。そしてDraw()でこれを描画します。
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 |
public class Stage { public void Update() { // 自機の移動 Direct direct = Form1.CurDirect; if(direct == Direct.Up) Jiki.Move(0, 0.5f); if(direct == Direct.Down) Jiki.Move(0, -0.5f); if(direct == Direct.Accelerate) Jiki.Move(0.5f, 0); // 通常の飛行 if(Jiki.X <= Form1.GetEyeX() + Jiki.startX) Jiki.Move(Form1.ScrollSpeed, 0); // ミサイルは発射されるか? MissilesLaunch(); MissilesMove(); // 自機から発射された弾丸を前方へ移動させる foreach(Bullet bullet in Bullets) bullet.Move(); } public void Draw() { DrawGround(); foreach(Bullet bullet in Bullets) bullet.Draw(); Jiki.Draw(); } } |
当たり判定も必要です。当たったかどうかは弾丸の矩形内に敵や地形が入ったかどうかで判定します。IsPiledRectangeメソッドは二つの矩形は重なっているかを調べて重なっている場合はtrueを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Stage { // 二つの矩形は重なっているか? bool IsPiledRectange(RectangleF rect1, RectangleF rect2) { if(rect1.Top > rect2.Bottom) return false; if(rect1.Right < rect2.Left) return false; if(rect1.Bottom < rect2.Top) return false; if(rect1.Left > rect2.Right) return false; return true; } } |
各クラスに弾丸やミサイルの当たり判定のための矩形を求めるメソッド DeadRectangleF()を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Bullet { // 中心の座標とオブジェクトの形状からオブジェクトを囲む矩形を取得し、これを返す public RectangleF DeadRectangleF() { float x = X - 0.25f; float y = Y - 0.25f; float width = 0.5f; float height = 0.04f; return new RectangleF(x, y, width, height); } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Missile { public RectangleF DeadRectangleF() { float x = X - 0.25f; float y = Y - 0.5f; float width = 0.5f; float height = 1f; return new RectangleF(x, y, width, height); } } |
1 2 3 4 |
public class Stage { static public float BlockAspect = 0.75f; } |
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Block { public RectangleF DeadRectangleF() { float x = X - 0.5f * Stage.BlockAspect; float y = Y - 0.5f; float width = 1f * Stage.BlockAspect; float height = 1f; return new RectangleF(x, y, width, height); } } |
当たり判定はStageクラスのUpdate()メソッドのなかでやればいいのですが、今後あれこれ追加すると長くなるかもしれないので分けました。
最後のGetObjectsHitBullet()メソッドが発射した弾丸が命中した敵(敵はミサイルだけではない)のリストを返します。
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 |
public class Stage { public void Update() { UpdateJikiPosition(); UpdateMissilesPosition(); UpdateBulletsPosition(); GetObjectsHitBullet(); } void UpdateJikiPosition() { Direct direct = Form1.CurDirect; if(direct == Direct.Up) Jiki.Move(0, 0.5f); if(direct == Direct.Down) Jiki.Move(0, -0.5f); if(direct == Direct.Accelerate) Jiki.Move(0.5f, 0); // 通常の飛行 if(Jiki.X <= Form1.GetEyeX() + Jiki.startX) Jiki.Move(Form1.ScrollSpeed, 0); } void UpdateMissilesPosition() { // ミサイルは発射されるか? MissilesLaunch(); MissilesMove(); } void UpdateBulletsPosition() { foreach(Bullet bullet in Bullets) bullet.Move(); } } |
GetObjectsHitBullet()メソッドを実行すれば、弾丸が命中した敵を取得することができます。この結果は点数計算をするときに必要になります。この処理を実行する前に、実行時に弾丸のなかで他のオブジェクトに命中したものや、右方向に飛んで画面上に存在しないものは取り除いています。
RemoveBulletsHitBlock()メソッドは地面に当たった弾丸を取り除きます。すべてのブロックについて調べる必要はありません。はるか右側にある見えないブロックやすでに通り過ぎた位置にあるブロックは調べる必要はありません。GetTestBlocks()メソッドで本当に調べる必要があるものだけ調べています。
GetMissilesHitBullet()メソッドは自機から発射された弾丸が命中したミサイルのオブジェクトを返します(同じタイミングで複数のミサイルを撃墜できるのは現実的にはなさそうですが・・・)。
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 |
public class Stage { void GetObjectsHitBullet() { RemoveBulletsHitBlock(); Bullets = Bullets.Where(x => !x.isDead && x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList(); // x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2 であれば // まだ右方向から外に飛び出していない List<Missile> hitMissiles = GetMissilesHitBullet(); } void RemoveBulletsHitBlock() { List<Block> blocks = GetTestBlocks(); foreach(Bullet bullet in Bullets) { bool isHit = false; RectangleF rect1 = bullet.DeadRectangleF(); foreach(Block block in blocks) { RectangleF rect2 = block.DeadRectangleF(); if(IsPiledRectange(rect1, rect2)) { bullet.isDead = true; isHit = true; break; } } if(isHit) continue; } Bullets = Bullets.Where(x => !x.isDead).ToList(); } List<Block> GetTestBlocks() { List<Block> ret = new List<Block>(); ret.AddRange(Type1s.Where(x => x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList()); ret.AddRange(Type2s.Where(x => x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList()); ret.AddRange(Type3s.Where(x => x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList()); ret.AddRange(Type4s.Where(x => x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList()); ret.AddRange(Type5s.Where(x => x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList()); return ret; } List<Missile> GetMissilesHitBullet() { List<Missile> ret = new List<Missile>(); List<Missile> missiles = Missiles.Where(x => !x.isDead).ToList(); foreach(Bullet bullet in Bullets) { RectangleF rect = bullet.DeadRectangleF(); Missile missile = missiles.FirstOrDefault(x => IsPiledRectange(rect, x.DeadRectangleF())); if(missile != null) { missile.isDead = true; bullet.isDead = true; ret.Add(missile); } } // 命中した弾丸はリストから取り除く Bullets = Bullets.Where(x => !x.isDead).ToList(); return ret; } } |
それから無制限に連射ができてしまうとゲームの難度が下がってしまうので、最後に発射された弾丸のX座標と自機のX座標にForm1.ProjectionWidthの3分の1以上の差ができないと弾丸を発射できないようにしました。
1 2 3 4 5 6 7 8 9 10 |
public class Stage { public bool CanShot() { if(Bullets.Min(x => x.X) - Jiki.X > Form1.ProjectionWidth / 3) return true; else return false; } } |
これでミサイルを撃墜することができるようになりました。スクランブルではまっすぐ前に飛ぶ弾丸の他に爆弾を投下して地上の敵を倒すことができます。次回は爆弾投下の機能を追加します。