在Unity中新建地形与脚本,并命名脚本为为CustomTerrain.cs。
把CustomTerrain.cs作为组件挂载至Terrain节点中。
为CustomTerrain添加如下代码:
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[ExecuteInEditMode]
public class CustomTerrain : MonoBehaviour
{
// 生成地形高度范围,默认0~0.1间
public Vector2 randomHeightRange = new Vector2(0, 0.1f);
public Terrain terrain;
// 存储着纹理与其它细节数据
public TerrainData terrainData;
private void OnEnable()
{
Debug.Log("Initialising Terrain Data");
terrain = GetComponent<Terrain>();
terrainData = Terrain.activeTerrain.terrainData;
}
// 随机生成地形高度实际算法
public void RandomTerrain()
{
float[,] heightMap;
heightMap = new float[terrainData.heightmapResolution, terrainData.heightmapResolution];
for(int x = 0; x < terrainData.heightmapResolution; x++)
{
for(int y = 0; y < terrainData.heightmapResolution; y++)
{
// 现在生成的高度范围为randomHeightRange所设置的值
heightMap[x, y] = Random.Range(randomHeightRange.x, randomHeightRange.y);
}
}
// 重新赋值至地形中
terrainData.SetHeights(0, 0, heightMap);
}
private void Awake()
{
// 标签管理器是从AssetDatabase加载,给定Path加载指定资源
SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
SerializedProperty tagsProp = tagManager.FindProperty("tags");
// 对标签管理器添加新的标签
AddTag(tagsProp, "Terrain");
AddTag(tagsProp, "Cloud");
AddTag(tagsProp, "Shore");
// 刷新 新的tag数据到AssetDatabase中
tagManager.ApplyModifiedProperties();
gameObject.tag = "Terrain";
}
void AddTag(SerializedProperty tagsProp, string tagName)
{
bool found = false;
// 遍历标签列表,以判断是否存在想要新增的tag
for(int i = 0; i < tagsProp.arraySize; i++)
{
SerializedProperty t = tagsProp.GetArrayElementAtIndex(i);
if(t.stringValue.Equals(tagName))
{
found = true;
break;
}
}
// 如果没有找到同名tag,则新增
if(!found)
{
tagsProp.InsertArrayElementAtIndex(0);
SerializedProperty newTagProp = tagsProp.GetArrayElementAtIndex(0);
newTagProp.stringValue = tagName;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
RandomTerrain函数是整个算法的核心,目前没有额外复杂的算法,很简单,就是在一个四边形地形中遍历heightmapResolution(为什么不是遍历地形本身的分辨率,而是heightMap分辨率这里不做说明,可以回看地形生成技术(一)),对每个地形顶点进行随机生成,生成范围取决于randomHeightRange中的值,生成后的值刷新数据给回地形,地形根据实际的物理高度(比如500)乘上该范围值,如范围值为0.1,那么生成的实际高度值即为50。
回到编辑器,可以看到Terrain节点的Tag更改为了"Terrain":
为了更大限度的自定义我们自己的地形编辑器,可以新建Editor/CustomTerrainEditor.cs。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using EditorGUITable;
[CustomEditor(typeof(CustomTerrain))]
[CanEditMultipleObjects]
public class CustomTerrainEditor : Editor
{
SerializedProperty randomHeightRange;
// 树形三角
bool showRandom = false;
private void OnEnable()
{
randomHeightRange = serializedObject.FindProperty("randomHeightRange");
}
public override void OnInspectorGUI()
{
// 更新所有序列化脚本的值
serializedObject.Update();
CustomTerrain customTerrain = target as CustomTerrain;
showRandom = EditorGUILayout.Foldout(showRandom, "Randoms");
// 是否展开Randoms选项
if (showRandom) {
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
GUILayout.Label("Set Heights Between Random Valus", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(randomHeightRange);
if(GUILayout.Button("Random Heights"))
{
// 生成随机高度
customTerrain.RandomTerrain();
}
}
serializedObject.ApplyModifiedProperties();
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
此时CustomTerrain组件的编辑界面如下:
我们通过点击Random Heights按钮,即可调用CustomTerrain的RandomTerrain函数,此时场景生成了这么个地形画面:
这是?武侠小说的钉子床!为了让地形能够显得不那么尖锐,此时可以有两个做法,一是更改TerrainHeight的物理高度,另一个就是调整Random Height Range的Y值,我们选择调整Random Height Range的Y值为0.01:
添加图片注释,不超过 140 字(可选)
现在已经好很多了,甚至有点杂乱美。好吧,尽管很无趣,但是这就是我们用程序生成的第一个地形。我们还是可以用地形内置工具对它修修剪剪。
可以继续修改RandomTerrain函数,并新增重置地形的方法。修改后的RandomTerrain函数整体功能不变,只是需要在原有地形的基础上加上随机范围值:
public void ResetTerrain()
{
float[,] heightMap = new float[terrainData.heightmapResolution, terrainData.heightmapResolution];
terrainData.SetHeights(0, 0, heightMap);
}
// 随机生成地形高度实际算法
public void RandomTerrain()
{
float[,] heightMap = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
for(int x = 0; x < terrainData.heightmapResolution; x++)
{
for(int y = 0; y < terrainData.heightmapResolution; y++)
{
// 现在生成的高度范围比值为randomHeightRange所设置的值
heightMap[x, y] += Random.Range(randomHeightRange.x, randomHeightRange.y);
}
}
// 重新赋值至地形中
terrainData.SetHeights(0, 0, heightMap);
}
该篇通过随机算法来简单实现程序生成地形的功能,只是为了加深对地形接口与HeightMap间关系的理解。虽然目前来看还没有实际用途,未完待续。