项目需要根据判断条件修改场景中的树木和附近的地面贴图。贴图部分比较好实现,修改SplatAlphaMap就可以显示不同层次的纹理。如何修改树木中文资料很少,不过搜索英文发现这个问题已经很好的解决了。下面简单介绍一下解决思路,主要是两个要点:
1、判断需要修改哪棵树的模型。此处只能
直接根据坐标进行判断,不能通过
碰撞体或者射线碰撞直接获得想要更改树木的坐标。因为虽然树木中可以有碰撞体,但是该碰撞体是作为整个terrain碰撞体的一部分存在的,所以碰撞检测不能将树木模型当成独立物体获取它的信息,只能通过treeinstance的坐标来确定想要修改的是哪棵树。
2、树木模型可以添加碰撞体,但是只有mesh和capsule可以正常检测到。没有碰撞体在确定选择的是哪棵树时会有一点问题,比如用鼠标点击时射线会穿过树木模型hit到后面的地形上,获取到的位置就会有一定的偏差,也不能通过场景中的物体进行碰撞判断,但是可以通过修改判断条件触发修改,依然能够获得较为准确的位置。
代码比较简单,不过多解释了,其中需要注意的地方有这么几个:
TreeInstance.position是树木在地形中的相对坐标,范围在(0,1)之间。需要经过转换才可以变为世界坐标。
Vector3.Scale(currentTree.position, terrain.size) + Terrain.activeTerrain.transform.position;
那么怎么得到
TreeInstance
.position呢,只能通过遍历数组
TerrainData
.treeInstances 。该数组保存了地形中所有树木的实例,treeinstance的属性包括position,和prototypeIndex等,主要用这两个。
先介绍下流程,详细看下面代码。首先新建一个列表保存所有树木实例,遍历树木坐标筛选符合条件的树木,修改树木原型索引prototypeIndex,或者销毁该模型并在其位置上实例化想要替换的预制体,比如砍树后的树桩什么的。
2、树木模型可以添加碰撞体,但是只有mesh和capsule可以正常检测到。没有碰撞体在确定选择的是哪棵树时会有一点问题,比如用鼠标点击时射线会穿过树木模型hit到后面的地形上,获取到的位置就会有一定的偏差,也不能通过场景中的物体进行碰撞判断,但是可以通过修改判断条件触发修改,依然能够获得较为准确的位置。
下面直接放代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class TreeChop : MonoBehaviour
{
public GameObject FallingTreePrefab;
private List<TreeInstance> TreeInstances;
// Use this for initialization
private void Start()
{
TreeInstances = new List<TreeInstance>(Terrain.activeTerrain.terrainData.treeInstances);
Debug.Log("Tree Instances:" + TreeInstances.Count);
}
// Update is called once per frame
private void Update()
{
// did we click on a tree?
if (Input.GetMouseButtonDown(0))
{
DateTime start = DateTime.Now;
RaycastHit hit;
// This ray will see where we clicked er chopped
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Did we hit anything at that point, out as far as 10 units?
if (Physics.Raycast(ray, out hit, 10.0f))
{
// Did we even click er chop on the terrain/tree?
if (hit.collider.name != Terrain.activeTerrain.name)
{
// No, must have been a frantic attack on a giant spider >:)
return;
}
// We hit the "terrain"! Now, how high is the ground at that point?
float sampleHeight = Terrain.activeTerrain.SampleHeight(hit.point);
// If the height of the exact point we clicked/chopped at or below ground level, all we did
// was chop dirt.
if (hit.point.y <= sampleHeight + 0.01f)
{
return;
}
TerrainData terrain = Terrain.activeTerrain.terrainData;
TreeInstance[] treeInstances = terrain.treeInstances;
// Our current closest tree initializes to far away
float maxDistance = float.MaxValue;
// Track our closest tree's position
Vector3 closestTreePosition = new Vector3();
// Let's find the closest tree to the place we chopped and hit something
int closestTreeIndex = 0;
for (int i = 0; i < treeInstances.Length; i++)
{
TreeInstance currentTree = treeInstances[i];
// The the actual world position of the current tree we are checking
Vector3 currentTreeWorldPosition = Vector3.Scale(currentTree.position, terrain.size) + Terrain.activeTerrain.transform.position;
// Find the distance between the current tree and whatever we hit when chopping
float distance = Vector3.Distance(currentTreeWorldPosition, hit.point);
// Is this tree even closer?
if (distance < maxDistance)
{
maxDistance = distance;
closestTreeIndex = i;
closestTreePosition = currentTreeWorldPosition;
}
}
// replace the tree prototypeIndex
var temptree = new TreeInstance();
temptree.prototypeIndex = 6;
temptree.position = treeInstances[closestTreeIndex].position;
temptree.color = new Color(1,1,1,1);
temptree.lightmapColor = new Color(1,1,1,1);//must add
TreeInstances.Add(temptree);
// Remove the tree from the terrain tree list
TreeInstances.RemoveAt(closestTreeIndex);
//set back treeInstances array
terrain.treeInstances = TreeInstances.ToArray();
// Now refresh the terrain, getting rid of the darn collider
float[,] heights = terrain.GetHeights(0, 0, 0, 0);
terrain.SetHeights(0, 0, heights);
// Terrain.activeTerrain.collider.enabled = false;
// Terrain.activeTerrain.collider.enabled = true;
// Terrain.activeTerrain.Flush();
// Put a falling tree in its place
Instantiate(FallingTreePrefab, closestTreePosition, Quaternion.identity);
Debug.Log(DateTime.Now - start);
}
}
}
}
代码比较简单,不过多解释了,其中需要注意的地方有这么几个:
1、treeInstances是内建数组,不能直接修改,需要新建数组读出
,修改,再写入。
2、
temptree.color = new Color(1,1,1,1);
temptree.lightmapColor = new Color(1,1,1,1);
这两句一定要加上,不然树木纹理显示不正常,也可以通过color设置透明效果。
3、删除树木后要刷新地形,不然树木的模型虽然被消除,但是碰撞体还在。
// Now refresh the terrain, getting rid of the darn collider
float[,] heights = terrain.GetHeights(0, 0, 0, 0);
terrain.SetHeights(0, 0, heights);
// Terrain.activeTerrain.collider.enabled = false;
// Terrain.activeTerrain.collider.enabled = true;
// Terrain.activeTerrain.Flush();
上面三种方法好像都可以,测试结果有点记不清了。
就这样。
附上参考资料
附上参考资料
1、【风宇冲】Unity3D教程宝典之地形
2、
Clearing the TreeInstance array removes all trees perminantly
3、
Remove trees from the terrain during runtime?
4、
Finally - Removing trees AND the colliders
5、
Remove trees dynamically - stuck with collider
6、Tom's Terrain Tools 地形工具