[Unity][C#Script] 自作スクリプトで自然な感じの地形を生成する(ダウンロードあり)
Unity の標準機能のみをつかって、地形を自動生成してみます。つまり無料でたくさんの地形をつくりまくれるという話です。どのような地形かというと、下のスクリーンショットのような、マインクラフトのような凹凸のある、しかしなだらかにしたような地形です。
使っている機能は「パーリンノイズ」と「メッシュ」です。これをプロシージャル(手続き的)に加工して、ゲームに向いた地形を作ってみようというわけです。
・パーリンノイズはマイクラの地形の自動生成にも使われている(と思われる)、連続性のあるランダムです。
・メッシュは、いわゆるポリゴンです。これは敷き詰めて地面にできます。
つまり、ポリゴンで地形をつくって、パーリンノイズでその頂点を上下させればいいんじゃない? という発想です。
1. メッシュの生成
平面を正三角形で埋めるスクリプトを作りました。
このスクリプトを使って、まずは平面を作ってみます。
1) Hierarchy に 空の GameObject を作ります。
2) Mesh Filter を追加します。
3) Mesh Rendererを追加します。
4) Projectウィンドウの適当なところで Create > Material にて新規マテリアルを作成し、先程の GameObject に追加します。
5) NewScript にて「AutoGenerateFloor」の名前で新規スクリプトを生成し、以下のサンプルを打ち込みました。
// K1Togami 2020/05/31
using UnityEngine;
public class AutoGenerateFloor : MonoBehaviour
{
public int width = 30;
public int height = 18;
public float scale = 5.0f;
const float TriangleHeight = 0.86660254f;
const float TriangleHeightDouble = 1.7320508f;
Mesh mesh;
[ContextMenu("生成")]
private void makeGround()
{
mesh = new Mesh();
int p;
mesh.Clear();
var vertices = new Vector3[((width + 1) * 2 + 1) * (height + 1) + width + 1];
var triangles = new int[(width * 2 + 1) * (height * 2 + 2) * 3];
// メッシュ作成
// 初段
for (p = 0; p <= width; p++)
{
vertices[p].x = p * scale;
vertices[p].z = 0f;
vertices[p].y = 0f;
}
for (int i = 0; i <= height; i++)
{
// 左端処理
vertices[p].x = 0f;
vertices[p].z = i * TriangleHeightDouble * scale + TriangleHeight * scale;
vertices[p].y = 0f;
p++;
for (int j = 0; (j <= width - 1); j++)
{
vertices[p].x = j * scale + 0.5f * scale;
vertices[p].z = i * TriangleHeightDouble * scale + TriangleHeight * scale;
vertices[p].y = 0f;
p++;
}
// 右端処理
vertices[p].x = width * scale;
vertices[p].z = i * TriangleHeightDouble * scale + TriangleHeight * scale;
vertices[p].y = 0f;
p++;
// 縦列はワンループで2つの三角形がペアです。
for (int j = 0; j <= width; j++)
{
vertices[p].x = j * scale;
vertices[p].z = (i + 1f) * TriangleHeightDouble * scale;
vertices[p].y = 0f;
p++;
}
}
p = 0;
// メッシュ順作成
for (int i = 0; i <= height; i++)
{
for (int j = 0; j < width; j++)
{
// 三角形4個を一組にして定義していきます。
triangles[p + 0] = j + (((width + 1) * 2 + 1) * i);
triangles[p + 1] = j + (width + 1) * (i * 2 + 1) + i;
triangles[p + 2] = j + (width + 1) * (i * 2 + 1) + i + 1;
triangles[p + 3] = j + (((width + 1) * 2 + 1) * i); //
triangles[p + 4] = j + (width + 1) * (i * 2 + 1) + i + 1;
triangles[p + 5] = j + (((width + 1) * 2 + 1) * i) + 1;
triangles[p + 6] = j + (width + 1) * (i * 2 + 1) + i;
triangles[p + 7] = j + (((width + 1) * 2 + 1) * (i + 1));
triangles[p + 8] = j + (width + 1) * (i * 2 + 1) + i + 1;
triangles[p + 9] = j + (width + 1) * (i * 2 + 1) + i + 1;
triangles[p + 10] = j + (((width + 1) * 2 + 1) * (i + 1));
triangles[p + 11] = j + (((width + 1) * 2 + 1) * (i + 1)) + 1;
p += 12;
}
// 右恥と、左端処理
triangles[p + 0] = width + (((width + 1) * 2 + 1) * i);
triangles[p + 1] = width + (width + 1) * (i * 2 + 1) + i;
triangles[p + 2] = width + (width + 1) * (i * 2 + 1) + i + 1;
triangles[p + 3] = width + (width + 1) * (i * 2 + 1) + i;
triangles[p + 4] = width + (((width + 1) * 2 + 1) * (i + 1));
triangles[p + 5] = width + (width + 1) * (i * 2 + 1) + i + 1;
p += 6;
}
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
var filter = GetComponent<MeshFilter>();
filter.sharedMesh = mesh;
}
}
6) Auto Generate Floor のあたりで右クリックすると、「生成」が選べるようになっているはずです。ここで「生成」してみます。
正三角形が敷き詰められたフロアができました。
Width と Height と Scale を変更すると、大きさが変更できます。
あまり大きい値を入れるとエラーになります。
2. パーリンノイズで頂点を上下させます。
サンプル中の頂点 yを、パーリンノイズで変化させます。
5箇所書き換えます。
vertices[p].y = 0f;
// ↓
vertices[p].y = Mathf.PerlinNoise(vertices[p].x, vertices[p].z) * scale;
「実行」するとちょっとモリっとします。
うーん、イマイチ。周期と振幅のせいだろうとあたりをつけ、
private void makeGround() の前に、周期と振幅を追加します。
public float wave = 0.05f;
public float peak = 20f;
この周期と振幅をつかって、パーリンノイズの効き具合を調整します。
vertices[p].y = Mathf.PerlinNoise(vertices[p].x, vertices[p].z) * scale;
// ↓
vertices[p].y = Mathf.PerlinNoise(vertices[p].x * wave, vertices[p].z * wave) * peak;
なるほど、なるほど……「パーリンノイズ」らしくはなりました。
でも、まだまだ全然「ゲームに使えそう」ではありません。
3.ランダムの調整
乱数を制するものは、ゲ制(ゲーム制作)を制す!
ということで、ここであきらめてはおもしろくありませんし、なんとなくイケる予感もするので、ひとくふうしてみました。
山のなかに、「道」を作ってみます。
山の中腹をフラットにして、道をつくるメソッドを追加します。
float groundHeight(float x, float z)
{
float y;
y = Mathf.PerlinNoise(x * wave, z * wave) * peak;
// 中間フラットにする。
if (y > (peak * 0.50f))
{
if (y < (peak * 0.65f))
{
y = peak * 0.50f;
}
else
{
y -= peak * 0.15f;
}
}
return y;
}
そして、また5箇所書き換えます。
vertices[p].y = Mathf.PerlinNoise(vertices[p].x * wave, vertices[p].z * wave) * peak;
// ↓
vertices[p].y = groundHeight(vertices[p].x, vertices[p].z);
おお、なんとなく、なんとなくイケる気がしてきました。
というところから、以下の修正を加えました。
・UVを貼れるようにした
・パーリンノイズの周期を3重にし、道を2段にした
・乱数とシードの追加。乱数は地形生成完了ごとに変更される。なので、同じ乱数シードからは同じ地形がつくられる。
・作った地形を保存する機能を追加した。メッシュの保存から保存できる。
4.スクリプトのダウンロードと使い方
以下からダウンロードして試してみることができます。
1) Unityのエディタ上で Hierarchy > Create Empty で Game Object を作ります。
2) Game Object に Add Compornent で Mesh > Mesh Filter と、Mesh > Mesh Renderer を追加します。
3) ダウンロードしたスクリプトを Project に放り込み、Game Object の Add Compornent から追加します。
4) Auto Generate Floor のあたりで右クリックすると、「生成」が選べるようになっているはずです。ここで「生成」できます。
5) MeshCollider を追加すれば、地形の上を歩くこともできます。
6) Static にすれば、NavMeshをつけることもできました。
7) マテリアルをわりあてます。
手頃なマテリアルに心当たりがない場合は、Unity社から提供されている Standard Assets をインポートして、Environment > TerrainAssets > SurfaceTexture にある、Criff Albedo Specular または GrassRock Albedo Specular をアサインするとよいでしょう。
マップの大きさにもよりますが、Tiling を X、Y ともに40など大きい値にすると、自然な感じになります。
なお、本スクリプトは特に使用制限はありませんので、ご自由にお使いください。バグがあった場合はごめんなさい。(こっそり教えてください)
5.サンプル画像・動画
空に、超オススメのアセット Mewlist さんの Massive Clouds を使ってみます。そして、地形テクスチャはそのへんにあったもの(自作)を貼ってみました。
パラメータ次第で、意外と表情がかわるかもしれません。
また、興味がございましたら、ぜひスクリプトを改変して、固定にしてある道を作る幅のパラメータをかえたり、敵やアイテムを自動生成などにチャレンジしてみてください。
6.ダンジョン用に改造
現在は、天井をつけてダンジョン用に改造しています。
そのうち公開すると思いますので、そのときはよろしくおねがいしますね。