【Unity】TerrainのHeightMap(ハイトマップ)を生成するエディタ拡張のサンプル

【Unity】TerrainのHeightMap(ハイトマップ)を生成するエディタ拡張のサンプル

UnityのTerrainは地形を作成するのに便利な機能で、キャラクターが動き回るステージなどを作成できます。ブラシを使って地形を盛り上げたり、テクスチャを塗って草原や山を表現したりと使い始めると沼のようにハマってしまい、日が暮れるまでずっと地形を作成していられるくらい楽しい機能です。

そうは言っても、1から地形を作る際になんらかのインスピレーションが欲しいこともあるかもしれません。

そんな時には、地形の大体の形を自動で生成してから手直ししていくのも良いかも。と、思い立ち、エディタ拡張としてTerrainで使っているHeightMap(ハイトマップ: 座標ごとに高さを表現したもの)を自動生成する機能を作成しました。

この機能の中ではパーリンノイズを使ってなだらかに変化するようにしています。

 

 

環境

macOS 11.1 Big Sur

Unity2019.4.4f1

 

TerrainのHeightMap(ハイトマップ)とは

地形を作成する際には「どの座標でどれくらいの高さ」という情報を持っています。これはHeightMap(ハイトマップ)と呼ばれていて、その名の通り、高さの情報がマッピングされているものです。

Terrainではこのハイトマップをグレースケールのテクスチャとして保持しています。グレースケールの画像の場合、0に近い値であれば黒に近づき、1に近い値であれば白に近づきます。1に近いほど高いと解釈すると、画像の白い部分はTerrainで高くなっている部分として表現できます。例えば以下のような感じです。

HeightMapの例
HeightMapの例

 

上の画像は、以下のような地形を元にエクスポートしています。

元となった地形
元となった地形

 

Terrainでは地形のハイトマップをインポート/エクスポートする機能があるので、これを使ってRaw(生の)データをエクスポートし、Photoshopで開いてみたのが上で紹介したグレースケールの画像です。

ハイトマップは2次元の配列として表現されるので、ここに値をセットすることで、スクリプトから地形データを作成することができます。いつも画面でブラシを塗り塗りしていたので実際にできると感動でした。

 

パーリンノイズで地形を生成する

地形をランダムに生成するために、パーリンノイズを使用します。パーリンノイズはパーリンさんが考えたノイズで、乱数のように完全ランダムな値を取得するのではなく、パターンに沿って値を取得することで、隣り合う値である程度関連を持っています。

例えばRandomクラスを使って値を取得すると、以下のように分散グラフのような見た目になります。

Randomだと無秩序な値を取る
Randomだと無秩序な値を取る

 

パーリンノイズを使って値を取得すると、以下のように波のような見た目になります。急激に変化するのではなく、徐々に変化していく値を取得することができるので、自然な揺れ(カメラの手振れ)などを表現するのに向いています。

隣り合う値と近い値になる
隣り合う値と近い値になる

 

パーリンノイズについては以下の記事で解説を行っています。簡単なサンプルコードもあるのでよかったらご覧くださいな。

 

今回はこのパーリンノイズを使って地形を生成します。また、パーリンノイズについては複数のパターンを重ね合わせることで、メリハリをつけるようにしています。メリハリをつける件については以下の記事もご覧ください。

 

スクリプトの作成

作成したいスクリプトは2種類です。

ひとつはインスペクターウィンドウから設定を入力して値を保持するためのスクリプト、もうひとつは先程のスクリプトをインスペクターウィンドウで表示しているときに地形生成用のボタンを表示するためのエディタ拡張のスクリプトです。

 

値を保持するスクリプト

値を保持するスクリプトはプロジェクト内の任意の場所に作成します。ここでは名前を『TerrainGenerator』にしました。このスクリプトはシーン内のオブジェクトにアタッチします。フィールドだけがあるクラスで、ゲーム内の処理は必要ないのでStart()やUpdate()を消しておきます。

terrainDataのフィールドには地形を変更したいTerrainのアセットをアタッチします。エディタ拡張のスクリプトの中でこのフィールドを使用します。

パーリンノイズに関する情報として、X軸の原点やY軸の原点を設定できるようにします。パーリンノイズはX軸の値とY軸の値を入力することで値を取得することができます。イメージとしては、以下の2次元のテクスチャにおいて、X座標、Y座標を指定した時にそのピクセルの値を読み取る感じです。scaleの値はパーリンノイズの座標を入力する際に使っていて、scaleの値が小さくなると座標の値も小さくなり緩やかに変化するパターンとして値を取得できます。逆に大きくなると座標の値も大きくなるので変化が大きくなります。

2次元的にテクスチャを生成
2次元的にテクスチャを生成

 

高さの補正値は、パーリンノイズを計算した後に、「もう少し高さを調整したいなー」というときにスクリプトをいじらずに変更できるようにしています。特に、0から1までの値を何度も掛け合わせているので、乗算の回数を増やすと値がどんどん小さくなっていきます。そんなときに補正値として任意の値を乗算できるようにしました。この値は地形全体に影響します。

乱数のシードはパーリンノイズを重ね合わせる際に使用します。パーリンノイズの別のパターンを取得する際、シードを固定しないとバラバラな座標の値を取得することになり、全体がえらいこっちゃになるのでフィールドを作成しています。シードを変えると全体の見た目も変わります。

なお、バラバラになった例が以下の画像です。こちらはTerrainではなく個別のGameObjectをインスタンス化していますが、隣り合う値でバラバラな値になっているのをイメージしていただけるかもしれません。

シードをセットしなかっただけなのに
シードをセットしなかっただけなのに

 

重ね合わせる回数も指定できるようにします。上でも触れましたが、乗算の回数が多くなるほど全体的に地形が低くなっていくので、補正値をかけて全体のメリハリを強調するようにします。

 

エディタ拡張のスクリプト

エディタ拡張のスクリプトはプロジェクト内に『Editor』フォルダを作成し、その中に作成します。ここでは名前を『TerrainGeneratorEditor』にしました。

地形を生成するボタンとリセットボタンを用意しています。

 

ちょっと長いので部分ごとに解説を。

 

クラスの宣言部分です。特定のスクリプトがアタッチされている場合にエディタの表示をカスタマイズするため、『CustomEditor』のAttributeをつけています。

このクラスではEditorを継承してインスペクターウィンドウにボタンを表示し、ボタンに対応する処理を作っていきます。

OnInspectorGUI()はインスペクターウィンドウに表示する内容を決めていて、DrawDefaultInspector()でデフォルトの表示、つまり『TerrainGenerator』クラスのフィールドを表示します。

generatorの変数にはゲームオブジェクトにアタッチされている『TerrainGenerator』のスクリプトへの参照がアサインされます。インスペクターウィンドウで値を入力したものをこのエディタ拡張のスクリプトで使うために使用しています。ボタンが押されたときに呼び出されるスクリプトにも引数として渡しています。

 

 

 

GenerateTerrainOnEditor()のメソッドではハイトマップに設定する値を計算していきます。ハイトマップは2の乗数+1の大きさになっていて、257*257や513*513といった解像度で表現されます。この値はスクリプトを使ってTerrainDataのクラスから取得できるので、これをハイトマップのサイズとして使います。

ハイトマップは2次元の配列で表現することができ、各要素に高さの値をセットして地形に適用することで実際に画面に描画されます。

解像度はint型で取得されるため、forループの上限として使います。パーリンノイズは縦横の座標を指定して取得します。そのためループのカウンタとして使っているxやzから座標を計算します。パーリンノイズの計算自体は後述のGetMultiplePerlinNoise()という複数のパーリンノイズを重ね合わせるメソッドを使います。

 

 

GetMultiplePerlinNoise()のメソッドではパーリンノイズを複数回乗算して計算しています。乱数のシードを固定した後、ループの中で乱数を生成して別のパターンを取得するようにしています。乱数のシードを固定することで抽出されるパターンが各点で共通するようにします。

パターンを抽出して重ね合わせる
パターンを抽出して重ね合わせる

 

ループの中では重ね合わせるパターンを取得して、ループ前に計算したベースとなるパーリンノイズの値に掛け合わせています。

 

 

こちらはリセット用の処理です。ハイトマップを初期化してそのままTerrainDataにセットしています。

 

Terrainの作成

スクリプトを保存したらUnityエディタに戻ります。ヒエラルキーウィンドウでコンテキストメニューを開き、[3D Object] -> [Terrain] からTerrainのオブジェクトを作成します。

作成したTerrainでは設定タブを選択し、解像度の設定を行います。

設定タブ
設定タブ

 

青い線がかぶってしまいましたが『Mesh Resolution』の項目を大体200くらいに、『Heightmap Resolution』の項目では [257 x 257] を選択します。大きすぎると重くなるのでちょっと減らしていますが、任意の値で大丈夫です。

解像度を変更する
解像度を変更する

 

スクリプトのアタッチ

任意の空のオブジェクトを作成し、スクリプトをアタッチします。今回はエディタ拡張のスクリプトを作成してあるので、スクリプトの表示領域にボタンが表示されます。『Terrain Data』のフィールドには作成したTerrainのアセットをアサインします。

スクリプトのアタッチ
スクリプトのアタッチ

 

 

動作確認

『フィールドの生成』ボタンをクリックすると、インスペクターウィンドウでの設定に応じてTerrainのハイトマップを生成します。例えば上の画像の設定であれば、以下のように表示されます。

ランダムな地形ができた
ランダムな地形ができた

 

これをベースに平地を増やしたり、高さを揃えてみたり、逆にもっと隆起させたりと、まっさらな状態から作業するよりは多少楽に……なるといいなぁ(願望)

リセットボタンをクリックするとTerrainがまっさらな状態に戻ります。いい感じの地形ができたら『Terrain Data』のフィールドをNoneにしておくと安心です(1敗)

Terrainもスクリプトから色々いじれると学びを得られて良かったです。

また、ハイトマップのデータを残しておきたい場合はRawデータ(ハイトマップの生のデータ)をエクスポートしておくと良いでしょう。エクスポートしたファイルはすぐにはUnityのプロジェクトウィンドウに表示されないので、エクスプローラやFinderで確認してください。

Rawデータのエクスポート
Rawデータのエクスポート

 

まとめ

TerrainのHeightMap(ハイトマップ)をスクリプトから作成するサンプルを紹介しました。

自然さをもったランダムな地形を作成するにはパーリンノイズを使うのが便利です。完全にこれだけで使うというよりは地形を作成するときのインスピレーションを得るようなイメージで使うのがいいかも。

余談ですが最初はTerrainのことをそのまま「テライン」と読んでいましたが、「テレイン」と読むのが一般的だと知った時はちょっと恥ずかしい気持ちになりました。

 

パーリンノイズ関係やそれに関連するメッシュ関係の記事は以下のページでまとめています。

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!