C# OpenTKで3Dカーレースっぽいゲームをつくります。前回のC# OpenTKでUnityのアセットは使えるのか?で取得したデータで自動車をつくります。
Contents
基本的な部分をOpenTKでつくる
まずは基本的な部分を示します。
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 57 58 59 60 61 62 63 64 65 66 67 |
using OpenTK; using OpenTK.Graphics.OpenGL; public partial class Form1 : Form { GameManager GameManager; // GameManagerクラスは後述 Timer Timer = new Timer(); public Form1() { InitializeComponent(); glControl.Load += GlControl_Load; glControl.Paint += GlControl_Paint; glControl.Resize += GlControl_Resize; Timer.Tick += Timer_Tick; Timer.Interval = 1000 / 60; Timer.Start(); GameManager = new GameManager(this, glControl); } private void Timer_Tick(object sender, EventArgs e) { glControl.Refresh(); } private void GlControl_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); GameManager.OnGLControlLoad(); } private void GlControl_Resize(object sender, EventArgs e) { SetProjection(); // 再描画 glControl.Refresh(); } private void GlControl_Paint(object sender, PaintEventArgs e) { GameManager.Draw(); } 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, 500.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } } |
車を描画するクラス
車を描画するためのクラスと関連するクラスを示します。
CarDataクラス
まず車を描画するために取得した座標を格納するクラスを示します。これは 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; } |
Charctorクラス
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 68 |
using OpenTK; using OpenTK.Graphics.OpenGL; public class Charctor { public Charctor() { } 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; Z += VecZ; 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をもとに描画するときに座標系がちがっているので修正の処理をいれています。そのまま描画するとX軸の方向を向いているので90を足して向こう側を向かせます。
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 { protected CarData CarData = null; protected List<int> Textures = new List<int>(); public int Rotate = 0; public float Speed = 0.0f; public bool IsDead = false; public Car(GameManager gameManager, CarData carData) { CarData = carData; GameManager = gameManager; Textures = CreateTextures(); } protected GameManager GameManager { get; } 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 override void Update() { double rad = Math.PI / 180 * Rotate; VecX = Speed * (float)Math.Cos(rad); VecZ = -Speed * (float)Math.Sin(rad); base.Update(); } public override void Draw() { GL.PushMatrix(); { GL.Translate(X, Y, Z); GL.Rotate(Rotate + 90, 0, 1, 0); if (CarData != null) DrawCar(CarData); } GL.PopMatrix(); } protected 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); } } } |
コースを描画する
次にコースを描画するためのCourseクラスを示します。長々と書いていますが、画像の輪郭を取得するにはに掲載したものを少しだけ変更しただけです。
コンストラクタにコースが書かれた画像ファイルを渡すとコースの内部の点と境界線の点の座標を取得できるようになります。
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 137 138 |
public class Course { List<Point> _insidePoints = new List<Point>(); List<Point> _borderPoints1 = new List<Point>(); List<Point> _borderPoints2 = new List<Point>(); public Course(string path) { GameManager = gameManager; using (Bitmap bitmap = new Bitmap(path)) { _insidePoints = GetPointsInside(bitmap, Color.Black); List<List<Point>> ptsList = GetContourPointsListFromBitmap(bitmap, Color.Black); _borderPoints1 = ptsList[0]; _borderPoints2 = ptsList[1]; } } GameManager GameManager { get; } List<Point> GetPointsInside(Bitmap bitmap, Color color) { List<Point> points = new List<Point>(); for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { Color pixelColor = bitmap.GetPixel(x, y); if (pixelColor.ToArgb() == color.ToArgb()) { points.Add(new Point(x, y)); } } } return points; } Bitmap GetBitmapContour(Bitmap bitmap, Color color) { Bitmap newBitmap = new Bitmap(bitmap.Width, bitmap.Height); for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { Color pixelColor = bitmap.GetPixel(x, y); if (pixelColor.ToArgb() == color.ToArgb()) { bool b1 = bitmap.GetPixel(x + 1, y).ToArgb() != color.ToArgb(); bool b2 = bitmap.GetPixel(x - 1, y).ToArgb() != color.ToArgb(); bool b3 = bitmap.GetPixel(x, y + 1).ToArgb() != color.ToArgb(); bool b4 = bitmap.GetPixel(x, y - 1).ToArgb() != color.ToArgb(); if (b1 || b2 || b3 || b4) newBitmap.SetPixel(x, y, color); } } } return newBitmap; } List<List<Point>> GetContourPointsListFromBitmap(Bitmap bitmap, Color color) { Bitmap bitmap2 = GetBitmapContour(bitmap, color); List<List<Point>> pointsList = GetPointListContour(bitmap2, color); bitmap2.Dispose(); return pointsList; } Point GetPointFromColor(Bitmap bitmap, Color color) { for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { Color color1 = bitmap.GetPixel(x, y); if (color1.ToArgb() == color.ToArgb()) { return new Point(x, y); } } } return Point.Empty; } List<List<Point>> GetPointListContour(Bitmap bitmap, Color color) { // 渡されたBitmapには輪郭部分しか残されていないのが前提 List<List<Point>> pointsList = new List<List<Point>>(); while (true) { List<Point> points = new List<Point>(); Point point = GetPointFromColor(bitmap, color); if (point == Point.Empty) break; Point tempPoint = point; points.Add(tempPoint); while (true) { Point nextPoint = Point.Empty; bitmap.SetPixel(tempPoint.X, tempPoint.Y, Color.Red); if (bitmap.GetPixel(tempPoint.X + 1, tempPoint.Y).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X + 1, tempPoint.Y); else if (bitmap.GetPixel(tempPoint.X - 1, tempPoint.Y).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X - 1, tempPoint.Y); else if (bitmap.GetPixel(tempPoint.X, tempPoint.Y - 1).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X, tempPoint.Y - 1); else if (bitmap.GetPixel(tempPoint.X, tempPoint.Y + 1).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X, tempPoint.Y + 1); else if (bitmap.GetPixel(tempPoint.X + 1, tempPoint.Y + 1).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X + 1, tempPoint.Y + 1); else if (bitmap.GetPixel(tempPoint.X - 1, tempPoint.Y - 1).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X - 1, tempPoint.Y - 1); else if (bitmap.GetPixel(tempPoint.X + 1, tempPoint.Y - 1).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X + 1, tempPoint.Y - 1); else if (bitmap.GetPixel(tempPoint.X - 1, tempPoint.Y + 1).ToArgb() == color.ToArgb()) nextPoint = new Point(tempPoint.X - 1, tempPoint.Y + 1); if (nextPoint == Point.Empty) break; tempPoint = nextPoint; points.Add(tempPoint); } pointsList.Add(points); } return pointsList; } } |
コースの境界線を取得する
Bitmapの1ピクセルを1とはせずに拡大処理を入れているため、上記にさらに以下を加えます。
まず2つの境界線を構成する点のリストを取得するプロパティを作成します。
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 |
public class Course { List<PointF> _expandedBorderPoints1 = new List<PointF>(); public List<PointF> ExpandedBorderPoints1 { get { if (_expandedBorderPoints1.Count == 0) { float rate = (float)GameManager.ExpansionRate; foreach (Point pt in _borderPoints1) { _expandedBorderPoints1.Add(new PointF(pt.X * rate, pt.Y * rate)); } } return _expandedBorderPoints1; } } List<PointF> _expandedBorderPoints2 = new List<PointF>(); public List<PointF> ExpandedBorderPoints2 { get { if (_expandedBorderPoints2.Count == 0) { float rate = (float)GameManager.ExpansionRate; foreach (Point pt in _borderPoints2) { _expandedBorderPoints2.Add(new PointF(pt.X * rate, pt.Y * rate)); } } return _expandedBorderPoints2; } } } |
コースの内部
コースはGetPointsInsideメソッドが返した点を中心とする矩形を敷き詰めてつくります。InsideRectanglesはその矩形のリストを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Course { List<RectangleF> _insideRectangles = new List<RectangleF>(); public List<RectangleF> InsideRectangles { get { if (_insideRectangles.Count == 0) { foreach (Point pt in _insidePoints) { PointF pointF = new PointF((pt.X - 0.5f) * (float)GameManager.ExpansionRate, (pt.Y - 0.5f) * (float)GameManager.ExpansionRate); SizeF sizeF = new SizeF((float)GameManager.ExpansionRate, (float)GameManager.ExpansionRate); _insideRectangles.Add(new RectangleF(pointF, sizeF)); } } return _insideRectangles; } } } |
IsPointInsideメソッドは渡された座標がコースの内部かどうかを返します。わたされた座標を拡大率で除算し、これが_insidePoints内に存在するか調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Course { public bool IsPointInside(float x, float z, bool isBorderInside) { int x1 = (int)Math.Round(x / GameManager.ExpansionRate); int z1 = (int)Math.Round(z / GameManager.ExpansionRate); if (_insidePoints.Any(pt => pt == new Point(x1, z1))) { if (isBorderInside) return true; else { if (_borderPoints1.Any(pt => pt == new Point(x1, z1))) return false; if (_borderPoints2.Any(pt => pt == new Point(x1, z1))) return false; return true; } } else return false; } } |
コースアウト対策
GetNeerestExpandedBorderPointsメソッドは引数でわたされた点にもっとも近い両側のブロックの座標を返します。自車がクラッシュしたときの復活地点の算出などに使用します。
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 |
public class Course { public PointF[] GetNeerestExpandedBorderPoints(PointF point) { double minDistanceSquared = double.MaxValue; PointF ret1 = PointF.Empty; foreach (PointF pt in ExpandedBorderPoints1) { double d = Math.Pow(point.X - pt.X, 2) + Math.Pow(point.Y - pt.Y, 2); if (minDistanceSquared > d) { minDistanceSquared = d; ret1 = pt; } } minDistanceSquared = double.MaxValue; PointF ret2 = PointF.Empty; foreach (PointF pt in ExpandedBorderPoints2) { double d = Math.Pow(point.X - pt.X, 2) + Math.Pow(point.Y - pt.Y, 2); if (minDistanceSquared > d) { minDistanceSquared = d; ret2 = pt; } } return new PointF[] { ret1, ret2 }; } } |
GetNeerestExpandedBorderPointsメソッドを追加したので、Carクラスに以下を追加します。車の初期座標をセットすれば最適な方向を向かせることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Car: Charctor { public void InitPosition(float x, fleat z) { X = x; Y = 0; Z = z; // 初期位置からもっとも近い2点を求める PointF[] points = GameManager.Course.GetNeerestExpandedBorderPoints(new PointF(x, z)); // 車の向きを最適化する double rad = Math.Atan2(points[1].Y - points[0].Y, points[1].X - points[0].X); int newRotate = 90 - (int)(rad * 180 / Math.PI); this.Rotate = newRotate; } } |
Courseクラスに話を戻します。GetCenterPointsメソッドはコースの両側にあるブロックからみて中間点にあたる点のリストを返します。この点を結べばセンターラインがひけるのかというとそうはなりません。コースの輪郭を1.0単位にしているのでギザギザになってしまうのです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Course { public List<PointF> GetCenterPoints() { List<PointF> ret = new List<PointF>(); foreach (PointF pt in this.ExpandedBorderPoints1) { var a = GetNeerestExpandedBorderPoints(pt); float x = (pt.X + a[1].X) / 2; float y = (pt.Y + a[1].Y) / 2; ret.Add(new PointF(x, y)); } return ret; } } |
IdealPointメソッドは引数として渡された点の近くにある理想の座標(コースの真ん中)を返します。ライバル車の動作を考えるときに必要です。
1 2 3 4 5 6 7 8 |
public class Course { public PointF IdealPoint(PointF point) { PointF[] points = GetNeerestExpandedBorderPoints(point); return new PointF((points[0].X + points[1].X) / 2, (points[0].Y + points[1].Y) / 2); } } |
GameManagerクラス
描画するオブジェクトの更新や描画をするためのGameManagerクラスを作成します。
初期化の処理
車とコースを描画します。車に対してコースの幅が狭いので調整しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class GameManager { // カメラの座標 Vector3 Eye = Vector3.UnitZ; Vector3 Target = Vector3.UnitZ; // 方向キーは押されているか? public bool LeftKeyDown = false; public bool RightKeyDown = false; public bool UpKeyDown = false; public bool DownKeyDown = false; // 自分の車 public Car MyCar = null; // コース public Course Course; // コースの拡大率 1ではちょっとコースの幅が狭いので1.3倍にする public double ExpansionRate = 1.3; } |
コンストラクタを示します。クラス内からForm1とGLControlにアクセスできるように引数を渡しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class GameManager { public GameManager(Form1 form1, GLControlEx glControlEx) { Form1 = form1; GLControlEx = glControlEx; Eye = Vector3.UnitZ; Target = Vector3.UnitZ; Course = new Course(Application.StartupPath + "\\course.png", this); } Form1 Form1 { get; } GLControlEx GLControlEx { get; } } |
視界と光源の設定
視界と光源の設定をする処理を示します。今回は自機(というか自分の車)の後ろをカメラが追うような描写をします。自車の方向に進行よっては逆光になることもあるので、そうはならないように光源を設定します。
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 class GameManager { void SetSight() { double rad = MyCar.Rotate * Math.PI / 180; float x = -(float)(5.6 * Math.Cos(rad)) + MyCar.X; float z = (float)(5.6 * Math.Sin(rad)) + MyCar.Z; // 視界の設定 Eye = new Vector3(x, 3, z); Target = new Vector3(MyCar.X, 1.8f, MyCar.Z); Matrix4 look = Matrix4.LookAt(Eye, Target, Vector3.UnitY); GL.LoadMatrix(ref look); // 法線の自動調節 GL.Enable(EnableCap.Normalize); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 float[] position = new float[] { 0, 1, 0, 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); } } |
車の頂点座標を読み出す
GLControlがロードされたらReadCarFilesメソッドでxmlファイルから頂点座標を読み出します。読み取るのはC# OpenTKでUnityのアセットは使えるのか?で保存したXMLファイルです。ここからダウンロードできます。
ReadCarFilesメソッドで取得したCarDataオブジェクトを引数にしてCarクラスのコンストラクタを呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class GameManager { public Car MyCar = null; public void OnGLControlLoad() { // 初期状態の座標は(148, 0, 120) MyCar = new Car(this, ReadCarFiles(Application.StartupPath + "\\OrangeCarData1.xml")); MyCar.InitPosition(148 * (float)ExpansionRate, 120 * (float)ExpansionRate); } 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; } } |
更新処理
更新処理に関する部分を示します。キーが押されていたら車の方向と速度を変えます。速度とコースアウトしていないかどうかをフォームに貼り付けているLabelに表示します。フォームのLabelはpublicにしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class GameManager { int UpdateCount = 0; void Update() { UpdateCount++; if (LeftKeyDown) MyCar.Rotate += 2; else if (RightKeyDown) MyCar.Rotate -= 2; MyCar.Update(); Form1.label1.Text = String.Format("速度 {0} Km/h", Math.Round(MyCar.Speed, 2) * 100); int x = (int)Math.Round(MyCar.X / ExpansionRate); int z = (int)Math.Round(MyCar.Z / ExpansionRate); Point point = new Point(x, z); if (Course.IsPointInside(point, false)) Form1.label2.Text = ""; else Form1.label2.Text = "コースアウト!"; } } |
描画の処理
描画の処理をする部分を示します。前述のUpdateメソッドでオブジェクトの表示位置を更新したあとCar.Drawメソッドと後述するDrawCourseメソッドで車とコースを描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class GameManager { public void Draw() { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.ClearColor(Color.LightBlue); Update(); SetSight(); Lighting(); DrawCourse(); MyCar.Draw(); GLControlEx.SwapBuffers(); } } |
DrawCourseメソッドを示します。
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 |
public class GameManager { void DrawCourse() { GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Green); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Green); GL.Begin(BeginMode.Quads); { GL.Normal3(Vector3.UnitY); GL.Vertex3(0, -0.02, 0); GL.Vertex3(1000, -0.02, 0); GL.Vertex3(1000, -0.02, 1000); GL.Vertex3(0, -0.02, 1000); } GL.End(); GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.FromArgb(255, 30, 30, 30)); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.FromArgb(255, 30, 30, 30)); GL.Begin(BeginMode.Quads); { foreach (Point pt in Course.InsidePoints) { GL.Normal3(Vector3.UnitZ); GL.Vertex3((pt.X + 0.5) * ExpansionRate, 0, (pt.Y + 0.5) * ExpansionRate); GL.Vertex3((pt.X + 0.5) * ExpansionRate, 0, (pt.Y - 0.5) * ExpansionRate); GL.Vertex3((pt.X - 0.5) * ExpansionRate, 0, (pt.Y - 0.5) * ExpansionRate); GL.Vertex3((pt.X - 0.5) * ExpansionRate, 0, (pt.Y + 0.5) * ExpansionRate); } } GL.End(); GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Gray); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Gray); List<Point> points1 = Course.BorderPoints1; foreach (Point pt in Course.BorderPoints1) { DrawCube(new PointF(pt.X * (float)ExpansionRate, pt.Y * (float)ExpansionRate)); } foreach (Point pt in Course.BorderPoints2) { DrawCube(new PointF(pt.X * (float)ExpansionRate, pt.Y * (float)ExpansionRate)); } GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.White); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.White); GL.LineWidth(1); } void DrawCube(PointF point) { double rate2 = 0.7; GL.Begin(BeginMode.Quads); { // 上面 GL.Normal3(0, 1, 0); GL.Vertex3(point.X - (ExpansionRate / 2) * rate2, 1f, point.Y - (ExpansionRate / 2) * rate2); GL.Vertex3(point.X - (ExpansionRate / 2) * rate2, 1f, point.Y + (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + (ExpansionRate / 2) * rate2, 1f, point.Y + (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + (ExpansionRate / 2) * rate2, 1f, point.Y - (ExpansionRate / 2) * rate2); // 下面 GL.Vertex3(point.X - ExpansionRate / 2, 0, point.Y - ExpansionRate / 2); GL.Vertex3(point.X - ExpansionRate / 2, 0, point.Y + ExpansionRate / 2); GL.Vertex3(point.X + ExpansionRate / 2, 0, point.Y + ExpansionRate / 2); GL.Vertex3(point.X + ExpansionRate / 2, 0, point.Y - ExpansionRate / 2); // 前面 GL.Normal3(0, 0.5, 1); GL.Vertex3(point.X - (ExpansionRate / 2) * rate2, 1f, point.Y - (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + (ExpansionRate / 2) * rate2, 1f, point.Y - (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + ExpansionRate / 2, 0, point.Y - ExpansionRate / 2); GL.Vertex3(point.X - ExpansionRate / 2, 0, point.Y - ExpansionRate / 2); // 後面 GL.Normal3(0, 0.5, -1); GL.Vertex3(point.X - (ExpansionRate / 2) * rate2, 1f, point.Y + (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + (ExpansionRate / 2) * rate2, 1f, point.Y + (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + ExpansionRate / 2, 0, point.Y + ExpansionRate / 2); GL.Vertex3(point.X - ExpansionRate / 2, 0, point.Y + ExpansionRate / 2); // 左面 GL.Normal3(-1, 0.5, 0); GL.Vertex3(point.X - (ExpansionRate / 2) * rate2, 1f, point.Y - (ExpansionRate / 2) * rate2); GL.Vertex3(point.X - (ExpansionRate / 2) * rate2, 1f, point.Y + (ExpansionRate / 2) * rate2); GL.Vertex3(point.X - ExpansionRate / 2, 0, point.Y + ExpansionRate / 2); GL.Vertex3(point.X - ExpansionRate / 2, 0, point.Y - ExpansionRate / 2); // 右面 GL.Normal3(1, 0.5, 0); GL.Vertex3(point.X + (ExpansionRate / 2) * rate2, 1f, point.Y - (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + (ExpansionRate / 2) * rate2, 1f, point.Y + (ExpansionRate / 2) * rate2); GL.Vertex3(point.X + ExpansionRate / 2, 0, point.Y + ExpansionRate / 2); GL.Vertex3(point.X + ExpansionRate / 2, 0, point.Y - ExpansionRate / 2); } GL.End(); } } |
では自車を動かしてみましょう。ハンドル操作は←キーと→キーで、アクセルは↑キー、ブレーキは↓キーです。バックはできない仕様になっています。またコースアウトしてもそのまま走り続けます。