スコアを表示させる機能を追加します。
Form1クラスのなかにScoreというフィールド変数を作成します。あとは敵のクラスでIsDeadがtrueになったときに点数を加算するようにします。
1 2 3 4 |
public partial class Form1 : Form { static public int Score = 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class EnemyBase: Character { // Characterクラスのものを書き換える int _life = 0; new public int Life { get { return _life; } set { _life = value; if(_life <= 0) { IsDead = true; // 敵を倒したらスコアを追加 Form1.Score += Score; } } } } |
あとはスコアをどうやって表示させるかですが、これまでのようにフォームにラベルを貼り付けてそこに表示させると簡単なのですが、GLControlのなかに書けないかやってみました。
3Dで表示させてきましたが、2Dの表示を組み合わせます。
Draw3D()メソッドはこれまでglControlEx1_Paint(object sender, PaintEventArgs e)メソッドのなかに書いてきたものをまとめただけです。問題はDraw2D()メソッドです。文字を描画するにはどうすればいいのでしょうか?
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 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Draw3D(); Draw2D(); glControl.SwapBuffers(); } void Draw3D() { GL.Enable(EnableCap.DepthTest); // 切り替えをするのでSetProjection()を毎回実行する SetProjection(); SetSight(); DrawField(); // 自機を描画 if(Jiki != null && !Jiki.IsDead) Jiki.Draw(); DrawJikiBurrets(); // 敵機を描画 // 自機死亡の場合は表示しない。 if(Jiki != null && !Jiki.IsDead) { DrawEnemies(); EnemiesBurretsDraw(); } DrawExplosions(); } } |
OpenTKでテクスチャーを使ってテキストを描画する – Qiitaによると「まずテキストのビットマップを作成し、それをテクスチャーとして貼りつける方法がよさそう」とのことです。そこでまずは表示したい文字をBitmapで取得します。
取得するものはスコアですが、それ以外に自分のライフがどうなっているか、対ボス戦闘をしているときはボスのライフも表示させたいです。スコアは上部に、ボスと闘っているときはスコアの下にボスのライフを表示させます。自機のライフは画面下に表示させます。
まずは描画したいもののBitmapを取得するメソッドを示します。
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 |
public partial class Form1 : Form { Bitmap GetBitmapForScore() { // 左右の余白 int margin = 20; Bitmap bitmap = new Bitmap(glControl.Width, 100); Graphics g = Graphics.FromImage(bitmap); g.DrawString(String.Format("Score {0:00000}", Score), new Font("MS ゴシック", 15, FontStyle.Bold), new SolidBrush(Color.White), new Point(margin, 20)); Color color = bitmap.GetPixel(0, 0); Boss boss = (Boss)Enemies.FirstOrDefault(x => x is Boss); if(boss != null && !boss.IsDead && Jiki != null && !Jiki.IsDead) { float f = 1f * boss.Life / boss.MaxLife; int bossMaxLifeWidth = (int)(glControl.Width - margin * 2); int bossLifeWidth = (int)(bossMaxLifeWidth * f); g.FillRectangle(new SolidBrush(Color.Blue), new RectangleF(margin, 50, bossMaxLifeWidth, 20)); g.FillRectangle(new SolidBrush(Color.Orange), new RectangleF(margin, 50, bossLifeWidth, 20)); } g.Dispose(); return bitmap; } Bitmap GetBitmapForJikiLife() { // 左の余白(自機のライフはボスほど横長にしない) int margin = 20; Bitmap bitmap = new Bitmap(glControl.Width, 30); if(Jiki != null && !Jiki.IsDead) { Graphics g = Graphics.FromImage(bitmap); int jikiMaxLifeWidth = (int)(glControl.Width * 0.5f); int jikiLifeWidth = (int)(jikiMaxLifeWidth * (1f * Jiki.Life / Jiki.MaxLife)); g.FillRectangle(new SolidBrush(Color.Blue), new RectangleF(margin, 0, jikiMaxLifeWidth, 20)); g.FillRectangle(new SolidBrush(Color.Orange), new RectangleF(margin, 0, jikiLifeWidth, 20)); g.Dispose(); } return bitmap; } } |
ここからテクスチャを取得します。これはCharacterクラスのものとほとんど同じです。戻り値だけが違います。
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 |
public partial class Form1 : Form { int CreateTexture(Bitmap bitmap) { GL.Enable(EnableCap.Texture2D); GL.AlphaFunc(AlphaFunction.Gequal, 0.5f); GL.Enable(EnableCap.AlphaTest); int texture; GL.GenTextures(1, out texture); GL.BindTexture(TextureTarget.Texture2D, texture); System.Drawing.Imaging.BitmapData data = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); bitmap.UnlockBits(data); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); GL.BindTexture(TextureTarget.Texture2D, 0); return texture; } } |
ここからテクスチャを取得したらあとは描画するだけなのですが、プロジェクションと視界の設定をする必要があります(ここでハマってしまった)。
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 |
public partial class Form1 : Form { private void SetProjection2() { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定(2Dにする) GL.MatrixMode(MatrixMode.Projection); float h = 1.0f, w = h * glControl.AspectRatio; Matrix4 proj = Matrix4.CreateOrthographic(w, h, 0.01f, 1f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } void SetSight2() { // 2Dのときの視界の設定 Vector3 eye = new Vector3(0, 0, 0.1f); Vector3 target = new Vector3(0, 0, 0); Vector3 up = Vector3.UnitY; Matrix4 look = Matrix4.LookAt(eye, target, up); GL.LoadMatrix(ref look); } } |
あとは描画するだけです。
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 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Draw3D(); Draw2D(); glControl.SwapBuffers(); } void Draw2D() { GL.Disable(EnableCap.Lighting); ShowScore(); ShowJikiLife(); } // いらなくなったテクスチャを削除できるように値を保存しておく。 int oldTextureForScore = 0; void ShowScore() { SetProjection2(); SetSight2(); float h = 1.0f, w = h * glControl.AspectRatio; Bitmap bitmap = GetBitmapForScore(); int texture = CreateTexture(bitmap); GL.PushMatrix(); { GL.Translate(0, h / 2, 0); GL.BindTexture(TextureTarget.Texture2D, texture); GL.Begin(BeginMode.Quads); { float d = 0f; GL.Normal3(Vector3.UnitZ); GL.TexCoord2(0, 0); GL.Vertex3(-w / 2, 0, d); GL.TexCoord2(1, 0); GL.Vertex3(w / 2f, 0, d); GL.TexCoord2(1, 1); GL.Vertex3(w / 2f, -h * bitmap.Height / bitmap.Width, d); GL.TexCoord2(0, 1); GL.Vertex3(-w / 2, -h * bitmap.Height / bitmap.Width, d); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); // いらなくなったテクスチャを削除する。 // これをやらないとメモリがだんだんなくなっていく・・・ if(oldTextureForScore != 0) GL.DeleteTexture(oldTextureForScore); oldTextureForScore = texture; } int oldTextureForJikiLife = 0; void ShowJikiLife() { SetProjection2(); SetSight2(); float h = 1.0f, w = h * glControl.AspectRatio; Bitmap bitmap = GetBitmapForJikiLife(); int texture = CreateTexture(bitmap); GL.PushMatrix(); { GL.Translate(0, -h / 2, 0); GL.BindTexture(TextureTarget.Texture2D, texture); GL.Begin(BeginMode.Quads); { float d = 0f; GL.Normal3(Vector3.UnitZ); GL.TexCoord2(0, 0); GL.Vertex3(-w / 2, h * bitmap.Height / bitmap.Width, d); GL.TexCoord2(1, 0); GL.Vertex3(w / 2f, h * bitmap.Height / bitmap.Width, d); GL.TexCoord2(1, 1); GL.Vertex3(w / 2f, 0, d); GL.TexCoord2(0, 1); GL.Vertex3(-w / 2, 0, d); } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } GL.PopMatrix(); if(oldTextureForJikiLife != 0) GL.DeleteTexture(oldTextureForJikiLife); oldTextureForJikiLife = texture; } } |
どこでハマったかというと、実行してみるとうまく表示されません。ライフが使っていない色である緑色に表示されます。
どうやらスコア表示のために生成した部分が影になっているようです。そしてゲーム開始前は緑色の直線が描画されているので、それが反射したわずかな環境光で緑色になっていたのです。わかってしまうとなんでもないですが、わからないと「なんでこうなるの?」となってしまいます。
ということでライトは無効にしました。Lighting()メソッドを呼ぶのをやめました。GL.Disable(EnableCap.Lighting)にします。
あともうひとつハマりどころがありました。実行時間が短時間だと気がつかなかったのですが、長時間プレイしているとメモリがなくなってしまいます。原因はつぎからつぎへとテクスチャをつくり続けていることです。使わないテクスチャはGL.DeleteTexture(int)しなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); // ライト無効 GL.Disable(EnableCap.Lighting); Draw3D(); Draw2D(); glControl.SwapBuffers(); } } |
ライトは無効にすると直線に色をつけるためには
1 2 |
GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Green); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Green); |
これではダメです。そこでDrawField()メソッドを書き換えます。
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 |
public partial class Form1 : Form { void DrawField() { GL.Color3(Color.Green); //GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Green); //GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Green); // 方眼を描画 for(int i = -50; i < 50; i++) { GL.Normal3(Vector3.UnitZ); GL.Begin(BeginMode.Lines); { GL.Vertex3(2 * i, BaseY + 200, -0.1); GL.Vertex3(2 * i, BaseY - 1, -0.1); } GL.End(); } int first = (int)BaseY / 4 * 4; for(int i = 0; i < 100; i++) { GL.Normal3(Vector3.UnitZ); GL.Begin(BeginMode.Lines); { GL.Vertex3(50, first + 2 * i, -0.1); GL.Vertex3(-50, first + 2 * i, -0.1); } GL.End(); } // 描画が終わったらColor.Whiteに戻す。 GL.Color3(Color.White); } } |
これで一応、完成ということにします。
お世話になっております。自機のライフが無くなったら、ゲームオーバーの表示を画面上に表示できるようにするしたいのですが、どのように変更すればいいですか?
お手数をおかけしますがよろしくお願いします。
これでどうでしょうか?
いつも丁寧な対応ありがとうございます。とても助かります。
こちらのサイトを参考にさせていただいております。自機のライフバーとボスのライフバーとフォントで画面に数値を表示させて連動させることは可能でしょうか?
また、やり方も教えてほしいです。お手数をおかけしますがよろしくお願いします。
可能だと思いますが、しばらくお待ちください。
返事が遅れてすみません。テスト勉強が忙しくて確認できていませんでした。
対応して頂きありがとうございます。