【Unity】パーリンノイズを使って地形を作ろう!【ランタイム編】
UnityのMathfクラスにはPerlinNoiseというメソッドが用意されています。このメソッドでは完全にランダムな値を返すのではなく、波のように徐々に変化する値を返してくれるので、連続的に値を取得することでそのまま波のように値を取得することができます。
このページではPerlinNoiseの機能を使って、ランタイム(ゲーム実行時)にシーン内のオブジェクトを生成し、いい感じに滑らかな地形を生成するサンプルを紹介します。
ただし、パーリンノイズの原理を知ることを目的としているため、パフォーマンスの問題でそのまま使うには辛いので、もし実際に使うなら調整は必要です。
環境
macOS 11.1 Big Sur
Unity2019.4.4f1
パーリンノイズとは
パーリンノイズは主にテクスチャを作成する際に使われるノイズで、完全にランダムではなく隣り合う値が近いものになっているノイズです。
例えばRandomクラスを使って値を取得すると、以下のように分散グラフのような見た目になります。
パーリンノイズを使って値を取得すると、以下のように波のような見た目になります。急激に変化するのではなく、徐々に変化していく値を取得することができるので、自然な揺れ(カメラの手振れ)などを表現するのに向いています。
パーリンノイズについては以下の記事で解説を行っています。簡単なサンプルコードもあるのでよかったらご覧くださいな。
地形を作ってみる
パーリンノイズでは波のように値が変化することから、地形のようになだらかに高さが変わるものに対しても使えます。ここでは簡単に地形を作ってみましょう。
やることは簡単で、Prefabを使って地形のセルを用意して、PerlinNoiseで高さに変化をつけて配置するだけです。まずは柱のような細長いオブジェクトを作成し、それを上から眺めることで地形の変化を確認します。
地形オブジェクトの作成
地形は細長いCubeを使ってみましょう。任意のシーンを開き、Cubeオブジェクトを作成します。
作成したCubeオブジェクトの名前を『FieldCell』に変更しました。TransformのScaleでは『Y』の値を [10] に変更します。このスケールは作成するフィールドの大きさや高さによって後で調整するようにします。
作成したゲームオブジェクトはPrefabとして作成します。このPrefabをゲーム実行時に生成しましょう。
Prefabを作成したらシーン内の『FieldCell』オブジェクトを削除し、パーツをインスタンス化した時に整理するための親オブジェクトを作成します。ここでは『FieldParent』にしました。たくさんのパーツをインスタンス化することになるので、こうした親オブジェクトを用意しておくのは大切ですね。
スクリプトの作成
任意のフォルダにスクリプトファイルを作成します。ここでは名前を『FieldGenerator』にしました。
作成したスクリプトでは以下の機能を実装します。
- フィールドの設定やパーリンノイズの設定を行うフィールドを追加
- 追加ボタンを押された時のメソッドを作成
- フィールドを生成するメソッドを作成
- 削除ボタンを押された時のメソッドを作成
画面内に生成ボタンと削除ボタンを配置して、ゲームの実行中に生成や削除を行えるようにします。
以下のコードがスクリプトのサンプルです。
フィールドの宣言部では、Prefabへの参照とパーツの親オブジェクトへの参照をセットできるようにします。
シーン内のフィールドの大きさを設定できるようにpublicなフィールド(ややこしいですね)にしています。お好みで[SerializedField]に変更してもらってもOKです。また、パーリンノイズについてもオフセットとなる座標を設定できるようにしています。scaleの項目はパーリンノイズで値を取得する間隔を決めていて、scaleが小さいとよりなだらかな変化、scaleが大きいとより激しい変化になります。
OnPressedGenerateButton()のメソッドは生成ボタンから呼びます。このメソッド内でシーン内のフィールドを生成するためのメソッドであるGenerateFieldParts()を呼びます。こちらではMathfクラスのPerlinNoiseメソッドを使って値を取得しています。パーリンノイズ内の座標を計算するためにシーン内のX座標とZ座標を使っています。パーリンノイズ側ではX座標、Y座標という形になっているので名前も合わせていますが、Zが入っていて紛らわしいですね(笑)
取得した値は0から1の範囲なので、そこに任意の高さをかけることでシーン内の高さ(TransformのPositionのY)を計算しています。ループカウンタとしてfloatの値を使っているのでそのままオブジェクトの座標として使い、インスタンス用のメソッドに渡しています。
OnPressedRemoveButton()のメソッドは削除ボタンから呼びます。こちらは親オブジェクトの下にあるパーツを全て削除します。
スクリプトをアタッチする
スクリプトを編集して保存したらUnityエディタに戻り、スクリプトをアタッチするためのオブジェクトを作成します。ヒエラルキーウィンドウでコンテキストメニューを開き [Create Empty] からオブジェクトを作成します。ここでは『FieldGeneratorObj』に名前を変更しました。
作成した [FieldGenerator] のスクリプトをアタッチして、『Field Parts』のフィールドにPrefabを、『Field Parent』のフィールドに親オブジェクトをそれぞれアサインします。
ボタンの作成
続いてボタンを作成します。ヒエラルキーウィンドウでコンテキストメニューを開き [UI] -> [Button] からボタンオブジェクトを作成します。
作成したボタンは画面内の任意の場所に配置し、以下のようにメソッドを呼ぶ設定を行います。
生成ボタンからは『OnPressedGenerateButton()』を、削除ボタンからは『OnPressedRemoveButton()』を呼ぶようにしてください。
ゲームの実行
ゲームを実行して生成ボタンをクリックするとオブジェクトが作成されます。スクリプトではフィールドのX, Z方向のサイズを50に設定していますが、最初は10や20などの小さい値で確認するのがおすすめです。というのも、描画コストがかなり高いためです。オブジェクト数が多くなると地獄のような重さになるのでびっくりするかもしれません。
また、カメラの位置は生成した地形に合わせて調整してください。
以下の画像は『Field Height』が20、『scale』が0.05の地形です。scaleの値を大きくすることで変化の度合いが大きくなるので、好みに合わせて値を調整するとグッド。
地形の大きさを変えると以下のようになります。200 * 200のサイズにしてみたので、オブジェクト数は40000個。興味深い起伏の様子を確認できますが、パフォーマンス面では大変なことになっています。
パフォーマンスについて
オブジェクトを数多くインスタンス化するため、パフォーマンス面では大変な状況になっています。試しにProfilerを覗いてみると、以下のように15FPSのラインをぶっちぎってました。黄緑色の部分はレンダリングの項目なので、オブジェクト数が多いといかに大変かお分かりいただけたかと思います。
ゲームウィンドウの『Stats』から統計情報を確認すると、以下のようにバッチ数が10万近い値になっています。地獄のような重さですね。毎フレームこれがあると処理についても納得の重さです。
UnityのTerrainを使って同じような地形を作ったときは、調整前のバッチ数が2000くらいで「これはヤバい」と言うくらいだったので、その25倍くらいのヤバさです。カメラに映るオブジェクト数を減らせば多少バッチ数も減りますが、流石にこの地形を歩き回るには重いですね。
パーリンノイズを使って地形を作成する場合は入力した値によって自動的にいい感じに地形を作ってくれるメリットがあるので、もしこの方向で使うのであれば、オブジェクトのスケールを上げてオブジェクト数を減らして使うことをおすすめします。あるいはメッシュの数を減らしたり、素直にTerrainを使うのがいいかも。
まとめ
パーリンノイズを使ってオブジェクトの高さを計算し、地形を作ってみるサンプルでした。パーリンノイズの原理や動きを知ることを目的としていたため、パフォーマンス面ではちょっと問題がある方法ですが、考えるための取っ掛かりになればと思います。
パフォーマンスを上げるためにはメッシュを結合したり、Editor拡張などを使って事前にシーンにオブジェクトを配置してstaticにしたりといくつか方法もあるのでそちらは別記事で解説したいと思います。
メッシュを結合する方法については以下の記事で解説しています。
パーリンノイズ系の記事は以下のページでまとめています。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】パーリンノイズ(PerlinNoise)って何ね? と思ったので遊んで試してみよう 2020.12.30
-
次の記事
【Unity】メッシュを結合するCombineMeshesを使って地形を結合してみる 2021.01.01
コメントを書く