前回の時計を表示させる TypeScript/JavaScriptでつくるチクタクバンバンのようなゲームではチクタクバンバンに出てきそうな時計を描画させました。今回は線路プレートを生成、表示させます。
Contents
線路プレートのタイプを表す列挙体
まず線路プレートのタイプを表す列挙体をつくります。
| 1 2 3 4 5 6 7 8 9 10 11 | enum PlateType {     NE,     NS,     NW,     SE,     SW,     WE,     NSWE,     NESW,     NWSE, } | 
それから線路プレートは縦横4列ずつならんでいるので、変数として以下を定義しておきます。
| 1 2 | let columMax = 4; let rowMax = 4; | 
TrackPlateクラス
まず線路プレートを生成する方法を考えます。そのあと移動させる方法を考えます(後者のほうが簡単です)。
コンストラクタ
最初にコンストラクタとプロパティ(線路プレート生成に関連する部分のみ)を示します。コンストラクタの引数は、初期状態で何行何列目に配置するかと配置する線路プレートの種類です。引数でわたされたPlateTypeから必要な線路プレートを生成します。
| 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 | class TrackPlate {     static Plates: TrackPlate[] = [];     Row = 0; // 初期状態で何行目に配置するか(手前の行なら0)     Colum = 0; // 初期状態で何列目に配置するか(一番左の列なら0)     PlateType: PlateType = null;     Group: THREE.Group = null;     static Size = 16; // プレートの縦と横の長さ     static AisleWidth = 0.4; //  // プレートの通路部分の幅     static Height = 4; // プレートの高さ     // 使用するマテリアル     Material: THREE.MeshStandardMaterial;     constructor(colum: number, row: number, plateType: PlateType) {         this.Row = row;         this.Colum = colum;         this.PlateType = plateType;         let roughness = 0.01; // THREE.MeshStandardMaterialに渡す値(粗さ)         if (plateType == PlateType.NS) {             this.Material = new THREE.MeshStandardMaterial({ color: 0xff4500, roughness: roughness });             this.Group = this.CreateNS(); // 南北に通る線路プレートを生成してTHREE.Groupを返す             scene.add(this.Group); // 生成したらシーンに追加         }         else if (plateType == PlateType.WE) {             this.Material = new THREE.MeshStandardMaterial({ color: 0xffff00, roughness: roughness });             this.Group = this.CreateWE();             scene.add(this.Group);         }         else if (plateType == PlateType.NSWE) {             this.Material = new THREE.MeshStandardMaterial({ color: 0x000ff, roughness: roughness });             this.Group = this.CreateNSWE();             scene.add(this.Group);         }         else if (plateType == PlateType.SW) {             this.Material = new THREE.MeshStandardMaterial({ color: 0xff00ff, roughness: roughness });             this.Group = this.CreateSW();             scene.add(this.Group);         }         else if (plateType == PlateType.SE) {             this.Material = new THREE.MeshStandardMaterial({ color: 0x00ff00, roughness: roughness });             this.Group = this.CreateSE();             scene.add(this.Group);         }         else if (plateType == PlateType.NE) {             this.Material = new THREE.MeshStandardMaterial({ color: 0xff00ff, roughness: roughness });             this.Group = this.CreateNE();             scene.add(this.Group);         }         else if (plateType == PlateType.NW) {             this.Material = new THREE.MeshStandardMaterial({ color: 0x00ffff, roughness: roughness });             this.Group = this.CreateNW();             scene.add(this.Group);         }         else if (plateType == PlateType.NWSE) {             this.Material = new THREE.MeshStandardMaterial({ color: 0x0000ff, roughness: roughness });             this.Group = this.CreateNWSE();             scene.add(this.Group);         }         else if (plateType == PlateType.NESW) {             this.Material = new THREE.MeshStandardMaterial({ color: 0xffff00, roughness: roughness });             this.Group = this.CreateNESW();             scene.add(this.Group);         }         // 隣の線路プレートとの境界線がわかるようにサイズをやや小さくする         this.Group.scale.x = 0.98;         this.Group.scale.z = 0.98;         // 線路プレート群の中心のX座標とZ座標が0になるように座標を調整する         this.Group.position.x = this.Row * TrackPlate.Size - TrackPlate.Size * (rowMax / 2 - 0.5);         this.Group.position.z = this.Colum * TrackPlate.Size - TrackPlate.Size * (columMax / 2 - 0.5);         // 配列 TrackPlate.Platesに格納して検索できるようにする         TrackPlate.Plates.push(this);     } } | 
南北に移動できる線路プレートを生成する
PlateTypeから必要な線路プレートを生成します。最初に南北に移動できる線路プレートを生成する関数を示します。
直方体を2つつくって通路部分をあけた状態で並べればできるのですが、他の線路プレートを場合との兼ね合いであえて面倒な方法をとりました。光があたる関係で通路の溝が見えにくくなる場合があるので溝のなかの壁はつくっていません。また溝が黒く見えるように黒い板を内部に張り付けています(後述:CreateBottomMesh関数)。
それから自分でgeometryに頂点座標をセットしてTHREE.Face3関数を実行する場合、geometry.computeFaceNormals();を実行しておかないと、ライティングが必要なマテリアルを適用しようとしてもうまく描画されなくなります。
| 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 | class TrackPlate {     CreateNS(): THREE.Group {         let group = new THREE.Group();         let halfSize = TrackPlate.Size / 2;         let aisleHalfWidth = TrackPlate.AisleWidth / 2;         let halfHeight = TrackPlate.Height / 2;         //@ts-ignore         var geometry = new THREE.Geometry();         geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(- halfSize, halfHeight, - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, 0 - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(- halfSize, halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(- halfSize, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(- halfSize, -halfHeight, - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, 0 - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(- halfSize, -halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(- halfSize, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, halfSize));         //面指定用頂点インデックスを追加         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 3, 1));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 2, 3));         //@ts-ignore         geometry.faces.push(new THREE.Face3(4, 6, 5));         //@ts-ignore         geometry.faces.push(new THREE.Face3(5, 6, 7));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 8, 2));         //@ts-ignore         geometry.faces.push(new THREE.Face3(2, 8, 10));         //@ts-ignore         geometry.faces.push(new THREE.Face3(6, 4, 12));         //@ts-ignore         geometry.faces.push(new THREE.Face3(6, 12, 14));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 11, 1));         //@ts-ignore         geometry.faces.push(new THREE.Face3(1, 11, 9));         //@ts-ignore         geometry.faces.push(new THREE.Face3(7, 15, 5));         //@ts-ignore         geometry.faces.push(new THREE.Face3(5, 15, 13));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 1, 9));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 9, 8));         //@ts-ignore         geometry.faces.push(new THREE.Face3(6, 14, 7));         //@ts-ignore         geometry.faces.push(new THREE.Face3(7, 14, 15));         // MeshStandardMaterialを使用するのでcomputeFaceNormals関数を忘れず実行しておく         geometry.computeFaceNormals();         let mesh = new THREE.Mesh(geometry, this.Material);         group.add(mesh);         group.add(this.CreateBottomMesh()); // 後述         return group;     } } | 
線路プレートの内部が黒く見えるようにするためのメッシュをふたつ作ります。一つ目は線路プレートの上面のすぐ下に、もうひとつは線路プレートの底面にあたる部分につくります。
| 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 | class TrackPlate {     CreateBottomMesh(): THREE.Mesh {         // 底面をつくる(色は黒)         let halfSize = TrackPlate.Size / 2;         let halfHeight = TrackPlate.Height / 2;         //@ts-ignore         var geometry = new THREE.Geometry();         geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, -halfSize));         geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight - 0.1, - halfSize));         geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight - 0.1, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight - 0.1, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight - 0.1, -halfSize));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 1, 3));         //@ts-ignore         geometry.faces.push(new THREE.Face3(1, 2, 3));         //@ts-ignore         geometry.faces.push(new THREE.Face3(4, 5, 7));         //@ts-ignore         geometry.faces.push(new THREE.Face3(5, 6, 7));         return new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000 }));     } } | 
東西に移動できる線路プレートを生成する
次に東西に移動できる線路プレートを生成するCreateWE関数ですが、これは簡単です。CreateNS関数で生成されたものを回転させてそのまま使います。
| 1 2 3 4 5 6 7 | class TrackPlate {     CreateWE(): THREE.Group {         let group = this.CreateNS();         group.rotation.y = Math.PI / 2;         return group;     } } | 
東西南北に移動できる線路プレートを生成する
東西南北に移動できる線路プレートを生成する関数を示します。
| 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 | class TrackPlate {     CreateNSWE(): THREE.Group {         let group = new THREE.Group();         let halfSize = TrackPlate.Size / 2;         let aisleHalfWidth = TrackPlate.AisleWidth / 2;         let halfHeight = TrackPlate.Height / 2;         //@ts-ignore         var geometry = new THREE.Geometry();         geometry.vertices.push(new THREE.Vector3(- halfSize, halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, halfHeight, -halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, -aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, halfHeight, - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, halfHeight, - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(- halfSize, -halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, -halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, -halfHeight, -halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, - halfSize));         geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, -aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, - aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, aisleHalfWidth));         geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, halfSize));         //面指定用頂点インデックスを追加         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 4, 1));         //@ts-ignore         geometry.faces.push(new THREE.Face3(1, 4, 5));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 2, 6));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 6, 7));         //@ts-ignore         geometry.faces.push(new THREE.Face3(12, 9, 8));         //@ts-ignore         geometry.faces.push(new THREE.Face3(13, 9, 12));         //@ts-ignore         geometry.faces.push(new THREE.Face3(11, 10, 14));         //@ts-ignore         geometry.faces.push(new THREE.Face3(11, 14, 15));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 19, 2));         //@ts-ignore         geometry.faces.push(new THREE.Face3(2, 19, 18));         //@ts-ignore         geometry.faces.push(new THREE.Face3(1, 17, 0));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 17, 16));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 16, 4));         //@ts-ignore         geometry.faces.push(new THREE.Face3(4, 16, 20));         //@ts-ignore         geometry.faces.push(new THREE.Face3(8, 22, 12));         //@ts-ignore         geometry.faces.push(new THREE.Face3(12, 22, 24));         //@ts-ignore         geometry.faces.push(new THREE.Face3(12, 24, 13));         //@ts-ignore         geometry.faces.push(new THREE.Face3(13, 24, 25));         //@ts-ignore         geometry.faces.push(new THREE.Face3(15, 14, 26));         //@ts-ignore         geometry.faces.push(new THREE.Face3(15, 26, 27));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 27, 11));         //@ts-ignore         geometry.faces.push(new THREE.Face3(11, 27, 23));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 7, 21));         //@ts-ignore         geometry.faces.push(new THREE.Face3(3, 21, 19));         geometry.computeFaceNormals();         let mesh = new THREE.Mesh(geometry, this.Material);         group.add(mesh);         group.add(this.CreateBottomMesh());         return group;     } } | 
カーブがある線路プレートを生成する
次にカーブがある線路プレートですが、ここは斜めに切って線路が存在する部分と存在しない部分にわけて考えます。ひとつできてしまえば組み合わせて線路プレートは完成です。完成した線路プレートを回転させればほかの線路プレートにも使えます。
まず簡単にできそうな線路が存在しない三角形の線路プレートを生成する関数を示します。
| 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 | class TrackPlate {     CreateTrackPlateTriangle(): THREE.Group {         let halfSize = TrackPlate.Size / 2;         let halfHeight = TrackPlate.Height / 2;         let group = new THREE.Group();         //@ts-ignore         var geometry = new THREE.Geometry();         geometry.vertices.push(new THREE.Vector3(0 - halfSize, halfHeight, 0 - halfSize));         geometry.vertices.push(new THREE.Vector3(0 - halfSize, halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, 0 - halfSize));         geometry.vertices.push(new THREE.Vector3(0 - halfSize, -halfHeight, 0 - halfSize));         geometry.vertices.push(new THREE.Vector3(0 - halfSize, -halfHeight, halfSize));         geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, 0 - halfSize));         //面指定用頂点インデックスを追加         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 1, 2));         //@ts-ignore         geometry.faces.push(new THREE.Face3(2, 5, 0));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 5, 3));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 3, 4));         //@ts-ignore         geometry.faces.push(new THREE.Face3(0, 4, 1));         geometry.computeFaceNormals();         var mesh = new THREE.Mesh(geometry, this.Material);         group.add(mesh);         return group;     } } | 
次がちょっと面倒です。カーブの内側部分であれば三角関数を使えば簡単にできますが、外側はどうやってつくればいいのでしょうか?
ちょっと関数が長くなってしまいました。もっとスマートなやり方があるはずなのですが、今回はこれで勘弁してください。
| 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 | class TrackPlate {     CreateTrackPlateTriangleCurve(): THREE.Group {         let halfSize = TrackPlate.Size / 2;         let aisleHalfWidth = TrackPlate.AisleWidth / 2;         let halfHeight = TrackPlate.Height / 2;         let group = new THREE.Group();         // カーブの内側をつくる         {             //@ts-ignore             var geometry = new THREE.Geometry();             //頂点座標データを追加             let max = 16;             let rad = Math.PI / 2 / max;             geometry.vertices.push(new THREE.Vector3(0 - halfSize, halfHeight, 0 - halfSize));             for (let i = 0; i <= max; i++)                 geometry.vertices.push(new THREE.Vector3(                     Math.sin(rad * i) * (halfSize - aisleHalfWidth) - halfSize,                     halfHeight,                     Math.cos(rad * i) * (halfSize - aisleHalfWidth) - halfSize));             //面指定用頂点インデックスを追加             for (let i = 0; i <= max; i++)                 //@ts-ignore                 geometry.faces.push(new THREE.Face3(0, i, i + 1));             geometry.computeFaceNormals();             let mesh = new THREE.Mesh(geometry, this.Material);             group.add(mesh);         }         // カーブの外側(ただし一部だけ)をつくる ①         {             //@ts-ignore             var geometry = new THREE.Geometry();             //頂点座標データを追加             let max = 16;             let rad = Math.PI / 2 / max;             geometry.vertices.push(new THREE.Vector3(0, halfHeight, 0));             for (let i = 0; i <= max; i++)                 geometry.vertices.push(new THREE.Vector3(                     Math.sin(rad * i) * (halfSize + aisleHalfWidth) - halfSize,                     halfHeight,                     Math.cos(rad * i) * (halfSize + aisleHalfWidth) - halfSize));             for (let i = 0; i <= max; i++)                 //@ts-ignore                 geometry.faces.push(new THREE.Face3(0, i + 1, i));             geometry.computeFaceNormals();             let mesh = new THREE.Mesh(geometry, this.Material);             group.add(mesh);         }         // カーブの外側(①で作りきれなかった部分)をつくる         {             //@ts-ignore             var geometry = new THREE.Geometry();             //頂点座標データを追加             geometry.vertices.push(new THREE.Vector3(0, halfHeight, 0));             geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, aisleHalfWidth));             geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, halfSize));             //@ts-ignore             geometry.faces.push(new THREE.Face3(0, 1, 2));             //@ts-ignore             geometry.faces.push(new THREE.Face3(3, 4, 0));             geometry.computeFaceNormals();             var mesh = new THREE.Mesh(geometry, this.Material);             group.add(mesh);         }         // 側面の壁ををつくる         {             //@ts-ignore             var geometry = new THREE.Geometry();             //頂点座標データを追加             geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, -aisleHalfWidth));             geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, aisleHalfWidth));             geometry.vertices.push(new THREE.Vector3(-halfSize, halfHeight, halfSize));             geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(halfSize, halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, -aisleHalfWidth));             geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, aisleHalfWidth));             geometry.vertices.push(new THREE.Vector3(-halfSize, -halfHeight, halfSize));             geometry.vertices.push(new THREE.Vector3(-aisleHalfWidth, -halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(aisleHalfWidth, -halfHeight, -halfSize));             geometry.vertices.push(new THREE.Vector3(halfSize, -halfHeight, -halfSize));             //@ts-ignore             geometry.faces.push(new THREE.Face3(0, 7, 1));             //@ts-ignore             geometry.faces.push(new THREE.Face3(1, 7, 8));             //@ts-ignore             geometry.faces.push(new THREE.Face3(2, 9, 3));             //@ts-ignore             geometry.faces.push(new THREE.Face3(3, 9, 10));             //@ts-ignore             geometry.faces.push(new THREE.Face3(6, 13, 5));             //@ts-ignore             geometry.faces.push(new THREE.Face3(5, 13, 12));             //@ts-ignore             geometry.faces.push(new THREE.Face3(4, 11, 0));             //@ts-ignore             geometry.faces.push(new THREE.Face3(0, 11, 7));             geometry.computeFaceNormals();             let mesh = new THREE.Mesh(geometry, this.Material);             group.add(mesh);         }         group.add(this.CreateBottomMesh());         return group;     } } | 
上記の関数で生成したものを組み合わせれば南西間を移動する線路プレートをつくることができます。そしてこれを回転させれば北西間、南東間を移動する線路プレートも生成できます。
| 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 | class TrackPlate {     CreateSW(): THREE.Group {         let group = new THREE.Group();         let triangle1 = this.CreateTrackPlateTriangleCurve();         let triangle2 = this.CreateTrackPlateTriangle();         triangle2.rotation.y = Math.PI;         group.add(triangle1);         group.add(triangle2);         return group;     }     CreateNW() {         let group = this.CreateSW();         group.rotation.y = -Math.PI / 2;         return group;     }     CreateNE() {         let group = this.CreateSW();         group.rotation.y = Math.PI;         return group;     }     CreateSE() {         let group = this.CreateSW();         group.rotation.y = Math.PI / 2;         return group;     } } | 
カーブがふたつある線路プレートを生成する
北東間と南西間、北西間と南東間を移動する線路プレートを生成するCreateNESW関数とCreateNWSE関数を示します。
| 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 | class TrackPlate {     CreateNESW(): THREE.Group {         let group = new THREE.Group();         let triangle1 = this.CreateTrackPlateTriangleCurve();         let triangle2 = this.CreateTrackPlateTriangleCurve();         triangle2.rotation.y = Math.PI;         group.add(triangle1);         group.add(triangle2);         return group;     }     CreateNWSE(): THREE.Group {         let group = new THREE.Group();         let triangle1 = this.CreateTrackPlateTriangleCurve();         let triangle2 = this.CreateTrackPlateTriangleCurve();         triangle2.rotation.y = Math.PI;         group.add(triangle1);         group.add(triangle2);         group.rotation.y = Math.PI / 2;         return group;     } } | 
これで線路プレートを生成することはできるようになりました。
線路プレートを移動する機能を追加する
次にこれを移動する機能を追加します。
静的プロパティとしてBlankColumとBlankRowがありますが、これは線路プレートが存在しない部分です。方向キーをおすとこれを埋めるように周囲の線路プレートが移動します。
線路プレートが移動するとそのX座標とZ座標が変化するだけでなくRowプロパティとColumプロパティも変化します。そしてBlankColumとBlankRowも変化します。うっかりミスをしやすい部分なのできっちり関数化してしまいましょう。
MoveNorth関数は線路プレートを上側に移動させます。GetPlate関数は引数で指定されたRowプロパティとColumプロパティをもつ線路プレートを返します。もしGetPlate関数がnullを返した場合は移動可能な線路プレートは存在しないということなのでなにもしません。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class TrackPlate {     static BlankColum = 0;     static BlankRow = 0;     static GetPlate(colum: number, row: number): TrackPlate {         return TrackPlate.Plates.find(x => x.Colum == colum && x.Row == row);     }     static MoveNorth(): boolean {         let plate = TrackPlate.GetPlate(TrackPlate.BlankColum, TrackPlate.BlankRow-1);         if (plate != null) {             plate.Row++;             plate.Group.position.x += TrackPlate.Size;             TrackPlate.BlankRow--;             return true;         }         else             return 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 | class TrackPlate {     static MoveSouth(): boolean {         let plate = TrackPlate.GetPlate(TrackPlate.BlankColum, TrackPlate.BlankRow + 1);         if (plate != null) {             plate.Row--;             plate.Group.position.x -= TrackPlate.Size;             TrackPlate.BlankRow++;             return true;         }         else             return false;     }     static MoveEast(): boolean {         let plate = TrackPlate.GetPlate(TrackPlate.BlankColum - 1, TrackPlate.BlankRow);         if (plate != null) {             plate.Colum++;             plate.Group.position.z += TrackPlate.Size;             TrackPlate.BlankColum--;             return true;         }         else             return false;     }     static MoveWest(): boolean {         let plate = TrackPlate.GetPlate(TrackPlate.BlankColum+1, TrackPlate.BlankRow);         if (plate != null) {             plate.Colum--;             plate.Group.position.z -= TrackPlate.Size;             TrackPlate.BlankColum++;             return true;         }         else             return false;     } } | 
となりにある線路プレートを取得する
以下は引数で渡された線路プレートの東西南北の位置にある線路プレートを返します。チクタクバンバンを移動させるときに必要になる関数です。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class TrackPlate {     static GetEastPlate(trackPlate: TrackPlate): TrackPlate {         return TrackPlate.GetPlate(trackPlate.Colum + 1, trackPlate.Row);     }     static GetWestPlate(trackPlate: TrackPlate): TrackPlate {         return TrackPlate.GetPlate(trackPlate.Colum-1, trackPlate.Row);     }     static GetSouthPlate(trackPlate: TrackPlate): TrackPlate {         return TrackPlate.GetPlate(trackPlate.Colum, trackPlate.Row-1);     }     static GetNorthPlate(trackPlate: TrackPlate): TrackPlate {         return TrackPlate.GetPlate(trackPlate.Colum, trackPlate.Row + 1);     } } | 
