C#でゲームのマップエディタを作る (2)
前回記事の続きです。
http://d.hatena.ne.jp/taiyakisun/20120331#1333204146
今回はピクチャボックスの描画を行います。
まずは、C#で変換行列を作成し、描画するコードを書くための肩慣らしとして、
エディタのグリッド線を描いてみましょう。
頂点のフォーマットは座標と色のみとします。
座標変換は自分で行います。
グリッド線を描画するクラス
まずは、以下のようにグリッドを描画するクラスを作成します。
using System; using System.Collections.Generic; using System.Text; using System.Drawing; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; class DrawGrid { private Device device = null; private CustomVertex.PositionColored[] verts = null; private VertexBuffer vertexBuffer = null; private Size mapSize; // マップサイズ private Size gridSize; // グリッドサイズ private Point gridNum = new Point(); // グリッド数(マップサイズ/グリッドサイズで算出される) private Point scrollOffset = new Point(); // スクロールバーによりずれるオフセット public DrawGrid(Device device){ this.device = device; } public void createGrid(Size mapSize, Size gridSize, Color color){ ... } public void onDraw(){ ... } }
グリッド描画に必要な情報を持ち、以下の機能を所持するクラスです。
- 描画に必要な情報の作成
- グリッド線の描画
描画に必要な情報の作成はcreateGrid関数で行います。
public void createGrid(Size mapSize, Size gridSize, Color color) {
マップサイズ(=ピクチャボックスのサイズ)、グリッドのサイズ、グリッド線の色は
コール元が決めて、このメソッドに渡します。
this.mapSize = mapSize; this.gridSize = gridSize; this.gridNum.X = mapSize.Width / gridSize.Width; this.gridNum.Y = mapSize.Height / gridSize.Height; this.verts = new CustomVertex.PositionColored[(this.gridNum.X * this.gridNum.Y)*2 + 2]; this.vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored), (this.gridNum.X * this.gridNum.Y)*2 + 2, this.device, 0, CustomVertex.PositionColored.Format, Pool.Managed);
マップサイズ、グリッドサイズをセットします。
グリッド数はこの2つの情報から算出します。
次に頂点情報(PositionColored)と、バーテックスバッファ(VertexBuffer)のメモリを確保します。
これは、"頂点の"数なので、線1本に対して2つの頂点データが必要になります。
そのため2倍の数を確保しています。
// 横線 int cnt = 0; int index = 0; for(cnt=0, index=0; cnt < (this.gridNum.Y+1); ++cnt, index+=2) { this.verts[index].X = 0; this.verts[index + 1].X = mapSize.Width; this.verts[index].Y = cnt * gridSize.Height; this.verts[index+1].Y = cnt * gridSize.Height; this.verts[index].Z = 0; this.verts[index+1].Z = 0; this.verts[index].Color = color.ToArgb(); this.verts[index+1].Color = color.ToArgb(); } // 縦線 for (cnt=0; cnt < (this.gridNum.X+1); ++cnt, index += 2) { this.verts[index].X = cnt * gridSize.Width; this.verts[index + 1].X = cnt * gridSize.Width; this.verts[index].Y = 0; this.verts[index + 1].Y = mapSize.Height; this.verts[index].Z = 0; this.verts[index+1].Z = 0; this.verts[index].Color = color.ToArgb(); this.verts[index + 1].Color = color.ToArgb(); }
横と縦に線を引きます。
ここは特に説明はいらないと思いますが、それぞれの線の始点と終点の座標を設定します。
始点と終点は、横線の場合はマップの幅、縦線の場合はマップの高さがそのまま長さになります。
あとはそれをグリッド数分設定するだけです。
Zはとりあえず0に。色は引数で指定されたものをそのまま渡します。
これで、グリッド線を構成する情報は揃いました。
public void onDraw() { // 平行移動は原点に戻す Matrix matWorld = Matrix.Translation(0, 0, 0); this.device.Transform.World = matWorld; // テクスチャは無効に this.device.SetTexture(0, null); Microsoft.DirectX.GraphicsStream stm = this.vertexBuffer.Lock(0, 0, 0); stm.Write(this.verts); this.vertexBuffer.Unlock(); this.device.SetStreamSource(0, this.vertexBuffer, 0); this.device.VertexFormat = CustomVertex.PositionColored.Format; this.device.DrawPrimitives(PrimitiveType.LineList, 0, (this.gridNum.X * this.gridNum.Y) + 1); }
グリッド線を描画します。
グリッド線は決まった場所(モデル座標そのまま)に描画しますので、ワールド変換行列は単位行列でよいです。
テクスチャは念のため無効にしておきます。
頂点バッファをロックして、書き込み先のストリームを取得したら、頂点情報を書き込んでロックを解除します。
デバイスにSetStreamSourceで描画情報と、フォーマット(PositionColored)を指定したら、
DrawPrimitivesで描画します。描画はLineList(線の集合)です。
また、第三引数は描画するプリミティブ(ここでは線)の"数"なので、ここは2倍しない点に注意です。
DrawGridクラスを利用する側が行うこと
おそらくDrawGridクラスを利用するのはフォームクラスになると思いますが、
以下のようにDrawGridクラスインスタンスを生成し、マップサイズ、グリッドサイズ、色を渡せばよいでしょう。
volatileは、複数スレッドが正しい値を取得できるようにするキーワードです。
最適化を抑止し、複数スレッドが同じタイミングで
同じ値を取得できるように、とりあえずつけておきます。
volatile DrawGrid dgrid = new DrawGrid(); : lock(thisLock) { grid.createGrid(new Size(640,480), new Size(16,16), new Color(Color.Goldenrod)); }
メインフォームのonDraw()内でグリッドを描画します。
メインスレッドと描画スレッド両方からアクセスされるため、メインフォームのロックオブジェクトで
lockしてアクセスします。
public void onDraw() { lock(thisLock) { this.device.BeginScene(); this.device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.SlateGray, 1.0f, 0 ); this.dgrid.onDraw(); this.device.EndScene(); this.device.Present(); } }
描画スレッドは、メインフォームのonDraw()をコールして描画します。
// mainLoop関数が描画スレッドとして起動される private MapEditorForm mainForm; public DrawThread(MapEditorForm mainForm) { this.mainForm = mainForm; } public void mainLoop() { while (this.mainForm.DrawFlg) { // とりあえず60FPS System.Threading.Thread.Sleep((int)(1000f / 60f)); this.mainForm.onDraw(); } }
追記(5/13)
すいません。上記コードだけでは、カメラが考えられていません。
以下のように、xy座標はスクリーンの中心を、z座標は-1.0fから、z座標0を
見るように設定してください。
Matrix matView = Matrix.LookAtLH(new Vector3(SCREENSIZE.Width / 2, SCREENSIZE.Height / 2, -1.0f), new Vector3(SCREENSIZE.Width / 2, SCREENSIZE.Height / 2, 0), new Vector3(0, 1, 0)); this.device.Transform.View = matView;