RectMask2D裁剪Canvas无效问题


类似下面这样的层级结构,白色区域为ScrollView可见区域,RectMask2D添加在ScrollView上。

可以看到Canvas下的Image1没被裁剪掉,不在Canvas下的Image2裁剪掉了。

 【原因分析】

RectMask2D内部有一个裁剪对象列表,只有在这个列表中的对象才会被裁剪,这边就是Image1没被添加到这个列表中造成的。

ugui的实现逻辑就是,如果MaskableGraphic对象和RectMask2D层级间,如果出现了Canvas,那这个对象就不会添加进去。

具体的实现可以查看:MaskableGraphic.UpdateClipParent,在这个函数中确定被哪个RectMask2D裁剪(添加到它的裁剪对象列表中)

【解决思路】

添加一个代理类,这个类对象会被添加到RectMask2D的裁剪对象列表中,然后在收到裁剪调用的时候,我们把调用转发到Image1去。

 1 [DisallowMultipleComponent]
 2 public class ClipProxy : MaskableGraphic
 3 {
 4     //手动设定转发给哪几个MaskableGraphic, 如果不指定将获取所有子孙上的MaskableGraphic组件
 5     [SerializeField] private MaskableGraphic[] _manualMaskables;
 6     private List _maskableList;
 7     
 8     public class ClippableEvent : UnityEventbool> {}
 9     
10     private ClippableEvent _onCull = new ClippableEvent();
11     public ClippableEvent onCull
12     {
13         get { return _onCull; }
14         set { _onCull = value; }
15     }
16     
17     private ClippableEvent _onSetClipRect = new ClippableEvent();
18     public ClippableEvent onSetClipRect
19     {
20         get { return _onSetClipRect; }
21         set { _onSetClipRect = value; }
22     }
23     
24     #region Empty4Raycast的功能
25 
26     protected ClipProxy()
27     {
28         useLegacyMeshGeneration = false;
29     }
30     protected override void OnPopulateMesh(VertexHelper toFill)
31     {
32         toFill.Clear();
33     }
34     
35     #endregion
36     
37     
38     protected override void Awake()
39     {
40         base.Awake();
41 
42         if (null == _manualMaskables || 0 == _manualMaskables.Length)
43         {
44             _maskableList = new List();
45             GetComponentsInChildren(false, _maskableList);
46         }
47         else
48         {
49             _maskableList = new List(_manualMaskables);
50         }
51     }
52 
53     public override void Cull(Rect clipRect, bool validRect)
54     {
55         base.Cull(clipRect, validRect);
56         if (null != _maskableList)
57         {
58             for (var j = 0; j < _maskableList.Count; ++j)
59             {
60                 var maskable = _maskableList[j];
61                 if (null != maskable & maskable != this)
62                     maskable.Cull(clipRect, validRect);
63             }
64         }
65         _onCull.Invoke(clipRect, validRect);
66     }
67 
68     public override void SetClipRect(Rect value, bool validRect)
69     {
70         base.SetClipRect(value, validRect);
71         if (null != _maskableList)
72         {
73             for (var j = 0; j < _maskableList.Count; ++j)
74             {
75                 var maskable = _maskableList[j];
76                 if (null != maskable && maskable != this)
77                     maskable.SetClipRect(value, validRect);
78             }
79         }
80         _onSetClipRect.Invoke(value, validRect);
81     }
82     
83 }

最终效果:

ScrollView嵌套ScrollView的情况也没有问题:

【关于IClippable接口】

void Cull(Rect clipRect, bool validRect)

void SetClipRect(Rect value, bool validRect)

这两个函数的参数类似,可能容易产生混淆。

Cull用于检测是否要把自己从渲染中剔除的,比如:可以在这边判断是否不在ScrollView的可见区域内,不在时可以从渲染中剔除,一定程度上提高渲染性能。

SetClipRect用于设置自己的裁剪区域,比如裁剪区域变大时,重新调整裁剪区域。或者裁剪区域没了,关掉裁剪区域。