ボスコニアンのようなオンライン対戦ゲームをつくる(1)の続きです。敵の動作を実装します。
Contents
Enemyクラスの定義
敵を移動させたり描画するためにEnemyクラスを定義します。
1 2 3 4 5 6 |
namespace Bosconian { public class Enemy { } } |
以降は名前空間の部分は省略して書きます。
1 2 3 |
public class Enemy { } |
コンストラクタとプロパティ
コンストラクタと各プロパティを示します。
オブジェクトが生成されたら弾丸オブジェクトを32個生成して以降はこれを使い回します。
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 |
public class Enemy { List<Bullet> _bullets = new List<Bullet>(); public Enemy() { // 初期の更新回数にばらつきを持たせる(敵弾を発射するタイミングをそれぞれズラすため) UpdateCount = _random.Next(100); _bullets = new List<Bullet>(); for (int i = 0; i < 32; i++) _bullets.Add(new Bullet()); Init(); // 後述 } // 座標 public int X { private set; get; } public int Y { private set; get; } // 移動速度 public int VX { private set; get; } public int VY { private set; get; } // 敵のタイプ(色は黄色?赤?青?) public int Type { private set; get; } // 更新回数 public int UpdateCount { get; private set; } // 敵は生成されたばかりか?(当たり判定の対象から外す) public bool IsJustBorn { private set; get; } // 発射されフィールド上に存在する弾丸のリスト public List<Bullet> Bullets { get { return _bullets.Where(bullet => !bullet.IsDead).ToList(); } } } |
初期化の処理
敵を初期化する処理を示します。ゲームが開始されたときや敵が撃墜されたときは敵を初期化します。敵をランダムな位置に配置してX方向とY方向にそれぞれ{ -2, -1, 1, 2 }のなかからどれかを選んで初速を与えます。そしてIsJustBornフラグをセットします。これは3秒後にクリアします。IsJustBornフラグがセットされているあいだは当たり判定の対象から除外されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Enemy { static Random _random = new Random(); int[] _v = { -2, -1, 1, 2 }; public void Init() { Type = _random.Next(3); X = _random.Next(Const.FIELD_SIZE - Const.CHARACTER_SIZE * 4) + Const.CHARACTER_SIZE * 2; Y = _random.Next(Const.FIELD_SIZE - Const.CHARACTER_SIZE * 4) + Const.CHARACTER_SIZE * 2; VX = _v[_random.Next(_v.Length)]; VY = _v[_random.Next(_v.Length)]; // 3秒間、当たり判定の対象から外す IsJustBorn = true; Task.Run(async () => { await Task.Delay(3000); IsJustBorn = false; }); } } |
移動の処理
敵を移動させる処理を示します。座標を速度ぶん移動させます。フィールドの端から外に出た場合は反対側にワープさせます。そのあと発射された弾丸の移動処理も行ないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Enemy { public void Move() { X += VX; Y += VY; if (X > Const.FIELD_SIZE) X -= Const.FIELD_SIZE; if (X < 0) X += Const.FIELD_SIZE; if (Y > Const.FIELD_SIZE) Y -= Const.FIELD_SIZE; if (Y < 0) Y += Const.FIELD_SIZE; // 更新回数をインクリメントする(敵弾を発射する処理で使う) UpdateCount++; foreach (Bullet bullet in Bullets) bullet.Move(); } } |
未使用の弾丸を取得する
弾丸を発射するときは_bulletsに格納されているものを使い回すのですが、新たに発射されるものはすでに発射されフィールド上に存在するものとは別のものでなければなりません。GetNewBulletメソッドは未使用の弾丸を返します。
1 2 3 4 5 6 7 |
public class Enemy { public Bullet? GetNewBullet() { return _bullets.FirstOrDefault(bullet => bullet.IsDead); } } |
Cannonクラスの定義
ボスコニアンに出てくる敵の要塞は中心にコアがあり、周囲に6つの砲台があります。先に本体部分ではなく砲台部分にあたるCannonクラスを定義します。
砲台に番号をつけます。要塞のすべての砲台から一斉に弾丸が発射されては困るので、番号によって時間差をつけることができるようにします。
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 |
namespace Bosconian { public class Cannon { public Cannon(int num) { Number = num; } public double X { set; get; } // 座標 public double Y { set; get; } // 砲台の番号 public int Number { private set; get; } // 砲台は破壊されているか? public bool IsDead { set; get; } } } |
Fortressクラスの定義
要塞の本体部分であるFortressクラスを定義します。
1 2 3 4 5 6 |
namespace Bosconian { public class Fortress { } } |
以降は名前空間の部分を省略して書きます。
1 2 3 |
public class Fortress { } |
コンストラクタとプロパティ
コンストラクタと各プロパティを示します。
弾丸と砲台に関しては他のクラスと同様に最初に生成したものを使い回します。
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 Fortress { static Random _random = new Random(); List<Bullet> _bullets = new List<Bullet>(); List<Cannon> _cannon = new List<Cannon>(); public Fortress() { for (int i = 0; i < 6; i++) _cannon.Add(new Cannon(i)); for (int i = 0; i < 64; i++) _bullets.Add(new Bullet()); Init(); // 後述 } // 中心部分の座標 public int X { set; get; } public int Y { set; get; } // 要塞の半径 public int Radius { get { return 48; } } // 要塞は生成されたばかりか? public bool IsJustBorn { private set; get; } // 更新回数(砲台からの弾丸の発射で必要) public int UpdateCount { set; get; } // 生きのこっている砲台のリスト public List<Cannon> Cannons { get { return _cannon.Where(cannon => !cannon.IsDead).ToList(); } } // フィールド上に存在する弾丸のリスト public List<Bullet> Bullets { get { return _bullets.Where(bullet => !bullet.IsDead).ToList(); } } } |
初期化と更新処理
要塞を初期化する処理を示します。
乱数で要塞をフィールド上にランダムに配置します。また中心部分の座標が決まれば6つの砲台の座標も決まります。このとき砲台の死亡フラグをクリアします。
要塞が初期化されたら、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 |
public class Fortress { public void Init() { // フィールド上にランダムに配置 X = _random.Next(Const.FIELD_SIZE - 100) + 50; Y = _random.Next(Const.FIELD_SIZE - 100) + 50; UpdateCount = 0; // コアの周囲に6個の砲台を配置する for (int i = 0; i < _cannon.Count; i++) { _cannon[i].IsDead = false; // 砲台の死亡フラグをクリア // 中心座標が定まれば各砲台の座標も決まる double rad = 2 * Math.PI * i / 6; _cannon[i].X = X + Radius * Math.Cos(rad); _cannon[i].Y = Y + Radius * Math.Sin(rad); } // 3秒間、当たり判定の対象から外す IsJustBorn = true; Task.Run(async () => { await Task.Delay(3000); IsJustBorn = false; }); } } |
更新処理を示します。ここではUpdateCountをインクリメントしているだけです。またGetNewBulletメソッドは_bulletのなかからフィールド上に存在しない未使用の弾丸オブジェクトを返します。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Fortress { public void Update() { UpdateCount++; } public Bullet? GetNewBullet() { return _bullet.FirstOrDefault(bullet => bullet.IsDead); } } |