[Unity] 新手引導(dǎo)3 - 鏤空遮罩

效果

  實(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……
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 全文解析圓形Image組件的實(shí)現(xiàn)原理,取關(guān)鍵代碼介紹算法細(xì)節(jié),源碼已經(jīng)上傳Github下載地址,歡迎下載試用。 一...
    立航閱讀 4,426評(píng)論 3 33
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,063評(píng)論 25 709
  • 0、牢騷 最近一周左右的時(shí)間在做新手引導(dǎo)的開發(fā),這是第二次負(fù)責(zé)引導(dǎo)了,我的內(nèi)心是崩潰的……上個(gè)項(xiàng)目的引導(dǎo)開發(fā)+維護(hù)...
    _Walker__閱讀 2,942評(píng)論 0 2
  • 我夸孩子的詞匯是匱乏單調(diào)的,而且最早我認(rèn)為如果她沒有做到我認(rèn)為的最好,我更多的是給她建議和批評(píng),而不是鼓勵(lì)和表?yè)P(yáng)。...
    老鼻子了閱讀 210評(píng)論 0 1
  • 你喜歡的人在城門外,你在城門內(nèi)。 而他喜歡的人在他心上,他在卻在你眼前。 他鼓起勇氣卻換了失敗和淚水,而你用隱忍和...
    心似野兔閱讀 335評(píng)論 0 1

友情鏈接更多精彩內(nèi)容