OpenTKでつくる3DカーレースゲームをTypeScript/JavaScriptでも作ってみることにします。デスクトップアプリではWeb上で遊んでもらうことができません。そこでブラウザゲームでも作ってみることにしました。そのためにはOpenTKでつくる3Dカーレースゲームで使用したデータを使う方法を考えなければなりません。今回はXMLで書かれたデータをTypeScriptでも使えるようにします。
頂点座標からGeometryを作成する
頂点座標からGeometryを作成しシーンに追加するためのスクリプトを作成します。
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 |
const width = 640; const height = 480; let scene: THREE.Scene; let camera: THREE.PerspectiveCamera; let renderer: THREE.WebGLRenderer; window.addEventListener('load', Init); // シーンを作成 scene = new THREE.Scene(); // カメラを作成 camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000); camera.position.set(0, 2, 12); camera.rotateX(-10 / 180 * Math.PI); // 平行光源 const light = new THREE.DirectionalLight(0xffffff); light.intensity = 2; // 光の強さを倍に light.position.set(1, 1, -1); scene.add(light); const light2 = new THREE.AmbientLight(0xFFFFFF, 0.7); scene.add(light2); //@ts-ignore var geometry = new THREE.Geometry(); // 頂点座標を追加 // ・・・・ renderer = new THREE.WebGLRenderer({ canvas: <HTMLCanvasElement>document.getElementById('can') }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(width, height); renderer.render(scene, camera); setInterval(Update, 1000 / 60); } function Update() { // レンダリング renderer.render(scene, camera); } |
頂点座標を追加の部分をうまく書けばよいのですが、どうすればよいのでしょうか?
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 |
<?xml version="1.0" encoding="utf-8"?> <CarData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <VertexInfos> <VertexInfo> <Name>Body</Name> <TranslateX>1.72712976E-06</TranslateX> <TranslateY>0.6581021</TranslateY> <TranslateZ>0.00695368554</TranslateZ> <Vertexes> <Vertex> <x>-0.699456334</x> // X座標 <y>0.289300323</y> // Y座標 <z>0.8952929</z> // Z座標 <nx>-0.744051337</nx> // 法線 <ny>0.6637925</ny> <nz>0.0759418458</nz> <u>0.561523438</u> // UV座標 <v>0.321289063</v> </Vertex> // <Vertexes> が続く <ints> <int>0</int> <int>1</int> <int>2</int> // 要素数は3の倍数 </ints> </VertexInfo> </VertexInfos> </CarData> |
そこで頂点座標を3つ取得してマテリアルを生成し、そのあとシーンオブジェクトに追加すればよいということになります。
引数として3点の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 |
function CreateMesh( x1: number, y1: number, z1: number, x2: number, y2: number, z2: number, x3: number, y3: number, z3: number, red: number, green: number, blue) { //@ts-ignore var geometry = new THREE.Geometry(); //頂点座標データを追加 geometry.vertices.push(new THREE.Vector3(x1, y1, z1)); geometry.vertices.push(new THREE.Vector3(x2, y2, z2)); geometry.vertices.push(new THREE.Vector3(x3, y3, z3)); //@ts-ignore geometry.faces[0] = new THREE.Face3(0, 1, 2); //マテリアル(材質)の宣言と生成 var material = new THREE.MeshBasicMaterial({ color: red * 256 * 256 + green * 256 + blue }); var Triangle = new THREE.Mesh(geometry, material); //シーンオブジェクトに追加 scene.add(Triangle); } |
ただ移動するときは車自体をまとめて移動させたいので以下のほうがいいかもしれません。
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 |
function CreateCarGroup( container: THREE.Group, x1: number, y1: number, z1: number, x2: number, y2: number, z2: number, x3: number, y3: number, z3: number, red: number, green: number, blue) { scene.add(container); //@ts-ignore var geometry = new THREE.Geometry(); //頂点座標データを追加 geometry.vertices.push(new THREE.Vector3(x1, y1, z1)); geometry.vertices.push(new THREE.Vector3(x2, y2, z2)); geometry.vertices.push(new THREE.Vector3(x3, y3, z3)); //@ts-ignore geometry.faces[0] = new THREE.Face3(0, 1, 2); //マテリアル(材質)の宣言と生成 var material = new THREE.MeshBasicMaterial({ color: red * 256 * 256 + green * 256 + blue }); var Triangle = new THREE.Mesh(geometry, material); // グループに追加する container.add(Triangle); } |
これをこんな感じで使います。
1 2 3 4 5 6 7 8 |
function CreateCar(container: THREE.Group) { scene.add(container); CreateCarGroup(container, -0.260561763514517, 0.391891568899155, -2.2936993688345, -0.369999258984564, 0.64659184217453, -2.21479998528957, -0.184999256600378, 0.646591804921627, -2.22329984605312, 148, 89, 0); CreateCarGroup(container, 0.260563255082133, 0.391891568899155, -2.29369913041592, 0.185000718365671, 0.646591804921627, -2.22329984605312, 0.370000690947535, 0.64659184217453, -2.21479974687099, 148, 89, 0); // 以下、略 } |
たしかにこれで実行すると車が描画されます。実行結果の確認の確認はこちらからお願いします。ダウンロードはこちらから。
XMLファイルから頂点座標と色を取得する
次にCreateCarGroup関数を呼び出すためのコードを生成する方法を考えます。
こんなのはどうでしょうか? WindowsFormsアプリでXMLファイルを読み込ませて(読み込ませるXMLファイルとPNGファイルはこちらにあります)コードを生成させます。
以下はファイルから読み込んだデータを格納するクラスです。
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; } |
以下はXMLファイルから読み込んだデータを加工したものを格納するクラスです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Triangle { public float X0 = 0; public float Y0 = 0; public float Z0 = 0; public float X1 = 0; public float Y1 = 0; public float Z1 = 0; public float X2 = 0; public float Y2 = 0; public float Z2 = 0; public int R = 0; public int G = 0; public int B = 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 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 |
public partial class Form1 : Form { 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; } List<Triangle> GetTriangles() { List<Triangle> triangles = new List<Triangle>(); int paletteTextureWidth = GetPaletteTextureSize().Width; int paletteTextureHeight = GetPaletteTextureSize().Height; int shadowBitmapWidth = GetShadowSize().Width; int shadowBitmapHeight = GetShadowSize().Height; Color[,] shadowColors = GetShadowColors(); Color[,] paletteTextureColors = GetPaletteTextureColors(); CarData carData = ReadCarFiles(Application.StartupPath + "\\OrangeCarData1.xml"); foreach (VertexInfo info in carData.VertexInfos) { string name = info.Name; float x = info.TranslateX; float y = info.TranslateY; float z = info.TranslateZ; int count = info.ints.Count; for (int i = 0; i < count; i += 3) { Triangle triangle = new Triangle(); int a = info.ints[i]; float u = info.Vertexes[a].u; float v = info.Vertexes[a].v; triangle.X0 = info.Vertexes[a].x + x; triangle.Y0 = info.Vertexes[a].y + y; triangle.Z0 = info.Vertexes[a].z + z; int b = info.ints[i + 1]; triangle.X1 = info.Vertexes[b].x + x; triangle.Y1 = info.Vertexes[b].y + y; triangle.Z1 = info.Vertexes[b].z + z; int c = info.ints[i + 2]; triangle.X2 = info.Vertexes[c].x + x; triangle.Y2 = info.Vertexes[c].y + y; triangle.Z2 = info.Vertexes[c].z + z; Color color; if (name != "Shadow") { color = paletteTextureColors[ (int)(paletteTextureWidth * u), paletteTextureHeight - 1 - (int)(paletteTextureHeight * v)]; } else { color = shadowColors[ (int)(shadowBitmapWidth * u), shadowBitmapHeight - 1 - (int)(shadowBitmapHeight * v)]; } triangle.R = color.R; triangle.G = color.G; triangle.B = color.B; triangles.Add(triangle); } } return triangles; } Color[,] GetPaletteTextureColors() { string paletteTexturePath = Application.StartupPath + "\\PaletteTexture.png"; Bitmap paletteTextureBitmap = new Bitmap(paletteTexturePath); int paletteTextureWidth = paletteTextureBitmap.Width; int paletteTextureHeight = paletteTextureBitmap.Height; Color[,] paletteTextureColors = new Color[1024, 1024]; for (int x = 0; x < paletteTextureWidth; x++) { for (int y = 0; y < paletteTextureHeight; y++) { paletteTextureColors[x, y] = paletteTextureBitmap.GetPixel(x, y); } } paletteTextureBitmap.Dispose(); return paletteTextureColors; } Size GetPaletteTextureSize() { string paletteTexturePath = Application.StartupPath + "\\PaletteTexture.png"; Bitmap paletteTextureBitmap = new Bitmap(paletteTexturePath); int paletteTextureWidth = paletteTextureBitmap.Width; int paletteTextureHeight = paletteTextureBitmap.Height; paletteTextureBitmap.Dispose(); return new Size(paletteTextureWidth, paletteTextureHeight); } Color[,] GetShadowColors() { string shadowPath = Application.StartupPath + "\\Shadow.png"; Bitmap shadowBitmap = new Bitmap(shadowPath); int shadowBitmapWidth = shadowBitmap.Width; int shadowBitmapHeight = shadowBitmap.Height; Color[,] shadowColors = new Color[shadowBitmapWidth, shadowBitmapHeight]; for (int x = 0; x < shadowBitmapWidth; x++) { for (int y = 0; y < shadowBitmapHeight; y++) { shadowColors[x, y] = shadowBitmap.GetPixel(x, y); } } shadowBitmap.Dispose(); return shadowColors; } Size GetShadowSize() { string shadowPath = Application.StartupPath + "\\Shadow.png"; Bitmap shadowBitmap = new Bitmap(shadowPath); int shadowBitmapWidth = shadowBitmap.Width; int shadowBitmapHeight = shadowBitmap.Height; shadowBitmap.Dispose(); return new Size(shadowBitmapWidth, shadowBitmapHeight); } } |
上記のようなクラスとメソッドを作成して以下のコードを実行すればTypeScriptのfunction CreateCarGroup内に書くべきCreateCarGroup関数を取得することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
List<Triangle> triangles = GetTriangles(); StringBuilder sb = new StringBuilder(); sb.Append("function CreateCar(container: THREE.Group) {\n"); foreach (Triangle triangle in triangles) { string str = String.Format(" CreateCarGroup(container, {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11});\n", triangle.X0, triangle.Y0, triangle.Z0, triangle.X1, triangle.Y1, triangle.Z1, triangle.X2, triangle.Y2, triangle.Z2, triangle.R, triangle.G, triangle.B); sb.Append(str); } sb.Append("}\n"); sb.ToString(); // これを取得する |
ただ困った問題があります。車をいくつも表示させようとした場合、動作が遅くなってしまいます。1台の場合と比較すると3台のほうが動作が遅いです。次回はこれを改善する方法を考えます。