今回はボスコニアンの要塞を描画します。ビットマップからテクスチャを作成します。ビットマップは他の人がYouTubeにアップしているプレイ動画から取得します。
まずコンストラクタですが、引数をひとつ増やしています。中央にあるコアに弾丸を撃ち込めば破壊できるのですが、コアの方向が縦方向になっているものと横方向になっているものがあるからです。
要塞には6個の砲台があります。砲台から自機に向けて弾丸が発射されます。また自機も砲台も弾丸を撃ち込めば破壊できます。要塞は6つの砲台をすべて破壊するか、中央のコアに弾丸を撃ち込むことで破壊できます。
要塞を描画するためには中心のコアと6つの砲台を描画する必要があるのと、それぞれに対する当たり判定を考えないといけません。ちょっと難しそうに感じたのですが、想像以上に時間がかかってしまいました。
まずコンストラクタ内で中央部分の座標が引数として渡されるので、ここからコアの描画位置と砲台の描画位置を決めます。
1 2 3 4 5 6 7 8 |
// 要塞に設置されている砲台を描画するためのクラス public class Cannon { public float X = 0; public float Y = 0; public double Angle = 0; public bool IsDead = false; } |
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 107 108 109 110 111 112 113 114 115 116 117 118 119 |
// 要塞を描画するためのクラス using OpenTK; using OpenTK.Graphics.OpenGL; public class Fortress : Character { public Fortress(Form1 form1, float x, float y, float vecX, float vecY, bool isVertical) : base(form1, x, y, vecX, vecY) { IsVertical = isVertical; Radius = Config.FortressRadius; int[] angles = new int[] {0, 60, 120, 180, 240, 300, }; foreach(int angle in angles) { int angle2 = angle; if(!IsVertical) angle2 += 90; double rad = angle2 * Math.PI / 180; float cannonX = x + (float)Math.Cos(rad) * (Radius - Config.FortressCannonRadius); float cannonY = y + (float)Math.Sin(rad) * (Radius - Config.FortressCannonRadius); Cannons.Add(new Cannon() { IsDead = false, X = cannonX, Y = cannonY, Angle = angle2, }); } // テクスチャを作成するのは最初の1回だけ。あとは使い回す。 if(Textures.Count == 0) Textures = CreateTextures(); } bool IsVertical = true; static List<int> Textures = new List<int>(); protected override List<Bitmap> GetBitmaps() { List<Bitmap> bitmaps = new List<Bitmap>(); bitmaps.Add(Properties.Resources.FortressCore); bitmaps.Add(Properties.Resources.FortressCannon); bitmaps.Add(Properties.Resources.BrokenCannon); return bitmaps; } List<Cannon> Cannons = new List<Cannon>(); static public float CoreRadius = 1f; static public float CannonRadius = 1f; public override void Draw() { if(!IsDead) { DrawCore(); DrawCannons(); } } void DrawCore() { GL.PushMatrix(); { GL.Translate(X, Y, 0); if(!IsVertical) GL.Rotate(90, 0, 0, 1); GL.BindTexture(TextureTarget.Texture2D, Textures[0]); GL.Begin(BeginMode.Quads); { GL.TexCoord2(1, 1); GL.Vertex3(CannonRadius, CannonRadius, 0); GL.TexCoord2(0, 1); GL.Vertex3(-CannonRadius, CannonRadius, 0); GL.TexCoord2(0, 0); GL.Vertex3(-CannonRadius, -CannonRadius, 0); GL.TexCoord2(1, 0); GL.Vertex3(CannonRadius, -CannonRadius, 0); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } void DrawCannons() { foreach(Cannon cannon in Cannons) { DrawCannon(cannon); } } void DrawCannon(Cannon cannon) { GL.PushMatrix(); { GL.Translate(cannon.X, cannon.Y, 0); GL.Rotate(cannon.Angle, 0, 0, 1); if(!cannon.IsDead) GL.BindTexture(TextureTarget.Texture2D, Textures[1]); else GL.BindTexture(TextureTarget.Texture2D, Textures[2]); GL.Begin(BeginMode.Quads); { GL.TexCoord2(1, 1); GL.Vertex3(CannonRadius, CannonRadius, 0); GL.TexCoord2(0, 1); GL.Vertex3(-CannonRadius, CannonRadius, 0); GL.TexCoord2(0, 0); GL.Vertex3(-CannonRadius, -CannonRadius, 0); GL.TexCoord2(1, 0); GL.Vertex3(CannonRadius, -CannonRadius, 0); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } |
当たり判定はまずコアに弾丸が命中したかどうかを調べます。命中している場合はExplosion()メソッドを呼び出して要塞を大爆発させます。
コアに当たっていない場合は砲台に命中したかどうか調べます。破壊されていない砲台に自機の弾丸が命中した場合は砲台が破壊されます。すでに破壊されている砲台に命中した場合、要塞はそれ以上のダメージをうけません。自機弾丸に死亡フラグが立つだけです。
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 |
using OpenTK; using OpenTK.Graphics.OpenGL; public class Fortress : Character { public void IsHited(List<Burret> burrets) { if(IsDead) return; foreach(Burret burret in burrets) { // 死亡フラグの立っている自機弾丸は判定対象外 if(burret.IsDead) continue; // 弾丸はコアに命中したか? if (IsCoreHited(burret)) { // コアを破壊したら要塞は大爆発する Explosion(); IsDead = true; burret.IsDead = true; // コアを破壊したら1500点加算 MainForm.Score += 1500; return; } // 弾丸は砲台に命中したか? foreach(Cannon cannon in Cannons) { if(IsCannonHited(burret, cannon)) { if (!cannon.IsDead) { MainForm.Explosions.Add(new Explosion(MainForm, burret.X, burret.Y, 0, 0)); MainForm.PlayHitSound(); // 砲台を破壊したら200点加算 MainForm.Score += 200; } cannon.IsDead = true; burret.IsDead = true; if(Cannons.Any(x => !x.IsDead)) { break; } else { // すべての砲台が破壊された場合は要塞は大爆発する IsDead = true; Explosion(); return; } } } } } // 弾丸はコアに命中したか? 命中しているのであれば trueを返す bool IsCoreHited(Burret burret) { double distance1 = Math.Sqrt(Math.Pow(X - burret.X, 2) + Math.Pow(Y - burret.Y, 2)); double distance2 = CoreRadius + burret.Radius; // 当たり判定を甘くするために1f加えている if (distance1 < distance2 +1f && (IsVertical && Math.Round(burret.VecX) == 0) || (!IsVertical && Math.Round(burret.VecY) == 0)) return true; else return false; } // 弾丸は破壊されていない砲台に命中したか? bool IsCannonHited(Burret burret, Cannon cannon) { double distance1 = Math.Sqrt(Math.Pow(cannon.X - burret.X, 2) + Math.Pow(cannon.Y - burret.Y, 2)); double distance2 = CoreRadius + burret.Radius; if(distance2 > distance1) return ret; else return false; } public void Explosion() { MainForm.BigExplosion(X + 1, Y + 1); MainForm.BigExplosion(X + 1, Y - 1); MainForm.BigExplosion(X - 1, Y - 1); MainForm.BigExplosion(X - 1, Y + 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 |
public partial class Form1 : Form { void InitStage(int stage) { UpdateCount = 0; UpdateCountEndLeft = -1; UpdateCountEndRight = -1; Jiki.X = 50; Jiki.Y = 10; Jiki.Rotate = 90; Fortresses.Clear(); Fortresses.Add(new Fortress(this, 40, 50, 0, 0, true)); Fortresses.Add(new Fortress(this, 60, 50, 0, 0, true)); Fortresses.Add(new Fortress(this, 30, 70, 0, 0, true)); Fortresses.Add(new Fortress(this, 70, 70, 0, 0, true)); Fortresses.Add(new Fortress(this, 40, 90, 0, 0, true)); Fortresses.Add(new Fortress(this, 60, 90, 0, 0, true)); PlayBGM1(); // いまはテストなのでザコ敵は考えないことにする。 } } |
Form1クラスにおいてUpdate()の変更はありません。要塞は移動しないからです。ただ破壊された要塞はFortressesリストから取り除かれないといけません。描画はDraw()メソッド内で自作メソッド DrawFortresses()を呼び出しておこないます。
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 |
public partial class Form1 : Form { void RemoveOutOfSightObject() { Burrets = Burrets.Where(x => GetDistance(x) < 20 && !x.IsDead).ToList(); Enemies = Enemies.Where(x => GetDistance(x) < 40 && !x.IsDead).ToList(); Explosions = Explosions.Where(x => GetDistance(x) < 40 && !x.IsDead).ToList(); // 破壊された要塞はリストから取り除く Fortresses = Fortresses.Where(x => !x.IsDead).ToList(); } void Draw() { DrawFieldLines(); if(!Jiki.IsDead) Jiki.Draw(); DrawBurrets(); DrawEnemies(); DrawFortresses(); DrawExplosions(); } void DrawFortresses() { foreach(Fortress fortress in Fortresses) { fortress.Draw(); } } } |
要塞との当たり判定に関する機能を追加します。要塞と自機が衝突した場合は自機だけでなく要塞も大爆発して消滅します。
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 { void HitJudge() { ExplosionIfHitEnemies(); ExplosionIfHitFortresses(); ExplosionIfJikiCrashEnemies(); ExplosionIfJikiCrashFortresses(); } // 自機の弾丸は要塞に命中したか? その判定とその後の処理はFortressesクラスに任せる void ExplosionIfHitFortresses() { foreach (Fortress fortress in Fortresses) { fortress.IsHited(Burrets); } } // 自機と要塞は衝突したか? void ExplosionIfJikiCrashFortresses() { if (Jiki.IsDead) return; foreach (Fortress fortress in Fortresses) { if (fortress.IsDead) continue; double distance = Math.Sqrt(Math.Pow(Jiki.X - fortress.X, 2) + Math.Pow(Jiki.Y - fortress.Y, 2)); // 当たり判定が厳しすぎるので -2 している if ((Jiki.Radius + fortress.Radius - 2) > distance) { fortress.IsDead = true; fortress.Explosion(); JikiDead(); return; } } } } |