类似下面这样的层级结构,白色区域为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<MaskableGraphic> _maskableList;
- 7
- 8 public class ClippableEvent : UnityEvent<Rect, bool> {}
- 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<MaskableGraphic>();
- 45 GetComponentsInChildren(false, _maskableList);
- 46 }
- 47 else
- 48 {
- 49 _maskableList = new List<MaskableGraphic>(_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 }
最终效果:

【关于IClippable接口】
void Cull(Rect clipRect, bool validRect)
void SetClipRect(Rect value, bool validRect)
这两个函数的参数类似,可能容易产生混淆。
Cull用于检测是否要把自己从渲染中剔除的,比如:可以在这边判断是否不在ScrollView的可见区域内,不在时可以从渲染中剔除,一定程度上提高渲染性能。
SetClipRect用于设置自己的裁剪区域,比如裁剪区域变大时,重新调整裁剪区域。或者裁剪区域没了,关掉裁剪区域。