で作成した壁よけゲームは見た目がイマイチなので改良します。
で自機を真後ろから追いかけるような描画をすることもできるようになったので、これも取り入れてみましょう。
自機が板では味気ないので、前のページで作成したサイコロをつかってみましょう。サイコロは飛行中、ランダムに回転します。
自機は平行移動するのではなく、進行方向を変更させます。自機には角度がつくため壁は目の前に垂直に立っているのではなく斜めにみえます。
まず自機を描画するクラスに変更を加えます。
Draw(float x, float y, float z, int rx, int ry, int rz)メソッドでサイコロを描画します。このときサイコロをX軸、Y軸、Z軸を中心に回転させます。回転速度を同じにすると見えない目があるので回転速度にばらつきを持たせています。
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 class Jiki { public Jiki() { } int i = 0; public void Draw(float x, float y, float z, int rx, int ry, int rz) { GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Blue); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Blue); GL.PushMatrix(); { GL.Translate(x, y, z); GL.Rotate(ry, 0, 1, 0); GL.Rotate(rx, 1, 0, 0); GL.Rotate(rz, 0, 0, 1); // 不規則に回転させる i++; GL.Rotate(i*3, 1, 0, 0); GL.Rotate(i*6, 0, 1, 0); GL.Rotate(i*9, 0, 0, 1); DrawDice(0.5); } GL.PopMatrix(); } } |
DrawDice(double size)メソッドで描画処理をおこないます。このとき各目を描画するためにテクスチャが必要です。テクスチャは1回つくってしまえばそれを再利用することができるので、はじめてDrawDice(double size)メソッドが呼び出されたときにだけテクスチャを作成しています。
すでにテクスチャが作成されている場合は描画処理をおこないます。
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 |
public class Jiki { bool isCreateTexture = false; List<int> Textures = new List<int>(); private void DrawDice(double size) { if(!isCreateTexture) { CreateTexture(Properties.Resources.one); CreateTexture(Properties.Resources.two); CreateTexture(Properties.Resources.three); CreateTexture(Properties.Resources.four); CreateTexture(Properties.Resources.five); CreateTexture(Properties.Resources.six); isCreateTexture = true; } double w = size / 2, h = size / 2, d = size / 2; GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.White); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.White); // 手前の面(6) GL.BindTexture(TextureTarget.Texture2D, Textures[5]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitZ); GL.TexCoord2(0, 0); GL.Vertex3(w, h, d); GL.TexCoord2(1, 0); GL.Vertex3(-w, h, d); GL.TexCoord2(0, 1); GL.Vertex3(w, -h, d); GL.TexCoord2(1, 1); GL.Vertex3(-w, -h, d); } GL.End(); // 奥の面(1) GL.BindTexture(TextureTarget.Texture2D, Textures[0]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitZ); GL.TexCoord2(0, 0); GL.Vertex3(w, -h, -d); GL.TexCoord2(1, 0); GL.Vertex3(-w, -h, -d); GL.TexCoord2(0, 1); GL.Vertex3(w, h, -d); GL.TexCoord2(1, 1); GL.Vertex3(-w, h, -d); } GL.End(); // 右面(2) GL.BindTexture(TextureTarget.Texture2D, Textures[1]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitX); GL.TexCoord2(0, 0); GL.Vertex3(w, h, -d); GL.TexCoord2(1, 0); GL.Vertex3(w, h, d); GL.TexCoord2(0, 1); GL.Vertex3(w, -h, -d); GL.TexCoord2(1, 1); GL.Vertex3(w, -h, d); } GL.End(); // 左面(5) GL.BindTexture(TextureTarget.Texture2D, Textures[4]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitX); GL.TexCoord2(0, 0); GL.Vertex3(-w, -h, -d); GL.TexCoord2(1, 0); GL.Vertex3(-w, -h, d); GL.TexCoord2(0, 1); GL.Vertex3(-w, h, -d); GL.TexCoord2(1, 1); GL.Vertex3(-w, h, d); } GL.End(); // 上面(3) GL.BindTexture(TextureTarget.Texture2D, Textures[2]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitY); GL.TexCoord2(0, 0); GL.Vertex3(w, h, -d); GL.TexCoord2(1, 0); GL.Vertex3(-w, h, -d); GL.TexCoord2(0, 1); GL.Vertex3(w, h, d); GL.TexCoord2(1, 1); GL.Vertex3(-w, h, d); } GL.End(); // 下面(4) GL.BindTexture(TextureTarget.Texture2D, Textures[3]); GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitY); GL.TexCoord2(0, 0); GL.Vertex3(-w, -h, d); GL.TexCoord2(1, 0); GL.Vertex3(-w, -h, -d); GL.TexCoord2(0, 1); GL.Vertex3(w, -h, d); GL.TexCoord2(1, 1); GL.Vertex3(w, -h, -d); } GL.End(); } } |
テクスチャが作成されていない場合はCreateTexture(Bitmap bmp)メソッドでテクスチャを作成します。引数のBitmapは1から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 |
public class Jiki { void CreateTexture(Bitmap bmp) { GL.Enable(EnableCap.Texture2D); int texture; GL.GenTextures(1, out texture); Textures.Add(texture); GL.BindTexture(TextureTarget.Texture2D, texture); System.Drawing.Imaging.BitmapData data = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); bmp.UnlockBits(data); GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, data.Width, data.Height, 0, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); } } |
Form1クラス内で自機の位置、進行方向を管理するプロパティを示します。前回はここでglControl.Refresh()メソッドを呼び出していたのですが、ここで呼び出すのはやめてTimer.Tickイベントを捕捉したときにおこなうことにします。
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 partial class Form1 : Form { // 自機のX座標 public float X { get; set; } // 自機のY座標 public float Y { get; set; } // 自機のZ座標 public float Z { get; set; } // X軸を中心とした自機の傾き float RotateX { get; set; } // Y軸を中心とした自機の傾き float RotateY { get; set; } // Z軸を中心とした自機の傾き float RotateZ { get; set; } // 自機の進行方向を上向きに void RotateUpTarget(float value) { RotateX += value; } // 自機の進行方向を下向きに void RotateDownTarget(float value) { RotateX -= value; } // 自機の進行方向を右向きに void RotateRightTarget(float value) { RotateY -= value; } // 自機の進行方向を左向きに void RotateLeftTarget(float value) { RotateY += value; } // 自機を進行方向にむかって前進させる void MoveTargetDirect(float value) { float radY = (float)(2f * Math.PI * RotateY / 360); float radX = (float)(2f * Math.PI * RotateX / 360); X += -value * (float)Math.Sin(radY) * (float)Math.Cos(radX); Y += value * (float)Math.Sin(radX); Z += -value * (float)Math.Cos(radY) * (float)Math.Cos(radX); } } |
Timer.Tickイベントが発生したらどのキーが押されているのかを調べて、方向転換をします。方向転換が無制限にできてしまうと壁よけゲームとして成り立たなくなるので、進行方向として設定できるのは上下左右いずれも30度とします。
そのあと自機を進行方向にむかって前進させた場合の自機の座標を求めて描画処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { private void Timer_Tick(object sender, EventArgs e) { // 上下左右 進路変更は30度までとする if(DoesMoveUp && RotateX < 30) RotateUpTarget(3); if(DoesMoveDown && RotateX > -30) RotateDownTarget(3); if(DoesMoveLeft && RotateY < 30) RotateLeftTarget(3); if(DoesMoveRight && RotateY > -30) RotateRightTarget(3); MoveTargetDirect(0.1f); SetSight(); glControl.Refresh(); } } |
描画処理をするときに視界の設定が必要です。これはOpenTKでオブジェクトを真後ろから追いかけるような描画をするとほとんど同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { // 視界の設定 void SetSight() { float Distance = 3f; float radY = (float)(2f * Math.PI * RotateY / 360); float radX = (float)(2f * Math.PI * RotateX / 360); float eyeX = Distance * (float)Math.Sin(radY) * (float)Math.Cos(radX) + X; float eyeY = -Distance * (float)Math.Sin(radX) + Y; float eyeZ = Distance * (float)Math.Cos(radY) * (float)Math.Cos(radX) + Z; Vector3 eye = new Vector3(eyeX, eyeY, eyeZ); Vector3 target = new Vector3(X, Y, Z); Vector3 up = Vector3.UnitY; Matrix4 look = Matrix4.LookAt(eye, target, up); GL.LoadMatrix(ref look); } } |
最後に描画処理を示します。これは最初に作成したC# OpenTKで壁よけゲームをつくると変わりません。
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); Lighting(); DrawWalls(); DrawJiki(); glControl.SwapBuffers(); } } |
まだまだゲームとしては不満な点が残ります。これからも改良していきます。