質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2748閲覧

回転行列を使って曲線を生成・オブジェクトをそれに沿って設置したい。

odakyutetu

総合スコア85

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2021/06/04 13:49

編集2021/06/05 06:37

実現したいこと

Unityで現在、鉄道運転ゲームを作成しています。
その際に、カーブしている線路を生成し、それに沿って走行・線路となるオブジェクトを設置したいです。
いくつかネットで調べたところ、回転行列を使用すれば実現できるとのことですが、具体的にどのようにすればいいのかわかりません。
数学が若干苦手なのですが、サンプルコード含め教えていただくことは可能でしょうか。

詳細

カーブの線形を作成(回転行列?などで指定した角度で計算)し、その線形に沿ってオブジェクトを一定間隔で設置、
その後その線形から座標をリアルタイムで取得し、電車の位置を計算するもの。

Result

イメージ説明
回答者の方、本当にありがとうございました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

回転行列を絡めて実現することも可能かもしれませんが、回転行列は回転同士の補間がやりづらかったりしていまいちかな...という気がしますね。
そういうことができそうなものをアセットストアで探してみてはいかがでしょうか。たとえば、私は未購入ですがCurvy Splinesだとかはなかなかすごそうに見えます。

お金をかけずにやるとしたら、Cinemachine Dolly Cartはどうでしょうか。「【Unity】Timelineで、指定したパスを想定した速度で歩かせる - テラシュールブログ」のような要領で、設置した経路の上に列車を走らせられるんじゃないかと思います。

ただし、これには目に見える線路を敷く機能はありませんので、自前で何とかする必要があるでしょう。一例として下記のようなスクリプトを考えてみました。

lang

1using System.Collections.Generic; 2using System.Linq; 3using Cinemachine; 4using UnityEngine; 5 6[RequireComponent(typeof(CinemachinePathBase), typeof(MeshFilter), typeof(MeshRenderer))] 7[ExecuteAlways] 8public class RailroadTrack : MonoBehaviour 9{ 10 private static readonly List<Vector3> Vertices = new List<Vector3>(); 11 private static readonly List<Vector2> Uvs = new List<Vector2>(); 12 private static readonly List<int> Triangles = new List<int>(); 13 14 [Min(0.0f)] public float trackGauge = 1.067f; // レールの間隔 15 [Min(0.0f)] public float trackWidth = 0.065f; // レールの太さ 16 [Min(0.0f)] public float trackHeight = 0.153f; // レールの高さ 17 [Min(0.0f)] public float trackInterval = 0.6f; // 曲線を折れ線近似する際の間隔 18 public float trackVerticalOffset = 0.05f; // レールを原点から浮かす高さ 19 public GameObject tiePrefab; // ここに枕木のプレハブをセットしておく 20 [Min(0.0f)] public float tieInterval = 0.6f; // 枕木を設置する間隔 21 22 private MeshFilter meshFilter; 23 private MeshRenderer meshRenderer; 24 private CinemachinePathBase path; 25 private float previousPathLength = float.NaN; 26 private float previousTieInterval = float.NaN; 27 private GameObject previousTiePrefab; 28 private float previousTrackGauge = float.NaN; 29 private float previousTrackHeight = float.NaN; 30 private float previousTrackInterval = float.NaN; 31 private float previousTrackVerticalOffset = float.NaN; 32 private float previousTrackWidth = float.NaN; 33 private Mesh trackMesh; 34 35 private void Update() 36 { 37 // 必要なコンポーネントを取得する 38 if (this.path == null) 39 { 40 this.path = this.GetComponent<CinemachinePathBase>(); 41 if (this.path == null) 42 { 43 return; 44 } 45 } 46 if (this.meshFilter == null) 47 { 48 this.meshFilter = this.GetComponent<MeshFilter>(); 49 if (this.meshFilter == null) 50 { 51 return; 52 } 53 } 54 if (this.meshRenderer == null) 55 { 56 this.meshRenderer = this.GetComponent<MeshRenderer>(); 57 if (this.meshFilter == null) 58 { 59 return; 60 } 61 if (this.meshRenderer.sharedMaterial == null) 62 { 63 var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); 64 this.meshRenderer.sharedMaterial = cube.GetComponent<Renderer>().sharedMaterial; 65 if (Application.isPlaying) 66 { 67 Destroy(cube); 68 } 69 else 70 { 71 DestroyImmediate(cube); 72 } 73 } 74 } 75 76 // 線路更新の必要がなければメソッドを終える 77 var pathLength = this.path.PathLength; 78 if ((pathLength == this.previousPathLength) && 79 (this.trackGauge == this.previousTrackGauge) && 80 (this.trackWidth == this.previousTrackWidth) && 81 (this.trackHeight == this.previousTrackHeight) && 82 (this.trackInterval == this.previousTrackInterval) && 83 (this.trackVerticalOffset == this.previousTrackVerticalOffset) && 84 (this.tiePrefab == this.previousTiePrefab) && 85 (this.tieInterval == this.previousTieInterval)) 86 { 87 return; 88 } 89 this.previousPathLength = pathLength; 90 this.previousTrackGauge = this.trackGauge; 91 this.previousTrackWidth = this.trackWidth; 92 this.previousTrackHeight = this.trackHeight; 93 this.previousTrackInterval = this.trackInterval; 94 this.previousTrackVerticalOffset = this.trackVerticalOffset; 95 this.previousTiePrefab = this.tiePrefab; 96 this.previousTieInterval = this.tieInterval; 97 98 // レールと枕木を更新する 99 this.UpdateMesh(); 100 this.UpdateTies(); 101 } 102 103 private void OnDestroy() 104 { 105 // RailroadTrackが破壊されたら、不要なオブジェクトも併せて破壊する 106 this.meshFilter.sharedMesh = null; 107 var ties = this.transform.Cast<Transform>().Select(t => t.gameObject).ToArray(); 108 if (Application.isPlaying) 109 { 110 Destroy(this.trackMesh); 111 foreach (var tie in ties) 112 { 113 Destroy(tie); 114 } 115 } 116 else 117 { 118 DestroyImmediate(this.trackMesh); 119 foreach (var tie in ties) 120 { 121 DestroyImmediate(tie); 122 } 123 } 124 } 125 126 private void UpdateMesh() 127 { 128 // レール用メッシュを作成する 129 if (this.trackMesh == null) 130 { 131 this.trackMesh = new Mesh {name = "Rail"}; 132 this.trackMesh.MarkDynamic(); 133 } 134 135 // pathからサンプリングしたパス上の位置をもとにレールの断面を配置していく 136 Vertices.Clear(); 137 Uvs.Clear(); 138 var pathLength = this.path.PathLength; 139 var sectionCount = Mathf.CeilToInt(pathLength / this.trackInterval); 140 var modifiedTrackInterval = pathLength / sectionCount; 141 sectionCount++; 142 var worldToLocal = this.transform.worldToLocalMatrix; 143 for (var k = 0; k <= sectionCount; k++) 144 { 145 var pos = k * modifiedTrackInterval; 146 var v0 = new Vector3(-this.trackGauge * 0.5f, this.trackVerticalOffset, 0.0f); 147 var v1 = v0 + new Vector3(0.0f, this.trackHeight, 0.0f); 148 var v2 = v1 + new Vector3(-this.trackWidth, 0.0f, 0.0f); 149 var v3 = v0 + new Vector3(-this.trackWidth, 0.0f, 0.0f); 150 var v4 = v0; 151 var v5 = v1; 152 var v6 = v2; 153 var v7 = v3; 154 var offset = this.trackGauge + this.trackWidth; 155 v4.x += offset; 156 v5.x += offset; 157 v6.x += offset; 158 v7.x += offset; 159 var t = worldToLocal * Matrix4x4.TRS( 160 this.path.EvaluatePositionAtUnit(pos, CinemachinePathBase.PositionUnits.Distance), 161 this.path.EvaluateOrientationAtUnit(pos, CinemachinePathBase.PositionUnits.Distance), 162 Vector3.one); 163 v0 = t.MultiplyPoint3x4(v0); 164 v1 = t.MultiplyPoint3x4(v1); 165 v2 = t.MultiplyPoint3x4(v2); 166 v3 = t.MultiplyPoint3x4(v3); 167 v4 = t.MultiplyPoint3x4(v4); 168 v5 = t.MultiplyPoint3x4(v5); 169 v6 = t.MultiplyPoint3x4(v6); 170 v7 = t.MultiplyPoint3x4(v7); 171 Vertices.Add(v0); 172 Vertices.Add(v1); 173 Vertices.Add(v1); 174 Vertices.Add(v2); 175 Vertices.Add(v2); 176 Vertices.Add(v3); 177 Vertices.Add(v3); 178 Vertices.Add(v0); 179 Vertices.Add(v4); 180 Vertices.Add(v5); 181 Vertices.Add(v5); 182 Vertices.Add(v6); 183 Vertices.Add(v6); 184 Vertices.Add(v7); 185 Vertices.Add(v7); 186 Vertices.Add(v4); 187 var v = pos / pathLength; 188 for (var j = 0; j < 2; j++) 189 { 190 for (var i = 0; i < 4; i++) 191 { 192 var u = ((j * 0.5f) - (i * 0.125f)) + 0.5f; 193 Uvs.Add(new Vector2(u, v)); 194 Uvs.Add(new Vector2(u - 0.125f, v)); 195 } 196 } 197 } 198 199 // パスが閉じていなければ、末端のフタ用の頂点を追加する 200 if (!this.path.Looped) 201 { 202 for (var i = 0; i < 8; i++) 203 { 204 var j = i * 2; 205 Vertices.Add(Vertices[j]); 206 Uvs.Add(Uvs[j]); 207 } 208 for (var i = 0; i < 8; i++) 209 { 210 var j = (sectionCount * 16) + (i * 2); 211 Vertices.Add(Vertices[j]); 212 Uvs.Add(Uvs[j]); 213 } 214 } 215 216 // レールの側面を張っていく 217 Triangles.Clear(); 218 for (var i = 0; i < (sectionCount - 1); i++) 219 { 220 var j0 = i * 16; 221 var j1 = j0 + 16; 222 for (var k = 0; k < 16; k += 2) 223 { 224 Triangles.Add(j0 + k); 225 Triangles.Add(j0 + 1 + k); 226 Triangles.Add(j1 + k); 227 Triangles.Add(j1 + 1 + k); 228 Triangles.Add(j1 + k); 229 Triangles.Add(j0 + 1 + k); 230 } 231 } 232 233 // パスが閉じていなければ、末端にフタを追加する 234 if (!this.path.Looped) 235 { 236 var o = (sectionCount + 1) * 16; 237 for (var i = 0; i < 2; i++) 238 { 239 var j = o + (i * 4); 240 Triangles.Add(j); 241 Triangles.Add(j + 3); 242 Triangles.Add(j + 2); 243 Triangles.Add(j + 2); 244 Triangles.Add(j + 1); 245 Triangles.Add(j); 246 } 247 for (var i = 2; i < 4; i++) 248 { 249 var j = o + (i * 4); 250 Triangles.Add(j); 251 Triangles.Add(j + 1); 252 Triangles.Add(j + 2); 253 Triangles.Add(j + 2); 254 Triangles.Add(j + 3); 255 Triangles.Add(j); 256 } 257 } 258 259 // メッシュを更新する 260 this.trackMesh.Clear(); 261 this.trackMesh.SetVertices(Vertices); 262 this.trackMesh.SetUVs(0, Uvs); 263 this.trackMesh.SetTriangles(Triangles, 0); 264 this.trackMesh.RecalculateNormals(); 265 this.trackMesh.RecalculateTangents(); 266 this.meshFilter.sharedMesh = this.trackMesh; 267 } 268 269 private void UpdateTies() 270 { 271 // 枕木の数を求め... 272 var pathLength = this.path.PathLength; 273 var tieCount = this.tiePrefab == null ? 0 : Mathf.FloorToInt(pathLength / this.tieInterval); 274 275 // 現在の枕木の数が過剰なら削除、不足なら追加する 276 var currentTieCount = this.transform.childCount; 277 for (var i = tieCount; i < currentTieCount; i++) 278 { 279 if (Application.isPlaying) 280 { 281 Destroy(this.transform.GetChild(i).gameObject); 282 } 283 else 284 { 285 DestroyImmediate(this.transform.GetChild(tieCount).gameObject); 286 } 287 } 288 for (var i = currentTieCount; i < tieCount; i++) 289 { 290 Instantiate(this.tiePrefab, this.transform); 291 } 292 293 // 枕木をtieIntervalの間隔で並べていく 294 for (var i = 0; i < tieCount; i++) 295 { 296 var pos = i * this.tieInterval; 297 this.transform.GetChild(i).SetPositionAndRotation( 298 this.path.EvaluatePositionAtUnit(pos, CinemachinePathBase.PositionUnits.Distance), 299 this.path.EvaluateOrientationAtUnit(pos, CinemachinePathBase.PositionUnits.Distance)); 300 } 301 } 302}

下図のような枕木プレハブを作り...

図1

ドリートラックにRailroadTrackをアタッチ、枕木プレハブをセットすると下図のような見た目になりました。

図2

投稿2021/06/05 01:14

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

odakyutetu

2021/06/05 06:18

ありがとうございます。 その後少しコードを変えてみたのですが、(上記質問を修正したので確認していただけますでしょうか...) 枕木の部分が反対に引き延ばされてしまうのです...
odakyutetu

2021/06/05 06:36

すみません、できるようになりました。 本当にありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問