使用的是URP项目,相机设置有所不同;该方法同样可以设置物体碎裂效果
一,制作屏幕碎片
1,新建一个立方体,设置material,按照屏幕比例设置尺寸,这里按照1920x1080设置
需要防止立方体的材质镜像,注意使用正确的面作为屏幕
2,安装Probuilder组件,并在Edit-Preference-Probuilder中打开实验特征
3,Tools-Probuilder-Probuilder Window打开probuilder窗口,选中之前创建的屏幕立方体,使用Probuilderize指令;再新建一个立方体2,厚度大于屏幕立方体,同样使用Probuilderize指令
4,如图打开Boolean Tool,将屏幕立方体1与立方体2分别拖入左侧和右侧。该窗口会将两个立方体的重合部分生成一个新的物体。
5,使用顶点选择模式,该模式可以调整拖动模型的顶点位置,可以同时使用2d模式方便选择,调整立方体2的顶点,按自己需要的设置成碎片形状,点击Boolean Tool的apply,即可按照重合部分生成碎片物体,选择生成的碎片,使用Probuilder窗口的center pivot命令设置好锚点(需要点两次)。重复以上操作直至完成所有碎片
二,预制体结构
将上面制作的碎片防在同一个父物体下,添加rigidbody,初始不使用重力。
为了体现加载进度,可以将一块完整的屏幕放在碎片下,随进度变成黑色
添加一个输出图像的相机:为了体现碎块的效果,使用的是透视相机,渲染类型是overlay,culling mask仅选择碎片所在的layer
三,主要脚本功能
1,Awake:记录每个碎片的初始相对坐标,为了在切换场景时不被销毁,使用DontDestroyOnLoad(this.gameObject);
2,SetPos:设置屏幕碎片放到输出相机前并铺满屏幕,涉及相机参数和API相关知识
3,Reset:设置碎片到初始位置和初始旋转,关闭重力影响,清零速度和角速度,重置背景颜色
4,SetCameraStack:将输出相机设置到当前场景的主相机的stack中,使之得以显示
5,ShowShot:关闭所有协程(中断没完成的协程),开启屏幕截图协程
6,ScreenShot:设置输出相机到主相机的stack中,等待此帧结束,新建一个屏幕大小的纹理,读取屏幕图像,开启输出相机,重置碎片位置,设置碎片材质的主纹理
7,ScreenExplosion:遍历每个碎片,开启重力影响,设置爆炸冲击力
8,StartColorChange:开启颜色变化协程,根据进度将背景逐渐变黑,结束后进行屏幕爆炸,等待一段时间后即可隐藏物体
public class ScreenBreaker : MonoBehaviour
{
public Camera outputCamera;
public Transform slices;
public Transform bg;
Vector3[] origLocalPos;
private void Awake()
{
origLocalPos = new Vector3[slices.childCount];
for(int i = 0; i < slices.childCount; i++)
{
origLocalPos[i] = slices.GetChild(i).localPosition;
}
SetPos();
DontDestroyOnLoad(this.gameObject);
}
public void SetPos()
{
int distance = 10;
//获取屏幕中心点世界位置,z轴设置>0
Vector3 pos = outputCamera.ScreenToWorldPoint(new Vector3(Screen.width / 2, Screen.height / 2, distance));
float halfFOV = outputCamera.fieldOfView * 0.5f; // 视野角度的一半
float cameraHeight = distance * Mathf.Tan(halfFOV * Mathf.Deg2Rad); // 相机在指定距离处的高度
float scale = cameraHeight / 5;
//放置到输出相机前
slices.transform.position = pos;
slices.gameObject.transform.localScale = Vector3.one * scale;
}
/// <summary>
/// 重置碎片位置并关闭重力影响
/// </summary>
private void Reset()
{
for (int i = 0; i < slices.childCount; i++)
{
Transform t = slices.GetChild(i);
t.localPosition = origLocalPos[i];
t.localRotation = Quaternion.identity;
Rigidbody rb = t.GetComponent<Rigidbody>();
if( rb != null )
{
rb.useGravity = false;
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
}
bg.GetComponent<Renderer>().material.color = Color.white;
bg.gameObject.SetActive(true);
bg.localRotation = Quaternion.Euler(0,90,0);
}
public void ShowShot(UnityAction callback)
{
StopAllCoroutines();
StartCoroutine(ScreenShot(callback));
}
bool isOver = false;
IEnumerator ScreenShot(UnityAction callback)
{
isOver = false;
SetCameraStack(Camera.main);
//等待到此帧结束,完成渲染
yield return new WaitForEndOfFrame();
//相机渲染纹理因ui大小与实际看起来不同,因此改成影响直接使用屏幕图像
Texture2D tex = new Texture2D(Screen.width, Screen.height, TextureFormat.ARGB32, false);
// 读取像素到Texture2D中
tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
tex.Apply();
//开启显示相机
outputCamera.gameObject.SetActive(true);
//重置碎片位置
Reset();
slices.gameObject.SetActive(true);
for(int i = 0;i< slices.childCount;i++)
{
slices.GetChild(i).GetComponent<Renderer>().material.mainTexture = tex;
}
//关闭渲染
//shotCamera.gameObject.SetActive(false);
progress = 0;
callback?.Invoke();
}
public void StartColorChange(Color target)
{
StartCoroutine(ColorChange(target));
}
float progress;
IEnumerator ColorChange(Color target)
{
Material m = bg.GetComponent<Renderer>().material;
float f = 0;
while (!isOver || f < progress)
{
if (f < progress)
f += 0.01f;
m.color = (1 - f) * Color.white + f * target;
yield return null;
}
ScreenExplosion();
onBreak?.Invoke();
yield return new WaitForSeconds(4f);
gameObject.SetActive(false);
//子对象也隐藏,回到初始状态
outputCamera.gameObject.SetActive(false);
slices.gameObject.SetActive(false);
//rt.Release();
}
public float explosionForce = 300;
public float hightPercent = 1;
private void ScreenExplosion()
{
//隐藏背景裂纹
bg.gameObject.SetActive(false);
var rs = slices.GetComponentsInChildren<Rigidbody>();
for (int i = 0; i < rs.Length; i++)
{
rs[i].useGravity = true;
rs[i].AddExplosionForce(explosionForce*UnityEngine.Random.Range(0.8f,1.2f), slices.transform.position + new Vector3(0, 0, -0.5f), slices.transform.localScale.x * 10 * hightPercent * UnityEngine.Random.Range(0.8f, 1.2f));
}
}
public void SetProgress(float progress)
{
this.progress = progress;
if(progress==1)
{
isOver = true;
}
}
UnityAction onBreak;
public void SetBreakCallBack(UnityAction callback)
{
this.onBreak = callback;
}
public void SetCameraStack(Camera main)
{
UniversalAdditionalCameraData mainData = main.GetUniversalAdditionalCameraData();
if (!mainData.cameraStack.Contains(outputCamera))
{
mainData.cameraStack.Add(outputCamera);
}
}
}
在切换场景时,生成该预制体,开启屏幕截图和颜色转换函数,在异步加载时设置prograss控制进度。当异步加载完成时设置prograss=1,设置屏幕爆炸效果
四,成果
Unity学习-屏幕破碎转场