自機と敵機から弾丸を発射する処理をおこないましたが、命中したか? 命中したらどうするかについては考えていませんでした。そこで今回は弾丸の当たり判定と爆発の処理をおこないます。
以下のコードでは当たり判定をおこなったあと爆発を描画する準備をしています。
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 |
public partial class Form1 : Form { // 爆発処理に関するオブジェクトを格納するリスト。Explosionクラスについては後述 List<Explosion> Explosions = new List<Explosion>(); int updateCount = 0; new void Update() { updateCount++; BaseY += Speed; if(Jiki != null && !Jiki.IsDead) { Jiki.Move(); CreateEnemies(); } MoveEnemies(); MoveJikiBurrets(); MoveEnemyBurrets(); // 自機と自機から放たれた弾丸と敵と敵弾を位置から当たり判定をおこなう。 HitJudeJikiBurrets(); // 敵機から放たれ移動した弾丸は自機に命中したか? HitJudeEnemyBurrets(); // 敵機自身と自機は衝突したか? CrashJudeEnemies(); MoveExplosions(); // 描画の必要がないものはリストから取り除く 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(); Explosions = Explosions.Where(x => !x.IsDead).ToList(); } void MoveExplosions() { foreach(Explosion explosion in Explosions) { explosion.Move(); } } // 自機から放たれ移動した弾丸は敵機に命中したか? void HitJudeJikiBurrets() { foreach(JikiBurret burret in JikiBurrets) { // 自機と敵機の半径の長さよりも自機から放たれた弾丸の半径と敵の半径の合計が短いなら // 命中したと考えてよい。 foreach(EnemyBase enemy in Enemies) { if(Math.Pow(burret.Radius + enemy.Radius, 2) > Math.Pow(burret.X - enemy.X, 2) + Math.Pow(burret.Y - enemy.Y, 2)) { OnBurretHitEnemy(enemy, burret); break; } } } } void OnBurretHitEnemy(EnemyBase enemy, JikiBurret burret) { // 敵のLfeを減らす。0なら自動的に死亡フラグが立つ。 enemy.Life--; burret.IsDead = true; // 命中した敵の死亡フラグが立っている場合は爆発させる。 // 固い敵に命中して死亡フラグが立っていない場合は小さな爆発にする。 // Explosionクラスのコンストラクタに爆発の中心になる点などの情報を渡す。 if(!enemy.IsDead) Explosions.Add(new Explosion(burret.X, burret.Y, 0, 0, 0.5f)); else BigExplosion(enemy.X, enemy.Y); } } // 敵機から放たれた弾丸は自機に命中したか? void HitJudeEnemyBurrets() { foreach(EnemyBurret burret in EnemyBurrets) { // 自機が存在しない、自機死亡の場合、無敵状態のときはなにもしないで終了。 if(Jiki == null || Jiki.IsDead || Jiki.IsMuteki) return; // 敵弾が命中していたら自機にダメージを与える if(Math.Pow(burret.Radius + Jiki.Radius, 2) > Math.Pow(burret.X - Jiki.X, 2) + Math.Pow(burret.Y - Jiki.Y, 2)) { OnJikiDamage(burret); return; } } } // 敵機自身と自機は衝突したか? void CrashJudeEnemies() { foreach(EnemyBase enemy in Enemies) { if(Jiki == null || Jiki.IsDead || Jiki.IsMuteki) return; if(Math.Pow(enemy.Radius + Jiki.Radius, 2) > Math.Pow(enemy.X - Jiki.X, 2) + Math.Pow(enemy.Y - Jiki.Y, 2)) { OnJikiDamage(enemy); return; } } } void OnJikiDamage(Character enemyChar) { // 敵機から発射された弾丸が自機に命中したなら爆発の描画の処理をおこなう。 // Lifeが0になって自機が死亡した場合は大爆発 // そうでない場合は立て続けにやられないためにしばらく無敵状態にする。 Jiki.Life--; enemyChar.IsDead = true; Explosions.Add(new Explosion(Jiki.X, Jiki.Y, 0, 0, 0.5f)); Jiki.IsBackColorRed = true; if(Jiki.IsDead) BigExplosion(Jiki.X, Jiki.Y); else Jiki.IsMuteki = true; } } |
では爆発の処理をするためにはどうすればよいでしょうか?
弾丸が命中しただけなら小爆発なので位置を変える必要はありません。キャラクターが死亡した場合は大きな爆発にしたいので発生した位置から放射状に広がるように移動させます。Explosionクラスはそのあと示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { void BigExplosion(float x, float y) { // 4方向へ広がるように Explosions.Add(new Explosion(x, y, 0.1f, 0.1f, 0.5f)); Explosions.Add(new Explosion(x, y, -0.1f, -0.1f, 0.5f)); Explosions.Add(new Explosion(x, y, -0.1f, 0.1f, 0.5f)); Explosions.Add(new Explosion(x, y, 0.1f, -0.1f, 0.5f)); // 上の処理だけでは単調な爆発描画しかできないので乱数で広がる量を決める for(int i = 0; i < 10; i++) { int r = Random.Next(10); float rx = (r - 5) * 0.02f; r = Random.Next(10); float ry = (r - 5) * 0.02f; Explosions.Add(new Explosion(x, y, rx, ry, 0.5f)); } } } |
以下のようなExplosionクラスを作成します。コンストラクタに爆発が発生したXY座標、その移動距離、オブジェクトの半径をわたすようにします。また爆発のテクスチャは時間の経過とともに変化させます。
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 |
public class Explosion: Character { public Explosion(float x, float y, float vecX, float vecY, float radius) { X = x; Y = y; VecX = vecX; VecY = vecY; Radius = radius; 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(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(17, 340, 24, 27), 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(44, 340, 30, 30), 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(79, 340, 32, 32), 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(116, 340, 36, 36), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } // 爆発のテクスチャ 4 { Bitmap bitmap1 = new Bitmap(25, 28); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, 25, 28), new Rectangle(153, 340, 34, 34), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); } // 爆発のテクスチャ 5 { Bitmap bitmap1 = new Bitmap(25, 28); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, 25, 28), new Rectangle(188, 340, 28, 30), 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); if(MoveCount < 4) GL.BindTexture(TextureTarget.Texture2D, Textures[0]); else if(MoveCount < 8) GL.BindTexture(TextureTarget.Texture2D, Textures[1]); else if(MoveCount < 12) GL.BindTexture(TextureTarget.Texture2D, Textures[2]); else if(MoveCount < 16) GL.BindTexture(TextureTarget.Texture2D, Textures[3]); else if(MoveCount < 20) GL.BindTexture(TextureTarget.Texture2D, Textures[4]); else if(MoveCount < 24) GL.BindTexture(TextureTarget.Texture2D, Textures[5]); else { IsDead = true; return; } 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(); } } |
ではForm1クラスに爆発を描画する機能を追加しましょう。すでにUpdateメソッドで爆発の描画に必要なオブジェクトは集められているので描画の処理だけ追加します。
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 |
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(); } // 爆発の描画 DrawExplosions(); glControl.SwapBuffers(); } void DrawExplosions() { foreach(Explosion explosion in Explosions) { explosion.Draw(); } } } |
こちらのサイトを参考にしています。
エネミーのアニメーションの描画切り替えのスピードはどこで調整しているのか分かれば教えてください。
エネミーの画像を変更してみたところ上手くいきませんでした。お手数をおかけしますがよろしくお願いします。
>エネミーのアニメーションの描画切り替えのスピードはどこで調整しているのか
Form1クラス内に生成したTimerのIntervalの値です。3Dっぽい縦シューティングゲームをつくる (その1)でTimer.Interval = 30;と指定しています。
>エネミーの画像を変更してみたところ上手くいきませんでした。
記事に書かれているとおりであればうまく動作しているのでしょうか?
画像を変更で問題がおきてしまった場合はただしく描画すべきBitmapが取得できていないように思います。
エネミーの弾とボスの弾とでそれぞれ違う画像を使用したいのですが可能ですか?
可能であれば、やり方を教えていただけますでしょうか。
現状では、エネミーの弾をボスの弾として利用しているところまでは確認できております。
以上、お手数おかけしますがよろしくお願いします。
エネミーの弾とボスの弾とでそれぞれ違う画像にすることは可能です。
まずボス用の弾丸に使う画像を用意してください。これはすみませんがご自身でお願いします。
BossEnemyBurretクラスに以下を追加、または書き換えます。
コンストラクタ内でCreateTexturesメソッドが実行されないと雑魚敵と同じ弾丸しか発射されないので、
基底クラスEnemyBurretの引数つきコンストラクタが実行されないようにします。