Unity 隨機(jī)地形生成(Terrain 組件)

效果圖:


噪聲生成
創(chuàng)建 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;
? ? }
}
地形顯示
創(chuàng)建 TerrainDisplay.cs 腳本 用于創(chuàng)建 Terrain 對(duì)象
terrainName:生成的游戲?qū)ο竺Q
terrainMaterial:生成的 Terrain 使用的材質(zhì)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainDisplay : MonoBehaviour
{
? ? // 地形名稱
? ? public string terrainName = "Terrain";
? ? // 地形材質(zhì)
? ? public Material terrainMaterial;
? ? /// <summary>
? ? /// 創(chuàng)建 Terrain
? ? /// </summary>
? ? public void CreateTerrain(TerrainData terrainData)
? ? {
? ? ? ? // 查找游戲?qū)ο?/p>
? ? ? ? GameObject terrainObj = GameObject.Find(this.terrainName);
? ? ? ? if (terrainObj == null)
? ? ? ? {
? ? ? ? ? ? // 創(chuàng)建游戲?qū)ο?/p>
? ? ? ? ? ? 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 的參數(shù)傳入的是 TerrainData 類型 而上面生成噪聲圖是 ?float[,] 二維數(shù)組
所以我們還需要對(duì)數(shù)據(jù)進(jìn)行處理才能使用
地形生成
創(chuàng)建 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;
? ? }
}
接下來我們調(diào)用上面兩個(gè)方法來生成地形
修改 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;
? ? // 持續(xù)
? ? [Range(0, 1)]
? ? public float persistance;
? ? // 空隙
? ? public float lacunarity;
? ? // 種子
? ? public int seed;
? ? // 偏移
? ? public Vector2 offset;
? ? // 自動(dòng)更新
? ? 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;
? ? }
}
現(xiàn)在地形生成部分就完成了
接下來我們還需要實(shí)現(xiàn)一個(gè) 點(diǎn)擊按鈕 生成地形的功能
創(chuàng)建 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();
? ? ? ? }
? ? }
}
地形生成測(cè)試
添加 空游戲?qū)ο竺麨?Map Generator

將剛才編寫的這兩個(gè)腳本添加到對(duì)象上

調(diào)整面板參數(shù)

點(diǎn)擊 Generate 按鈕即可生成地形

紋理繪制
想要在 Terrain 上繪制紋理需要在 Terrain 中添加 Layer

在繪制紋理前 我們先實(shí)現(xiàn) 添加 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 并點(diǎn)擊 Generate 測(cè)試,可以看見地形已經(jīng)繪制上紋理了

接下來實(shí)現(xiàn)基于高度繪制不同的紋理的功能
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);

紋理繪制測(cè)試
添加 Layer 并點(diǎn)擊 Generate 紋理成功繪制

參考視頻:https://www.bilibili.com/video/BV1sJ411e7nt/?share_source=copy_web&vd_source=3ff483a8930d34f7fca4f473c89b4c15
