非动态实现墙体破坏,需要预先准备裁剪好的预制体,这个方式只适合小面积的破坏效果,如果想实习每次破坏后的墙体碎片不一样就需要准备一个墙体碎片的预制体集合来随机调用破坏后的墙体预制体集合来实现。
准备:
1.一个完整的墙体 2.一个与完成墙体完全一样的破碎墙体(如图下:)
代码实现:
这段代码是挂载在完好的游戏墙体上的。
这代码主要就是通过OnCollisionEnter函数判断碰撞力度,拿到碰撞点的位置,然后调用函数禁用当前游戏对象(需要注意的是通过SetActive禁用的游戏对象后,后续代码照常执行,但是协程是无法执行的),复制预制体对每个Rigidbody调用AddExplosionForce函数实现爆炸效果。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestructibleWall : MonoBehaviour
{
[SerializeField]
private GameObject destructibleWallPrefab;//碎片墙体的预制体
private GameObject destructibleWall; //通过这个属性拿到碎片墙体的实例
[SerializeField] private float explosionForce = 30f; //爆炸的力度
[SerializeField] private float explosionRadius = 10f; //爆炸的半径
private void OnCollisionEnter(Collision other)//这是游戏对象内置的一个函数用来检测当前物体与其他物体发生碰撞 Collision other包含了与该物体碰撞的信息
{
if (other.relativeVelocity.magnitude > 5f) //该属性为碰撞时的相对速度,通过这个能判断碰撞的力的大小,我们通过控制这个参数来实现大于多少的力度墙体就破碎
{
DestroyWalls(other.contacts[0].point);
}
}
private void DestroyWalls(Vector3 position)
{
gameObject.SetActive(false);
destructibleWall = Instantiate(destructibleWallPrefab, transform.position, Quaternion.identity);
Rigidbody[] fragments = destructibleWall.GetComponentsInChildren<Rigidbody>();
foreach (Rigidbody fragment in fragments)
{
fragment.AddExplosionForce(explosionForce,position,explosionRadius,1f, ForceMode.Impulse);//内置的一个爆炸函数 参数分别是:爆炸力度,爆炸点,爆炸半径,>1时向上实现力的效果,力的类型
}
}
}
这段代码是挂载在破碎的墙体上的:
这段代码就是通过判断碎片是否静止,当静止时后通过Translate函数将碎片对象下落到场景外后销毁来实现淡出碎片。
这种方式会有一个问题:就是它是通过renderer.bounds.size.y包围盒的y轴来判断物体高度来均匀下落的,整个下落过程就是renderer.bounds.size.y的高度,当部分碎片处于高处时,这种实现仍会只下落包围盒的y轴的高度就销毁,这样就会比较突兀,碎片对象在空中就会被销毁,当全部碎片处于地面上才会有比较好的效果。需要调整这种效果有一解决方案就是通过一个函数拿到最高的碎片对象,每次下落的高度由最高的碎片对象的renderer.bounds.size.y决定,将下落高度平摊到最高碎片上就能是实现最高的物体下落完成才销毁整个碎片集合。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BrokenWall : MonoBehaviour
{
[SerializeField] private float fadeDestroyRigidbodyTime = 3f;
private void Start()
{
StartCoroutine(FadeoutRigidbodies(gameObject.transform.GetComponentsInChildren<Rigidbody>()));//拿到所有子对象的Rigidbody组件,子对象就是每个裁剪好的墙体碎片
}
IEnumerator FadeoutRigidbodies(Rigidbody[] rigidbodies)
{
WaitForSeconds wait = new WaitForSeconds(5f);//等待5s再开始判断碎片是否静止
int activeRigidbodies = rigidbodies.Length;//拿到碎片个数
while (activeRigidbodies>0)
{
// activeRigidbodies=rigidbodies.Length;//可加可不加这段表达式,加了就是必须所有物体静止才跳出while
yield return wait; //注意这个位置是必须要这个表达式的,因为协程每帧调用,这里while中又嵌套了for语句来判断碎片是否静止,如果调用后不进入等待就会导致电脑消耗过大导致死机,调用也有一个好处就是只要还有物体没有进入静止我们就再等带物体静止,符合用户的体验需求
foreach (Rigidbody rigidbody in rigidbodies)
{
if (rigidbody.IsSleeping())//Rigidbody内置函数判断当前物体是否静止
activeRigidbodies--;
}
}
yield return new WaitForSeconds(2f);//等待2s后开始淡出碎片
Renderer[] renderers = Array.ConvertAll(rigidbodies, GetRendererFromRigidbodies);// Array.ConvertAll函数是C#中的一个内置的函数,参数1.数组 2.函数
用法就是调用参数2的函数将参数1的数组转换为新的数组
foreach (Rigidbody body in rigidbodies)
{
Destroy(body.GetComponent<Collider>());
Destroy(body);
}//移除碎片对象上的Collider和Rigidbody组件
float time = 0;
while (time<1)
{
float step = Time.deltaTime / fadeDestroyRigidbodyTime;//控制淡出时间,这里淡出是3s
foreach (Renderer renderer in renderers)
{
if (renderer != null)
{
float fallDistance = Mathf.Lerp(0, renderer.bounds.size.y, time);
renderer.transform.Translate(Vector3.down * step*renderer.bounds.size.y, Space.World);//Vector3.down * step*renderer.bounds.size.y 表达式是将3s的总位置平摊到每一次下落
}
}
time += step;
Debug.Log("time :"+time);
yield return null;
}
foreach (Renderer renderer in renderers)
{
Destroy(renderer.gameObject);
}
Destroy(gameObject);
}
private Renderer GetRendererFromRigidbodies(Rigidbody rigidbody)
{
return rigidbody.GetComponent<Renderer>();
}
}