
實(shí)現(xiàn)的效果就是上圖的樣子,先有一個(gè)直觀的感受,才好理解這里寫得是個(gè)什么東西~
HollowOutMask剛做出來的時(shí)候挺開心,覺得用到引導(dǎo)上是個(gè)挺好的工具。但是后面測(cè)試時(shí)發(fā)現(xiàn)一個(gè)嚴(yán)重的問題,它就變的有些雞肋了Orz……
1、核心說明
這個(gè)組件做了兩件事情:表現(xiàn)上鏤空一塊區(qū)域;不攔截鏤空范圍上的事件
1.1 鏤空顯示
這里的做法是,自己創(chuàng)建有8個(gè)頂點(diǎn)的Mesh,內(nèi)外邊界都是四邊形(矩形)。只生成內(nèi)、外邊之間的Mesh,內(nèi)層矩形就產(chǎn)生了鏤空效果(高亮部分)。
外層的4個(gè)頂點(diǎn),是組件自身RectTransform的四個(gè)頂點(diǎn);內(nèi)層的4個(gè)頂點(diǎn),使用鏤空目標(biāo)(_target)RectTransform的四個(gè)頂點(diǎn)。確定內(nèi)層的頂點(diǎn)的時(shí)候需要注意,多數(shù)情況下_target和HollowOutMask都不在同一個(gè)本地坐標(biāo)空間,所以需要使用CalculateRelativeRectTransformBounds計(jì)算出HollowOutMask空間下坐標(biāo)。
這種鏤空的表現(xiàn),可以稍稍提高下性能。因?yàn)殓U空的位置不參與渲染,Overdraw會(huì)降低。
1.2 事件穿透
UGUI提供了ICanvasRaycastFilter接口,我們實(shí)現(xiàn)IsRaycastLocationValid方法,就可以很方便的控制,HollowOutMask是否要攔截下在某一點(diǎn)觸發(fā)的事件。
2、代碼
/// <summary>
/// 實(shí)現(xiàn)鏤空效果的Mask組件
/// </summary>
public class HollowOutMask : MaskableGraphic, ICanvasRaycastFilter
{
[SerializeField]
private RectTransform _target;
private Vector3 _targetMin = Vector3.zero;
private Vector3 _targetMax = Vector3.zero;
private bool _canRefresh = true;
private Transform _cacheTrans = null;
/// <summary>
/// 設(shè)置鏤空的目標(biāo)
/// </summary>
public void SetTarget(RectTransform target)
{
_canRefresh = true;
_target = target;
_RefreshView();
}
private void _SetTarget(Vector3 tarMin, Vector3 tarMax)
{
if (tarMin == _targetMin && tarMax == _targetMax)
return;
_targetMin = tarMin;
_targetMax = tarMax;
SetAllDirty();
}
private void _RefreshView()
{
if(!_canRefresh) return;
_canRefresh = false;
if (null == _target)
{
_SetTarget(Vector3.zero, Vector3.zero);
SetAllDirty();
}
else
{
Bounds bounds = RectTransformUtility.CalculateRelativeRectTransformBounds(_cacheTrans, _target);
_SetTarget(bounds.min, bounds.max);
}
}
protected override void OnPopulateMesh(VertexHelper vh)
{
if (_targetMin == Vector3.zero && _targetMax == Vector3.zero)
{
base.OnPopulateMesh(vh);
return;
}
vh.Clear();
// 填充頂點(diǎn)
UIVertex vert = UIVertex.simpleVert;
vert.color = color;
Vector2 selfPiovt = rectTransform.pivot;
Rect selfRect = rectTransform.rect;
float outerLx = -selfPiovt.x*selfRect.width;
float outerBy = -selfPiovt.y*selfRect.height;
float outerRx = (1 - selfPiovt.x)*selfRect.width;
float outerTy = (1 - selfPiovt.y)*selfRect.height;
// 0 - Outer:LT
vert.position = new Vector3(outerLx, outerTy);
vh.AddVert(vert);
// 1 - Outer:RT
vert.position = new Vector3(outerRx, outerTy);
vh.AddVert(vert);
// 2 - Outer:RB
vert.position = new Vector3(outerRx, outerBy);
vh.AddVert(vert);
// 3 - Outer:LB
vert.position = new Vector3(outerLx, outerBy);
vh.AddVert(vert);
// 4 - Inner:LT
vert.position = new Vector3(_targetMin.x, _targetMax.y);
vh.AddVert(vert);
// 5 - Inner:RT
vert.position = new Vector3(_targetMax.x, _targetMax.y);
vh.AddVert(vert);
// 6 - Inner:RB
vert.position = new Vector3(_targetMax.x, _targetMin.y);
vh.AddVert(vert);
// 7 - Inner:LB
vert.position = new Vector3(_targetMin.x, _targetMin.y);
vh.AddVert(vert);
// 設(shè)定三角形
vh.AddTriangle(4, 0, 1);
vh.AddTriangle(4, 1, 5);
vh.AddTriangle(5, 1, 2);
vh.AddTriangle(5, 2, 6);
vh.AddTriangle(6, 2, 3);
vh.AddTriangle(6, 3, 7);
vh.AddTriangle(7, 3, 0);
vh.AddTriangle(7, 0, 4);
}
bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 screenPos, Camera eventCamera)
{
if (null == _target) return true;
// 將目標(biāo)對(duì)象范圍內(nèi)的事件鏤空(使其穿過)
return !RectTransformUtility.RectangleContainsScreenPoint(_target, screenPos, eventCamera);
}
protected override void Awake()
{
base.Awake();
_cacheTrans = GetComponent<RectTransform>();
}
#if UNITY_EDITOR
void Update()
{
_canRefresh = true;
_RefreshView();
}
#endif
}
最后說一下,把它變成雞肋的問題……
引導(dǎo)過程中通常都會(huì)有領(lǐng)獎(jiǎng)的地方,而且是在一個(gè)可滑動(dòng)的獎(jiǎng)勵(lì)列表里領(lǐng)取其中一個(gè)。HollowOutMask把高亮區(qū)域的所有事件都透過去了,使得這些地方玩家可以拖動(dòng)列表,然后產(chǎn)生了兩個(gè)問題。
- 表現(xiàn)上的穿幫:獎(jiǎng)勵(lì)被拖出顯示區(qū)域后,引導(dǎo)的高亮區(qū)域也跟著出去了
- 引導(dǎo)卡死:一般項(xiàng)目中(為了提高性能)都會(huì)使用Wrap的方式做道具、獎(jiǎng)勵(lì)的列表。在這種方式下,獎(jiǎng)勵(lì)被拖出顯示區(qū)域后會(huì)被deactive,玩家就不能把它再拖回來,也就沒法領(lǐng)獎(jiǎng),然后引導(dǎo)卡死在這里Orz……