unityUnity乱七八糟技巧

Unity3D中的对象可见性管理

2018-01-06  本文已影响161人  acb348921872

背景

在游戏开发过程中,出于优化的目的,我们期望当角色可见或不可见时可以做一些事情。例如显示(隐藏)血条,或者当角色不可见时忽略一些开销较大的计算。
Unity提供了可见与不可见的回调,它们是OnBecameVisibleOnBecameInvisible。但是这两个回调只有当组件挂载在Renderer上时才会被触发。另一个问题是角色可能由多个Renderer组成,这样在可见性判断上就需要将它们都考虑进去。

解决方案

出于以上提到的原因,我实现了一套可见性管理模块,基本思想是VisibilityModule会负责管理所有的Renderer,并在可见性发生变化时发出事件通知。
先来看一下VisibilityModule:

/******************************************************************************
 * DESCRIPTION: 可见性模块
 * 
 *     Copyright (c) 2018, 谭伟俊 (TanWeijun)
 *     All rights reserved
 * 
 * COMPANY:
 * CREATED: 2018.01.06, 15:49, CST
*******************************************************************************/

using UnityEngine;

public class VisibilityModule
{
    #region Variables
    public event System.Action<VisibilityModule> OnVisibilityChanged;

    private BitMask m_BitMask;
    private bool m_IsVisible;
    private VisibilityComponent[] m_VisComponentArray;
    #endregion

    #region Properties
    public bool IsVisible
    {
        get
        {
            return m_IsVisible;
        }

        set
        {
            if (value != m_IsVisible)
            {
                m_IsVisible = value;
                if (null != OnVisibilityChanged)
                {
                    OnVisibilityChanged(this);
                }
            }
        }
    }
    #endregion

    #region Methods
    public VisibilityModule(int _length = 16)
    {
        m_BitMask = new BitMask(_length);
        m_VisComponentArray = new VisibilityComponent[_length];
    }

    public void Refresh(Transform _trans, bool _clear = true)
    {
        if (_clear)
            Clear();

        Renderer[] rendererArray = _trans.GetComponentsInChildren<Renderer>();
        for (int i = 0; i < rendererArray.Length; ++i)
        {
            if (i >= m_VisComponentArray.Length)
                throw new System.IndexOutOfRangeException("Renderer length beyonds bit length.");

            m_VisComponentArray[i] = rendererArray[i].gameObject.AddComponent<VisibilityComponent>();
            m_VisComponentArray[i].BitIndex = i;
            m_VisComponentArray[i].VisModule = this;

            m_BitMask.Set(i, rendererArray[i].isVisible);
        }

        IsVisible = m_BitMask.HasTrue;
        if (_clear && !IsVisible)
        {
            // Clear之后m_IsVisible默认为false,如果此时就是不可见的,则在设置IsVisible时不会
            // 触发OnVisibilityChanged事件,所以在这里强制触发一次
            if (null != OnVisibilityChanged)
            {
                OnVisibilityChanged(this);
            }
        }
    }

    public void Clear()
    {
        for (int i = 0; i < m_VisComponentArray.Length; ++i)
        {
            if (null != m_VisComponentArray[i])
            {
                Object.Destroy(m_VisComponentArray[i]);
                m_VisComponentArray[i] = null;
            }
        }

        m_IsVisible = false;
    }

    public void BecameVisible(VisibilityComponent _visComponent)
    {
        SetVisible(_visComponent.BitIndex, true);
    }

    public void BecameInvisible(VisibilityComponent _visComponent)
    {
        SetVisible(_visComponent.BitIndex, false);
    }

    private void SetVisible(int _index, bool _value)
    {
        m_BitMask.Set(_index, _value);
        IsVisible = m_BitMask.HasTrue;
    }
    #endregion
}

最主要的接口就是Refresh,他会负责获取所有的Renderer组件,在其上挂载VisibilityComponent组件,并使用BitMask来管理可见性。当可见性发生变化时,就会触发OnVisibilityChanged事件。
VisibilityComponent组件的作用就是在OnBecameVisibleOnBecameInvisible被触发时通知VisibilityModule:

/******************************************************************************
 * DESCRIPTION: 可见性组件
 * 
 *     Copyright (c) 2018, 谭伟俊 (TanWeijun)
 *     All rights reserved
 * 
 * COMPANY:
 * CREATED: 2018.01.06, 16:01, CST
*******************************************************************************/

using UnityEngine;

public class VisibilityComponent : MonoBehaviour
{
    #region Variables
    private int m_BitIndex;
    private VisibilityModule m_VisModule;
    #endregion

    #region Properties
    public int BitIndex
    {
        get
        {
            return m_BitIndex;
        }

        set
        {
            m_BitIndex = value;
        }
    }

    public VisibilityModule VisModule
    {
        get
        {
            return m_VisModule;
        }

        set
        {
            m_VisModule = value;
        }
    }
    #endregion

    #region MonoBehaviour
    private void OnBecameVisible()
    {
        VisModule.BecameVisible(this);
    }

    private void OnBecameInvisible()
    {
        VisModule.BecameInvisible(this);
    }
    #endregion
}

而BitMask就是对BitArray的封装。在我们的模块中,使用BitMask来记录Renderer的可见性,每个位对应一个Renderer:

/******************************************************************************
 * DESCRIPTION: 位掩码
 * 
 *     Copyright (c) 2018, 谭伟俊 (TanWeijun)
 *     All rights reserved
 * 
 * COMPANY:
 * CREATED: 2018.01.06, 15:33, CST
*******************************************************************************/

using System.Collections;

public class BitMask
{
    #region Variables
    private BitArray m_BitArray;
    #endregion

    #region Properties
    public bool IsAllTrue
    {
        get
        {
            foreach (bool value in m_BitArray)
            {
                if (!value)
                    return false;
            }

            return true;
        }
    }

    public bool HasTrue
    {
        get
        {
            foreach (bool value in m_BitArray)
            {
                if (value)
                    return true;
            }

            return false;
        }
    }
    #endregion

    #region Methods
    public BitMask(int _length = 32, bool _defaultValue = false)
    {
        m_BitArray = new BitArray(_length, _defaultValue);
    }

    public void Set(int _index, bool _value)
    {
        m_BitArray[_index] = _value;
    }

    /// <summary>
    /// 设置最后_length个位值
    /// </summary>
    /// <param name="_length"></param>
    /// <param name="_value"></param>
    public void SetLast(int _length, bool _value)
    {
        int bitArrayLength = m_BitArray.Length;
        for (int i = 0; i < _length; ++i)
        {
            m_BitArray[bitArrayLength - i - 1] = _value;
        }
    }
    #endregion
}

用例

我们在场景中创建一个Cube: 场景中的Cube

然后在Cube上挂载脚本:

void Start ()
{
    m_VisModule = new VisibilityModule();
    m_VisModule.OnVisibilityChanged += OnVisibilityChanged;
    m_VisModule.Refresh(transform);
}

private void OnVisibilityChanged(VisibilityModule _visModule)
{
    Debug.Log(_visModule.IsVisible);
}
当Cube从场景里消失时,会触发OnVisibilityChanged事件: Cube在场景里变得不可见

参考资料:

本文固定链接: https://www.jianshu.com/p/049278580088
转载请注明: EnigmaJJ 2018年01月06日 于 简书 发表

上一篇下一篇

猜你喜欢

热点阅读