C# OpenTKでスクランブルのようなゲームを作成しています。
スクランブルは山岳、洞口、ファイアボール、ビル、要塞、基地中枢の6つのステージがあり、最後の基地中枢ステージで敵の指令基地を破壊したらゲームクリアとなります。ゲームクリアをするとまた最初のステージにもどります。ここでは最後の基地中枢のプログラミングをおこないます。
まず敵の指令基地を描画するためのクラスを作成します。
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 |
public class Base { public Base(float x, float y) { X = x; Y = y; } public float X { get; private set; } public float Y { get; private set; } public bool isDead { get; set; } = false; public void Draw() { if(isDead) return; GL.PushMatrix(); { GL.Translate(X, Y, 0); GL.Color3(Color.White); GL.BindTexture(TextureTarget.Texture2D, Form1.BaseTexture); GL.Begin(BeginMode.Quads); { GL.TexCoord2(1, 1); GL.Vertex3(0.7, 0.7, 0.1); GL.TexCoord2(1, 0); GL.Vertex3(0.7, -0.7, 0.1); GL.TexCoord2(0, 0); GL.Vertex3(-0.7, -0.7, 0.1); GL.TexCoord2(0, 1); GL.Vertex3(-0.7, 0.7, 0.1); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } public RectangleF DeadRectangleF() { float x = X - 0.7f; float y = Y - 0.7f; float width = 1.4f; float height = 1.4f; return new RectangleF(x, y, width, height); } } |
敵の司令基地を破壊することができなかった場合はそのまま自機は飛行を続けます。すると次の敵の司令基地が出現します。そして以降は自機が墜落するか敵の司令基地を破壊するまでこの繰り返しになります。
マップのなかには司令基地の位置を3箇所指定しています。3回でも破壊できない場合は燃料切れで墜落することになるのでそれ以上は指定する必要は無いと思われます。
まずマップを初期化するときに敵の司令基地の位置を読み込みます(StageクラスのInit()メソッドを参照)。そして司令基地がある位置まで自機が進むとDraw()メソッド内でBaseクラスのDraw()メソッドが呼び出されて基地が描画されるようになります。
最初にStageクラスのInit()メソッドを示します。
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 |
public class Stage { public bool Init() { ClearMap(); Fireballs.Clear(); List<string> Blocks = LoadMapFile(); if(Blocks == null) return false; int countY = 0; float x = Form1.ProjectionWidth / 2; float y = Form1.ProjectionHeight / 2; foreach(string str in Blocks) { Char[] vs = str.ToArray(); int countX = 0; foreach(Char char1 in vs) { if(char1 == 'B') Bases.Add(new Base(BlockAspect * countX - x, countY - y)); // 以下は前回と同じなので省略 // ステージの境界線を見つけたら保存する // if(char1 == '1') // if(char1 == '2') // if(char1 == '3') // if(char1 == '4') // if(char1 == '5') // if(char1 == '6') // 地形に関する情報 // if(char1 == '■') // if(char1 == '▲') // if(char1 == '△') // if(char1 == '▼') // if(char1 == '▽') // if(char1 == '●') // 敵に関する情報 // if(char1 == 'M') // if(char1 == 'F') // if(char1 == 'U') countX++; } countY++; } return true; } void ClearMap() { Type1s = new List<Block>(); Type2s = new List<Block>(); Type3s = new List<Block>(); Type4s = new List<Block>(); Type5s = new List<Block>(); Type6s = new List<Block>(); Missiles = new List<Missile>(); FuelTanks = new List<FuelTank>(); UFOs = new List<UFO>(); Bases = new List<Base>(); } } |
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 |
public class Stage { public void Draw() { DrawGround(); foreach(Missile missile in Missiles) missile.Draw(); foreach(FuelTank fuelTank in FuelTanks) fuelTank.Draw(); foreach(UFO ufo in UFOs) ufo.Draw(); foreach(Fireball fireball in Fireballs) fireball.Draw(); // 敵の基地を描画する List<Base> bases = Bases.Where(x => !x.isDead).ToList(); foreach(Base base1 in bases) base1.Draw(); foreach(Bullet bullet in Bullets) bullet.Draw(); foreach(Bomb bomb in Bombs) bomb.Draw(); Explosions = Explosions.Where(x => !x.isDead).ToList(); foreach(Explosion explosion in Explosions) explosion.Draw(); Jiki.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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
public class Stage { bool DoesHitBulletBase() { List<Base> bases = Bases.Where(x => !x.isDead).ToList(); bool ret = false; foreach(Bullet bullet in Bullets) { RectangleF rect = bullet.DeadRectangleF(); Base base1 = bases.FirstOrDefault(x => IsPiledRectange(rect, x.DeadRectangleF())); if(base1 != null) { base1.isDead = true; bullet.isDead = true; Explosions.Add(new Explosion(base1.X, base1.Y, 0.7, 0.7)); ret = true; break; } } if(ret == true) { // 命中した弾丸はリストから取り除く Bullets = Bullets.Where(x => !x.isDead).ToList(); return true; } else return false; } bool DoesHitBombBase() { List<Base> bases = Bases.Where(x => !x.isDead).ToList(); bool ret = false; foreach(Bomb bomb in Bombs) { RectangleF rect = bomb.DeadRectangleF(); Base base1 = bases.FirstOrDefault(x => IsPiledRectange(rect, x.DeadRectangleF())); if(base1 != null) { base1.isDead = true; bomb.isDead = true; Explosions.Add(new Explosion(base1.X, base1.Y, 0.7, 0.7)); ret = true; break; } } if(ret == true) { // 命中した弾丸はリストから取り除く Bombs = Bombs.Where(x => !x.isDead).ToList(); return true; } else return false; } } |
DoesHitBulletBase()メソッドやDoesHitBombBase()メソッドが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 |
public class Stage { public event EventHandler StageClear; void GetObjectsHitBullet() { RemoveBulletsHitBlock(); Bullets = Bullets.Where(x => !x.isDead && x.X < Form1.GetEyeX() + Form1.ProjectionWidth / 2).ToList(); List<Missile> hitMissiles = GetMissilesHitBullet(); AddScoreHitMissile(hitMissiles); List<FuelTank> hitFuelTanks = GetFuelTanksHitBullet(); AddScoreHitFuelTank(hitFuelTanks); List<UFO> hitUFOs = GetUFOsHitBullet(); AddScoreHitUFO(hitUFOs); // 指令基地を破壊できたか? if(DoesHitBulletBase()) { // このときの追加点は800点。ステージクリアのイベントを発生させる AddScore?.Invoke(this, new AddScoreArgs(800)); StageClear?.Invoke(this, new EventArgs()); } } void GetObjectsHitBomb() { RemoveBombsHitBlock(); Bombs = Bombs.Where(x => !x.isDead && x.Y > - Form1.ProjectionHeight / 2).ToList(); List<Missile> bombMissiles = GetMissilesHitBomb(); AddScoreHitMissile(bombMissiles); List<FuelTank> bombFuelTanks = GetFuelTanksHitBomb(); AddScoreHitFuelTank(bombFuelTanks); List<UFO> bombUFOs = GetUFOsHitBomb(); AddScoreHitUFO(bombUFOs); // 指令基地を破壊できたか? if(DoesHitBombBase()) { // このときの追加点は800点。ステージクリアのイベントを発生させる AddScore?.Invoke(this, new AddScoreArgs(800)); StageClear?.Invoke(this, new EventArgs()); } } } |
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); FormSizeFix(); timer.Tick += Timer_Tick; TimerIntervalReset(); this.BackColor = Color.Black; InitScore(); InitFuel(); Stage.JikiDead += Stage_JikiDead; Stage.StageClear += Stage_StageClear; } bool isStageClear = false; private void Stage_StageClear(object sender, EventArgs e) { isStageClear = true; Timer stageClearTimer = new Timer(); stageClearTimer.Interval = 3000; stageClearTimer.Tick += StageClearTimer_Tick; stageClearTimer.Start(); } private void StageClearTimer_Tick(object sender, EventArgs e) { Timer t = (Timer)sender; t.Stop(); t.Dispose(); StartNewStage(); } void StartNewStage() { isStageClear = false; StageCount++; double restartX = Stage.FirstBegin; Stage.Jiki.IsShow = true; CurDirect = Direct.None; Fuel = FuelMax; // 敵の位置も元に戻さないといけないので必要 Stage.Init(); EyeX = restartX + ProjectionWidth / 2; Stage.Jiki.SetStartPosition(); Stage.Jiki.X += (float)EyeX; glControl1.Refresh(); timer.Start(); } } |
上記コードには isStageClearというフィールド変数があります。スクランブルの仕様では敵基地を破壊したあと自機が墜落するなどのミスをしてもこれはノーカウントとなり、ミスにはなりません。しかもこのゲームは、ステージが進むにつれてだんだん自機の燃費が悪くなり、敵基地を破壊するころには燃料切れということが増えてきます。そのためミス時にすでにゲームクリアをしているのかどうかの判定は重要になります。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { void Miss() { // 敵基地を破壊したあとであればミス時の処理はしない if(isStageClear) return; // ミスをしたときの通常の処理 // 省略 } } |
あと現在挑戦しているステージが何ステージなのかわかるように表示できるようにしてみました。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); FormSizeFix(); timer.Tick += Timer_Tick; TimerIntervalReset(); this.BackColor = Color.Black; InitScore(); InitFuel(); // ラベルの初期化 InitLabelStage(); Stage.JikiDead += Stage_JikiDead; Stage.StageClear += Stage_StageClear; } void InitLabelStage() { labelStage.BackColor = Color.Black; labelStage.ForeColor = Color.White; labelStage.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; } int _stageCount = 0; int StageCount { get { return _stageCount; } set { _stageCount = value; labelStage.Text = "Stage " + _stageCount; } } } |