UGUI中粒子特效的裁剪显示
在Unity游戏中,常会需要粒子特效需要显示在UI上的情况。对于列表中的粒子特效,则需要自己实现裁剪,如图 所示。
实现粒子裁剪第一版:
1. 向带SystemParticle的结点挂一个MonoBehaviour脚本。
2. 修改粒子渲染shader,添加_ClipRect, 与UI/Default中类似。
3. 在脚本中获取上层Mask或Mask2D的rect, 传入_ClipRect。此处需要注意的是,需要调用rect.GetWorldCorners(wcs)方法将UI坐标系下在区域转换为世界坐标系。
当在多层列表的情况下,以上方法就出问题了,它并没有被上上层列表裁剪掉。比如这样:
于是有了处理第二版:向上取多次Mask2D,再取它们的区域交集,传入Shader中。
那么,有没有其它方法呢? 其实我们可以参考UGUI中其它组件的实现方式,以下是第三版。
1. 脚本继承于UIBehaviour, IClippable。
2. 实现SetClipRect方法,并在OnEnable和OnTransformParentChanged回调中,向Mask2D进行注册。此时SetClipRect函数中的Rect就是多个Mask2D的交集了。
3. 将SetClipRect中的Rect转换为世界坐标系,传给Shader。
using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class RD_ParticleClip : UIBehaviour, IClippable { [SerializeField] private Renderer[] renderers; // 支持多个粒子特效同时设置 static Vector3[] wcs = new Vector3[2]; private static MaterialPropertyBlock block; private RectMask2D m_ParentMask = null; [XLua.BlackList]
// 由于美术在特效中使用的Transform, 故取父结点的RectTransform public RectTransform rectTransform => transform.parent as RectTransform; protected override void Awake() { ParticleSystem[] particleSystems = this.GetComponentsInChildren<ParticleSystem>(true); if (particleSystems.Length > 0) { renderers = new Renderer[particleSystems.Length]; for (int i = 0; i < particleSystems.Length; ++i) { renderers[i] = particleSystems[i].GetComponent<Renderer>(); } } if (block == null) block = new MaterialPropertyBlock(); } protected override void OnEnable() { base.OnEnable(); if (rectTransform != null) UpdateClipParent(); } protected override void OnDisable() { base.OnDisable(); if (rectTransform != null) UpdateClipParent(); } protected override void OnTransformParentChanged() { base.OnTransformParentChanged(); if (!isActiveAndEnabled) return; if (rectTransform != null) UpdateClipParent(); } // 代码来自UISystem/MaskableGraphic private void UpdateClipParent() { var newParent = IsActive() ? MaskUtilities.GetRectMaskForClippable(this) : null; // if the new parent is different OR is now inactive if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive())) m_ParentMask.RemoveClippable(this); // don't re-add it if the newparent is inactive if (newParent != null && newParent.IsActive()) newParent.AddClippable(this); m_ParentMask = newParent; } public void RecalculateClipping() { //Debug.Log("RecalculateClipping:" + gameObject.name); } public void Cull(Rect clipRect, bool validRect) { //Debug.Log("Cull:" + gameObject.name); } public void SetClipRect(Rect value, bool validRect) { wcs[0] = new Vector3(value.xMin, value.yMin, 0); wcs[1] = new Vector3(value.xMax, value.yMax, 0); var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); if (rootCanvas != null) {
// 坐标系转换 Matrix4x4 mat = rootCanvas.transform.localToWorldMatrix; for (int i = 0; i < 2; ++i) wcs[i] = mat.MultiplyPoint(wcs[i]); for (int i = 0; i < renderers.Length; ++i) { Renderer render = renderers[i]; render.GetPropertyBlock(block); block.SetVector("_ClipRect", new Vector4(wcs[0].x, wcs[0].y, wcs[1].x, wcs[1].y)); block.SetFloat("_UseClipRect", 1); render.SetPropertyBlock(block); } } } }