Unity遊戲框架設計之訊息管理器
簡單介紹
訊息管理器又可以稱為工作管理員,主要解決延遲執行某些程式碼的問題。比如,我們希望一些程式碼可以延遲指定的時間後才執行,或者我們希望一些程式碼可以在固定的時間執行,又或者我們希望一些程式碼可以每隔一段時間就執行一次。訊息管理器就是為了實現上述功能而開發的。下述的訊息管理器是基於 Unity 的協程進行實現。
程式碼設計
public class MessageManager : SingletonMono<MessageManager>
{
private readonly Dictionary<string, MessageConfig> _messageSet = new();
private void CreateMessage<T>(string messageName, float delayTime, float intervalTime, int cycleCount, Action<T> action, T param)
{
if (FloatUtils.IsEqualsTo(intervalTime, 0f) && cycleCount <= -1)
{
LogManager.Instance.DebugWarning($"無法建立間隔時間為 0 的無限迴圈訊息。messageName={messageName}");
return;
}
IEnumerator task = CycleMessage(messageName, 0f, action, param);
MessageConfig messageConfig = new MessageConfig(task, delayTime, intervalTime, cycleCount);
_messageSet.Add(messageName, messageConfig);
}
public void PublishDelayMessage<T>(string messageName, float delayTime, Action<T> action, T param)
{
CreateMessage(messageName, delayTime, float.MaxValue, 1, action, param);
PublishMessage(messageName);
}
public void PublishFiniteCycleMessage<T>(string messageName, float delayTime, float intervalTime, int cycleCount, Action<T> action, T param)
{
CreateMessage(messageName, delayTime, intervalTime, cycleCount, action, param);
PublishMessage(messageName);
}
public void PublishInfiniteCycleMessage<T>(string messageName, float delayTime, float intervalTime, Action<T> action, T param)
{
CreateMessage(messageName, delayTime, intervalTime, -1, action, param);
PublishMessage(messageName);
}
public void PublishTimingMessage<T>(string messageName, float fixedTime, Action<T> action, T param)
{
CreateMessage(messageName, fixedTime - Time.realtimeSinceStartup, float.MaxValue, 1, action, param);
PublishMessage(messageName);
}
private void PublishMessage(string messageName)
{
if (!_messageSet.ContainsKey(messageName))
{
LogManager.Instance.DebugWarning($"訊息不存在。messageName={messageName}");
return;
}
MessageConfig messageConfig = _messageSet[messageName];
StartCoroutine(SleepTime(messageConfig.DelayTime, () => StartCoroutine(messageConfig.Message)));
}
public void RemoveMessage(string messageName)
{
if (!_messageSet.ContainsKey(messageName))
{
LogManager.Instance.DebugWarning($"訊息不存在。messageName={messageName}");
return;
}
MessageConfig messageConfig = _messageSet[messageName];
StopCoroutine(messageConfig.Message);
_messageSet.Remove(messageName);
}
public void RemoveMessageIfExists(string messageName)
{
if (_messageSet.ContainsKey(messageName))
{
MessageConfig messageConfig = _messageSet[messageName];
_messageSet.Remove(messageName);
StopCoroutine(messageConfig.Message);
}
}
public bool ContainMessage(string messageName)
{
return _messageSet.ContainsKey(messageName);
}
private IEnumerator CycleMessage<T>(string messageName, float intervalTime, Action<T> action, T param)
{
if (!_messageSet.ContainsKey(messageName))
{
yield break;
}
MessageConfig messageConfig = _messageSet[messageName];
if (messageConfig.CycleCount >= 1)
{
messageConfig.CycleCount--;
}
else if (messageConfig.CycleCount == 0)
{
_messageSet.Remove(messageName);
yield break;
}
yield return new WaitForSeconds(intervalTime);
action.Invoke(param);
if (FloatUtils.IsEqualsTo(intervalTime, 0f) && FloatUtils.IsNotEqualsTo(messageConfig.IntervalTime, 0f))
{
messageConfig.Message = CycleMessage(messageName, messageConfig.IntervalTime, action, param);
}
else
{
messageConfig.Message = CycleMessage(messageName, intervalTime, action, param);
}
StartCoroutine(messageConfig.Message);
}
private IEnumerator SleepTime(float delayTime, Action callback)
{
if (FloatUtils.IsGreaterThan(delayTime, 0f))
{
yield return new WaitForSeconds(delayTime);
}
callback();
}
private class MessageConfig
{
public IEnumerator Message;
public readonly float DelayTime;
public readonly float IntervalTime;
public int CycleCount;
public MessageConfig(IEnumerator message, float delayTime, float intervalTime, int cycleCount)
{
Message = message;
DelayTime = delayTime;
IntervalTime = intervalTime;
CycleCount = cycleCount;
}
}
}
程式碼說明
(一)實現延遲訊息(倒數計時器)、定時訊息(定時器)、有限迴圈訊息和無限迴圈訊息(迴圈排程器)。
(二)messageName 命名格式推薦 $"{ScriptName}{MethodName}{ID}" 。必須為所有的 messageName 新增唯一的 ID 字尾,防止出現衝突。唯一 ID 可以透過時間戳和遞增的計數器來實現。由於在 Unity 中是單執行緒執行的,因此不必考慮執行緒安全問題,也不必考慮加鎖問題。
(三)在傳遞的 action 引數中,必須先對當前執行環境進行檢查,才能執行訊息處理的程式碼。因為訊息可能是延遲的,而延遲後的執行環境可能並不滿足訊息處理的條件。比如訊息依賴的某些物件被禁用或銷燬,此時應當停止訊息處理,而是退出函式,甚至直接刪除訊息。
後記
由於個人能力有限,文中不免存在疏漏之處,懇求大家斧正,一起交流,共同進步。