C# OpenTKで壁よけゲームをつくります。自機を操作して壁の穴をくぐり抜けるという単純なゲームです。
とりあえず大枠から。0.05秒で0.1だけ自機が前方に移動します。壁は動きません。自機を後方3.0から眺めた様子を表示します。この段階では自機も壁も表示されません。
1 2 3 4 5 6 7 8 |
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 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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); Timer.Tick += Timer_Tick; Timer.Interval = 50; Timer.Start(); } Timer Timer = new Timer(); private void Timer_Tick(object sender, EventArgs e) { } private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Lighting(); // 壁と自機を描画する処理 glControl.SwapBuffers(); } private void glControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // 法線の自動調節 GL.Enable(EnableCap.Normalize); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); // 視界の設定 SetSight(); 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.1f, 100.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } private void glControl_Resize(object sender, EventArgs e) { SetProjection(); // 再描画 glControl.Refresh(); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 float[] position = new float[] { 1.0f, 1.0f, 1.0f, 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); } void SetSight() { // 視界の設定 // 未定義 } } |
まず自機の位置を示すプロパティを作成します。自機の座標を変更するとどこから自機を眺めるのかも変更しないといけないので当時にその処理もおこなっています。
SetSight()メソッド内で視点を自機の手前3.0に設定し、再描画をしています。
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 |
public partial class Form1 : Form { // 自機のX座標 float _x = 0; public float X { get { return _x; } set { _x = value; SetSight(); glControl.Refresh(); } } // 自機のY座標 float _y = 0; public float Y { get { return _y; } set { _y = value; SetSight(); glControl.Refresh(); } } // 自機のZ座標 float _z = 0; public float Z { get { return _z; } set { _z = value; SetSight(); glControl.Refresh(); } } void SetSight() { // 視界の設定 Vector3 eye = new Vector3(X, Y, Z + 3f); Vector3 target = new Vector3(X, Y, Z); Matrix4 look = Matrix4.LookAt(eye, target, Vector3.UnitY); GL.LoadMatrix(ref look); } } |
さて、このままでは自機も壁も描画されません。そこで自機を描画します。自機と言っても飛行機のような形状ではなく、ただの正方形の板です。いまは仮の状態です。あとでもっとちゃんとしたものに作り直します。
Draw(float x, float y, float z, int rx, int ry, int rz)メソッドの引数は自機が存在するxyz座標と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 |
public class Jiki { public Jiki() { } // 今は縦横0.5の正方形を描画するだけ 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(rx, 1, 0, 0); GL.Rotate(ry, 0, 1, 0); GL.Rotate(rz, 0, 0, 1); GL.Normal3(Vector3.UnitZ); GL.Begin(BeginMode.Quads); { GL.Vertex3(0.25, 0.25, 0); GL.Vertex3(-0.25, 0.25, 0); GL.Vertex3(-0.25, -0.25, 0); GL.Vertex3(0.25, -0.25, 0); } GL.End(); } GL.PopMatrix(); } } |
自機だけではなく壁もつくります。
壁の中心はXY座標が0の地点です。Wallクラスのコンストラクタの引数は幅、高さ、穴の中心のX座標とY座標、穴の幅と高さ、スタート地点からの距離と色です。
幅、高さ、穴の中心のX座標とY座標、穴の幅と高さ、スタート地点からの距離と色は一度決まってしまうと変更されることはないので読み取りだけ可能なプロパティにしています。
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 |
public class Wall { public Wall(float width, float height, float holeCenterX, float holeCenterY, float holeWidth, float holeHeight, float distance, Color color) { Width = width; Height = height; HoleCenterX = holeCenterX; HoleCenterY = holeCenterY; HoleWidth = holeWidth; HoleHeight = holeHeight; Distance = distance; Color = color; } public float Width { get; protected set; } public float Height { get; protected set; } public float HoleCenterX { get; protected set; } public float HoleCenterY { get; protected set; } public float HoleWidth { get; protected set; } public float HoleHeight { get; protected set; } public float Distance { get; protected set; } public Color Color { get; protected set; } public void DrawWall() { GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color); GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color); GL.Normal3(Vector3.UnitZ); GL.Begin(BeginMode.Quads); { GL.Vertex3(Width / 2, Height / 2, Distance); GL.Vertex3(-Width / 2, Height / 2, Distance); GL.Vertex3(-Width / 2, HoleCenterY + (HoleHeight / 2f), Distance); GL.Vertex3(Width / 2, HoleCenterY + (HoleHeight / 2f), Distance); } GL.End(); GL.Begin(BeginMode.Quads); { GL.Vertex3(Width / 2, HoleCenterY - (HoleHeight / 2f), Distance); GL.Vertex3(-Width / 2, HoleCenterY - (HoleHeight / 2f), Distance); GL.Vertex3(-Width / 2, -Height / 2, Distance); GL.Vertex3(Width / 2, -Height / 2, Distance); } GL.End(); GL.Begin(BeginMode.Quads); { GL.Vertex3(-Width / 2, Height / 2, Distance); GL.Vertex3(HoleCenterX - HoleWidth / 2, Height / 2, Distance); GL.Vertex3(HoleCenterX - HoleWidth / 2, -Height / 2, Distance); GL.Vertex3(-Width / 2, -Height / 2, Distance); } GL.End(); GL.Begin(BeginMode.Quads); { GL.Vertex3(Width / 2, Height / 2, Distance); GL.Vertex3(HoleCenterX + HoleWidth / 2, Height / 2, Distance); GL.Vertex3(HoleCenterX + HoleWidth / 2, -Height / 2, Distance); GL.Vertex3(Width / 2, -Height / 2, Distance); } GL.End(); } } |
では実際に描画してみましょう。プログラムが開始されたらCreateWalls()メソッドで壁をつくります。Timer.Tickイベントが発生したら自機を前(Z座標を0.1減らす)に移動させます。すると自機の表示位置は変わらず、壁がこちらに向かってくるように見えます。
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 partial class Form1 : Form { Timer Timer = new Timer(); public Form1() { InitializeComponent(); CreateWalls(); Timer.Tick += Timer_Tick; Timer.Interval = 50; Timer.Start(); } void CreateWalls() { Walls.Add(new Wall(6f, 6f, -1f, 1f, 2f, 2f, -10f, Color.Yellow)); Walls.Add(new Wall(6f, 6f, 1f, -1f, 2f, 2f, -15f, Color.LightBlue)); } private void Timer_Tick(object sender, EventArgs e) { Z -= 0.1f; } } |
自機を壁から回避させます。上下左右のキーを押すと自機が上下左右に移動します。キーをおすとすぐに反応するようにキーが押されるとDoesMoveUpなどのフラグがtrueに変わり、離すとfalseになります。フラグがtrueになっている方向に移動します。
XプロパティやYプロパティが変更されると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 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 |
public partial class Form1 : Form { bool DoesMoveUp = false; bool DoesMoveDown = false; bool DoesMoveLeft = false; bool DoesMoveRight = false; protected override void OnKeyDown(KeyEventArgs e) { if(e.KeyCode == Keys.Up) DoesMoveUp = true; if(e.KeyCode == Keys.Down) DoesMoveDown = true; if(e.KeyCode == Keys.Left) DoesMoveLeft = true; if(e.KeyCode == Keys.Right) DoesMoveRight = true; base.OnKeyDown(e); } protected override void OnKeyUp(KeyEventArgs e) { if(e.KeyCode == Keys.Up) DoesMoveUp = false; if(e.KeyCode == Keys.Down) DoesMoveDown = false; if(e.KeyCode == Keys.Left) DoesMoveLeft = false; if(e.KeyCode == Keys.Right) DoesMoveRight = false; base.OnKeyUp(e); } private void Timer_Tick(object sender, EventArgs e) { if(DoesMoveUp) Y += 0.1f; if(DoesMoveDown) Y -= 0.1f; if(DoesMoveLeft) X -= 0.1f; if(DoesMoveRight) X += 0.1f; Z -= 0.1f; } } |
最後に描画の処理を示します。
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(); } } |