スクランブルでは戦闘機は左から右方向へ移動しています。しかし実際には前方向には移動はしないで(加速・減速した場合は別)、もとの位置にとどまっています。そして背景が右から左方向に移動しています。
まず自機の移動距離を調べます。そして山や谷に対する相対距離を調べて移動しているように表示させます。
Jikiクラスに新しいフィールド変数を追加します。displacementはゲームの進行とともに増加していきます。これによって地形は右から左へと移動していきます。
1 2 3 4 |
public class Jiki { public int Displacement = 0; } |
地形の描画はTopographyクラスが担当しているのですが、自機がどれだけ移動したかがわからないと地形をどのように描画していいのかわかりません。そこでTopographyクラスのコンストラクタに引数を渡すことでForm1クラス内のフィールド変数やプロパティにアクセスが可能になるようにします。
1 2 3 4 5 6 7 8 |
class Topography { Form1 MainForm = null; public Topography(Form1 form) { MainForm = form; } } |
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 |
public partial class Form1 : Form { public Jiki Jiki = new Jiki(); int jikiSpeed = 5; // 自機のスピードは5 Topography Topography = null; public Form1() { InitializeComponent(); Topography = new Topography(this); InitPanelEx(); InitTimer(); } void TimerTick_JikiMove() { // 自機を前進させる(背景を後ろに移動させる) Jiki.Displacement += jikiSpeed; if(JikiDirect == JikiDirect.Up) Jiki.MoveUp(); if(JikiDirect == JikiDirect.Down) Jiki.MoveDown(); if(JikiDirect == JikiDirect.Left) Jiki.MoveLeft(); if(JikiDirect == JikiDirect.Right) Jiki.MoveRight(); } } |
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 |
public class Topography { Size blockSize = new Size(20, 20); public void Show(Graphics g) { SolidBrush brush = new SolidBrush(Color.Red); List<string> listTopography = GetListTopography(); Point[] UphillTreePoints = GetTreePointsUphill(); Point[] DownhillTreePoints = GetTreePointsDownhill(); int displacement = MainForm.Jiki.Displacement; int y = 0; foreach(string str in listTopography) { Char[] vs = str.ToArray(); int x = 0; foreach(Char c in vs) { if(c == '■') { g.FillRectangle(brush, new Rectangle(new Point(x * blockSize.Width - displacement, y * blockSize.Height), blockSize)); } if(c == '▲') { Point[] pts = UphillTreePoints.Select(x1 => { x1.X += x * blockSize.Width; x1.Y += y * blockSize.Height; return x1; }).ToArray(); pts = pts.Select(x1 => { x1.X -= displacement; return x1; }).ToArray(); g.FillPolygon(brush, pts); } if(c == '△') { Point[] pts = DownhillTreePoints.Select(x1 => { x1.X += x * blockSize.Width; x1.Y += y * blockSize.Height; return x1; }).ToArray(); pts = pts.Select(x1 => { x1.X -= displacement; return x1; }).ToArray(); g.FillPolygon(brush, pts); } x++; } y++; } } } |
自機が山(?)に衝突した場合はミスとなります。地形と自機の当たり判定について考えてみることにします。地形を構成するブロックと自機が接触した場合がこれに該当します。また自機の後方からジェット噴射のようなものが出ていますが、これと山が接触しても衝突したとはならないのではないでしょうか?
そこでまずはどの部分に接触した場合がダメなのかを考えます。
この画像の緑の枠で囲ったあたりであれば自機に接触したと考えていいのではないでしょうか?
であるとすれば当たり判定は、自機の左上の座標を(x,y)とすると(X+30, Y+5)、, 幅44ピクセル、高さ18ピクセルの矩形内部となります。
地形は矩形の集まりなので、これと自機の位置を比較することで山などに衝突しているか判断することができます。
Topography.GetRectangles()メソッドは地形を構成する矩形の配列を返します。ただし自機の後ろに過ぎ去ったものは取得しません。
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 |
public class Topography { public Rectangle[] GetRectangles() { List<string> listTopography = GetListTopography(); int displacement = MainForm.Jiki.Displacement; int y = 0; List<Rectangle> rects = new List<Rectangle>(); foreach(string str in listTopography) { Char[] vs = str.ToArray(); int x = 0; foreach(Char c in vs) { if(c == '■' || c == '▲' || c == '△') { if(x * blockSize.Width - displacement >= 0) rects.Add(new Rectangle(new Point(x * blockSize.Width - displacement, y * blockSize.Height), blockSize)); } x++; } y++; } return rects.ToArray(); } } |
自機の本体部分の内部(ジェット噴射の部分ではない部分)に敵キャラや地形のブロックがある場合はミスとなります。
まず自機の死亡判定を得るための矩形を取得するプロパティを作成します。
1 2 3 4 5 6 7 |
public class Jiki { public Rectangle DeadRectangle { get { return new Rectangle(X + 30, Y + 5, 44, 18); } } } |
これは与えられた矩形が自機のDeadRectangle内にあるかどうかを判定するメソッドです。自機の上部が敵キャラや地形の下部よりも下にある場合や自機の右側が敵キャラの左側よりも左にある場合は接触していないことになります。接触していない条件4つのうち、どれかひとつを満たしていれば接触していないということになります。
1 2 3 4 5 6 7 8 9 10 |
public class Jiki { bool IsDead(Rectangle rect) { if(DeadRectangle.Top > rect.Bottom || DeadRectangle.Right < rect.Left || DeadRectangle.Bottom < rect.Top || DeadRectangle.Left > rect.Right) return false; else return true; } } |
敵キャラや地形のブロックの配列を渡してどれも IsDead(Rectangle rect) == trueにならなかった場合は自機にはなにも衝突していないことになります。ひとつでも衝突しているものがあれば死亡判定となります。
1 2 3 4 5 6 7 8 9 10 |
public class Jiki { public bool IsDead(Rectangle[] rectangles) { if(rectangles.Any(x => IsDead(x))) return true; else return false; } } |
Form1クラスのイベントハンドラTimer_Tickで自機がやられていないかチェックをしています。CheckIsDead()メソッドでは地形を構成する矩形の配列を取得して、Jiki.IsDeadメソッドに渡して当たり判定をしています。そして地面に接触したのであれば、タイマーを止めてメッセージボックスを表示しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { private void Timer_Tick(object sender, EventArgs e) { TimerTick_JikiMove(); CheckIsDead(); panelEx1.Invalidate(); } void CheckIsDead() { bool isDead = Jiki.IsDead(Topography.GetRectangles()); if(isDead) { Timer.Stop(); MessageBox.Show("Miss"); } } } |
再度ゲームを開始するときは最初に戻す必要があります。フィールド変数Displacementを0に戻す必要があります。また自機の出現位置を最初と同じ位置に戻す必要もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { private void GameStartMenuItem_Click(object sender, EventArgs e) { JikiInit(); Timer.Start(); } void JikiInit() { Jiki.Displacement = 0; Jiki.X = 0; Jiki.Y = 0; JikiDirect = JikiDirect.None; Jiki.DoesShow = true; } } |