Unity3D中的对象可见性管理
2018-01-06 本文已影响161人
acb348921872
背景
在游戏开发过程中,出于优化的目的,我们期望当角色可见或不可见时可以做一些事情。例如显示(隐藏)血条,或者当角色不可见时忽略一些开销较大的计算。
Unity提供了可见与不可见的回调,它们是OnBecameVisible与OnBecameInvisible。但是这两个回调只有当组件挂载在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组件的作用就是在OnBecameVisible与OnBecameInvisible被触发时通知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://msdn.microsoft.com/en-us/library/system.collections.bitarray(v=vs.110).aspx
- https://docs.unity3d.com/ScriptReference/Renderer.OnBecameVisible.html
本文固定链接: https://www.jianshu.com/p/049278580088
转载请注明: EnigmaJJ 2018年01月06日 于 简书 发表