以下类实现了在Unity中动态的修改Terrain的功能,可以在运行时升高、降低以及平滑地形高度。在Unity的Play Mode修改地形后退出Play Mode仍然会保留修改;当游戏打包成独立的可执行文件后退出游戏则不能保留对地形的修改,需要手动将地形数据序列化保存,下次启动时重新赋值。

方法概述(省略了参数):

  • int[] GetHeightmapIndex() :返回Terrain上某点在HeightMap中的索引。
  • Vector3 GetRelativePosition() :返回Terrain上的GameObject与Terrain在世界坐标下的相对位置。
  • float GetPointHeight() :返回Terrain上指定的点在世界坐标系下的高度。
  • float[,] GetHeightMap() :返回Terrain的HeightMap。
  • void Rise() :升高地形。
  • void Sink() :降低地形。
  • void Smooth() :平滑地形。
  • void Flatten() :压平地形并提升到指定高度。
  • void SetHeights() :将指定的HeightMap设置给Terrain。

源代码

using UnityEngine;public class TerrainUtil
{/** * Terrain的HeightMap坐标原点在左下角*   y*   ↑*   0 → x*//// <summary>/// 返回Terrain上某一点的HeightMap索引。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的某点</param>/// <returns>该点在HeightMap中的位置索引</returns>public static int[] GetHeightmapIndex(Terrain terrain, Vector3 point){TerrainData tData = terrain.terrainData;float width = tData.size.x;float length = tData.size.z;// 根据相对位置计算索引int x = (int)((point.x - terrain.GetPosition().x) / width * tData.heightmapWidth);int y = (int)((point.z - terrain.GetPosition().z) / length * tData.heightmapHeight);return new int[2] { x, y };}/// <summary>/// 返回GameObject在Terrain上的相对(于Terrain的)位置。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="go">GameObject</param>/// <returns>相对位置</returns>public static Vector3 GetRelativePosition(Terrain terrain, GameObject go){return go.transform.position - terrain.GetPosition();}/// <summary>/// 返回Terrain上指定点在世界坐标系下的高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的某点</param>/// <param name="vertex">true: 获取最近顶点高度  false: 获取实际高度</param>/// <returns>点在世界坐标系下的高度</returns>public static float GetPointHeight(Terrain terrain, Vector3 point, bool vertex = false){// 对于水平面上的点来说,vertex参数没有影响if (vertex){// GetHeight得到的是离点最近的顶点的高度int[] index = GetHeightmapIndex(terrain, point);return terrain.terrainData.GetHeight(index[0], index[1]);}else{// SampleHeight得到的是点在斜面上的实际高度return terrain.SampleHeight(point);}}/// <summary>/// 返回Terrain的HeightMap,这是一个 height*width 大小的二维数组,并且值介于 [0.0f,1.0f] 之间。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="xBase">检索HeightMap时的X索引起点</param>/// <param name="yBase">检索HeightMap时的Y索引起点</param>/// <param name="width">在X轴上的检索长度</param>/// <param name="height">在Y轴上的检索长度</param>/// <returns></returns>public static float[,] GetHeightMap(Terrain terrain, int xBase = 0, int yBase = 0, int width = 0, int height = 0){if (xBase + yBase + width + height == 0){width = terrain.terrainData.heightmapWidth;height = terrain.terrainData.heightmapHeight;}return terrain.terrainData.GetHeights(xBase, yBase, width, height);}/// <summary>/// 升高Terrain上某点的高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的点</param>/// <param name="opacity">升高的高度</param>/// <param name="size">笔刷大小</param>/// <param name="amass">当笔刷范围内其他点的高度已经高于笔刷中心点时是否同时提高其他点的高度</param>public static void Rise(Terrain terrain, Vector3 point, float opacity, int size, bool amass = true){int[] index = GetHeightmapIndex(terrain, point);Rise(terrain, index, opacity, size, amass);}/// <summary>/// 升高Terrain上的某点。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="index">HeightMap索引</param>/// <param name="opacity">升高的高度</param>/// <param name="size">笔刷大小</param>/// <param name="amass">当笔刷范围内其他点的高度已经高于笔刷中心点时是否同时提高其他点的高度</param>public static void Rise(Terrain terrain, int[] index, float opacity, int size, bool amass = true){TerrainData tData = terrain.terrainData;int bound = size / 2;int xBase = index[0] - bound >= 0 ? index[0] - bound : 0;int yBase = index[1] - bound >= 0 ? index[1] - bound : 0;int width = xBase + size <= tData.heightmapWidth ? size : tData.heightmapWidth - xBase;int height = yBase + size <= tData.heightmapHeight ? size : tData.heightmapHeight - yBase;float[,] heights = tData.GetHeights(xBase, yBase, width, height);float initHeight = tData.GetHeight(index[0], index[1]) / tData.size.y;float deltaHeight = opacity / tData.size.y;// 得到的heights数组维度是[height,width],索引为[y,x]ExpandBrush(heights, deltaHeight, initHeight, height, width, amass);tData.SetHeights(xBase, yBase, heights);}/// <summary>/// 降低Terrain上某点的高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的点</param>/// <param name="opacity">降低的高度</param>/// <param name="size">笔刷大小</param>/// <param name="amass">当笔刷范围内其他点的高度已经低于笔刷中心点时是否同时降低其他点的高度</param>public static void Sink(Terrain terrain, Vector3 point, float opacity, int size, bool amass = true){int[] index = GetHeightmapIndex(terrain, point);Sink(terrain, index, opacity, size, amass);}/// <summary>/// 降低Terrain上某点的高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="index">HeightMap索引</param>/// <param name="opacity">降低的高度</param>/// <param name="size">笔刷大小</param>/// <param name="amass">当笔刷范围内其他点的高度已经低于笔刷中心点时是否同时降低其他点的高度</param>public static void Sink(Terrain terrain, int[] index, float opacity, int size, bool amass = true){TerrainData tData = terrain.terrainData;int bound = size / 2;int xBase = index[0] - bound >= 0 ? index[0] - bound : 0;int yBase = index[1] - bound >= 0 ? index[1] - bound : 0;int width = xBase + size <= tData.heightmapWidth ? size : tData.heightmapWidth - xBase;int height = yBase + size <= tData.heightmapHeight ? size : tData.heightmapHeight - yBase;float[,] heights = tData.GetHeights(xBase, yBase, width, height);float initHeight = tData.GetHeight(index[0], index[1]) / tData.size.y;float deltaHeight = -opacity / tData.size.y;  // 注意负号// 得到的heights数组维度是[height,width],索引为[y,x]ExpandBrush(heights, deltaHeight, initHeight, height, width, amass);tData.SetHeights(xBase, yBase, heights);}/// <summary>/// 根据笔刷四角的高度来平滑Terrain,该方法不会改变笔刷边界处的Terrain高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="point">Terrain上的点</param>/// <param name="opacity">平滑灵敏度,值介于 [0.05,1] 之间</param>/// <param name="size">笔刷大小</param>public static void Smooth(Terrain terrain, Vector3 point, float opacity, int size){int[] index = GetHeightmapIndex(terrain, point);Smooth(terrain, index, opacity, size);}/// <summary>/// 根据笔刷四角的高度来平滑Terrain,该方法不会改变笔刷边界处的Terrain高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="index">HeightMap索引</param>/// <param name="opacity">平滑灵敏度,值介于 [0.05,1] 之间</param>/// <param name="size">笔刷大小</param>public static void Smooth(Terrain terrain, int[] index, float opacity, int size){TerrainData tData = terrain.terrainData;if (opacity > 1 || opacity <= 0){opacity = Mathf.Clamp(opacity, 0.05f, 1);Debug.LogError("Smooth方法中的opacity参数的值应该介于 [0.05,1] 之间,强制将其设为:" + opacity);}// 取出笔刷范围内的HeightMap数据数组int bound = size / 2;int xBase = index[0] - bound >= 0 ? index[0] - bound : 0;int yBase = index[1] - bound >= 0 ? index[1] - bound : 0;int width = xBase + size <= tData.heightmapWidth ? size : tData.heightmapWidth - xBase;int height = yBase + size <= tData.heightmapHeight ? size : tData.heightmapHeight - yBase;float[,] heights = tData.GetHeights(xBase, yBase, width, height);// 利用笔刷4角的高度来计算平均高度float avgHeight = (heights[0, 0] + heights[0, width - 1] + heights[height - 1, 0] + heights[height - 1, width - 1]) / 4;Vector2 center = new Vector2((float)(height - 1) / 2, (float)(width - 1) / 2);for (int i = 0; i < height; i++){for (int j = 0; j < width; j++){// 点到矩阵中心点的距离float toCenter = Vector2.Distance(center, new Vector2(i, j));float diff = avgHeight - heights[i, j];// 判断点在4个三角形区块上的位置// 利用相似三角形求出点到矩阵中心点与该点连线的延长线与边界交点的距离float d = 0;if (i == height / 2 && j == width / 2)  // 中心点{d = 1;toCenter = 0;}else if (i >= j && i <= size - j)  // 左三角区{// j/((float)width / 2) = d/(d+toCenter),求出距离d,其他同理d = toCenter * j / ((float)width / 2 - j);}else if (i <= j && i <= size - j)  // 上三角区{d = toCenter * i / ((float)height / 2 - i);}else if (i <= j && i >= size - j)  // 右三角区{d = toCenter * (size - j) / ((float)width / 2 - (size - j));}else if (i >= j && i >= size - j)  // 下三角区{d = toCenter * (size - i) / ((float)height / 2 - (size - i));}// 进行平滑时对点进行升降的比例float ratio = d / (d + toCenter);heights[i, j] += diff * ratio * opacity;}}tData.SetHeights(xBase, yBase, heights);}/// <summary>/// 压平Terrain并提升到指定高度。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="height">高度</param>public static void Flatten(Terrain terrain, float height){TerrainData tData = terrain.terrainData;float scaledHeight = height / tData.size.y;float[,] heights = new float[tData.heightmapWidth, tData.heightmapHeight];for (int i = 0; i < tData.heightmapWidth; i++){for (int j = 0; j < tData.heightmapHeight; j++){heights[i, j] = scaledHeight;}}tData.SetHeights(0, 0, heights);}/// <summary>/// 设置Terrain的HeightMap。/// </summary>/// <param name="terrain">Terrain</param>/// <param name="heights">HeightMap</param>/// <param name="xBase">X起点</param>/// <param name="yBase">Y起点</param>public static void SetHeights(Terrain terrain, float[,] heights, int xBase = 0, int yBase = 0){terrain.terrainData.SetHeights(xBase, yBase, heights);}// TODO // public static void SaveHeightmapData(Terrain terrain, string path) {}/// <summary>/// 扩大笔刷作用范围。/// </summary>/// <param name="heights">HeightMap</param>/// <param name="deltaHeight">高度变化量[-1,1]</param>/// <param name="initHeight">笔刷中心点的初始高度</param>/// <param name="row">HeightMap行数</param>/// <param name="column">HeightMap列数</param>/// <param name="amass">当笔刷范围内其他点的高度已经高于笔刷中心点时是否同时提高其他点的高度</param>private static void ExpandBrush(float[,] heights, float deltaHeight, float initHeight, int row, int column, bool amass){// 高度限制float limit = initHeight + deltaHeight;for (int i = 0; i < row; i++){for (int j = 0; j < column; j++){if (amass) { heights[i, j] += deltaHeight; }else  // 不累加高度时{if (deltaHeight > 0)  // 升高地形{heights[i, j] = heights[i, j] >= limit ? heights[i, j] : heights[i, j] + deltaHeight;}else  // 降低地形{heights[i, j] = heights[i, j] <= limit ? heights[i, j] : heights[i, j] + deltaHeight;}}}}}#region 弃用的旧方法/*public static*/[System.Obsolete]void Rise_Old(Terrain terrain, int[] index, float opacity, int size, bool amass = true){if (index.Length != 2){Debug.LogError("参数错误!");return;}TerrainData tData = terrain.terrainData;// heights中存储的是顶点高度,不是斜面的准确高度float[,] heights = tData.GetHeights(0, 0, tData.heightmapWidth, tData.heightmapHeight);float deltaHeight = opacity / tData.size.y;ExpandBrush_Old(heights, index, deltaHeight, size, amass, tData.heightmapWidth, tData.heightmapHeight);tData.SetHeights(0, 0, heights);}/*private static*/[System.Obsolete]void ExpandBrush_Old(float[,] heights, int[] index, float deltaHeight, int size, bool amass, int xMax, int yMax){float limit = heights[index[0], index[1]] + deltaHeight;int bound = size / 2;for (int offsetX = -bound; offsetX <= bound; offsetX++){int x = index[0] + offsetX;if (x < 0 || x > xMax) continue;for (int offsetY = -bound; offsetY <= bound; offsetY++){int y = index[1] + offsetY;if (y < 0 || y > yMax) continue;if (amass){heights[x, y] += deltaHeight;}else{if (deltaHeight > 0){// 升高地形heights[x, y] = heights[x, y] >= limit ? heights[x, y] : heights[x, y] + deltaHeight;}else{// 降低地形heights[x, y] = heights[x, y] <= limit ? heights[x, y] : heights[x, y] + deltaHeight;}}}}// 平滑方程:y = (cos(x) + 1) / 2;//float rad = 180.0f * (smooth / 9) * Mathf.Deg2Rad;//float height = (Mathf.Cos(rad) + 1) / 2;}#endregion
}

Unity中动态修改Terrain地形相关推荐

  1. linux 软件集成工具箱,在PB中动态修改SQL语句

    在PB中动态修改SQL语句 分享到: 江苏省南通电信局网管中心 黄莹 ---- PowerBuilder是图形界面的Client/Server应用程序开发环境,可以很容易开发出功能强大的应用程序,在当 ...

  2. Unity使用c#开发遇上的问题(六)(3dmax围绕指定中心旋转,unity中动态调用预制体并根据模型旋转指定角度)

    文章目录 前言 一.3dmax创建子弹.炮塔及武器库 1.相关模型 2.炮塔模型引入unity,无法绕旋转球旋转,重新调整 1.3dmax中默认炮管的中心点 2.选择层次界面 3.选择编辑工作轴 4. ...

  3. Pyqt5在程序中动态修改多界面的语言(英语转中文或者中文转英语)

    继上次写完<如何用Pyqt5实现在程序中动态修改界面的语言(英语转中文或者中文转英语)>一文后,有的朋友提出希望在多个界面中实现动态修改界面语言,而我上次写的文章只能实现主界面的动态语言的 ...

  4. [专栏精选]Unity中动态构建NavMesh

    本文节选自洪流学堂公众号专栏<郑洪智的Unity2018课>,未经允许不可转载. 洪流学堂公众号回复专栏,查看更多专栏文章. 小新:"Unity内置的Navigation系统是不 ...

  5. Unity中使用插件在地形中制作道路

    这是一款非常方便的的插件,在平时我们想要在地形中创建道路可能还需要有模型然后在放到道路上面,通过这款插件,我们可以非常便捷的就制作出道路.下面一起来看看这款插件吧. 插件的下载会在文章结尾给出 是Pr ...

  6. 在Unity中 改变地形(Terrain),并加上水面、树、草地、材质(地板上色)

    在Unity中 如何使用地形(Terrain),并加上水面.树.草地.材质(地板上色) 目录 在Unity中 如何使用地形(Terrain),并加上水面.树.草地.材质(地板上色) 一.水面素材包 导 ...

  7. uniapp中动态修改导航栏标题

    在我们日常开发过程中,往往会用到根据各种条件去动态改变顶部标题,在不自定义顶部标题的情况下,可以使用下面这种方法来实现动态修改 uni.setNavigationBarTitle({title: '修 ...

  8. js中动态修改frame的src属性,frame自己刷新。。。

    弄了半天,结果画蛇添足...:动态修改frame的src属性,然后parent.frame.location.reload()反而错误... 无须自己手工来进行reload() frame会因为src ...

  9. Unity动态编辑Terrain地形(四)植被编辑

    **** 完整代码我已经上传到了我的Github上,需要的话可以直接去下载https://github.com/xdedzl/RunTimeTerrainEditor,里面有一个TerrainModi ...

最新文章

  1. Golang之单元测试
  2. 信息系统项目管理师:第8章:项目质量管理-章节真题+解析
  3. 安装linux无驱动黑屏,ubuntu16.04安装黑屏与显卡安装笔记
  4. php curl_error源码,PHP curl_error函数
  5. matlab的和操作
  6. 【java】创建一个JFrame,可以使得一个字符串用按钮进行颜色的选择
  7. 数电-汽车尾灯控制电路设计
  8. matlab绘图和python绘图
  9. vue的基础总结(vue的非脚手架总结)
  10. 引用百度新闻热门搜索html,百度新闻搜索技巧(之一)
  11. 易企秀怎么转换成html5,易企秀怎么免费制作h5?
  12. Python实现PDF(图片版)水印的去除
  13. 路由交换技术实战七 FR 网络中配置 OSPF( 完成版 )
  14. 计算机输入法如何显示在桌面快捷方式,在桌面显示/隐藏输入法及输入法热键的设置...
  15. HDU 5984 Pocky
  16. 初学者都能看懂的MYSQL索引基础
  17. linux acpidtd 进程,贴子里说明很详细,求帮助,卡在DSMOS has arrived,ACPI_SMC_PlatformPlugin::...
  18. Vue3学习笔记:了解并使用Pinia状态管理
  19. 机器视觉毕业设计 Python图像拼接算法研究与实现 - opencv
  20. 上海华腾软件系统有限公司怎么样

热门文章

  1. yjv是电缆还是电线_yjv电缆是硬线还是软线
  2. iOS - RunLoop 深入理解
  3. 免费的天气预报webservice接口
  4. Postfix+dovecot 部署
  5. 在vue中使用图表组件工具echarts(一)
  6. “子域”和“限界上下文”
  7. Ionic实现微信、qq、微博分享
  8. Nacos学习二(nacos安装)
  9. 手机综合测试仪 CUM
  10. KBQA的主要流程及部分Top竞赛方案总结