今回は敵機を作成します。とりあえず敵機は2種類です(気分が変わったら追加するかもしれない)。
敵機もCharacterクラスを継承してつくりますが、自機や弾丸に存在しない要素として撃墜したときの得点や進行方向を変更などがあります。敵機の共通部分なのでEnemyBaseクラスを作成して、これを継承して各敵機クラスを作ります。
1 2 3 4 5 6 7 8 9 10 11 |
public class EnemyBase: Character { public EnemyBase() { } static protected Random Random = new Random(); public int Score = 0; } |
EnemyBaseクラスはこれだけです。進路変更や弾丸発射のために必要な乱数を持たせています。
次にひとつめの敵機クラスを作成します。ひとつめの敵機は途中まで下まで来て進路反転して上昇します。そしてその際に弾丸を発射します。早い話、プログラミング講座 第28回【シューティングゲーム作成(8)/JavaScript】のマネです。
自機のY座標よりも 5大きい値よりもY座標が小さくなったらフィールド変数 doesAwayをtrueにして下降から上昇に転じます。そのときに弾丸を発射します。プログラミング講座 第28回【シューティングゲーム作成(8)/JavaScript】のそのまんまマネではおもしろくないので発射する弾丸を1発ではなく3発にして差別化(?)しています。
弾丸を発射する角度を求めるために自機の進行速度を考慮しなければなりません。それから弾丸を発射する角度を求めるためにアークタンジェントをもちいていますが、Math.Atanメソッドの戻り値が -π / 2 ~ π / 2 であることに対して、Math.Atan2では -π ~ π となります。そのためMath.Atanメソッドではπラジアン逆の値が取得されてしまうことがあります。
1 2 3 4 5 6 |
float BurretSpeed = 0.2f; Jiki jiki = Form1.Jiki; float x = Form1.Jiki.X - X; float y = Form1.Jiki.Y - Y; double angle = Math.Atan2(y , x); |
このとき弾丸の初速は
X方向ならMath.Cos(angle) * BurretSpeed、
Y方向ならMath.Sin(angle) * BurretSpeed + Form1.Speed
でよい。
ところが
1 |
double angle = Math.Atan(y / x); |
とやってしまうと符号を入れ替えるか、angle += Math.PIとしなければならない場合があるのです。これに気づかず時間をとられてしまいました。
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
public class Enemy1 : EnemyBase { public Enemy1(float x, float y, float radius, int life, int score) { X = x; Y = y; Radius = radius; IsDead = false; Score = score; Life = life; VecY = -0.3f; if(Textures.Count == 0) Textures = CreateTextures(); } static List<int> Textures = new List<int>(); float BurretSpeed = 0.2f; protected override List<Bitmap> GetBitmaps() { Bitmap bitmap = Properties.Resources.sprite2; List<Bitmap> bitmaps = new List<Bitmap>(); // ザコ敵(ピンク色)0 { Bitmap bitmap1 = new Bitmap(25, 28); Graphics g = Graphics.FromImage(bitmap1); g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; g.DrawImage(bitmap, new Rectangle(0, 0, 25, 28), new Rectangle(4, 94, 25, 28), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } // ザコ敵(ピンク色)1 { Bitmap bitmap1 = new Bitmap(25, 28); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, 25, 28), new Rectangle(36, 94, 25, 28), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } // ザコ敵(ピンク色)2 { Bitmap bitmap1 = new Bitmap(25, 28); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, 25, 28), new Rectangle(68, 94, 25, 28), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } // ザコ敵(ピンク色)3 { Bitmap bitmap1 = new Bitmap(25, 28); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, 25, 28), new Rectangle(99, 94, 25, 28), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } return bitmaps; } bool doesAway = false; public override void Move() { Jiki jiki = Form1.Jiki; if(jiki == null) return; if(Y < Form1.Jiki.Y + 5) { // 方向転換して弾丸を発射する doesAway = true; if(jiki.X < X) VecX = 0.1f; else VecX = -0.1f; VecY = 2.0f; float x = Form1.Jiki.X - X; float y = Form1.Jiki.Y - Y; // 弾丸の発射角度とXY方向の速度を求める double angle1 = Math.Atan2(y , x); double angle2 = angle1 + 0.3f; double angle3 = angle1 - 0.3f; // 発射した弾丸はリストに格納する Form1.EnemyBurrets.Add(new EnemyBurret(X, Y, (float)Math.Cos(angle1) * BurretSpeed, (float)Math.Sin(angle1) * BurretSpeed + Form1.Speed)); Form1.EnemyBurrets.Add(new EnemyBurret(X, Y, (float)Math.Cos(angle2) * BurretSpeed, (float)Math.Sin(angle3) * BurretSpeed + Form1.Speed)); Form1.EnemyBurrets.Add(new EnemyBurret(X, Y, (float)Math.Cos(angle3) * BurretSpeed, (float)Math.Sin(angle3) * BurretSpeed + Form1.Speed)); } // 時々自機がある方向に進路変更する if(!doesAway && MoveCount % 8 == 0 && Random.Next(2) == 0) { if(jiki.X < X) VecX = -0.1f; else VecX = 0.1f; } base.Move(); } public override void Draw() { GL.PushMatrix(); { GL.Translate(X, Y, 0); GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.White); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.White); int i = MoveCount % 16; if(i < 4) GL.BindTexture(TextureTarget.Texture2D, Textures[0]); else if(i < 8) GL.BindTexture(TextureTarget.Texture2D, Textures[1]); else if(i < 12) GL.BindTexture(TextureTarget.Texture2D, Textures[2]); else if(i < 16) GL.BindTexture(TextureTarget.Texture2D, Textures[3]); GL.Begin(BeginMode.Quads); { GL.Normal3(Vector3.UnitY); GL.TexCoord2(1, 0); GL.Vertex3(Radius, 0, Radius); GL.TexCoord2(0, 0); GL.Vertex3(-Radius, 0, Radius); GL.TexCoord2(0, 1); GL.Vertex3(-Radius, 0, -Radius); GL.TexCoord2(1, 1); GL.Vertex3(Radius, 0, -Radius); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } |
次に敵弾を描画するためのクラスを作成します。敵弾は最初にXY方向の移動量が決まるとその方向に飛び続けます。これも早い話、プログラミング講座 第28回【シューティングゲーム作成(8)/JavaScript】のマネをして飛行中にテクスチャを変更させます。
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 |
public class EnemyBurret : Character { public EnemyBurret(float x, float y, float vecX, float vecY) { X = x; Y = y; VecX = vecX; VecY = vecY; if(Textures.Count == 0) Textures = CreateTextures(); } static List<int> Textures = new List<int>(); protected override List<Bitmap> GetBitmaps() { Bitmap bitmap = Properties.Resources.sprite2; List<Bitmap> bitmaps = new List<Bitmap>(); // 敵弾のテクスチャ 0 { Bitmap bitmap1 = new Bitmap(8, 8); Graphics g = Graphics.FromImage(bitmap1); g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; g.DrawImage(bitmap, new Rectangle(0, 0, 8, 8), new Rectangle(32, 49, 8, 8), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } // 敵弾のテクスチャ 1 { Bitmap bitmap1 = new Bitmap(12, 12); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, 12, 12), new Rectangle(42, 47, 12, 12), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } return bitmaps; } public override void Draw() { GL.PushMatrix(); { GL.Translate(X, Y, 0); GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.White); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.White); Radius = 0.1f; if(MoveCount % 8 < 4) { GL.BindTexture(TextureTarget.Texture2D, Textures[0]); } else { GL.BindTexture(TextureTarget.Texture2D, Textures[1]); Radius = 0.15f; } GL.Begin(BeginMode.Quads); { GL.Normal3(Vector3.UnitY); GL.TexCoord2(1, 0); GL.Vertex3(Radius, 0, Radius); GL.TexCoord2(0, 0); GL.Vertex3(-Radius, 0, Radius); GL.TexCoord2(0, 1); GL.Vertex3(-Radius, 0, -Radius); GL.TexCoord2(1, 1); GL.Vertex3(Radius, 0, -Radius); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } |
では敵キャラその1を描画する機能を追加してみましょう。Update()メソッド内でCreateEnemies()を実行して敵を生成するかどうかを決めます。敵を生成したらEnemiesリストに追加します。また撃墜された敵や描画する必要がない敵と敵弾はリストから取り除きます。
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 |
public partial class Form1 : Form { List<EnemyBase> Enemies = new List<EnemyBase>(); static public List<EnemyBurret> EnemyBurrets = new List<EnemyBurret>(); Random Random = new Random(); new void Update() { BaseY += Speed; if(Jiki != null && !Jiki.IsDead) { Jiki.Move(); CreateEnemies(); } MoveJikiBurrets(); MoveEnemies(); MoveEnemyBurrets(); JikiBurrets = JikiBurrets.Where(x => !IsOutOfField(x) && !x.IsDead).ToList(); Enemies = Enemies.Where(x => !IsOutOfField(x) && !x.IsDead).ToList(); EnemyBurrets = EnemyBurrets.Where(x => !IsOutOfField(x) && !x.IsDead).ToList(); } void CreateEnemies() { if(Enemies.Count < 10 && Random.Next(100) % 8 == 1) { int r = Random.Next(800); float x = 1f * r / 100 - 4f; Enemies.Add(new Enemy1(x, BaseY + 40, 0.5f, 1, 50)); } } void MoveEnemies() { foreach(EnemyBase enemy in Enemies) { enemy.Move(); } } void MoveEnemyBurrets() { foreach(EnemyBurret burret in EnemyBurrets) { burret.Move(); } } } |
敵と敵を実際に描画する処理を示します。自機が存在しない場合と自機死亡の場合は敵と敵弾を表示しません。プログラミング講座 第32回【シューティングゲーム作成(12)/JavaScript】では敵は表示させて弾丸だけ発射しないようにしていますが、この部分はマネはしません(面倒くさくなった ← ってオイっ!)。
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 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Lighting(); SetSight(); DrawField(); // 自機を描画 if(Jiki != null && !Jiki.IsDead) Jiki.Draw(); DrawJikiBurrets(); // 敵機を描画 // 自機死亡の場合は表示しない。 if(Jiki != null && !Jiki.IsDead) { DrawEnemies(); EnemiesBurretsDraw(); } glControl.SwapBuffers(); } private void DrawEnemies() { foreach(EnemyBase enemy in Enemies) { enemy.Draw(); } } void EnemiesBurretsDraw() { foreach(EnemyBase enemy in Enemies) { foreach(EnemyBurret burret in EnemyBurrets) { burret.Draw(); } } } } |