前几天同事做了一个效果,希望在原本使用了遮罩组件 Mask 的技能图标(让技能图标变成圆形)上在添加一个置灰的功能,但问题来了:因为是动态根据游戏中玩家的条件才动态置灰,以修改 Mask 下子节点 Image 组件的材质来实现的,但是实际上怎么修改也不起作用,呈现出的效果都只停留在第一次运行时的样子。
一开始我也以为是 shader 的问题,修改的 property 没有生效,但是通过各种测试发现 shader 是已经修改成功的了。就是没有应用上,在查阅了各方资料无效果的情况下只能翻阅 unity 托管在 BitBucket 上的 UI 源码了(赞一个)。
先看看 Mask.cs,通过 StencilBuffer 实现遮罩,继承自IMaterialModifier,需要实现接口:Material GetModifiedMaterial(Material baseMaterial); 这个接口是用来修改获取的材质来实现遮罩效果。在各种 MonoBehaviour 改变时都会通过MaskUtilities 这个静态类来向所有的子节点发送 Stencil 状态改变的消息。
所以想知道为什么材质效果总是维持在第一次启动执行时,就看看Mask.OnEnable 里调用的MaskUtilities.NotifyStencilStateChanged(this); 做了什么。
1 public static voidNotifyStencilStateChanged(Component mask) 2 { 3 var components = ListPool<Component>.Get(); 4 mask.GetComponentsInChildren(components); 5 for (var i = 0; i < components.Count; i++) 6 { 7 if (components[i] == null || components[i].gameObject ==mask.gameObject) 8 continue; 9 10 var toNotify = components[i] asIMaskable; 11 if (toNotify != null) 12 toNotify.RecalculateMasking(); 13 } 14 ListPool<Component>.Release(components); 15 }
看以上代码可以得知,Mask 会调用所有子节点中继承自 IMaskable 组件(Image 继承 MaskableGraphic,MaskableGraphic 继承自此)的 RecalculateMasking() 函数。该函数将
MaskableGraphic 中的m_ShouldRecalculateStencil 修改为 true,这样当开始渲染时,每个组件都会被调用GetModifiedMaterial 以返回一个适用于当前渲染的 材质(有可能会返回一个修改过的拷贝),当 Imange.m_ShouldRecalculateStencil = true 时,会在GetModifiedMaterial 中返回一个支持 Stencil 的修改过的材质,用于实现 Mask 遮罩效果,所以问题也很明了了,修改 Mask 下的 Image 组件原始的材质是不起作用的,因为实际渲染使用的不是它。
那么如何修改?只需要自己继承一个比如Image 组件,并重载GetModifiedMaterial 方法,将基类返回材质保存即可,这就是实际渲染使用的材质,当你想修改置灰时,使用这个材质即可。代码如下:
1 public classCustomImage : Image 2 { 3 public overrideMaterial GetModifiedMaterial(Material baseMaterial) 4 { 5 Material cModifiedMat = base.GetModifiedMaterial(baseMaterial); 6 //Do whatever you want with this "cModifiedMat"... 7 //You can also hold this and process it in your grayscale code. 8 //... 9 returncModifiedMat; 10 } 11 }
也可以去看看我在 Unity Answer 上对于该问题的回答:http://answers.unity3d.com/questions/1130203/ui-mask-override-my-shaders-custom-property.html