Category: “UE”
執筆: “H.A”
はじめに
はじめまして。プログラマーのH.Aです。
ゲームのレベルデザインで道路を敷くことになったときに、自由度の高い道路のシステムがあると、道のバリエーションを出しつつ、作業効率を上げることができます。
今回は、そんな自由に道路を敷く方法を紹介します。
この記事の内容
Splineに沿って自動で道路のメッシュを生成する処理について、実装手順を踏んで解説します。
ファイルを用意しよう
今回は以下のバージョンのUnrealEngineを使用します。
ver 5.3.2
道路はKenney様から以下のURLのモデルを拝借いたします。https://kenney.nl/assets/city-kit-roads
実装してみよう
早速実装していきます。
今回はサードパーソンを選択してプロジェクトを作成します。
(別にブランクでも問題ないのですが、道路を敷いた後に動き回って確認したいんで)

まず、SplineComponentを持ったActorの作成、メッシュの準備をしていきます。
Actorを継承したブループリントクラスとして「BP_SplineActor」を作成し、コンポーネントとしてSplineを追加します。

次に、道路として使用するメッシュを準備します。
使用するメッシュは前に説明した通り、Kenney様で配布されているCity Kitから「road_straight.fbx」を使用します。
そのまま使用すると少々メッシュが小さいため、お好みのサイズに拡大するなど、調整してみてください。
ここからは、BP_SplineActorのConstruction Scriptにスプラインに沿ってスプラインメッシュを生成していきます。
その前に生成時に使う各定数の定義を行います。

各変数の説明は以下のようになります。
Road Length : 生成途中の道路の長さの変数
RoadMesh Length:道路メッシュ1つ分の長さの変数

以上のような形でノードをつなぎ、道路の長さがスプラインの長さを超えるまでwhileループの中でメッシュを生成していきます。
ループごとにSplineMeshComponentを生成して、始点と終点の座標とタンジェントをSplineから取得して設定します。

SplineMeshComponentの生成と設定の後、コリジョン判定を有効にして、どこまで道路を生成したかを保持するRoadLength変数に今回ループ分の長さを足して、ループ中の処理は終了です。
ここのコリジョン判定は用途に合わせてTypeを変えたり、処理を飛ばしたりしてください。
以下にここまでのBlueprintを記載するので、試してみたい場合はコピーして実際に動かしてみてください。
https://blueprintue.com/blueprint/fowyer69
ここで一度、Splineに合わせて道路が敷かれているかテストをします。
BP_SplineActorを実際にレベル上に配置して、Splineを伸ばしてみてください。
うまくいけば、以下のようにSplineに沿って道路が生成されます。

コリジョン判定を有効にしていれば実際に道路上を歩いたりできるので遊んでみてください。
さて、ここまででタイトル通りに道路の生成ができましたが、ここまでのことはLandscapeのEditSpline機能で同じことができちゃいます。
また、EditSplineの場合、同時に周囲の地形も調整してくれるため、道路と地形の一体感をだすだけなら、Landscapeの方が楽です。
ただし、この道路に沿ってNPCを歩かせたり、装飾をランダムで配置するなど、ここからプラスアルファをやる場合は、Actorから自作する必要があります。
ここからは、そんなプラスアルファの1つとして、交差点を作成していこうと思います。
交差点のBPを作成すると、以下のようにBP_SplineActorを交差点の分岐先に近づけると自動的にスナップして、綺麗につなげることができます。

まずは、交差点用に新しくActorを作成します。
BP_SplineActorと同様に、Actorを継承したブループリントクラス、「BP_Intersection」クラスを作成します。
作成したら、まず、コンポーネントと変数を作成します。
DefaultSceneRootの子コンポーネントとして、StaticMeshComponentを追加します。
変数には、スナップする範囲の変数として「SnapRadius」を作成します。

次に、関数を作成していきます。
まずは、スナップ地点を配列にまとめる関数、CreateSnapPointLocationsを作成します。
戻り値にVector型の配列を設定して、中身の処理は空っぽにします。
この関数の処理は、このクラスを継承したクラスで定義します。
次に、BP_SplineActorを自身にスナップさせるかの判別する関数を作成します。
引数にVector型の「Spline Point Location」を作成し、戻り値にBool型の「Is Snap」Vector型の「Snap Location」を作成します。
前に作ったCreateSnapPointLocationsから自身のスナップ地点を取得して、スナップ地点の座標と判別するスプラインポイントの座標の距離がSnapRadius以下であれば、IsSnapをTrueとして、スナップ地点の座標を返します。
どのスナップ地点もSnapRadiusより離れていれば、IsSnapをFalseにして、SnapLocationは0を返します。

https://blueprintue.com/blueprint/3wq9g8c-
以上がBP_Intersectionの処理となります。
ここからは、BP_Intersectionで定義した関数をBP_SplineActorで呼び出して、スナップする処理を作っていきます。
BP_SplineActorの道路メッシュを生成する処理の前に定義していきます。
レベル内のすべてのBP_Intersectionを取得して、1つ1つスナップ処理を行っていきます。

先ほど作成したSnapSplineMeshで始点と終点それぞれがスナップするかの判別を行い、スナップする場合、始点または終点のスプラインポイントの座標とTangentをスナップする地点に合わせて更新します。

https://blueprintue.com/blueprint/cflrjmt7
これで後は、BP_Intersectionを継承して、十字路を作成するだけです。
BP_Intersectionを継承したブループリントクラス、「BP_CrossRoad」を作成します。
StaticMeshComponentに使いたいStaticMeshを設定します。
スナップ地点の目印として、StaticMeshComponentの子コンポーネントとしてSceneComponentを作成します。
生成したSceneComponentはそれぞれ、分岐先に位置を移動させます。(今回は十字路なのでそれぞれ4辺の中心に配置します)

最後に、CreateSnapPointLocationsをオーバーライドして、先ほど追加したSceneComponentのワールド座標を配列にして返すようにします。

起動
レベル上にBP_CrossRoadを配置して実際にテストしてみましょう。
BP_SplineActorの始点または終点を動かして、十字路に近づけてみてください。
スナップの距離はSnapRadiusで調整してください。


まとめ
今回はSplineを使った道路の生成と交差点の作り方についてまとめました。
Splineを使えばNPCを道路に沿って移動させたり、装飾品を自動で配置したりと自由度が高く、他のシステムと連携がしやすい仕組みで作ることができます。
ゲームで都市マップを作るときやレースゲームのコースを作ったりする際に、ぜひ活用してください。
関連記事
- https://historia.co.jp/archives/21842/