ソースコードはGitHubで公開しています。⇒ https://github.com/mi3w2a1/SimpleShooter
前回までに作成したシューティングゲームでは爆発の描画がありませんでした。そのため弾丸が命中するとそのまま消えてしまってシューティングゲームとしては物足りないものになっていました。今回は爆発の描画をすることにします。
色つきの円を移動させて爆発しているかのように見せかけます。以下のようなクラスを作成します。
コンストラクタ内で爆発っぽくみせる色を6色リストにいれています。これをランダムに表示させます。引数は開始時の座標と移動量です。16回移動したら(約4分の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 |
public class Ellipse { public Ellipse(int x, int y, double vx, double vy) { X = x; Y = y; VX = vx; VY = vy; IsDead = false; Colors.Add(Color.FromArgb(0xff, 0x7f, 0x50)); Colors.Add(Color.FromArgb(0xff, 0x63, 0x47)); Colors.Add(Color.FromArgb(0xff, 0x45, 0x00)); Colors.Add(Color.FromArgb(0xff, 0x00, 0x00)); Colors.Add(Color.FromArgb(0xff, 0x8c, 0x00)); Colors.Add(Color.FromArgb(0xff, 0xff, 0x00)); } List<Color> Colors = new List<Color>(); double X { get; set; } double Y { get; set; } double VX { get; set; } double VY { get; set; } public bool IsDead { get; set; } int MoveCount = 0; public void Move() { X += VX; Y += VY; MoveCount++; } static Random Random = new Random(); public void Draw(Graphics graphics) { if (MoveCount < 16) { Color color = Colors[Random.Next(Colors.Count)]; graphics.FillEllipse(new SolidBrush(color), new Rectangle((int)X, (int)Y, 4, 4)); } else 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 55 |
public partial class Form1 : Form { List<Ellipse> Ellipses = new List<Ellipse>(); void Explosion(int centerX, int centerY) { // 128個の直径4ピクセルの赤~オレンジ色の円をつくる // 乱数でうまい具合に広がるようにする for (int i = 0; i < 128; i++) { double vx = (Random.Next(40) - 20) / 10.0; double vy = (Random.Next(40) - 20) / 10.0; Ellipses.Add(new Ellipse(centerX, centerY, vx, vy)); } } void EnemyDead(Enemy enemy) { // 敵の中心が爆発の中心になるようにする int x = enemy.X + this.EnemyBitmap1.Width / 2; int y = enemy.Y + this.EnemyBitmap1.Height / 2; Explosion(x, y); } void JikiDead() { // 自機の中心が爆発の中心になるようにする int x = Jiki.X + this.JikiBitmap.Width / 2; int y = Jiki.Y + this.JikiBitmap.Height / 2; Explosion(x, y); } private void Timer_Tick(object sender, EventArgs e) { // 省略 //最後のほうにこれを追加 Ellipses = Ellipses.Where(x => !x.IsDead).ToList(); foreach (Ellipse ellipse in Ellipses) ellipse.Move(); Invalidate(); } protected override void OnPaint(PaintEventArgs e) { // 省略 //最後のほうにこれを追加 foreach (Ellipse ellipse in Ellipses) ellipse.Draw(e.Graphics); base.OnPaint(e); } } |
さて、ここまでつくったのでスコア表示もできるようにしてしまいましょう。
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 |
public partial class Form1 : Form { int Score = 0; void ShowScore(Graphics graphics) { string scoreString = Score.ToString("00000"); graphics.DrawString(scoreString, new Font("MS ゴシック", 18), new SolidBrush(Color.White), new Point(10,10)); } void EnemyDead(Enemy enemy) { int x = enemy.X + this.EnemyBitmap1.Width / 2; int y = enemy.Y + this.EnemyBitmap1.Height / 2; Explosion(x, y); Score += 10; // 10点追加 } protected override void OnPaint(PaintEventArgs e) { // 省略 //最後のほうにこれを追加 ShowScore(e.Graphics); base.OnPaint(e); } } |
自機が死亡したらゲームオーバーです。一発終了です。一発終了は厳しすぎるというのであれば残機制とかシールド制に書き換えてみましょう。
ゲームオーバーになったらGame Over、Retry Press S Keyと表示させます。
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 partial class Form1 : Form { protected override void OnPaint(PaintEventArgs e) { // 省略 //最後のほうにこれを追加 ShowGameOverIfDead(e.Graphics); base.OnPaint(e); } void ShowGameOverIfDead(Graphics graphics) { if (!Jiki.IsDead) return; string gameOver = "Game Over"; Font gameOverFont = new Font("MS ゴシック", 32); string retry = "Retry Press S Key"; Font retryFont = new Font("MS ゴシック", 24); // 描画される文字の幅、高さを調べるために画面の外側に書いてみる Point pt = new Point(10, this.Height); Size gameOverSize = TextRenderer.MeasureText(graphics, gameOver, gameOverFont, new Size(this.ClientSize.Width, this.ClientSize.Height), TextFormatFlags.NoPadding); Size retrySize = TextRenderer.MeasureText(graphics, retry, retryFont, new Size(this.ClientSize.Width, this.ClientSize.Height), TextFormatFlags.NoPadding); // 描画される文字の幅、高さが取得できる int gameOverWidth = gameOverSize.Width; int gameOverHeight = gameOverSize.Height; int retryWidth = retrySize.Width; int retryHeight = retrySize.Height; // 画面の中央になるように「Game Over」と描画する int gameOverX = (this.ClientSize.Width - gameOverWidth) / 2; int gameOverY = (this.ClientSize.Height - gameOverHeight) / 2; graphics.DrawString(gameOver, gameOverFont, new SolidBrush(Color.White), new Point(gameOverX, gameOverY)); // その下に「Retry Press S Key」と描画する int retryX = (this.ClientSize.Width - retryWidth) / 2; int retryY = gameOverY + gameOverHeight + 20; graphics.DrawString(retry, retryFont, new SolidBrush(Color.White), new Point(retryX, retryY)); } } |
実際にSキーをおしたらゲームに再挑戦できるようにしなければなりません。
現状ではゲームオーバーになっても弾丸の発射ができるようになっていたので修正しました。Sキーが押されたら
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 |
public partial class Form1 : Form { protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Left) Jiki.MoveLeft = true; if (e.KeyCode == Keys.Right) Jiki.MoveRight = true; if (e.KeyCode == Keys.Up) Jiki.MoveUp = true; if (e.KeyCode == Keys.Down) Jiki.MoveDown = true; if (e.KeyCode == Keys.Space && !Jiki.IsDead) Shot(); if (e.KeyCode == Keys.S && Jiki.IsDead) Retry(); base.OnKeyDown(e); } void Retry() { // 敵と敵の弾丸をクリアする Enemies.Clear(); EnemyBurrets.Clear(); // 自機を初期位置に戻し、死亡状態から回復させる Jiki.X = (FormClientSize.Width - JikiBitmap.Width) / 2; Jiki.Y = (int)(FormClientSize.Height * 0.8); Jiki.IsDead = false; // スコアを0に戻してゲームスタート Score = 0; } } |