地形生成技术(三):自定义地形编辑器

Number of views 210

在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间关系的理解。虽然目前来看还没有实际用途,未完待续。

0 Answers