自機から弾丸を発射させます。またBGMや効果音もいれます。
BGMはこれを使います。
サイバー42 無料ダウンロード/魔王魂 フリー高音質BGM音楽素材
自機からの弾丸発射音も命中音、自機被弾のときの効果音も、上記の魔王魂 フリー効果音素材を使わせていただきましょう。
ではさっそくはじめましょう。
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 |
using OpenTK; using OpenTK.Graphics.OpenGL; public class Burret : Character { public Burret(Form1 form1, float x, float y, float vecX, float vecY) : base(form1, x, y, vecX, vecY) { Radius = Config.BurretRadius; 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>(); Bitmap bitmap1 = new Bitmap(5, 5); Graphics g = Graphics.FromImage(bitmap1); g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; g.DrawImage(bitmap, new Rectangle(0, 0, 5, 5), new Rectangle(4, 50, 5, 5), GraphicsUnit.Pixel); g.Dispose(); bitmaps.Add(bitmap1); return bitmaps; } public override void Draw() { double rad = Math.Atan2(VecY, VecX); double angle = 180 * rad / Math.PI; GL.PushMatrix(); { GL.Translate(X, Y, 0); GL.Rotate(angle-90, 0, 0, 1); GL.BindTexture(TextureTarget.Texture2D, Textures[0]); GL.Begin(BeginMode.Quads); { GL.TexCoord2(1, 1); GL.Vertex3(Radius, Radius, 0); GL.TexCoord2(1, 0); GL.Vertex3(Radius, -Radius, 0); GL.TexCoord2(0, 0); GL.Vertex3(-Radius, -Radius, 0); GL.TexCoord2(0, 1); GL.Vertex3(-Radius, Radius, 0); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } |
では弾丸を発射させてみましょう。
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 |
public partial class Form1 : Form { List<Burret> Burrets = new List<Burret>(); int updateCountWhenShot = 0; protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Left) { UpdateCountEndLeft = UpdateCount + 5; } if (e.KeyCode == Keys.Right) { UpdateCountEndRight = UpdateCount + 5; } if (e.KeyCode == Keys.Down) { UpdateCountEndRight = UpdateCount + 5 * 4; } if (e.KeyCode == Keys.Space) Shot(); base.OnKeyDown(e); } void Shot() { // 連射に制限をかける if (updateCountWhenShot + 5 > UpdateCount && updateCountWhenShot < UpdateCount) return; double rad = Math.PI * (Jiki.Rotate) / 180; float burretVX = (float)Math.Cos(rad) * Config.BurretSpeed + Jiki.VecX; float burretVY = (float)Math.Sin(rad) * Config.BurretSpeed + Jiki.VecY; Burrets.Add(new Burret(this, Jiki.X, Jiki.Y, burretVX, burretVY)); Burrets.Add(new Burret(this, Jiki.X, Jiki.Y, -burretVX, -burretVY)); PlayShotSound(); updateCountWhenShot = UpdateCount; } WMPLib.WindowsMediaPlayer mediaPlayerShot = new WMPLib.WindowsMediaPlayer(); public void PlayShotSound() { string path = Application.StartupPath + "\\se_maoudamashii_battle09.mp3"; if (System.IO.File.Exists(path)) { mediaPlayerShot.settings.autoStart = true; mediaPlayerShot.URL = path; } } } |
発射させたら動かしたり描画する処理も必要です。Update()メソッドと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 |
public partial class Form1 : Form { int UpdateCount = 0; new void Update() { MoveJiki(); UpdateEnemies(); // 弾丸を移動させる UpdateBurrets(); // 遠くに飛んでいった弾丸は存在しないものとして扱う RemoveOutOfSightObject(); UpdateCount++; } void UpdateBurrets() { foreach(Burret burret in Burrets) { burret.Update(); } } void RemoveOutOfSightObject() { Burrets = Burrets.Where(x => GetDistance(x) < 20 && !x.IsDead).ToList(); } double GetDistance(Character character) { return Math.Sqrt(Math.Pow(Jiki.X - character.X, 2) + Math.Pow(Jiki.Y - character.Y, 2)); } void Draw() { DrawFieldLines(); if(!Jiki.IsDead) Jiki.Draw(); DrawBurrets(); } void DrawEnemies() { foreach(EnemyBase enemy in Enemies) { if(!enemy.IsDead) enemy.Draw(); } } } |
次に当たり判定を考えます。あと爆発の描画もやります。
距離が両者の半径の和よりも小さい場合は命中していると考えます。この処理は敵クラスに弾丸が格納されたリストを渡しておこなわせます。
EnemyBase.IsHitedメソッドは自機の弾丸にあたったかどうかを調べるためのものです。もし自機の弾丸に当たっているのであれば仮想メソッド OnHited()が呼び出されます。
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 |
using OpenTK; using OpenTK.Graphics.OpenGL; class EnemyBase:Character { public bool IsHited(List<Burret> burrets) { if (IsDead) return false; foreach (Burret burret in burrets) { if(IsHited(burret)) return true; } return false; } bool IsHited(Burret burret) { double distance1 = Math.Sqrt(Math.Pow(X - burret.X, 2) + Math.Pow(Y - burret.Y, 2)); double distance2 = Config.EnemyRadius + burret.Radius; if (distance2 > distance1) { IsDead = true; burret.IsDead = true; OnHited(); return true; } return false; } public virtual void OnHited() { } } |
ゲーム開始以降はBGMも流します。それからスコアや残機の表示もInitGame()メソッドのなかでやってしまいましょう。
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 |
public partial class Form1 : Form { DateTime bgmEndTime = DateTime.Now; WMPLib.WindowsMediaPlayer mediaPlayerBGM = new WMPLib.WindowsMediaPlayer(); public void PlayBGM1() { string path = Application.StartupPath + "\\bgm_maoudamashii_cyber42.mp3"; if (System.IO.File.Exists(path)) { mediaPlayerBGM.settings.autoStart = true; mediaPlayerBGM.URL = path; } // BGMの再生時間は2分7秒である。BGMが終了する時刻を記憶する。 bgmEndTime = DateTime.Now + new TimeSpan(0, 2, 7); } WMPLib.WindowsMediaPlayer mediaPlayerHit = new WMPLib.WindowsMediaPlayer(); public void PlayHitSound() { string path = Application.StartupPath + "\\se_maoudamashii_explosion06.mp3"; if (System.IO.File.Exists(path)) { mediaPlayerHit.settings.autoStart = true; mediaPlayerHit.URL = path; } } void InitGame() { Jiki = new Jiki(this, 0, 0, 0, 0); JikiRest = Config.MaxJikiRest; Score = 0; IsGameOver = false; Stage = 0; InitStage(Stage); PlayBGM1(); } void InitStage(int stage) { // 自機は上向き、座標(50,10)に現れる Jiki.X = 50; Jiki.Y = 10; Jiki.Rotate = 90; UpdateCount = 0; UpdateCountEndLeft = -1; UpdateCountEndRight = -1; PlayBGM1(); } // 残機を表示する int _jikiRest = 0; int JikiRest { get { return _jikiRest; } set { _jikiRest = value; if(value > 0) labelRest.Text = "残 " + value.ToString(); else labelRest.Text = "Game Over"; } } // スコアを表示する int _score = 0; public int Score { get { return _score; } set { _score = value; labelScore.Text = "Score " + value.ToString(); } } // 現在のステージを表示する int _stage = 0; int Stage { get { return _stage; } set { _stage = value; labelStage.Text = "Stage " + (value +1).ToString(); // 人間界では数字は「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 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 |
public partial class Form1 : Form { // 爆発処理に関するオブジェクトを格納するリスト public List<Explosion> Explosions = new List<Explosion>(); new void Update() { MoveJiki(); CreateEnemyIfNeed(); UpdateEnemies(); UpdateBurrets(); HitJudge(); UpdateExplosions(); RemoveOutOfSightObject(); // BGMが止まったら再生する if (DateTime.Now > bgmEndTime) PlayBGM1(); UpdateCount++; } void HitJudge() { ExplosionIfHitEnemies(); ExplosionIfJikiCrashEnemies(); } // 自機の弾丸が敵に命中したら爆発させる void ExplosionIfHitEnemies() { foreach (EnemyBase enemy in Enemies) { if (enemy.IsDead) continue; if (enemy.IsHited(Burrets)) { Explosions.Add(new Explosion(this, enemy.X, enemy.Y, 0, 0)); PlayHitSound(); break; } } } // 自機と敵が衝突したら爆発させる void ExplosionIfJikiCrashEnemies() { if (Jiki.IsDead) return; foreach (EnemyBase enemy in Enemies) { if (enemy.IsDead) continue; double distance1 = Math.Sqrt(Math.Pow(Jiki.X - enemy.X, 2) + Math.Pow(Jiki.Y - enemy.Y, 2)); double distance2 = enemy.Radius + Jiki.Radius; if (distance1 < distance2) { enemy.IsDead = true; JikiDead(); return; } } } void JikiDead() { Explosions.Add(new Explosion(this, Jiki.X, Jiki.Y, 0, 0)); BigExplosion(Jiki.X, Jiki.Y); Jiki.IsDead = true; mediaPlayerBGM.controls.stop(); PlayHitSound(); } void UpdateExplosions() { foreach(Explosion explosion in Explosions) { explosion.Update(); } } void Draw() { DrawFieldLines(); if(!Jiki.IsDead) Jiki.Draw(); DrawBurrets(); DrawEnemies(); DrawExplosions(); } void DrawExplosions() { foreach(Explosion explosion in Explosions) { explosion.Draw(); } } 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(); } } |
爆発を描画するためのクラスを示します。基本は下記と同じです。
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 |
using OpenTK; using OpenTK.Graphics.OpenGL; public class Explosion : Character { public Explosion(Form1 form1, float x, float y, float vecX, float vecY) : base(form1, x, y, vecX, vecY) { if(Textures.Count == 0) Textures = CreateTextures(); Radius = Config.ExplosionRadius; } static List<int> Textures = new List<int>(); Bitmap GetBitmap(Point point, Size destSize, Size sourceSize) { Bitmap bitmap = Properties.Resources.sprite2; Bitmap bitmap1 = new Bitmap(destSize.Width, destSize.Height); Graphics g = Graphics.FromImage(bitmap1); g.DrawImage(bitmap, new Rectangle(0, 0, destSize.Width, destSize.Height), new Rectangle(sourcePoint.X, sourcePoint.Y, sourceSize.Width, sourceSize.Height), GraphicsUnit.Pixel); g.Dispose(); bitmap.Dispose(); return bitmap1; } protected override List<Bitmap> GetBitmaps() { List<Bitmap> bitmaps = new List<Bitmap>(); // 爆発のテクスチャ bitmaps.Add(GetBitmap(new Size(25, 28), new Point(17, 340), new Size(24, 27))); bitmaps.Add(GetBitmap(new Size(25, 28), new Point(44, 340), new Size(30, 30))); bitmaps.Add(GetBitmap(new Size(25, 28), new Point(79, 340), new Size(32, 32))); bitmaps.Add(GetBitmap(new Size(25, 28), new Point(116, 340), new Size(36, 36))); bitmaps.Add(GetBitmap(new Size(25, 28), new Point(153, 340), new Size(34, 34))); bitmaps.Add(GetBitmap(new Size(25, 28), new Point(188, 340), new Size(28, 30))); return bitmaps; } public override void Draw() { GL.PushMatrix(); { GL.Translate(X, Y, 0); if(UpdateCount < 4) GL.BindTexture(TextureTarget.Texture2D, Textures[0]); else if(UpdateCount < 8) GL.BindTexture(TextureTarget.Texture2D, Textures[1]); else if(UpdateCount < 12) GL.BindTexture(TextureTarget.Texture2D, Textures[2]); else if(UpdateCount < 16) GL.BindTexture(TextureTarget.Texture2D, Textures[3]); else if(UpdateCount < 20) GL.BindTexture(TextureTarget.Texture2D, Textures[4]); else if(UpdateCount < 24) GL.BindTexture(TextureTarget.Texture2D, Textures[5]); else { IsDead = true; return; } GL.Begin(BeginMode.Quads); { GL.TexCoord2(1, 1); GL.Vertex3(Radius, Radius, 0); GL.TexCoord2(0, 1); GL.Vertex3(-Radius, Radius, 0); GL.TexCoord2(0, 0); GL.Vertex3(-Radius, -Radius, 0); GL.TexCoord2(1, 0); GL.Vertex3(Radius, -Radius, 0); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); } } |
Enemy1に命中した場合は50点を加算します。
1 2 3 4 5 6 7 |
class Enemy1 : EnemyBase { public override void OnHited() { MainForm.Score += 50; } } |