Curvyを使って３D横スクロールステージを作ろう！

# はじめに

この記事はUnity アセット真夏のアドベントカレンダー 2021 Summer! 11日目の記事です。
10日目は[たぬ](https://twitter.com/tanuh_yy)さんの「[MeshEffectsを使って、Vroidモデルに武器を持たせて動かし撮影しよう！](https://tanuh-yy.hatenablog.com/entry/2021/08/10/mesh?_ga=2.171441696.1074574609.1627985235-1707083470.1627386746　"タイトル")」です。

12日目は[れいちゃん](https://twitter.com/Eilzy)さんの「[SuperEditorの紹介](https://qiita.com/xiumida/items/ff2edd8dbc5aa6cbc33a)」です。

今回紹介するアセットはCurvy SplinesというUnity上でスプラインの作成ができるアセットです。
執筆時点での価格は55ドルです。

https://assetstore.unity.com/packages/tools/utilities/curvy-splines-7038

## 環境

- Curvy Splines 7.1.4
- Unity 2020.3.1
- 適当な3D環境アセット（後述）

## この記事で作成するもの

サイドビューの３D横スクロールステージを作成します。![video_24.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/2f14dd00-8d36-46bf-a190-db0375cd9e85.gif)

使用した環境アセットは「Fantasy Demon Caves」です。トップダウンの環境アセットですが、床、壁といったパーツ単位で構成されていればなんでもOKです。

https://assetstore.unity.com/packages/3d/environments/dungeons/fantasy-demon-caves-39503

以下のような人に向いています
- 横一直線のステージを作りたい
- 3Dアセットを購入したけれど、どうやって配置すればいいのかわからない
- 背景を作りながらステージの長さを調節したい

# Splineの作成
Curvy Splinesをインポート後、ヒエラルキービューの「**＋**」→「**Curvy**」→「**Spline**」からスプラインを作成できます。
![2020-3-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/d61ffe0f-b287-2760-5d67-abdf41b4325a.png)

シーンビュー上にスプライン作成のUIが表示されます。**①Draw Splineボタンを選択**し、②**配置する空間を選択**しましょう。今回は高さのないステージにしたいので「XZ Plane」を選択しました。
シーンビュー上を<kbd>Ctrl</kbd>キーを押しながらクリックし、③**開始ポイント**と④**終了ポイント**を作成してください。各ポイントはあとから修正できるので適当でOKです。
![2020-3-66.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/3fe5ded4-3bff-3f67-8b33-f3210f391038.png)

作成したスプラインが親、各コントロールポイントが子としてヒエラルキー上に作成されます。
それぞれ単体のゲームオブジェクトなので、移動ツールやインスペクターから位置調整できます。
スプラインの方はあとで参照される側になるので、**わかりやすい名前を付けておきましょう。**
![2020-3-68.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/1539d824-1994-2400-34b4-fd8d3d25eb34.png)

2点しか作成していませんが、直線マップなのでスプラインの作成は以上です。

# Generatorの作成
スプラインに沿ってオブジェクトを生成するGeneratorを作成します。

ヒエラルキービューの「**＋**」→「**Curvy**」→「**Generator**」から作成できます。
![2020-3-69.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/4fe55775-37ba-42ac-83eb-20cfc6e1ffea.png)

ヒエラルキーにGeneratorが追加され、Generatorウィンドウが開きます。Generatorウィンドウはシーンビューの「Edit」やインスペクターからも開くことができます。（メニューのWindowからは開けないので注意）
![2020-3-70.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/0e0d40b7-4b02-00f9-c268-60264359e13f.png)

ゲームオブジェクト配置用Generatorを作ります。
生成するオブジェクトはGameObjectとMeshの二通り用意されていますが、本稿ではGameObjectから生成します。
Generatorウィンドウ上で「**右クリック**」→「**Add Template**」→「**Spot GameObjects**」を選択します。
![2020-3-71.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/3854ce84-2587-b126-6576-58b69530c1e8.png)

以下のようなグラフが生成されます。各ノードをつなげることで最終的な生成結果になります。
![2020-3-72.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/d4648eec-d55b-c57e-b9ae-e1cac8948282.png)

図解すると以下のようになります。まさかり持った金太郎あめをイメージするとわかりやすい（？）です。
![2020-3-72a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/0db2063e-d826-013c-3fe3-beb0c21cdae1.png)

今回はスプライン上に沿ってオブジェクトの配置をしたいだけなので、シェイプの指定と押し出しは不要です。「**右クリック**」→「**Delete**」で削除しましょう。

![2020-3-73.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/d86f317f-cdb8-447c-a87a-7a6a2ed2a3bb.png)

代わりにパスのラスタライズ化を行います。「**右クリック**」→「**Add**」→「**Build**」→「**Rasterize Path**」を選択します。
![2020-3-74.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/cbe1018c-d674-d0b9-6523-c10350d85e67.png)

作成したノード間の接続をします。「**Input Spline Path**」→「**Rasterize Path**」と、「**Rasterize Path**」→「**Rasterize Path**」→「**Volume Spots**」を接続します。
![2020-3-75.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/7d2fe36f-350f-12eb-0318-3270596dabf5.png)


## 床オブジェクトの配置

まずはステージの基盤である床を配置していきます。Input Spline Pathノードにある**Splineの×ボタンを押し、デフォルトで割り当てられているスプラインを削除してください。**![2020-3-78.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/39a00717-6a08-7928-da7a-b8accd29fc24.png)

代わりに作成した**直線のスプラインを指定**します。スプラインにわかりやすい名前を付けておくとここで混乱せずに済みます。

![2020-3-79.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/1534104e-008b-db59-2483-2413f7b3be6d.png)

Input GameObjectsノードの**Serialized Propertyのリストに配置したいゲームオブジェクトを指定**します。
![2020-3-80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/15277a3d-a4fa-9144-e660-ffcdf5e9ae20.png)

ここでは以下のオブジェクトを配置することにしました。かっこいい。
![2020-3-81.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/aa70cb0b-bbdc-688a-6a67-5cd2fed5020f.png)

Vokume Spotsノードの**0:Group**を選択し、**Itemsリスト**の**+ボタン**を押して指定したオブジェクトを登録します。
![2020-3-82.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/587898ed-cebe-373c-d28d-4c17715eea77.png)

シーンビューを見るとパスに沿ってオブジェクトが配置されているのがわかります。
![2020-3-83.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/88d43fbc-ee7c-5acc-4ce6-408a6ffbb2dd.png)

Input GameObjectsノードの**Propertiesを変更**し、横向きに並ぶようにします。なお、Propertiesは複数のオブジェクトを登録していても、オブジェクトごとに設定が可能です。
![2020-3-84.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/d4e8b08d-10f0-4ff2-54d3-c210359a79ec.png)

横向きに配置ができました。パスに沿って生成されるので長さを変更するとオブジェクトの生成数も変わります。

![video_22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/6e915dd8-04cd-4861-864b-92e49ee69e80.gif)

Generatorは各ノードを子として、ノードから生成されたオブジェクトを孫としてヒエラルキー上に配置されます。今後Generatorは複数作成することになるので分かりやすい名前を付けておいた方が良いです。
![2020-3-85.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/272ec222-d2c8-7b29-e542-50cea2d31952.png)

## テンプレート化

作成したグラフは上部メニューの💾よりテンプレート化できます。ここでテンプレート化しておくと、右クリック→「Add Template」のメニューに追加することができます。テンプレート化されるのは選択されたノードになるので、ヒエラルキー上で各ノードを複数選択した状態で保存するようにしましょう。

![2020-3-76.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/b4508143-9e0a-e19a-9029-49881b07ff29.png)

## 飾り用オブジェクトの配置

床だけだと味気ないので以下のような2種類の岩オブジェクトをランダム配置します。
![2020-3-86.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/4ac5a4d5-329d-f36a-6360-97ac21121563.png)
![2020-3-87.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/0105578c-f1bf-9656-ad2f-ef23bb16f748.png)

新たなGeneratorを作成し、テンプレートから以下のようにグラフを作ります。Volume Spotsノードでは生成間隔やオフセット座標の指定ができます。入力フィールド横のチェックボックスをオンにすると二値間のランダム値が指定できます。
![2020-3-88.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/67e65bca-2b08-02f0-2fce-489b0e3b257a.png)

床の奥側の壁として配置しました。
![2020-3-89.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/87080580-1cc7-d590-f600-7a6d6539b8c1.png)

ランダム配置の結果が気に食わなければSeed OptionsにあるSeed値を変更すればその都度配置を変えてくれます。
![2020-3-90.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/c28508c9-bd6e-ed3e-006c-e62f28756842.png)
![video_23.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/73711043-da05-1c79-6e4f-aa3bd57a95ff.gif)

## 末端の作成
末端など一定の場所に配置する特別なオブジェクトはGeneratorを使用して生成する必要はありません。
コントロールポイントの子供にするとスプライン変更時に追従してくれるので、階層構造を気にしないのであれば勧めです。
![2020-3-91.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/fa7294ad-0246-c5fd-5186-dc477c58f32e.png)

以下のようにスプラインの末端からはみ出てしまうオブジェクトが出たときは**Rasterize PathのRangeを調整**して生成範囲を制限してください。

![2020-3-95.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/8a2805fb-e25f-f960-dd37-8471136b999b.png)

![2020-3-96.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/cb6bad15-771b-87ca-e59d-b0d592d61d41.png)

# 仕上げ
以上の手順を繰り返して小物や背景用のオブジェクトを配置します。階段用のスプラインを繋げて高さも付けてみました。
![2020-3-92.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/5528aec6-3b12-6543-c31f-7c33c6a66669.png)

## 負荷対策
Generatorを複数作るとコントロールポイントの位置変更がとてつもなく重くなります。対策としてGeneratorコンポーネントの `Refresh Delay Editor` の値をGeneratorごとにずらしてスパイクを分散させる方法があります。床のGeneratorはパスの変更があってから10ミリ秒後に更新、岩のGeneratorは30ミリ秒後に更新…といった具合になります。（もっと良い方法があったら教えてください…）
![2020-3-97.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177227/2c843e05-2c3f-4fc7-48d2-7a02a64a3d39.png)


# まとめ
CurvyではSplineを生成して様々な応用ができます。
アセットに含まれているサンプルでは湾曲した線路や道路、経路上に沿った移動などに使われていましたが、スプラインに沿った配置機能が充実していたので、今回はシンプルな直線ステージの作成に使用してみました。
各コントロールポイントはゲームオブジェクトとして生成されるので、Prefab化したり、スクリプトをくっつけてコンストレイントのような仕組みを作ったりと応用の幅も広そうです。
大変有用なツールなので機会があればぜひ触ってみてください。
本記事が何かの参考になれたら幸いです。