介绍

之前在知乎上看到有人发的 UGUI 点击性能优化,看到后感觉不错就收藏了。正好最近要优化这块,翻出来之后实践了一番。

主要参考以下文章:

环境

  • Unity 5.6.6f2

实践

跟 Gordon 的选择相同,主要采用第一篇与第二篇文章的优化方法。另外,由于项目内 UI 使用 SetActive 方式显隐 UI,因此并没有使用 Gordon 的优化方法。

作者并为介绍替换 GraphicRaycaster 为子类的方法,其实在 Unity 中替换很简单,只要全局文本替换 GUID 就可以了。

但是在实际替换时遇到了 Unity 的坑,由于某种未知原因导致项目内的 GraphicRaycaster 有两个 GUID,因此最终需要替换为同一个子类脚本的 GUID。

在查找替换时一定要仔细检查,确保所有内容都被正确替换。

另外强烈建议将自定义的 GraphicRaycaster 子类放在 Plugins 目录中,与游戏脚本隔离,这样也会提高编译速度。

建议代码使用 ReSharper 之类的重构工具整理一下,去除无用变量、using 声明之类的,提高代码可读性。

最终 Android 真机上的耗时由 1.2ms 降低为 0.7ms。

致谢

非常感谢两位作者 魔术师DixGordon 的辛勤工作。

代码

CustomGraphicRaycaster.cs - Unity UGUI EventSystem Optimization

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CustomGraphicRaycaster : GraphicRaycaster
{
    public Camera TargetCamera;

    public override Camera eventCamera
    {
        get
        {
            if (TargetCamera == null)
            {
                TargetCamera = base.eventCamera;
            }

            return TargetCamera;
        }
    }

    private Canvas canvas;

    protected override void Awake()
    {
        canvas = GetComponent<Canvas>();
    }

    #region 事件点击部分;

    [NonSerialized] private readonly List<Graphic> m_RaycastResults = new List<Graphic>();

    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
    {
        if (canvas == null)
            return;

        var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
        if (canvasGraphics == null || canvasGraphics.Count == 0)
            return;

        int displayIndex;
        var currentEventCamera = eventCamera; // Propery can call Camera.main, so cache the reference

        if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
            displayIndex = canvas.targetDisplay;
        else
            displayIndex = currentEventCamera.targetDisplay;

        var eventPosition = Display.RelativeMouseAt(eventData.position);
        if (eventPosition != Vector3.zero)
        {
            int eventDisplayIndex = (int)eventPosition.z;
            if (eventDisplayIndex != displayIndex)
                return;
        }
        else
        {
            eventPosition = eventData.position;
        }

        // Convert to view space
        Vector2 pos;
        if (currentEventCamera == null)
        {
            float w = Screen.width;
            float h = Screen.height;
            if (displayIndex > 0 && displayIndex < Display.displays.Length)
            {
                w = Display.displays[displayIndex].systemWidth;
                h = Display.displays[displayIndex].systemHeight;
            }

            pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
        }
        else
            pos = currentEventCamera.ScreenToViewportPoint(eventPosition);

        if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
            return;

        float hitDistance = float.MaxValue;

        Ray ray = new Ray();

        if (currentEventCamera != null)
            ray = currentEventCamera.ScreenPointToRay(eventPosition);

        m_RaycastResults.Clear();
        Raycast(currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
        int totalCount = m_RaycastResults.Count;
        for (var index = 0; index < totalCount; index++)
        {
            var go = m_RaycastResults[index].gameObject;
            bool appendGraphic = true;

            if (ignoreReversedGraphics)
            {
                if (currentEventCamera == null)
                {
                    var dir = go.transform.rotation * Vector3.forward;
                    appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
                }
                else
                {
                    var cameraFoward = currentEventCamera.transform.rotation * Vector3.forward;
                    var dir = go.transform.rotation * Vector3.forward;
                    appendGraphic = Vector3.Dot(cameraFoward, dir) > 0;
                }
            }

            if (appendGraphic)
            {
                float distance;

                if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
                    distance = 0;
                else
                {
                    Transform trans = go.transform;
                    Vector3 transForward = trans.forward;
                    distance = (Vector3.Dot(transForward, trans.position - currentEventCamera.transform.position) /
                                Vector3.Dot(transForward, ray.direction));
                    if (distance < 0)
                        continue;
                }

                if (distance >= hitDistance)
                    continue;
                var castResult = new RaycastResult
                {
                    gameObject = go,
                    module = this,
                    distance = distance,
                    screenPosition = eventPosition,
                    index = resultAppendList.Count,
                    depth = m_RaycastResults[index].depth,
                    sortingLayer = canvas.sortingLayerID,
                    sortingOrder = canvas.sortingOrder
                };
                resultAppendList.Add(castResult);
            }
        }
    }

    /// <summary>
    /// Perform a raycast into the screen and collect all graphics underneath it.
    /// </summary>
    private static void Raycast(Camera eventCamera, Vector2 pointerPosition,
        IList<Graphic> foundGraphics, List<Graphic> results)
    {
        int totalCount = foundGraphics.Count;
        Graphic upGraphic = null;
        int upIndex = -1;
        for (int i = 0; i < totalCount; ++i)
        {
            Graphic graphic = foundGraphics[i];

            //不响应点击事件的就不要执行了;
            if (!graphic.raycastTarget) continue;

            int depth = graphic.depth;
            if (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))
            {
                if (depth > upIndex)
                {
                    upIndex = depth;
                    upGraphic = graphic;
                }
            }
        }

        if (upGraphic != null)
            results.Add(upGraphic);
    }

    #endregion
}