完成品はこんな感じです。
待機中の敵を移動させる
タイマーイベントが発生したときにすることとして
- 自機の移動
- 自機から発射された弾丸の移動
- 待機中の敵の移動
- 敵を待機状態から攻撃状態へ移行させる
- 攻撃状態にある敵の移動と弾丸の発射
- 敵の弾丸の移動
- あたり判定
があります。待機状態にある敵を移動させます。待機状態にあるかどうかはEnemyクラスのフィールド変数EnemyStatusをみればわかります。
そして敵X座標の最大値と最小値から端まで移動したか調べて、必要なら移動方向の転換をおこなっています。
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 |
int ENEMY_WIDTH = 30; int ENEMY_HEIGHT = 30; int EMEMY_MOVE_SPEED = 1; void StandingByEnemysMove() { var StandbyEnemies = EnemyManager.Enemies.Where(x => x.EnemyStatus == EnemyStatus.Standby).ToList(); if (StandbyEnemies.Count == 0) return; if (StandbyEnemies.Min(x=> x.Location.X) < 0) EnemyManager.Direct = Direct.Right; if(StandbyEnemies.Max(x => x.Location.X + ENEMY_WIDTH) > panel1.Width) EnemyManager.Direct = Direct.Left; foreach (var enemy in StandbyEnemies) { Point pt = enemy.Location; if (EnemyManager.Direct == Direct.Left) pt.X -= 1; else pt.X += 1; enemy.Location = pt; } foreach (var enemy in EnemyManager.Enemies) { Point pt = enemy.OldPos; if (EnemyManager.Direct == Direct.Left) pt.X -= 1; else pt.X += 1; enemy.OldPos = pt; } } public class Enemy : PictureBox { int _id = 0; public Point OldPos = new Point(0, 0); // これを追加した public EnemyStatus EnemyStatus = EnemyStatus.Standby; public int ID { get { return _id; } } public Direct AttackDirect = Direct.None; public static Enemy CreateEnemy(int x, int y, int width, int height, int id, Image image, Panel panel) { Enemy enemy = new Enemy(); enemy.Size = new Size(width, height); enemy.Location = new Point(x, y); enemy.Parent = panel; enemy.Image = image; enemy.SizeMode = PictureBoxSizeMode.StretchImage; enemy.OldPos = enemy.Location; // これを追加した enemy._id = id; return enemy; } } |
それから敵を生成したときに、その座標を記憶し、待機中の敵が移動するとそれに応じてその座標を変化させています。攻撃してきた敵が下までくると、元の位置に戻す必要があります。そのときにこの座標が必要になります。
攻撃に参加する敵を決める
待機中の敵が攻撃にうつるためのメソッドです。
攻撃状態に移行する敵を決める
攻撃は単体か?編隊か?
攻撃を開始するかどうかは1秒ごとに乱数をつかって判定し、攻撃する場合はまた乱数をつかって攻撃に参加する敵を決めます。その攻撃は単体でおこなわれるのか、それとも複数機による編隊攻撃なのかも決めます。
タイマーイベントは50ミリ秒ごとに発生するので、
タイマーイベントの回数をカウントして20で割り切れる場合は1秒
と考えます。
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 |
int countTimerTick = 0; private void Timer1_Tick(object sender, EventArgs e) { countTimerTick++; SpaceshipMove(); BulletsMove(); StandingByEnemysMove(); BeginAttack(); AttackEnemiesMove(); } Random r = new Random(); void BeginAttack() { if (countTimerTick % 20 != 0) return; var StandbyEnemies = EnemyManager.Enemies.Where(x => x.EnemyStatus == EnemyStatus.Standby).ToList(); if (StandbyEnemies.Count == 0) return; // 確率は3分の1 int i = r.Next(3); if (i == 0) { // 攻撃に参加するのは各列の端にいる敵 // 候補を列挙 // IDは一番上 0~ // 2列目 100~ // 3列目 200~ // 4列目 300~ なのでこれらの最大値と最小値を調べる List<int> ids = new List<int>(); if (StandbyEnemies.Where(x ==> x.ID < 100).Any()) { ids.Add(StandbyEnemies.Where(x => x.ID < 100).Min(x => x.ID)); ids.Add(StandbyEnemies.Where(x => x.ID < 100).Max(x => x.ID)); } if (StandbyEnemies.Where(x => 100 <= x.ID && x.ID < 200).Any()) { ids.Add(StandbyEnemies.Where(x => 100 <= x.ID && x.ID < 200).Min(x => x.ID)); ids.Add(StandbyEnemies.Where(x => 100 <= x.ID && x.ID < 200).Max(x => x.ID)); } if (StandbyEnemies.Where(x => 200 <= x.ID && x.ID < 300).Any()) { ids.Add(StandbyEnemies.Where(x => 200 <= x.ID && x.ID < 300).Min(x => x.ID)); ids.Add(StandbyEnemies.Where(x => 200 <= x.ID && x.ID < 300).Max(x => x.ID)); } if (StandbyEnemies.Where(x => 300 <= x.ID && x.ID < 400).Any()) { ids.Add(StandbyEnemies.Where(x => 300 <= x.ID && x.ID < 400).Min(x => x.ID)); ids.Add(StandbyEnemies.Where(x => 300 <= x.ID && x.ID < 400).Max(x => x.ID)); } i = r.Next(ids.Count); int id = ids[i]; // 攻撃開始をする敵が確定 Enemy enemy = StandbyEnemies.First(x => x.ID == id); enemy.EnemyStatus = EnemyStatus.Attack1; enemy.AttackDirect = EnemyManager.Direct; // 編隊攻撃なのか? // 編隊を組む相手はいるのか? // IDが一つ上または下 // 99から101を足したものがあるかどうか? // 確率は3分の1 i = r.Next(3); if (i == 0) { // 単独攻撃 } if (i == 1) { // 2機編隊 Enemy enemy1 = StandbyEnemies.FirstOrDefault(x => (x.ID == id - 1) || (x.ID == id + 1) || (x.ID == id + 99) || (x.ID == id + 100) || (x.ID == id + 101) ); if (enemy1 != null) { enemy1.EnemyStatus = EnemyStatus.Attack1; enemy1.AttackDirect = EnemyManager.Direct; } } if (i == 2) { // 3機編隊 var enemies = StandbyEnemies.Where(x => (x.ID == id - 1) || (x.ID == id + 1) || (x.ID == id + 99) || (x.ID == id + 100) || (x.ID == id + 101) ).Take(2).ToList(); foreach (var enemy1 in enemies) { enemy1.EnemyStatus = EnemyStatus.Attack1; enemy1.AttackDirect = EnemyManager.Direct; } } } } |
クソ長いメソッドになってしまいましたが、攻撃に参加する敵の候補になるものを調べて乱数をつかって決定しています。攻撃は単体か編隊か、編隊のときはその相手になるものはどれかもあわせて処理をしています。
攻撃中の敵を動かす
AttackEnemiesMoveメソッドで攻撃に参加している敵を動かします。両端にあたると跳ね返って移動します。単純な動きです。
Enemyクラスに attachTime というフィールド変数を追加して、一定間隔で弾丸を発射しています。
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 |
void AttackEnemiesMove() { var AttackEnemies = EnemyManager.Enemies.Where(x => x.EnemyStatus == EnemyStatus.Attack1).ToList(); foreach (Enemy enemy in AttackEnemies) { Point pt = enemy.Location; if (enemy.Location.X < 0) enemy.AttackDirect = Direct.Right; if (enemy.Location.X + ENEMY_WIDTH > panel1.Width) enemy.AttackDirect = Direct.Left; if (enemy.AttackDirect == Direct.Right) pt.X += 5; else pt.X -= 5; pt.Y += 5; enemy.Location = pt; enemy.attachTime++; int interval = 15; if (enemy.ID < 100) interval -= 4; else if(enemy.ID < 200) interval -= 2; if (enemy.attachTime % interval == 0) { EnemyBulletLaunch(enemy); } if (enemy.Location.Y > panel1.Height) { enemy.EnemyStatus = EnemyStatus.Standby; enemy.Location = enemy.OldPos; } } } |
敵に弾丸を発射させる
敵に弾丸を発射させるためのメソッドです。敵のタイプによって連射間隔や弾丸のスピードを変化させています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int BULLET_SPEED_A = 9; int BULLET_SPEED_B = 8; int BULLET_SPEED_C = 7; void EnemyBulletLaunch(Enemy enemy) { int bulletSpeed = BULLET_SPEED_C; if (enemy.ID < 100) bulletSpeed = BULLET_SPEED_A; else if (enemy.ID < 200) bulletSpeed = BULLET_SPEED_B; EnemyBullet bullet = EnemyBullet.CreateBullet(enemy, bulletSpeed); EnemyManager.EnemyBullets.Add(bullet); } |
敵の弾丸の生成はピクチャーボックスではなくそれを継承したクラスを使っています。敵の種類によって弾丸のスピードが違うので、それを管理しやすくするためです。
生成したら EnemyManagerクラスに管理を任せています。
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 |
public class EnemyBullet : PictureBox { public int speed = 0; static int ENEMY_BULLET_WIDTH = 2; static int ENEMY_BULLET_HEIGHT = 8; public static EnemyBullet CreateBullet(Enemy enemy, int speed) { int center = (enemy.Left + enemy.Right) / 2; EnemyBullet bullet = new EnemyBullet(); bullet.Location = new Point(center, enemy.Bottom); bullet.Size = new Size(ENEMY_BULLET_WIDTH, ENEMY_BULLET_HEIGHT); bullet.BackColor = Color.Red; bullet.Parent = enemy.Parent; bullet.speed = speed; return bullet; } } public class EnemyManager { static List<EnemyBullet> _enemyBullets = new List<EnemyBullet>(); static public List<EnemyBullet> EnemyBullets { get { _enemyBullets = _enemyBullets.Where(x => !x.IsDisposed).ToList(); return _enemyBullets; } } static public void EnemyBulletsMove() { foreach (EnemyBullet bullet in EnemyBullets) { Point pt = bullet.Location; pt.Y += bullet.speed; bullet.Location = pt; if (pt.Y > bullet.Parent.Height) bullet.Dispose(); } } } |
画面の下まで移動した弾丸は Dispose しています。
Timer1_Tickメソッドは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 |
private void Timer1_Tick(object sender, EventArgs e) { countTimerTick++; SpaceshipMove(); BulletsMove(); StandingByEnemysMove(); BeginAttack(); AttackEnemiesMove(); EnemyManager.EnemyBulletsMove(); } |
次回は当たり判定、加点、ミス時の処理などをおこないます。