Unity 随机地形生成(Terrain 组件)

效果图:


噪声生成
创建 Noise.cs 脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class Noise
{
public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, int seed, float scale, int octaves, float persistance, float lacunarity, Vector2 offset)
{
float[,] noiseMap = new float[mapWidth, mapHeight];
System.Random prng = new System.Random(seed);
Vector2[] octavesOffset = new Vector2[octaves];
for (int i = 0; i < octaves; i++)
{
float offsetX = prng.Next(-100000, 100000) + offset.x;
float offsetY = prng.Next(-100000, 100000) + offset.y;
octavesOffset[i] = new Vector2(offsetX, offsetY);
}
if (scale <= 0)
{
scale = 0.0001f;
}
float maxNoiseHeight = float.MinValue;
float minNoiseHeight = float.MaxValue;
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
float amplitude = 1;
float frequency = 1;
float noiseHeight = 0;
for (int i = 0; i < octaves; i++)
{
float sampleX = (x + offset.x) / scale * frequency + octavesOffset[i].x;
float sampleY = (y + offset.y) / scale * frequency + octavesOffset[i].y;
float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
noiseHeight += perlinValue * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
}
if (noiseHeight > maxNoiseHeight)
{
maxNoiseHeight = noiseHeight;
}
else if (noiseHeight < minNoiseHeight)
{
minNoiseHeight = noiseHeight;
}
noiseMap[x, y] = noiseHeight;
}
}
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
noiseMap[x, y] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
}
}
return noiseMap;
}
}
地形显示
创建 TerrainDisplay.cs 脚本 用于创建 Terrain 对象
terrainName:生成的游戏对象名称
terrainMaterial:生成的 Terrain 使用的材质
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainDisplay : MonoBehaviour
{
// 地形名称
public string terrainName = "Terrain";
// 地形材质
public Material terrainMaterial;
/// <summary>
/// 创建 Terrain
/// </summary>
public void CreateTerrain(TerrainData terrainData)
{
// 查找游戏对象
GameObject terrainObj = GameObject.Find(this.terrainName);
if (terrainObj == null)
{
// 创建游戏对象
terrainObj = new GameObject(this.terrainName);
terrainObj.name = this.terrainName;
// 添加地形组件
terrainObj.AddComponent<Terrain>();
// 添加地形碰撞体组件
terrainObj.AddComponent<TerrainCollider>();
}
Terrain terrain = terrainObj.GetComponent<Terrain>();
TerrainCollider terrainCollider = terrainObj.GetComponent<TerrainCollider>();
terrain.terrainData = terrainData;
terrainCollider.terrainData = terrainData;
terrain.materialTemplate = terrainMaterial;
}
}
CreateTerrain 的参数传入的是 TerrainData 类型 而上面生成噪声图是 float[,] 二维数组
所以我们还需要对数据进行处理才能使用
地形生成
创建 TerrainGenerator.cs 脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainGenerator : MonoBehaviour
{
public static TerrainData CreateTerrainData(float[,] heightMap, float terrainHeight, int heightmapResolution)
{
TerrainData terrainData = new TerrainData();
int width = heightMap.GetLength(0);
int height = heightMap.GetLength(1);
terrainData.heightmapResolution = heightmapResolution;
terrainData.alphamapResolution = heightmapResolution - 1;
terrainData.size = new Vector3(width, terrainHeight, height);
terrainData.SetHeights(0, 0, heightMap);
return terrainData;
}
}
接下来我们调用上面两个方法来生成地形
修改 TerrainGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainGenerator : MonoBehaviour
{
// 高度图分辨率
public enum HeightmapResolution
{
h33 = 33,
h65 = 65,
h129 = 129,
h257 = 257,
h513 = 513,
h1025 = 1025,
h2049 = 2049,
h4097 = 4097,
}
public HeightmapResolution heightmapResolution = HeightmapResolution.h33;
// 缩放
public float scale;
// 抵消
public int octaves;
// 持续
[Range(0, 1)]
public float persistance;
// 空隙
public float lacunarity;
// 种子
public int seed;
// 偏移
public Vector2 offset;
// 自动更新
public bool autoUpdate;
// 地形高度
public float terrainHeight = 1;
public void GenerateMap()
{
TerrainDisplay display = FindObjectOfType<TerrainDisplay>();
float[,] noiseMap = Noise.GenerateNoiseMap((int)heightmapResolution,
(int)heightmapResolution, seed, scale, octaves, persistance, lacunarity, offset);
display.CreateTerrain(CreateTerrainData(noiseMap, terrainHeight, (int)heightmapResolution));
}
public static TerrainData CreateTerrainData(float[,] heightMap, float terrainHeight, int heightmapResolution)
{
TerrainData terrainData = new TerrainData();
int width = heightMap.GetLength(0);
int height = heightMap.GetLength(1);
terrainData.heightmapResolution = heightmapResolution;
terrainData.alphamapResolution = heightmapResolution - 1;
terrainData.size = new Vector3(width, terrainHeight, height);
terrainData.SetHeights(0, 0, heightMap);
return terrainData;
}
}
现在地形生成部分就完成了
接下来我们还需要实现一个 点击按钮 生成地形的功能
创建 TerrainEditor.cs 脚本
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof (TerrainGenerator))]
public class TerrainEditor : Editor
{
public override void OnInspectorGUI()
{
TerrainGenerator terrainGenerator = (TerrainGenerator)target;
if (DrawDefaultInspector())
{
if (terrainGenerator.autoUpdate)
{
terrainGenerator.GenerateMap();
}
}
if (GUILayout.Button("Generate"))
{
terrainGenerator.GenerateMap();
}
}
}
地形生成测试
添加 空游戏对象命名为 Map Generator

将刚才编写的这两个脚本添加到对象上

调整面板参数

点击 Generate 按钮即可生成地形

纹理绘制
想要在 Terrain 上绘制纹理需要在 Terrain 中添加 Layer

在绘制纹理前 我们先实现 添加 Layer 的功能
TerrainDisplay.cs 添加代码
[System.Serializable]
public struct TerrainLayerInfo
{
public TerrainLayer terrainLayer;
[Range(0, 1)]
public float layerMaxHeight;
[Range(0, 1)]
public float layerMinHeight;
}
添加到最后即可

TerrainDisplay.cs 添加字段
public TerrainLayerInfo[] terrainLayerInfos;
TerrainDisplay.cs 中添加方法
public void TerrainLayerGenerator(Terrain terrain)
{
TerrainLayer[] terrainLayers = new TerrainLayer[terrainLayerInfos.Length];
for (int i = 0; i < terrainLayerInfos.Length; i++)
{
terrainLayers[i] = terrainLayerInfos[i].terrainLayer;
}
terrain.terrainData.terrainLayers = terrainLayers;
}

在脚本中添加 Layer 并点击 Generate 测试,可以看见地形已经绘制上纹理了

接下来实现基于高度绘制不同的纹理的功能
TerrainDisplay.cs 添加代码
public void TerrainLayerAlphaGenerator(Terrain terrain, float[,,] terrainAlphamp, float heightmapResolution)
{
float[,] terrainHeightMap = terrain.terrainData.GetHeights(0, 0, (int)heightmapResolution, (int)heightmapResolution);
for (int layerIndex = 0; layerIndex < terrainAlphamp.GetLength(2); layerIndex++)
{
for (int y = 0; y < terrainAlphamp.GetLength(1); y++)
{
for (int x = 0; x < terrainAlphamp.GetLength(0); x++)
{
if (terrainHeightMap[x, y] <= terrainLayerInfos[layerIndex].layerMaxHeight &&
terrainHeightMap[x, y] >= terrainLayerInfos[layerIndex].layerMinHeight)
{
terrainAlphamp[x, y, layerIndex] = 1;
}
else
{
terrainAlphamp[x, y, layerIndex] = 0;
}
}
}
}
terrain.terrainData.SetAlphamaps(0, 0, terrainAlphamp);
}
CreateTerrain 方法中添加
float[,,] alphamaps = terrain
.terrainData
.GetAlphamaps(0, 0, terrain.terrainData.alphamapWidth, terrain.terrainData.alphamapHeight);
TerrainLayerAlphaGenerator(terrain,alphamaps,terrainData.heightmapResolution);

纹理绘制测试
添加 Layer 并点击 Generate 纹理成功绘制

参考视频:https://www.bilibili.com/video/BV1sJ411e7nt/?share_source=copy_web&vd_source=3ff483a8930d34f7fca4f473c89b4c15
