网上的unity破碎插件很多,不过想着可以以自己的方式实现也不失为一种乐趣,虽然整体的表现性上显得有些差,但也并不会影响最终的效果,接下来我大致讲解一下破碎一个物体的流程,因为用到了协程计算碎片的原因,所以会在所有碎片计算完成以后才会触发碎片的物理效果,所以有些模型可能会显得卡顿一下。
添加MeshBroken脚本,目前破碎参数还较少,可以进行一些适当的调节以达到不同的破碎效果,不过建议面数过多的物体慎用,因为这里破碎的碎块数量必须大于等于原网格的面数(至于为什么呢,那是因为我目前还没想到可以数目随机、位置随机的组合多个三角面的方式,如果有相关思路的欢迎一起讨论)。
IsBrokenOnHit:物体受到碰撞时自动破碎,无论是实际碰撞还是触发器碰撞;
FragmentNum:碎片数量;
SurfaceFragmentThickness:面片破碎时单个面片厚度;
GridFragmentMinThickness:块状破碎时单个碎块厚度变化最小值;
GridFragmentMaxThickness:块状破碎时单个碎块厚度变化最大值;
FragmentStyle:碎片类型,可选择surface(面片破碎),grid(块状破碎);
BrokenStyle:破碎方式,可选择statice(静态破碎),explode(爆炸破碎);
ExplosiveForce:爆炸破碎时的爆炸力;
ExplosionRange:爆炸破碎时的爆炸力作用范围;
效果演示。
实现的大致流程。
首先,开始破碎时,我们先要计算所有的碎片组成(所以比较耗性能,破碎过程是即时计算的),因为目前的破碎方式是按模型原本面数进行计算的,如果模型的面数小于碎片需求,则需要按一定的规则破开部分面
/// <summary> /// 计算所有碎片 /// </summary> List<List<List<int>>> ComputeAllFragment() { List<List<List<int>>> allFragment = new List<List<List<int>>>(); //碎片数量大于面数,则破开一定的面 if (_FragmentNum > _AllTriangleList.Count) { int num = _FragmentNum - _AllTriangleList.Count; while (num > 0) { CutApartSurface(_AllTriangleList[Random.Range(0, _AllTriangleList.Count)]); num -= 1; } } //按模型面数进行破碎 for (int i = 0; i < _AllTriangleList.Count; i++) { List<List<int>> fragment = new List<List<int>>(); fragment.Add(_AllTriangleList[i]); allFragment.Add(fragment); } return allFragment; }
破开一个面的方式
/// <summary> /// 将一个面割开 /// </summary> /// <param name="triangle">面片</param> void CutApartSurface(List<int> triangle) { Vector3 vertices0 = _AllVerticesList[triangle[0]]; Vector3 vertices1 = _AllVerticesList[triangle[1]]; Vector3 vertices2 = _AllVerticesList[triangle[2]]; //计算新顶点坐标 Vector3 newVertex = new Vector3((vertices1.x - vertices2.x) / 2.0f + vertices2.x, (vertices1.y - vertices2.y) / 2.0f + vertices2.y, (vertices1.z - vertices2.z) / 2.0f + vertices2.z); //计算新顶点的UV Vector2[] uv = _Uv; Vector2[] Newuv = new Vector2[uv.Length + 1]; for (int i = 0; i < uv.Length; i++) { Newuv[i] = uv[i]; } Newuv[Newuv.Length - 1] = new Vector2((uv[triangle[1]].x - uv[triangle[2]].x) / 2 + uv[triangle[2]].x , (uv[triangle[1]].y - uv[triangle[2]].y) / 2 + uv[triangle[2]].y); _Uv = Newuv; //计算新顶点的法线 Vector3[] normal = _Normal; Vector3[] Newnormal = new Vector3[normal.Length + 1]; for (int i = 0; i < normal.Length; i++) { Newnormal[i] = normal[i]; } Newnormal[Newnormal.Length - 1] = _Normal[triangle[2]]; _Normal = Newnormal; //新顶点加入所有顶点集合 _AllVerticesList.Add(newVertex); //记录新顶点索引 int _index = _AllVerticesList.IndexOf(newVertex); //割开三角面 List<int> newTriangle1 = new List<int>(); List<int> newTriangle2 = new List<int>(); newTriangle1.Add(triangle[0]); newTriangle1.Add(triangle[1]); newTriangle1.Add(_index); newTriangle2.Add(_index); newTriangle2.Add(triangle[2]); newTriangle2.Add(triangle[0]); _AllTriangleList.Remove(triangle); _AllTriangleList.Add(newTriangle1); _AllTriangleList.Add(newTriangle2); }
所有的碎片组成方式都计算完毕之后,便保存所有碎片集合,遍历集合以生成所有碎片
/// <summary> /// 生成网格碎片 /// </summary> void ProduceGridFragment(List<List<int>> fragment) { //新建碎片物体 GameObject obj = new GameObject(transform.name + "_fragment" + transform.childCount); obj.transform.position = transform.position; obj.transform.rotation = transform.rotation; obj.transform.localScale = transform.localScale; obj.transform.SetParent(transform); obj.AddComponent<MeshFilter>(); obj.AddComponent<MeshRenderer>(); obj.GetComponent<MeshRenderer>().material = GetComponent<MeshRenderer>().material; Mesh _mesh = new Mesh(); _mesh.Clear(); //不合法的碎片 if (fragment.Count < 1 || fragment[0].Count < 3) return; //计算碎片的顶点 Vector3[] _fragment = new Vector3[] { _AllVerticesList[fragment[0][0]], _AllVerticesList[fragment[0][1]], _AllVerticesList[fragment[0][2]], _AllVerticesList[fragment[0][2]] - _Normal[fragment[0][2]] * Random.Range(_GridFragmentMinThickness,_GridFragmentMaxThickness), _AllVerticesList[fragment[0][1]] - _Normal[fragment[0][2]] * Random.Range(_GridFragmentMinThickness,_GridFragmentMaxThickness), _AllVerticesList[fragment[0][0]] - _Normal[fragment[0][2]] * Random.Range(_GridFragmentMinThickness,_GridFragmentMaxThickness) }; //计算碎片的三角面 int[] _triangle = new int[] { 0,1,2, 3,4,5, 2,1,4, 4,3,2, 0,2,3, 0,3,5, 5,4,1, 5,1,0 }; //计算碎片的uv Vector2[] _uv = new Vector2[] { _Uv[fragment[0][0]], _Uv[fragment[0][1]], _Uv[fragment[0][2]], _Uv[fragment[0][2]], _Uv[fragment[0][1]], _Uv[fragment[0][0]] }; _mesh.vertices = _fragment; _mesh.triangles = _triangle; _mesh.uv = _uv; //生成碎片 _mesh.name = transform.name + "_fragment" + transform.childCount; _mesh.RecalculateNormals(); obj.GetComponent<MeshFilter>().mesh = _mesh; _Fragment.Add(obj); }
之后在其他脚本里,想要动态的控制物体的破碎的话,可以在外部调用破碎开关函数
/// <summary> /// 开始破碎 /// </summary> public void BeginBroken() { if (IsCanBroken) { StartCoroutine(BroKening()); IsCanBroken = false; } else Debug.Log("由于未知原因,目标无法破碎!"); }
最后附上动态演示图:
-----by MeshEditor