10

Sweet Snippet 之 Timeslice Update

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/105846595
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Sweet Snippet 之 Timeslice Update

本文简述了一种基于时间片(Timeslice)的对象更新(Update)方法

游戏开发中,我们一般都要进行大量的对象更新(Update)操作,拿 Unity 中的 MonoBehavior 举例,其便支持定义专门的 Update 方法来处理相关 Component 的更新:

using UnityEngine;

public class CustomScript : MonoBehaviour 
{
    void Update()
    {
        // do CustomScript update here ...
    }
}

但是,当更新操作需要事件触发时(MonoBehaviour 的 Update 方法每帧都会执行,属于轮询触发),上述的方法就不太合适了,更好的一种方式是游戏逻辑自己来管理相关对象的更新触发:

using UnityEngine;

public class UpdateObject
{
    public void Update()
    {
        // do UpdateObject update here ...
    }
}
using System.Collections.Generic;
using UnityEngine;

public class UpdateManager : MonoBehaviour
{
    List<UpdateObject> m_objectList = new List<UpdateObject>();

    public void UpdateRequest(UpdateObject uo)
    {
        Debug.Assert(uo != null);
        if (!m_objectList.Contains(uo))
        {
            m_objectList.Add(uo);
        }
    }

    void Update()
    {
        if (m_objectList.Count > 0)
        {
            for (int i = 0; i < m_objectList.Count; ++i)
            {
                m_objectList[i].Update();
            }

            m_objectList.Clear();
        }
    }
}

上述代码中的 UpdateManager 便用于管理相关对象的更新触发操作(通过 UpdateRequest 方法).

现在我们解决了事件触发对象更新的问题,但是当对象更新比较耗时时(轮询触发也存在这个问题),我们还是会遇到对象更新时产生的卡顿问题:

using UnityEngine;

public class UpdateObject
{
    public void Update()
    {
        // Update method may be very time cost,
        // like "log using Debug"
        Debug.Log("UpdateObject Update");
    }
}

最普遍的解决方法就是优化 Update,使其耗时降低,但是很多情况下,这种方式也比较局限,更通用的一种方式则是分帧,即将对象的更新操作分摊到多帧中进行(而不是全部在一帧中执行完成).

实现分帧更新的方式很多,这里我使用了队列(并且做了进一步的优化,保证队列中的元素唯一):

using System.Collections.Generic;
using UnityEngine;

public class UniqueQueue<T>
{
    Queue<T> m_innerQueue = new Queue<T>();
    HashSet<T> m_elementSet = new HashSet<T>();

    public int Count
    {
        get
        {
            Debug.Assert(m_innerQueue.Count == m_elementSet.Count);
            return m_innerQueue.Count;
        }
    }

    public bool Contains(T element)
    {
        return m_elementSet.Contains(element);
    }

    public T Dequeue()
    {
        if (Count > 0)
        {
            var element = m_innerQueue.Peek();
            m_elementSet.Remove(element);

            return m_innerQueue.Dequeue();
        }
        else
        {
            return default(T);
        }
    }

    public bool Enqueue(T element)
    {
        if (!m_elementSet.Contains(element))
        {
            m_innerQueue.Enqueue(element);
            m_elementSet.Add(element);
            return true;
        }

        return false;
    }

    public void Clear()
    {
        m_innerQueue.Clear();
        m_elementSet.Clear();
    }
}

基于上述的 UniqueQueue, 我们便可以编写基于时间片(Timeslice)的对象更新了(即分帧进行对象更新):

using UnityEngine;

public class TimesliceUpdateManager : MonoBehaviour
{
    [SerializeField]
    float m_maxTimeslice = 1 / 30.0f;

    UniqueQueue<UpdateObject> m_objectUniqueQueue = new UniqueQueue<UpdateObject>();

    public void UpdateRequest(UpdateObject uo)
    {
        Debug.Assert(uo != null);
        m_objectUniqueQueue.Enqueue(uo);
    }

    void Update()
    {
        if (m_objectUniqueQueue.Count > 0)
        {
            float elapsedTime = 0;

            do
            {
                var curTime = Time.realtimeSinceStartup;

                var uo = m_objectUniqueQueue.Dequeue();
                uo.Update();

                elapsedTime += Time.realtimeSinceStartup - curTime;
            } while (elapsedTime < m_maxTimeslice && m_objectUniqueQueue.Count > 0);
        }
    }
}

TimesliceUpdateManager 的使用方法和上述的 UpdateManager 是一致的,但可以减少因为对象更新而引起的卡顿问题.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK