Unity UI點擊事件


UI點擊事件

UGUI的事件本質上就是發送射線,由於UI的操作有一些復雜的手勢,所以UGUI幫我們又封裝了一層。創建任意UI時都會自動創建EventSystem對象,並且綁定EventSystem.cs和StandaloneInputModule.cs如下代碼所示,EventSystem會將該對象綁定的所有InputModule腳本收集起來保存在SystemInputModules對象中。

然后在EventSystem的Update()方法中更新它們,通常情況下我們只需要一個StandaloneInputModule即可。

當存在可執行的Module 會調用它的m_CurrentInputModule.Process();方法。那么UI是如何確定出點擊到那個元素上的呢?如下代碼所示,在EventSystem中遍歷所有module.Raycast()方法。 

EventSystem.cs(部分代碼)

public class EventSystem : UIBehaviour
{
    //...略

    public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
    {
        raycastResults.Clear();
        //獲得當前激活狀態下每個Canvas上綁定的GraphicRaycaster的對象
        var modules = RaycasterManager.GetRaycasters();
        for (int i = 0; i < modules.Count; ++i)
        {
            var module = modules[i];
            if (module == null || !module.IsActive())
                continue;
            //開始發送射線
            module.Raycast(eventData, raycastResults);
        }
        //對發送射線的結果進行排序,保證在前面的UI優先處理
        raycastResults.Sort(s_RaycastComparer);
    }

    private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;

    private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
    {
        if (lhs.module != rhs.module)
        {
            var lhsEventCamera = lhs.module.eventCamera;
            var rhsEventCamera = rhs.module.eventCamera;
            if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
            {
                // 比較camera的深度
                if (lhsEventCamera.depth < rhsEventCamera.depth)
                    return 1;
                if (lhsEventCamera.depth == rhsEventCamera.depth)
                    return 0;

                return -1;
            }
            //比較射線結果的排序優先級
            if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
                return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);
            //比較射線結果的渲染優先級
            if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
                return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
        }

        if (lhs.sortingLayer != rhs.sortingLayer)
        {
            // 比較SortingLayer
            var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
            var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
            return rid.CompareTo(lid);
        }

        //比較sortOrder
        if (lhs.sortingOrder != rhs.sortingOrder)
            return rhs.sortingOrder.CompareTo(lhs.sortingOrder);

        //比較深度
        if (lhs.depth != rhs.depth)
            return rhs.depth.CompareTo(lhs.depth);
        //比較距離
        if (lhs.distance != rhs.distance)
            return lhs.distance.CompareTo(rhs.distance);
        //最后比較index 
        return lhs.index.CompareTo(rhs.index);
    }
}

  還記得每個Canvas要想監聽點擊事件必須綁定GraphicRaycaster腳本嗎?上面代碼中的RaycasterManager.GetRaycasters();方法就是獲取當前到底有多少個綁定GraphicRaycaster腳本的對象,那么同時參與點擊事件的Canvas越多效率也就越低了,游戲中有很多界面是疊在一起的,最上面的界面已經擋住了所有界面,但是由於下面的界面還有GraphicRaycaster對象,那么必然產生額外的計算開銷,所以這種情況可以DeActive不需要參與點擊事件的Canvas。 

最后我們來看看到底如何判斷點擊的事件的,如下代碼所示,首先遍歷Canvas下每一個參與渲染的Graphic對象,如果勾選了raycastTarget並且點擊射線與它們相交,此時先存起來。

由於多個UI有相交的情況,但由於Mesh都合批了第一個與射線相交的對象是沒有意義的,但是我們只需要響應在最上面的UI元素,這里只能根據depth來做個排序了,找到最上面的UI元素,最后再拋出正確的點擊事件。 
GraphicRaycaster.cs(部分代碼)

public class GraphicRaycaster : BaseRaycaster
{
    //...略
    [NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
    private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
    {
        //遍歷,將每個參與渲染的UI添加到s_SortedGraphics對象中。
        int totalCount = foundGraphics.Count;
        for (int i = 0; i < totalCount; ++i)
        {
            Graphic graphic = foundGraphics[i];

            if (graphic.depth == -1 || !graphic.raycastTarget || graphic.canvasRenderer.cull)
                continue;

            if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
                continue;

            if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
                continue;
           
            if (graphic.Raycast(pointerPosition, eventCamera))
            {
                s_SortedGraphics.Add(graphic);
            }
        }
        //根據depth開始對它進行排序
        s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
        totalCount = s_SortedGraphics.Count;
        //最后將排序過的正確順序保存起來
        for (int i = 0; i < totalCount; ++i)
            results.Add(s_SortedGraphics[i]);

        s_SortedGraphics.Clear();
    }
}

所以說GraphicRaycaster組件越多越卡,raycastTarget勾選的越多越卡,其實開發中很多UI是不需要響應點擊事件的,但是卻被無意地勾選上了。這里我提供一個我開發的經驗,如下圖11-1所示,我會在Scene界面中將所有勾選過raycastTarget的對象用藍色矩形框標記出來,這樣做UI的人可以很方便地看到,如果有不需要參與點擊的UI元素,那么就及時取消勾選吧。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM