前回は宇宙船を生成して移動させ描画する処理を実装しました。
今回は自機から弾丸を発射します。弾丸も残像があります。点を移動させその部分を記憶しておき、これを直線でつないで残像のように見せます。
弾丸を生成する
まずコンストラクタを示します。弾丸が通過した点を不透明度が異なる白線で描画するので、Penのリストを作成します。これは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 |
public class Burret { protected static List<Pen> AfterimagePens = new List<Pen>(); public Burret(double x, double y, double vx, double vy) { X = x; Y = y; VX = vx; VY = vy; if (AfterimagePens.Count == 0) AfterimagePens = CreatePens(AfterimageCount, 3); } public double X { get; protected set; } public double Y { get; protected set; } double VX; double VY; int AfterimageCount = 32; List<Pen> CreatePens(int afterimageCount, int width) { List<Pen> pens = new List<Pen>(); for (int i = 0; i < afterimageCount; i++) { int a = 255 - 255 / afterimageCount * i; pens.Add(new Pen(Color.FromArgb(a, Color.White), width)); } return pens; } } |
移動の処理を示します。弾丸はどこまでも飛び続けるわけではなく標的に命中しなくても途中で消滅します。Moveメソッドが実行されるたびにLifeが減り、0になったら消滅します。
1 2 3 4 5 6 7 8 9 10 11 |
public class Burret { int Life = 64; void Move() { X += VX; Y += VY; Life--; } } |
描画の処理を示します。これもPlayerクラスと同様に残像のように見せかけるために描画前に弾丸の位置をリストに保存して最新のAfterimageCount個だけを使います。宇宙船とちがって直線を描画するだけなので新しいものから不透明度を下げながら白線を描画しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Burret { List<Point> AfterimagePoints = new List<Point>(); void Draw(Graphics graphics) { AfterimagePoints.Insert(0, new Point((int)X, (int)Y)); AfterimagePoints = AfterimagePoints.Take(AfterimageCount).ToList(); int count = AfterimagePoints.Count; for (int i = 1; i < count; i++) { Point pointTo = AfterimagePoints[i - 1]; Point pointFrom = AfterimagePoints[i]; graphics.DrawLine(AfterimagePens[i], pointFrom, pointTo); } } } |
複数の弾丸を移動し描画する
次に複数の弾丸を移動し描画する処理を示します。これまで弾丸のリストをForm1クラスやGameManagerのようなクラスのなかに作成していましたが、Burretクラス内に静的メンバーをつくってそこに格納すれば移動と描画の処理はBurretクラス内で完結します。そこで今回はその方式を採用します。自機が発射した弾丸にあたっても死ぬ仕様にしたいのでこちらのほうが合理的かもしれません。
弾丸を発射したらAddBurretメソッドで静的メンバーであるリストに追加していきます。そして静的メソッドであるMoveAllメソッドでLifeが0以下になっている弾丸や死亡フラグが立っている弾丸は取り除いて移動させ、DrawAllメソッドで描画します。
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 Burret { public static List<Burret> Burrets = new List<Burret>(); public bool IsDead = false; public static void AddBurret(Burret burret) { Burrets.Add(burret); } public static void MoveAll() { Burrets = Burrets.Where(x => x.Life > 0 && !x.IsDead).ToList(); foreach (Burret burret in Burrets) burret.Move(); } public static void DrawAll(Graphics graphics) { foreach (Burret burret in Burrets) burret.Draw(graphics); } } |
弾丸の当たり判定
次に当たり判定に関する処理を示します。Playerクラスに以下を追加します。
IsPointInsideメソッドは引数で渡されたPointがAngle度だけ回転している宇宙船が描画されている矩形の内部にあるかどうかを判定します。これは回転した矩形は重なっているか?とほぼ同じものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Player { public bool IsPointInside(Point point) { Matrix matrix = new Matrix(); matrix.RotateAt(-Angle, new Point((int)CenterX, (int)CenterY)); Point[] points = { point, }; matrix.TransformPoints(points); int x = (int)CenterX - Size.Width / 2; int y = (int)CenterY - Size.Height / 2; Rectangle rectangle = new Rectangle(x, y, Size.Width, Size.Height); if (rectangle.Left > points[0].X || rectangle.Right < points[0].X) return false; if (rectangle.Top > points[0].Y || rectangle.Bottom < points[0].Y) return false; return 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 class Player { public bool DoesRectangleOverlap(Player player) { Matrix matrix = new Matrix(); matrix.RotateAt(-Angle, new Point((int)CenterX, (int)CenterY)); Point[] points = player.GetBorderPoints(); matrix.TransformPoints(points); Rectangle rectangle = new Rectangle( (int)CenterX - Size.Width / 2, (int)CenterY - Size.Height / 2, Size.Width, Size.Height ); foreach (Point pt in points) { if (rectangle.Left > pt.X || rectangle.Right < pt.X) continue; if (rectangle.Top > pt.Y || rectangle.Bottom < pt.Y) continue; return true; } return false; } Point[] GetBorderPoints() { Matrix matrix = new Matrix(); matrix.RotateAt(Angle, new Point((int)CenterX, (int)CenterY)); Rectangle rectangle = new Rectangle( (int)CenterX - Size.Width / 2, (int)CenterY - Size.Height / 2, Size.Width, Size.Height ); List<Point> pointList = new List<Point>(); for (int x = rectangle.Left; x <= rectangle.Right; x++) pointList.Add(new Point(x, rectangle.Top)); for (int y = rectangle.Top; y <= rectangle.Bottom; y++) pointList.Add(new Point(rectangle.Right, y)); for (int x = rectangle.Right; x >= rectangle.Left; x--) pointList.Add(new Point(x, rectangle.Bottom)); for (int y = rectangle.Bottom; y >= rectangle.Top; y--) pointList.Add(new Point(rectangle.Left, y)); Point[] points = pointList.ToArray(); matrix.TransformPoints(points); return points; } } |
それからSpacewar!には中心に太陽があり、太陽の近くでは引力が働き太陽に衝突するとミスとなります。この当たり判定の処理は別のところで示します。
爆発の描画
次に爆発の描画をするために必要なSparkクラスを作成します。
火花の生成と描画
コンストラクタ内で最初に描画される座標と火花の移動速度を指定します。爆発で周囲に飛ぶ火花も残像があります。これも最初に描画のために必要なPenのリストを生成して、静的メンバーにこれを格納して使い回します。
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 Spark { int Life = 120; int AfterimageCount = 32; static List<Pen> AfterimagePens = new List<Pen>(); public Spark(double x, double y, double vx, double vy) { X = x; Y = y; VX = vx; VY = vy; if (AfterimagePens.Count == 0) AfterimagePens = CreatePens(AfterimageCount); } public double X { get; protected set; } public double Y { get; protected set; } double VX; double VY; List<Pen> CreatePens(int afterimageCount) { List<Pen> pens = new List<Pen>(); for (int i = 0; i < afterimageCount; i++) { int alpha = 255 - 255 / afterimageCount * i; pens.Add(new Pen(Color.FromArgb(alpha, Color.White))); } return pens; } } |
火花の移動と描画の処理を示します。
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 |
public class Spark { void Move() { X += VX; Y += VY; Life--; } List<Point> AfterimagePoints = new List<Point>(); void Draw(Graphics graphics) { AfterimagePoints.Insert(0, new Point((int)X, (int)Y)); AfterimagePoints = AfterimagePoints.Take(AfterimageCount).ToList(); int count = AfterimagePoints.Count; for (int i = 1; i < count; i++) { Point pointTo = AfterimagePoints[i - 1]; Point pointFrom = AfterimagePoints[i]; graphics.DrawLine(AfterimagePens[i], pointFrom, pointTo); } } } |
新しく火花を追加したり、すべての火花を移動させ描画する処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Spark { static List<Spark> Sparks = new List<Spark>(); public static void AddSpark(Spark spark) { Sparks.Add(spark); } public static void MoveAll() { Sparks = Sparks.Where(x => x.Life > 0).ToList(); foreach (Spark spark in Sparks) spark.Move(); } public static void DrawAll(Graphics graphics) { foreach (Spark spark in Sparks) spark.Draw(graphics); } } |
爆発の描画
最後に爆発の処理を示します。乱数で爆発地点から火花がどの方向にどのくらいの速度でいつまで移動するのかを決めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Spark { static Random Random = new Random(); public static void Explosion(int x, int y) { for (int i = 0; i < 128; i++) { double r = 1.5 + Random.NextDouble() * 1.5; double rad = Random.NextDouble() * Math.PI * 2; double vx = r * Math.Cos(rad); double vy = r * Math.Sin(rad); int life = 25 + Random.Next(5); Spark spark = new Spark(x, y, vx, vy); spark.Life = life; Sparks.Add(spark); } } } |