C# OpenTKで戦車を描画します。そのうち戦車同士で打ち合うゲームを作ってみようと考えています。
どこから見るか、どこを見るかを指定できるようにしました。それではコードをみてみましょう。
視点のXYZ座標を指定したら視界の設定ができるようにプロパティを作ります。変更されるとどのように変更されたのかも表示させます。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); ShowLabel(); Tank.Color = Color.Red; } // 視点の X座標 float _eyeX = 0; float EyeX { get { return _eyeX; } set { _eyeX = (float)Math.Round(value, 2, MidpointRounding.AwayFromZero); ShowLabel(); SetSight(); } } // 視点の Y座標 float _eyeY = 0; float EyeY { get { return _eyeY; } set { _eyeY = (float)Math.Round(value, 2, MidpointRounding.AwayFromZero); ShowLabel(); SetSight(); } } // 視点の Z座標 float _eyeZ = 0; float EyeZ { get { return _eyeZ; } set { _eyeZ = (float)Math.Round(value, 2, MidpointRounding.AwayFromZero); ShowLabel(); SetSight(); } } // 視線のターゲット X座標 float _targetX = 0; float TargetX { get { return _targetX; } set { _targetX = (float)Math.Round(value, 2, MidpointRounding.AwayFromZero); ShowLabel(); SetSight(); } } // 視線のターゲット Y座標 float _targetY = 0; float TargetY { get { return _targetY; } set { _targetY = (float) Math.Round(value, 2, MidpointRounding.AwayFromZero); ShowLabel(); SetSight(); } } // 視線のターゲット Z座標 float _targetZ = 0; float TargetZ { get { return _targetZ; } set { _targetZ = (float)Math.Round(value, 2, MidpointRounding.AwayFromZero); ShowLabel(); SetSight(); } } } |
これは視界の情報が変更されたら反映させます。自作メソッド SetSight()で視界の設定をおこない、ShowLabel()メソッドで情報の表示をおこないます。
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 { void SetSight() { // 視界の設定 Vector3 eye = new Vector3(EyeX, EyeY, EyeZ); Vector3 target = new Vector3(TargetX, TargetY, TargetZ); Vector3 up = new Vector3(0, 1, 0); Matrix4 look = Matrix4.LookAt(eye, target, up); GL.LoadMatrix(ref look); } void ShowLabel() { labelRotateX.Text = RotateX.ToString(); labelRotateY.Text = RotateY.ToString(); labelRotateZ.Text = RotateZ.ToString(); labelEyeX.Text = EyeX.ToString(); labelEyeY.Text = EyeY.ToString(); labelEyeZ.Text = EyeZ.ToString(); labelTargetX.Text = TargetX.ToString(); labelTargetY.Text = TargetY.ToString(); labelTargetZ.Text = TargetZ.ToString(); } } |
以下は初期設定に関するものです。
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 |
public partial class Form1 : Form { private void glControl_Load(object sender, EventArgs e) { Init(); } void Init() { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); // 法線の自動調節 GL.Enable(EnableCap.Normalize); EyeX = 0f; EyeY = 1f; EyeZ = 2f; Lighting(); glControl.Refresh(); } private void SetProjection() { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver3, glControl.AspectRatio, 0.01f, 30.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 float[] position = new float[] { 0.8f, 1.0f, 1.2f, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); GL.Enable(EnableCap.Light0); } private void glControl_Resize(object sender, EventArgs e) { SetProjection(); // 再描画 glControl.Refresh(); } private void glControl_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); glControl.SwapBuffers(); } } |
以下は戦車を描画するためのクラスです。
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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
public class Tank { public Tank() { } // 戦車の位置 public float X { get; set; } = 0; public float Y { get; set; } = 0; public float Z { get; set; } = 0; // 戦車のX軸を中心とした回転角度 public float RotateX { get; set; } = 0; // 戦車のY軸を中心とした回転角度 public float RotateY { get; set; } = 0; // 戦車のZ軸を中心とした回転角度 public float RotateZ { get; set; } = 0; public Color Color { get; set; } public void Draw() { GL.PushMatrix(); { GL.Rotate(RotateX, 1, 0, 0); GL.Rotate(RotateY, 0, 1, 0); GL.Rotate(RotateZ, 0, 0, 1); Draw0(); } GL.PopMatrix(); } public void Draw0() { GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color); GL.Begin(BeginMode.Quads); { // 左 GL.Normal3(-Vector3.UnitX); GL.Vertex3(-0.25, 0.5, 0.35); GL.Vertex3(-0.25, 0.25, 0.35); GL.Vertex3(-0.25, 0.25, -0.35); GL.Vertex3(-0.25, 0.5, -0.35); // 右 GL.Normal3(Vector3.UnitX); GL.Vertex3(0.25, 0.5, 0.35); GL.Vertex3(0.25, 0.25, 0.35); GL.Vertex3(0.25, 0.25, -0.35); GL.Vertex3(0.25, 0.5, -0.35); // 正面 GL.Normal3(Vector3.UnitZ); GL.Vertex3(0.25, 0.5, 0.35); GL.Vertex3(0.25, 0.25, 0.35); GL.Vertex3(-0.25, 0.25, 0.35); GL.Vertex3(-0.25, 0.5, 0.35); // 背面 GL.Normal3(-Vector3.UnitZ); GL.Vertex3(0.25, 0.5, -0.35); GL.Vertex3(0.25, 0.25, -0.35); GL.Vertex3(-0.25, 0.25, -0.35); GL.Vertex3(-0.25, 0.5, -0.35); // 上面 GL.Normal3(Vector3.UnitY); GL.Vertex3(0.25, 0.5, 0.35); GL.Vertex3(-0.25, 0.5, 0.35); GL.Vertex3(-0.25, 0.5, -0.35); GL.Vertex3(0.25, 0.5, -0.35); // 砲塔左面 GL.Normal3(-Vector3.UnitX); GL.Vertex3(-0.15, 0.7, 0.1); GL.Vertex3(-0.15, 0.5, 0.1); GL.Vertex3(-0.15, 0.5, -0.2); GL.Vertex3(-0.15, 0.7, -0.2); // 砲塔右面 GL.Normal3(Vector3.UnitX); GL.Vertex3(0.15, 0.7, 0.1); GL.Vertex3(0.15, 0.5, 0.1); GL.Vertex3(0.15, 0.5, -0.2); GL.Vertex3(0.15, 0.7, -0.2); // 砲塔正面 GL.Normal3(Vector3.UnitZ); GL.Vertex3(-0.15, 0.7, 0.1); GL.Vertex3(0.15, 0.7, 0.1); GL.Vertex3(0.15, 0.5, 0.1); GL.Vertex3(-0.15, 0.5, 0.1); // 砲塔背面 GL.Normal3(-Vector3.UnitZ); GL.Vertex3(-0.15, 0.7, -0.2); GL.Vertex3(0.15, 0.7, -0.2); GL.Vertex3(0.15, 0.5, -0.2); GL.Vertex3(-0.15, 0.5, -0.2); // 砲塔上面 GL.Normal3(Vector3.UnitY); GL.Vertex3(-0.15, 0.7, 0.1); GL.Vertex3(0.15, 0.7, 0.1); GL.Vertex3(0.15, 0.7, -0.2); GL.Vertex3(-0.15, 0.7, -0.2); // 車体 GL.Normal3(Vector3.UnitY); GL.Vertex3(0.35, 0.25, 0.5); GL.Vertex3(-0.35, 0.25, 0.5); GL.Vertex3(-0.35, 0.25, -0.5); GL.Vertex3(0.35, 0.25, -0.5); // 砲身 int n = 16; for(int i = 0; i < n; i++) { Vector3d vec = new Vector3d(Cos(2 * PI / n * i), Sin(2 * PI / n * i), 0); GL.Normal3(vec); GL.Vertex3(Cos(2 * PI / n * i) * 0.07, Sin(2 * PI / n * i) * 0.07 + 0.6, 0.5); GL.Vertex3(Cos(2 * PI / n * (i + 1)) * 0.07, Sin(2 * PI / n * (i + 1)) * 0.07 + 0.6, 0.5); GL.Vertex3(Cos(2 * PI / n * (i + 1)) * 0.07, Sin(2 * PI / n * (i + 1)) * 0.07 + 0.6, 0.1); GL.Vertex3(Cos(2 * PI / n * i) * 0.07, Sin(2 * PI / n * i) * 0.07 + 0.6, 0.1); } } GL.End(); // キャタピラ GL.Begin(BeginMode.Quads); { for(int i = 0; i < 12; i++) { if(i >= 3 && i <= 8) continue; double r = 0.25 / 2; double z = 0.5 - r; Vector3d vec = new Vector3d(Cos(2 * PI / 12 * i), Sin(2 * PI / 12 * i), 0); GL.Normal3(vec); GL.Vertex3(0.35, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(0.15, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(0.15, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); GL.Vertex3(0.35, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); } for(int i = 0; i < 12; i++) { if(i >= 3 && i <= 8) continue; double r = 0.25 / 2; double z = 0.5 - r; Vector3d vec = new Vector3d(Cos(2 * PI / 12 * i), Sin(2 * PI / 12 * i), 0); GL.Normal3(vec); GL.Vertex3(-0.35, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(-0.15, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(-0.15, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); GL.Vertex3(-0.35, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); } for(int i = 0; i < 12; i++) { if(i >= 3 && i <= 8) { double r = 0.25 / 2; double z = -0.5 + r; Vector3d vec = new Vector3d(Cos(2 * PI / 12 * i), Sin(2 * PI / 12 * i), 0); GL.Normal3(vec); GL.Vertex3(0.35, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(0.15, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(0.15, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); GL.Vertex3(0.35, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); } } for(int i = 0; i < 12; i++) { if(i >= 3 && i <= 8) { double r = 0.25 / 2; double z = -0.5 + r; Vector3d vec = new Vector3d(Cos(2 * PI / 12 * i), Sin(2 * PI / 12 * i), 0); GL.Normal3(vec); GL.Vertex3(-0.35, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(-0.15, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); GL.Vertex3(-0.15, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); GL.Vertex3(-0.35, Sin(2 * PI / 12 * (i + 1)) * r + r, Cos(2 * PI / 12 * (i + 1)) * r + z); } } GL.Normal3(-Vector3.UnitY); GL.Vertex3(0.35, 0, 0.35); GL.Vertex3(0.15, 0, 0.35); GL.Vertex3(0.15, 0, -0.35); GL.Vertex3(0.35, 0, -0.35); GL.Normal3(-Vector3.UnitY); GL.Vertex3(-0.35, 0, 0.35); GL.Vertex3(-0.15, 0, 0.35); GL.Vertex3(-0.15, 0, -0.35); GL.Vertex3(-0.35, 0, -0.35); } GL.End(); // キャタピラ内部の車輪 GL.Begin(BeginMode.TriangleFan); { GL.Normal3(Vector3.UnitX); double r = 0.25 / 2; double z = 0.5 - r; GL.Vertex3(0.3, r, z); for(int i = 0; i <= 12; i++) GL.Vertex3(0.3, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); } GL.End(); GL.Begin(BeginMode.TriangleFan); { GL.Normal3(Vector3.UnitX); double r = 0.25 / 2; double z = 0.5 - r - r * 3; GL.Vertex3(0.3, r, z); for(int i = 0; i <= 12; i++) GL.Vertex3(0.3, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); } GL.End(); GL.Begin(BeginMode.TriangleFan); { GL.Normal3(Vector3.UnitX); double r = 0.25 / 2; double z = -0.5 + r; GL.Vertex3(0.3, r, z); for(int i = 0; i <= 12; i++) GL.Vertex3(0.3, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); } GL.End(); GL.Begin(BeginMode.TriangleFan); { GL.Normal3(-Vector3.UnitX); double r = 0.25 / 2; double z = 0.5 - r; GL.Vertex3(-0.3, r, z); for(int i = 0; i <= 12; i++) GL.Vertex3(-0.3, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); } GL.End(); GL.Begin(BeginMode.TriangleFan); { GL.Normal3(-Vector3.UnitX); double r = 0.25 / 2; double z = 0.5 - r - r * 3; GL.Vertex3(-0.3, r, z); for(int i = 0; i <= 12; i++) GL.Vertex3(-0.3, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); } GL.End(); GL.Begin(BeginMode.TriangleFan); { GL.Normal3(-Vector3.UnitX); double r = 0.25 / 2; double z = -0.5 + r; GL.Vertex3(-0.3, r, z); for(int i = 0; i <= 12; i++) GL.Vertex3(-0.3, Sin(2 * PI / 12 * i) * r + r, Cos(2 * PI / 12 * i) * r + z); } GL.End(); } double PI { get { return Math.PI; } } double Sin(double d) { return Math.Sin(d); } double Cos(double d) { return Math.Cos(d); } } |
Form1クラスから戦車の回転角度を設定できるようにします。
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 |
public partial class Form1 : Form { // X軸を中心とした回転角度 float RotateX { get { return Tank.RotateX; } set { if(value < 0) value += 360; Tank.RotateX = value % 360; ShowLabel(); SetSight(); } } // Y軸を中心とした回転角度 float RotateY { get { return Tank.RotateY; } set { if(value< 0) value += 360; Tank.RotateY = value % 360; ShowLabel(); SetSight(); } } // Z軸を中心とした回転角度 float _rotateZ = 0; float RotateZ { get { return Tank.RotateZ; } set { if(value < 0) value += 360; Tank.RotateZ = value % 360; ShowLabel(); SetSight(); } } } |
実際に戦車を描画するためのコードです。
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 partial class Form1 : Form { Tank Tank = new Tank(); public Form1() { InitializeComponent(); ShowLabel(); Tank.Color = Color.Red; } private void glControl_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); // 視点を変更する SetSight(); Tank.Draw(); glControl.SwapBuffers(); } } |
チェックボックスにチェックを入れて上下のキーを押すと、EyeX-Z、TargetX-Z、RotateX-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 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 |
public partial class Form1 : Form { protected override bool ProcessDialogKey(Keys keyData) { //キーの本来の処理を //させたくないときは、trueを返す if((keyData & Keys.KeyCode) == Keys.Left) { OnDown(); return true; } else if((keyData & Keys.KeyCode) == Keys.Right) { OnUp(); return true; } else if((keyData & Keys.KeyCode) == Keys.Up) { OnUp(); return true; } else if((keyData & Keys.KeyCode) == Keys.Down) { OnDown(); return true; } return base.ProcessDialogKey(keyData); } void OnUp() { if(checkBoxRotateX.Checked) RotateX += 5; if(checkBoxRotateY.Checked) RotateY += 5; if(checkBoxRotateZ.Checked) RotateZ += 5; if(checkBoxEyeX.Checked) EyeX += 0.1f; if(checkBoxEyeY.Checked) EyeY += 0.1f; if(checkBoxEyeZ.Checked) EyeZ += 0.1f; if(checkBoxTargetX.Checked) TargetX += 0.1f; if(checkBoxTargetY.Checked) TargetY += 0.1f; if(checkBoxTargetZ.Checked) TargetZ += 0.1f; glControl.Refresh(); } void OnDown() { if(checkBoxRotateX.Checked) RotateX -= 5; if(checkBoxRotateY.Checked) RotateY -= 5; if(checkBoxRotateZ.Checked) RotateZ -= 5; if(checkBoxEyeX.Checked) EyeX -= 0.1f; if(checkBoxEyeY.Checked) EyeY -= 0.1f; if(checkBoxEyeZ.Checked) EyeZ -= 0.1f; if(checkBoxTargetX.Checked) TargetX -= 0.1f; if(checkBoxTargetY.Checked) TargetY -= 0.1f; if(checkBoxTargetZ.Checked) TargetZ -= 0.1f; glControl.Refresh(); } } |
実際に動かしてみるとこんな感じです。
初めまして
最近、こちらのサイトを見つけて、50の手習いですがC#&OPENGLを使ったプログラムに挑戦しようと一念発起したところです
”C# OpenTKで戦車を描画する”のページのプログラムをコピーしてペースト入力して実行したのですが、キーが押されglcontrol.Refresh()を呼び出していますが、glcontrol_Paint()が毎回呼び出されず、飛び飛びに再描画されます。
例えば視界EYEのXYをチェックし、↑キーを押した場合、10回目にglcontrol_Paint()が呼び出されます
視界TARTGETのXYをチェックしても10回目の呼び出しです
回転XYZに関しては、300度程度回転で一回Paint呼び出しです
いろいろと確認をしておりますが、一向に解決できません
何か良きアドバイスがあれば、よろしくお願いいたします
掲載したプログラムを実際に試してみましたが、「glcontrol_Paint()が毎回呼び出されず、飛び飛びに再描画」という現象は確認できませんでした。
こちらで作成して実際にうまく動作することを確認したもの(ソースコードと実行ファイル)をアップロードしておきました。
これでうまく動かない場合はPCのスペックに原因があるのではないかと思われます。
https://lets-csharp.com/zip/open-tk-draw-tank.zip
当方のPCのスペックは以下のとおりです。メモリーを16GBに増やしていますが、これを作成したときは8GBでした。
プロセッサ Intel(R) Core(TM) i5-3320M CPU @ 2.60GHz 2.60 GHz
実装 RAM 16.0 GB (15.7 GB 使用可能)
システムの種類 64 ビット オペレーティング システム、x64 ベース プロセッサ
ZIPファイルのアップロードをありがとうございます
上手く動きました
どこが間違っていたか確認します