Unity控制物体显示与隐藏方法及优化总结

Tandre的碎碎念:我开始用Unity做项目接触最早的接口就是Unity的GameObject的SetActive接口,当时甚至只用这一个接口就解决了一个项目中所有关于显示与隐藏的问题,但实际上它的问题很多,很多时候需要尽量避免使用或避免直接使用,这篇博客就记录下关于显示与隐藏的优化问题。

SetActive接口

可以说是最常用的控制显示与隐藏的接口了,但往往并不推荐使用

示例代码
GameObject go
go.SetActive(true);
go.SetActive(false);
问题

1.会导致网格重建,触发Unity的垃圾回收机制(GC),总之很影响性能
2.引起OnEnable和OnDisable的调用,会有一定的消耗

优化方案

因为对于该接口的GC问题很难解决,所以只能尽量的避免。

SetActive在底层会先对物体进行判断是否处于Active状态,而SetActive这个接口在调用时本身就是在C#层调用底层,所以为避免不必要的底层判断,可以在C#层对物体的Active状态进行判断。示例代码如下:

static public class MyTools
{
    public static void MSetActive(this GameObject go, bool State)
    {
        if (go)
        {
            if (go.activeSelf != State)
            {
                go.SetActive(State);
            }
        }
    }
}

写完这样一个方法后,在后续需要设置显示隐藏时只需要调用这个方法

GameObject go;
go.MsetActive(true);

UI的显示隐藏

因为UI的显示与隐藏是最经常面对的需求之一,所以需要单独写一下

SetActive()

首先应尽可能地不用SetActive这个接口,它在使用时会遍历这个物体下所有继承MonoBehaviour的脚本,并执行脚本上相应的OnEnable和OnDisable脚本,所以基本相当于对每个子物体都执行了一遍SetActive方法

如果一定要用也可以借助上面自定义的方法(MSetActive())减少一定的不必要性能消耗。

Canvas group组件(推荐使用)

通过在父物体上挂载CanvasGroup组件,然后控制它的Alppa的1/0来控制显示/隐藏,相对SetActive的性能相差并不大,两者都不会触发DrawCall,但CanvadGroup组件不会执行子节点的Awake方法,而SetActive(true)会执行Awake()方法
使用CanvasGroup显示隐藏物体的方法示例:

public static void MSetActive(this CanvasGroup cgrop,bool active)
{
    if (cgrop!=null)
    {
        if (active)
        {
            cgrop.alpha = 1;
            cgrop.interactable = true;
            cgrop.blocksRaycasts = true;
        }
        else
        {
            cgrop.alpha = 0;
            cgrop.interactable = false;
            cgrop.blocksRaycasts = false;
        }
    }
}

其中的Alpha表示透明度,可用于UI淡入淡出的控制,Interactable表示是否禁用输入交互,BlockRaycasts是否禁用射线检测,IgnoreParentGroups是否忽略父级的CanvasGroup。这些属性将作用于该组件及其所有子物体,所以利用该组件可以较便捷的实现很多实用的效果。

其他的显示与隐藏方法

缩小Scale

采用缩小Scale的方法,即:

transform.localScale=Vector3.zero;

这种方法可以避免SetActive带来的很多性能消耗,但当Scale改回原大小时会触发DrawCall带来相对更多的性能消耗。

移动物体

通过更改物体位置(Position),将物体移动到屏幕外,使相机不渲染此物体,缺点在于,更改物体位置会导致它和它的所有子物体重新进行位置计算。

设置MeshRenderer状态

需要物体带有MeshRenderer组件,相当于将物体隐身,并不会影响物体的脚本运行,物体的碰撞体也依然存在。示例如下:

gameObject.GetComponent<MeshRenderer>().enabled = true;
gameObject.GetComponent<MeshRenderer>().enabled = false;
设置Shader透明度

这种方法与上一个类似,但需要的是材质球上的shader存在可以控制透明度的属性,示例如下:

material.SetColor("_Color", Color.white);
修改物体Layer

将物体的layer设置在相机的CulingMask之外,使相机不渲染此物体,示例如下:

gameObject.layer=LayerMask.NameToLayer("isDisabled");

这里layer的名字需要自行根据需要添加或使用

结语

对于UI的显示与隐藏相对好的方式是使用CanverGroup,但其更好的点在于组件上属性的控制而并非对显示隐藏的优化上面。对于物体显示与隐藏,为避免网格重建和大量GC消耗,优化方向便是想办法让其不被相机渲染,或尽可能减少不必要的计算。