C# OpenTKで3Dカーレースっぽいゲームをつくります。前回のC# OpenTKでUnityのアセットは使えるのか?で取得したデータで自動車をつくります。
まずは基本的な部分を示します。
1 2 3 4 5 6 7 8 9 10 11 |
using OpenTK; using OpenTK.Graphics.OpenGL; public class GLControlEx: OpenTK.GLControl { public GLControlEx() { // ないとForm1でKeyDown、KeyUpイベントが捕捉できない SetStyle(ControlStyles.Selectable, false); } } |
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 |
using OpenTK; using OpenTK.Graphics.OpenGL; public partial class Form1 : Form { public Form1() { InitializeComponent(); InitCarsData(); } private void glControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); } private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.ClearColor(Color.White); Update(); SetSight(); Lighting(); Draw(); glControl.SwapBuffers(); } new void Update() { } new void Draw() { } private void glControl_Resize(object sender, EventArgs e) { SetProjection(); // 再描画 glControl.Refresh(); } protected override void OnKeyDown(KeyEventArgs e) { } } |
視体積の設定や視界の設定をおこなうメソッドを作成します。今回は自機(というか自分の車)の後ろをカメラが追うような描写をします。自車の方向に進行よっては逆光になることもあるので、そうはならないように光源を設定します。
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 { // Carクラスに関しては後述 Car MyCar = null; private void SetProjection() { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver3, glControl.AspectRatio, 0.5f, 100.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } Vector3 Eye = Vector3.UnitZ; void SetSight() { double rad = MyCar.Rotate * Math.PI / 180; float x = -(float)(5.6 * Math.Cos(rad)) + MyCar.X; float y = -(float)(5.6 * Math.Sin(rad)) + MyCar.Y; // 視界の設定 Eye = new Vector3(x, y, 3); Vector3 target = new Vector3(car.X, car.Y, 1.8f); Matrix4 look = Matrix4.LookAt(Eye, target, Vector3.UnitZ); GL.LoadMatrix(ref look); // 法線の自動調節 GL.Enable(EnableCap.Normalize); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 // 逆光にならないようにする float[] position = new float[] { Eye.X, Eye.Y, Eye.Z, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); float[] ambientColor = new float[] { 0.1f, 0.1f, 0.1f, 1.0f }; GL.Light(LightName.Light0, LightParameter.Ambient, ambientColor); GL.Light(LightName.Light0, LightParameter.Diffuse, Color.White); GL.Enable(EnableCap.Light0); } } |
では車を描画するためのクラスと関連するクラスを示します。
まず車を描画するために取得した座標を格納するクラスを示します。これは C# OpenTKでUnityのアセットは使えるのか? の最後の部分ででてきたものと同じです。
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 |
public class CarData { public List<VertexInfo> VertexInfos = new List<VertexInfo>(); } public class VertexInfo { public List<Vertex> Vertexes = new List<Vertex>(); public List<int> ints = new List<int>(); public string Name = ""; public float TranslateX = 0; public float TranslateY = 0; public float TranslateZ = 0; } public class Vertex { public float x = 0; public float y = 0; public float z = 0; public float nx = 0; public float ny = 0; public float nz = 0; public float u = 0; public float v = 0; } |
次にCarクラスの基底クラスであるCharctorクラスを示します。
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 |
public class Charctor { public Charctor(Form1 form1) { MainForm = form1; } protected Form1 MainForm = null; public float X = 0; public float Y = 0; public float Z = 0; public float VecX = 0; public float VecY = 0; public float VecZ = 0; protected int UpdateCount = 0; public virtual void Update() { X += VecX; Y += VecY; UpdateCount++; } public virtual void Draw() { } protected List<int> CreateTextures() { List<int> textures = new List<int>(); GL.Enable(EnableCap.Texture2D); GL.AlphaFunc(AlphaFunction.Gequal, 0.5f); GL.Enable(EnableCap.AlphaTest); List<Bitmap> bitmaps = GetBitmaps(); foreach (Bitmap bitmap0 in bitmaps) { int texture; GL.GenTextures(1, out texture); textures.Add(texture); GL.BindTexture(TextureTarget.Texture2D, texture); bitmap0.RotateFlip(RotateFlipType.RotateNoneFlipY); System.Drawing.Imaging.BitmapData data = bitmap0.LockBits( new Rectangle(0, 0, bitmap0.Width, bitmap0.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); bitmap0.UnlockBits(data); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); GL.BindTexture(TextureTarget.Texture2D, 0); } return textures; } protected virtual List<Bitmap> GetBitmaps() { List<Bitmap> bitmaps = new List<Bitmap>(); return bitmaps; } } |
次にCarクラスを示します。carDataをもとに描画するときに座標系がちがっているので修正の処理をいれています。フィールド変数Rotateが0のときは車はX軸方向を向いています。
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 |
public class Car: Charctor { public Car(Form1 form1, CarData carData) : base(form1) { CarData = carData; if (Textures.Count == 0) Textures = CreateTextures(); } static protected List<int> Textures = new List<int>(); public int Rotate = 0; CarData CarData = null; protected override List<Bitmap> GetBitmaps() { List<Bitmap> bitmaps = new List<Bitmap>(); Bitmap bitmap1 = Properties.Resources.PaletteTexture; bitmaps.Add(bitmap1); Bitmap bitmap2 = Properties.Resources.Shadow; bitmaps.Add(bitmap2); return bitmaps; } public float Speed = 0.1f; public bool IsDead = false; public override void Update() { double rad = Math.PI / 180 * Rotate; VecX = Speed * (float)Math.Cos(rad); VecY = Speed * (float)Math.Sin(rad); base.Update(); } public override void Draw() { GL.PushMatrix(); { GL.Translate(X, Y, Z); GL.Rotate(Rotate, 0, 0, 1); GL.Rotate(90, 0, 0, 1); GL.Rotate(90, 1, 0, 0); if (CarData != null) DrawCar(CarData); } GL.PopMatrix(); } void DrawCar(CarData carData) { if (carData == null) return; foreach (VertexInfo info in carData.VertexInfos) { if (info.Name != "Shadow") GL.BindTexture(TextureTarget.Texture2D, Textures[0]); else GL.BindTexture(TextureTarget.Texture2D, Textures[1]); GL.Enable(EnableCap.Normalize); GL.PushMatrix(); { GL.Translate(info.TranslateX, info.TranslateY, info.TranslateZ); GL.Begin(BeginMode.Triangles); { foreach (int i in info.ints) { GL.TexCoord2(info.Vertexes[i].u, info.Vertexes[i].v); GL.Normal3(new Vector3(info.Vertexes[i].nx, info.Vertexes[i].ny, info.Vertexes[i].nz)); GL.Vertex3(info.Vertexes[i].x, info.Vertexes[i].y, info.Vertexes[i].z); } } GL.End(); } GL.PopMatrix(); GL.BindTexture(TextureTarget.Texture2D, 0); } } } |
CarDataクラスのなかにデータを格納するのが自作メソッドForm1.InitCarsData()です。C# OpenTKでUnityのアセットは使えるのか?で保存したXMLファイルを読み取ってフィールド変数のなかに格納します。
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 |
using System.IO; using System.Xml.Serialization; public partial class Form1 : Form { CarData BlueCarData1 = null; CarData BlueCarData2 = null; CarData RedCarData1 = null; CarData RedCarData2 = null; CarData OrangeCarData1 = null; CarData YellowCarData1 = null; void InitCarsData() { string path = Application.StartupPath + "\\BlueCarData1.xml"; BlueCarData1 = ReadCarFiles(Application.StartupPath + "\\BlueCarData1.xml"); BlueCarData2 = ReadCarFiles(Application.StartupPath + "\\BlueCarData2.xml"); RedCarData1 = ReadCarFiles(Application.StartupPath + "\\RedCarData1.xml"); RedCarData2 = ReadCarFiles(Application.StartupPath + "\\RedCarData2.xml"); OrangeCarData1 = ReadCarFiles(Application.StartupPath + "\\OrangeCarData1.xml"); YellowCarData1 = ReadCarFiles(Application.StartupPath + "\\YellowCarData1.xml"); } CarData ReadCarFiles(string path) { XmlSerializer xml = new XmlSerializer(typeof(CarData)); StreamReader sr = new StreamReader(path); CarData carData = (CarData)xml.Deserialize(sr); sr.Close(); return carData; } } |
そしてファイルの読み取りが完了し、GlControlがロードされたら、これをもとにCarオブジェクトを生成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { Car MyCar = null; private void glControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); SetProjection(); GL.Enable(EnableCap.DepthTest); MyCar = new Car(this, OrangeCarData1); } } |
では自車を動かしてみましょう。ハンドル操作は←キーと→キーで、アクセルは↑キー、ブレーキは↓キーです。バックはできない仕様になっています。
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 { Timer Timer = new Timer(); public Form1() { InitializeComponent(); InitCarsData(); Timer.Tick += Timer_Tick; Timer.Interval = 1000 / 60; Timer.Start(); } private void Timer_Tick(object sender, EventArgs e) { glControl.Refresh(); } new void Update() { MyCar.Update(); } void Draw() { DrawFloor(); MyCar.Draw(); } protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Left) { MyCar.Rotate += 5; } else if (e.KeyCode == Keys.Right) { MyCar.Rotate -= 5; } else if (e.KeyCode == Keys.Up) { MyCar.Speed += 0.1f; } else if (e.KeyCode == Keys.Down) { MyCar.Speed -= 0.1f; if (MyCar.Speed < 0) MyCar.Speed = 0; } } } |