Unity中如何实现拖拽操作生成地形而非普通游戏对象?
地形生成与普通游戏对象生成的核心差异
- 底层逻辑不同:普通游戏对象生成是实例化独立的GameObject资源,自带完整的组件、变换属性。Unity内置Terrain是基于TerrainData资源驱动的组件,修改地形本质是修改TerrainData存储的高度图、纹理混合、细节植被等数组数据,而非频繁实例化新的Terrain对象。
- 性能逻辑不同:单块Terrain会自动做网格合批、LOD优化,若按普通Prefab的思路每拖拽一次生成一块新Terrain,会快速升高DrawCall导致卡顿。大部分拖拽生成地形的需求都基于修改现有Terrain的高度数据实现,仅大世界流式加载场景才会分块实例化Terrain。
- 坐标映射逻辑不同:普通Prefab实例化可直接传入世界坐标赋值位置。修改地形需要先将鼠标的屏幕坐标转世界坐标,再换算为Terrain局部空间的相对坐标,最终映射到高度图的像素索引才能做修改。
拖拽修改地形的实现方案
不需要将代码挂载到Terrain对象上,你可以将脚本挂载到任意全局常驻对象上,也可以直接挂载到Terrain对象上,参考代码如下:
using UnityEngine; public class DragModifyTerrain : MonoBehaviour { public Terrain targetTerrain; // Inspector中拖入要修改的地形对象 public int brushSize = 5; // 笔刷大小,单位是高度图像素 public float heightDelta = 0.01f; // 每次拖拽的抬升幅度 private TerrainData _terrainData; private int _heightmapResolution; void Start() { if (targetTerrain == null) { targetTerrain = GetComponent<Terrain>(); } _terrainData = targetTerrain.terrainData; _heightmapResolution = _terrainData.heightmapResolution; } void Update() { // 按住鼠标左键拖拽时触发地形修改 if (Input.GetMouseButton(0)) { // 射线检测获取鼠标指向的地形世界坐标 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit)) { // 将世界坐标转换为地形局部的相对坐标 Vector3 terrainLocalPos = hit.point - targetTerrain.transform.position; // 换算为高度图对应的UV坐标 float normalizedX = terrainLocalPos.x / _terrainData.size.x; float normalizedZ = terrainLocalPos.z / _terrainData.size.z; // 换算高度图的像素索引 int heightmapX = Mathf.RoundToInt(normalizedX * _heightmapResolution); int heightmapZ = Mathf.RoundToInt(normalizedZ * _heightmapResolution); // 避免索引越界 int startX = Mathf.Clamp(heightmapX - brushSize / 2, 0, _heightmapResolution - 1); int endX = Mathf.Clamp(heightmapX + brushSize / 2, 0, _heightmapResolution - 1); int startZ = Mathf.Clamp(heightmapZ - brushSize / 2, 0, _heightmapResolution - 1); int endZ = Mathf.Clamp(heightmapZ + brushSize / 2, 0, _heightmapResolution - 1); // 获取对应区域的高度数组,修改后写回 float[,] heights = _terrainData.GetHeights(startX, startZ, endX - startX, endZ - startZ); for (int x = 0; x < endX - startX; x++) { for (int z = 0; z < endZ - startZ; z++) { heights[z, x] += heightDelta; } } _terrainData.SetHeights(startX, startZ, heights); } } } }
配置说明
- 若脚本挂载在Terrain对象上,
targetTerrain会自动读取,无需手动赋值;若挂载在其他对象上,需要在Inspector中将目标地形拖入对应字段。 - 调整
brushSize控制笔刷范围,调整heightDelta控制每次拖拽地形抬升的速度,若填负数则会实现降低地形的效果。
拓展说明
如果你的需求是拖拽生成独立的地形地块而非修改现有地形高度,可以直接将带Terrain组件的地形块做成Prefab,复用你现有的实例化逻辑即可,额外需要注意相邻地形块的高度图接缝对齐,避免出现高度断层。
内容的提问来源于stack exchange,提问作者user11749375
相关产品推荐
相关产品推荐

