スクランブルもどきにUFOを出現させます。
UFOの動きは三角関数を利用しています。Sin、Cosの引数はUFOオブジェクトが生成されてから呼び出されたMove()メソッドの回数を加工して使っています。またUFOのY座標の初期値が同じにならないように乱数をつかって分散させています(コンストラクタを参照)。
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 |
public class UFO { static Random Random = new Random(DateTime.Now.Millisecond); public UFO(float x, float heightMax, float heightMin) { X = x; HeightMax = heightMax; HeightMin = heightMin; MoveCount = Random.Next(100); } float X { get; set; } float Y { get; set; } float HeightMax { get; set; } float HeightMin { get; set; } int MoveCount = 0; public void Move() { MoveCount++; if(MoveCount > 245) MoveCount = 0; double rad = (double)MoveCount / 5; double a = (HeightMax - HeightMin) / 2; float center = (HeightMax + HeightMin) / 2; Y = (float)(a * Math.Sin(rad)) + center; } public bool isDead = false; public void Draw() { if(!isDead) { GL.PushMatrix(); { GL.Translate(X, Y, 0); GL.Color3(Color.White); GL.BindTexture(TextureTarget.Texture2D, Form1.UfoTexture); GL.Begin(BeginMode.Quads); { GL.TexCoord2(1, 1); GL.Vertex3(0.5, 0.3, 0); GL.TexCoord2(1, 0); GL.Vertex3(0.5, -0.3, 0); GL.TexCoord2(0, 0); GL.Vertex3(-0.5, -0.3, 0); GL.TexCoord2(0, 1); GL.Vertex3(-0.5, 0.3, 0); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } public RectangleF DeadRectangleF() { float x = X - 0.5f; float y = Y - 0.3f; float width = 1f; float height = 0.6f; return new RectangleF(x, y, width, height); } } |
ゲームが始まったらミサイルの初期化と同時にUFOの初期化もおこないます。
UFOクラスのインスタンスの引数はX座標、Y座標の最大値と最小値でした。マップファイルを読み込んだら「U」がある位置を調べてUFOのX座標を求めます。そしてそこからUFOのY座標が取り得る最大値と最小値を求めます。下側にある地形の一番上よりも1.0大きい値が最小値となり、上側の地形の一番下よりも1.0小さい値が最大値となります。
効率よく求めるために文字列のリストを文字の二元配列に変換しています。
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 |
public class Stage { List<UFO> UFOs = new List<UFO>(); // 最終攻撃目標の「基地中枢」(詳細はまた後日) List<Base> Bases = new List<Base>(); // ステージの境界線を保存する変数 public float FirstBegin = 0; public float SecondBegin = 0; public float ThirdBegin = 0; public float ForthBegin = 0; public float FifthBegin = 0; public float FinalBegin = 0; void ClearMap() { Type1s = new List<Block>(); Type2s = new List<Block>(); Type3s = new List<Block>(); Type4s = new List<Block>(); Type5s = new List<Block>(); Missiles = new List<Missile>(); FuelTanks = new List<FuelTank>(); UFOs = new List<UFO>(); } public bool Init() { ClearMap(); List<string> Blocks = LoadMapFile(); if(Blocks == null) return false; int countY = 0; float x = Form1.ProjectionWidth / 2; float y = Form1.ProjectionHeight / 2; // 二元配列に変換する int max = Blocks.Max(str => str.Length); Char[,] charXY = new Char[max, 15]; for(int i = 0; i < 15; i++) { string s = Blocks[i]; int len = s.Length; Char[] vs = s.ToArray(); for(int k = 0; k < len; k++) { charXY[k, i] = vs[k]; } } foreach(string str in Blocks) { Char[] vs = str.ToArray(); int countX = 0; foreach(Char char1 in vs) { // 以下は前回と同じなので省略 // ステージの境界線を見つけたら保存する // 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') // UFOに関する情報 if(char1 == 'U') { int maxY = countY+1; while(maxY < 15 && charXY[countX, maxY] == ' ') maxY++; int minY = countY - 1; while(charXY[countX, minY] == ' ' && minY > 0) minY--; UFOs.Add(new UFO(BlockAspect * countX - x, maxY - Form1.ProjectionHeight/2 -1, minY - Form1.ProjectionHeight / 2 +1)); } // 敵の中枢基地に関する情報 if(char1 == 'B') { // あとで考える } countX++; } countY++; } return true; } } |
Update()メソッドが呼び出されたらUFOを動かします。また自機から発射された弾丸との当たり判定をおこないます。
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 Stage { public void Update() { UpdateJikiPosition(); UpdateMissilesPosition(); // UFOの位置を移動させる UpdateUFOsPosition(); if(IsDead()) { Jiki.IsShow = false; // 自機死亡のイベントを発生させる JikiDead?.Invoke(this, new EventArgs()); return; } UpdateBulletsPosition(); GetObjectsHitBullet(); UpdateBombsPosition(); GetObjectsHitBomb(); } } |
UFOを動かす処理は自機がUFOを見ることができるエリアにいるときだけです。自機との当たり判定や弾丸、爆弾との当たり判定も同じです。
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 |
public class Stage { void UpdateUFOsPosition() { // 視界のなかに存在しないUFOの移動処理はおこなわない。 List<UFO> UFOs1 = UFOs.Where(x => !x.isDead && IsInSight(x.X, x.Y)).ToList(); foreach(UFO ufo in UFOs1) ufo.Move(); } // 視界のなかに存在するかどうか? bool IsInSight(float x, float y) { if(x < Form1.GetEyeX() - Form1.ProjectionWidth / 2 +1) return false; if(x > Form1.GetEyeX() + Form1.ProjectionWidth / 2) return false; if(y < - Form1.ProjectionHeight / 2 + 1) return false; if(y > Form1.ProjectionHeight / 2) return false; return true; } bool IsDead() { RectangleF deadRect = Jiki.DeadRectangleF(); // 地面に接触または墜落したか? List<Block> blocks = GetTestBlocks(); if(blocks.Any(x => IsPiledRectange(deadRect, x.DeadRectangleF()))) return true; // ミサイルに接触したか? if(Missiles.Any(x => !x.isDead && IsPiledRectange(deadRect, x.DeadRectangleF()))) return true; // 燃料タンクに接触したか? if(FuelTanks.Any(x => !x.isDead && IsPiledRectange(deadRect, x.DeadRectangleF()))) return true; // UFOに接触したか? List<UFO> ufos = UFOs.Where(x => !x.isDead && IsInSight(x.X, x.Y)).ToList(); if(ufos.Any(x => IsPiledRectange(deadRect, x.DeadRectangleF()))) return true; return false; } 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); // 弾丸が命中したUFOがあるか調べる List<UFO> hitUFOs = GetUFOsHitBullet(); AddScoreHitUFO(hitUFOs); } 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); // 爆弾が命中したUFOがあるか調べる List<UFO> bombUFOs = GetUFOsHitBomb(); AddScoreHitUFO(bombUFOs); } void AddScoreHitUFO(List<UFO> hitUFOs) { foreach(UFO ufo in hitUFOs) { AddScore?.Invoke(this, new AddScoreArgs(100)); } } } |
それからUFOとファイアボールが飛んでくるステージではミサイルは発射されません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Stage { public void MissilesLaunch() { // 視界の外にあるミサイルの処理はおこなわない。 // また、X座標がSecondBeginより大きく ForthBeginよりも小さいミサイルは // UFOとファイアボールのステージに設置されているミサイルである。 // このミサイルは発射されない。 List<Missile> missiles = Missiles.Where(x => !x.isDead && IsInSight(x.X, x.Y) && (x.X < SecondBegin || x.X > ForthBegin)).ToList(); Missile missile = missiles.FirstOrDefault(x => !x.isDead && !x.isMove && (x.Timing > (x.X - Jiki.X) - (Jiki.Y - x.Y) * Form1.ScrollSpeed / Missile.Speed)); if(missile != null) { missile.isMove = true; } } } |
描画をするためのDraw()メソッドにUFOを描画するための処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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(Bullet bullet in Bullets) bullet.Draw(); foreach(Bomb bomb in Bombs) bomb.Draw(); Jiki.Draw(); } } |
スクランブルのステージ構成は、山岳地帯、UFOが出現する洞窟、火球、ビル、要塞、基地中枢の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 |
public partial class Form1 : Form { void Restart() { double restartX; double deadX = EyeX; if(deadX < Stage.SecondBegin) restartX = Stage.FirstBegin; else if(deadX < Stage.ThirdBegin) restartX = Stage.SecondBegin; else if(deadX < Stage.ForthBegin) restartX = Stage.ThirdBegin; else if(deadX < Stage.FifthBegin) restartX = Stage.ForthBegin; else if(deadX < Stage.FinalBegin) restartX = Stage.FifthBegin; else restartX = Stage.FinalBegin; Stage.Jiki.IsShow = true; CurDirect = Direct.None; Fuel = FuelMax; // 敵の位置も元に戻さないといけないので必要 Stage.Init(); EyeX = restartX + ProjectionWidth / 2; Stage.Jiki.SetStartPosition(); // Stage.Jiki.SetStartPosition()でセットされる自機のX座標は EyeX == 0 の場合の値。 // EyeX != 0 の場合は調整する Stage.Jiki.X += (float)EyeX; glControl1.Refresh(); timer.Start(); } } |