【Unity】パーリンノイズを使った地形を作るサンプル!【エディタ拡張編】
ここしばらくメッシュの結合に興味を持ってしまってそればっかりで遊んでいます。
パーリンノイズを使って地形を作ったり、生成した地形のメッシュを結合したりと、生成からパフォーマンス面での調整まで触ることができました。
メッシュに関してはランタイムで結合する事でもドローコールバッチングを減らすことができますが、事前に生成しておいてstaticにすることでよりドローコールバッチングを減らせるかもしれません。地形として使うなら、おそらくオブジェクトを移動させることはないと思うので、事前にライトをベイクしておいたりするとさらにいい感じの準備ができると思います。
そこでこのページではエディタ拡張を使って、パーリンノイズを使ってなだらかに変化する地形を生成し、メッシュを結合するところまでやってみたいと思います。
環境
macOS 11.1 Big Sur
Unity2019.4.4f1
このページの前提となるもの
パーリンノイズを使って地形の高さを調整するサンプルコードを以下の記事で紹介しています。こちらのコードを使って動的に地形となるオブジェクトを生成します。
続いて以下の記事でメッシュの結合を扱いました。複数のメッシュを結合することでCPUのオーバーヘッドが減り、処理がだいぶ軽くなります。
このメッシュの結合の際に、対象のメッシュ数が多くなると以下のように意図しない形で結合されてしまうため、インデックスバッファの値を変更して結合したのが以下の記事です。
これらのメッシュの結合を行う際に、結合後にひとつのマテリアルを設定するようにしていましたが、実際に使うことを考えると全体でひとつではなく、同じマテリアル単位で結合して、結合後もマテリアルを維持するようにしました。
続き物みたいになっちゃったので、別のページで作成したスクリプトをそのまま使っていたりします。今回はエディタ拡張のスクリプトを作成しますが、地形の生成やメッシュの結合の考え方は上記のページのものを使っているので、可能ならこれらのページをご覧いただいた後にこのページを進めていただくとスムーズになるかもしれません。
パーリンノイズ系の記事は以下のページでもまとめているので、どんな意図で修正を加えてきたかをつかむ時には覗いてみてください。
エディタ拡張とは
エディタ拡張(Editor拡張)とは、Unityエディタにデフォルトで存在する機能に加えて、私たちユーザ側で機能を追加できる機能です。エディタ上でメニューを作ったり、インスペクターウィンドウにボタンやテキストエリアを作成したり、ウィンドウを作ったりと、C#でAPIが用意されているので、これを組み合わせて自分好みにカスタマイズすることができます。
このページではボタンを押した時に地形オブジェクトを作成し、メッシュを結合する処理を実装してみます。
エディタ拡張については以下の記事もご覧くださいな。
スクリプトの作成
エディタ拡張のスクリプトは「Editor」フォルダの中に作成します。まずはAssetsの下に「Editor」フォルダを作成しましょう。
「Editor」フォルダを開き、スクリプトを作成します。名前は任意でつけてOK。ここでは『FieldGeneratorEditor』にしました。
このスクリプトは独自のウィンドウを作成するのではなく、インスペクターウィンドウの表示をカスタマイズしてボタンを押せるようにします。属性(Attriubte)を使って表示をカスタマイズするクラスを指定することができるので、シーン内のオブジェクトにアタッチした『FieldGenerator』のスクリプトでボタンを表示させるようにしましょう。こうすることで、インスペクターウィンドウから『FieldGenerator』のフィールドに設定した値を使用することができます。
ここでは以下の3つのボタンを作成して、それぞれ機能をつけてみます。
- フィールドの生成
- フィールドパーツの削除
- フィールドメッシュの結合
イメージとしては下の画像のような感じです。
スクリプトの編集
スクリプトは以下のように編集しました。超長いのでブログで紹介するのもどうかと思いましたが、全文を掲載した後に部分ごと解説していきます。
解説: usingディレクティブとクラスの宣言
usingで名前空間を指定する部分では「UnityEditor」と「UnityEditor.SceneManagement」を追加しています。エディタ拡張を行う際は「UnityEditor」を使います。「UnityEditor.SceneManagement」の方は、生成したメッシュをアセットとして保存するためにシーン名のフォルダを作成したかったので使います。イメージとしてはベイクしたライトマップが保存される時のような感じです。
クラス名の宣言の上に[CustomEditor(typeof(FieldGenerator))]の属性を記載します。これにより、『FieldGenerator』のスクリプトをアタッチしているゲームオブジェクトをInspectorウィンドウで表示した時に、カスタマイズした部分を表示してくれるようになります。
解説: ボタンの表示
InspectorウィンドウでUIパーツを表示するため、『OnInspectorGUI()』に処理を記載します。『target』の変数はこのエディタスクリプトの対象となっている『FieldGenerator』のスクリプトを指しています。これをキャストすることでエディタスクリプト内でpublicにした設定値にアクセスすることができます。今回の例だと地形の大きさやパーリンノイズの設定などにアクセスできるようになります。
『DrawDefaultInspector()』は通常のInspectorウィンドウでの表示を行うもので、publicにしているフィールドなどをカスタマイズなしでそのまま表示してくれます。
『if (GUILayout.Button(“フィールドの生成“))』の部分はボタンを表示しています。ボタンが押された時にtrueが返され、if文の中に書いた処理を呼び出してくれます。
解説: 地形オブジェクト(フィールド)の生成
フィールドの生成ボタンが押された時には『GenerateFieldOnEditor』のメソッドが呼ばれます。XZ平面でゲームオブジェクトを生成しつつ、パーリンノイズを使ってY軸の高さを計算することで、生成された地形の高さに変化をつけています。
どのPrefabをインスタンス化するか、地形の大きさ、パーリンノイズの調整、といった値は引数として渡される『FieldGenerator』のスクリプトで設定されているものを使います。
また、ここでは『InstantiateFieldParts』のメソッドの中でマテリアルをランダムにセットする処理を含めていますが、実際にPrefabをインスタンス化する場合はコメントアウトしてください。
解説: 地形オブジェクト(フィールド)の削除
削除ボタンを押した時に呼ばれる『RemoveFieldParts』のメソッドでは、『fieldParent』の子オブジェクトを全て削除するようにしています。『DestroyImmediate』はエディタ上でゲームオブジェクトを破棄する場合に使うメソッドです。
解説: 地形オブジェクト(フィールド)の結合
メッシュを結合する処理では子オブジェクトの『MeshFilter』と『MeshRenderer』を配列として取得し、その数で結合するメッシュの数を決めています。また、マテリアルごとに結合するメッシュを分けていて、辞書(Dictionary)を使って分類しています。Dictionaryの値としてリストを使うことで、マテリアル名に対応したメッシュをリスト形式で保持することができます。
foreachの中では、結合後のメッシュをセットするオブジェクトを作成した後、マテリアルごとのリストに含まれるメッシュを結合していきます。地形オブジェクトの数が大きくなると処理に時間がかかるので、プログレスバーを表示するようにしています。
結合するメッシュを『CombineInstance』に追加したら結合処理を行います。プログレスバーの表示を行っているためスクリプトが長くなっていますが、やっていることは6ステップです。
結合したメッシュをゲームオブジェクトにセットして、UV2を自動生成し、アセットとして保存した後にマテリアル、コライダーをセットして表示しています。UV2を作成するフェーズはかなり時間がかかります。
解説: 結合後のメッシュを表示するゲームオブジェクトを作成するメソッド
メッシュを表示するためのゲームオブジェクトを生成する処理はまとめて別メソッドとして切り出しました。
解説: コンポーネントを確認するメソッド
コンポーネントがアタッチされているかどうかの確認を行う処理もメソッドとして切り出しました。ジェネリックのメソッドなのでコンポーネントが変わっても使えるようにしています。whereで型パラメータの種別を指定できるので、『Component』クラスを継承している型に制限しています。
このメソッドはエディタ拡張以外でも使えるので、ランタイム時にコンポーネントを確認するのにも便利です。毎回nullを確認して、なければアタッチして……とやるのは面倒ですからね。
解説: アセットの保存
生成したメッシュはアセットとして保存します。保存先はシーン名のフォルダにしていて、フォルダがなければ作成します。アセットを保存する場合はファイル名に『.asset』をつけないとUnityで使えないので注意が必要です(かつての1敗)
動作確認
スクリプトを保存したら、シーンをセットアップします。
もし他のページで『FieldGenerator』のスクリプトをアタッチしたゲームオブジェクトを作っていれば、それを使えばOKです。なければ空のオブジェクトを作成し、以下のページで作成した『FieldGenerator』のスクリプトをアタッチします。
アタッチしたスクリプトには画像のようにボタンが表示されるようになります。『FieldParts』に生成するPrefabを、『Field Parent』に生成したオブジェクトの親オブジェクトを、『Mat List』の項目にマテリアルをアサインしましょう。
「フィールドの生成」ボタンをクリックすると設定に応じて地形オブジェクトが生成され、「フィールドパーツの削除」ボタンをクリックすると生成したオブジェクトが削除されます。
「フィールドメッシュの結合」ボタンをクリックすると、生成したオブジェクトを結合することができます。エディタで結合しておくとライトのベイクなどもできるので便利です。
まとめ
ほとんどがこの長いスクリプトの解説でしたが、エディタ上で地形オブジェクトを生成し、それを結合しておくことでランタイムの処理をいくらか軽減できます。また、事前にメッシュが存在していればライトをベイクすることもできるので調整もしやすいのがグッド。
ここでは仕組みを解説する意図がメインでしたが、もしガッツリとメッシュを調整しておきたい場合は『Mesh Baker』などのアセットを使うのがおすすめです。
無料版もあるのでこちらで試すのもいいかも。
パーリンノイズ関係やそれに関連するメッシュ関係の記事は以下のページでまとめています。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【ロックマン11】歯車城(ワイリーステージ)1がめっちゃキツかったです(小並感) 2021.01.22
-
次の記事
【Unity】TransformのSetParentで親子関係を設定して表示を整理する 2021.01.24
コメントを書く