在Unity游戏开发中,高效地管理游戏循环和协程是至关重要的。今天将深入探讨三个相关的脚本,它们分别实现了单例模式的MonoSystem类、协程工具类以及一个测试脚本,让我们一起来揭开它们的神秘面纱😎!
1. MonoSystem类:实现不继承MonoBehaviour使用Unity生命周期函数🎮
1.1 概述
在Unity开发里,通常只有继承了MonoBehaviour
的类才能使用诸如Update
、LateUpdate
和FixedUpdate
等生命周期函数。而MonoSystem
类的出现,就是为了让不继承MonoBehaviour
的类也能使用这些重要的生命周期函数。它通过单例模式和事件委托的方式,统一管理这些生命周期函数的调用,使得其他类可以方便地注册和移除对这些函数的监听,同时还提供了协程的管理功能。
1.2 代码解析
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace JKFrame
{
/// <summary>
/// 整个游戏只有一个Update、LateUpdate等
/// </summary>
public class MonoSystem : MonoBehaviour
{
private MonoSystem() { }
private static MonoSystem instance;
private Action updateEvent;
private Action lateUpdateEvent;
private Action fixedUpdateEvent;
public static void Init()
{
instance = JKFrameRoot.RootTransform.GetComponent<MonoSystem>();
instance.updateEvent = null;
instance.lateUpdateEvent = null;
instance.fixedUpdateEvent = null;
}
#region 生命周期函数
/// <summary>
/// 添加Update监听
/// </summary>
/// <param name="action"></param>
public static void AddUpdateListener(Action action)
{
instance.updateEvent += action;
}
/// <summary>
/// 移除Update监听
/// </summary>
/// <param name="action"></param>
public static void RemoveUpdateListener(Action action)
{
instance.updateEvent -= action;
}
/// <summary>
/// 添加LateUpdate监听
/// </summary>
/// <param name="action"></param>
public static void AddLateUpdateListener(Action action)
{
instance.lateUpdateEvent += action;
}
/// <summary>
/// 移除LateUpdate监听
/// </summary>
/// <param name="action"></param>
public static void RemoveLateUpdateListener(Action action)
{
instance.lateUpdateEvent -= action;
}
/// <summary>
/// 添加FixedUpdate监听
/// </summary>
/// <param name="action"></param>
public static void AddFixedUpdateListener(Action action)
{
instance.fixedUpdateEvent += action;
}
/// <summary>
/// 移除FixedUpdate监听
/// </summary>
/// <param name="action"></param>
public static void RemoveFixedUpdateListener(Action action)
{
instance.fixedUpdateEvent -= action;
}
private void Update()
{
updateEvent?.Invoke();
}
private void LateUpdate()
{
lateUpdateEvent?.Invoke();
}
private void FixedUpdate()
{
fixedUpdateEvent?.Invoke();
}
#endregion
#region 协程
private Dictionary<object, List<Coroutine>> coroutineDic = new Dictionary<object, List<Coroutine>>();
private static ObjectPoolModule poolModule = new ObjectPoolModule();
/// <summary>
/// 启动一个协程序
/// </summary>
public static Coroutine Start_Coroutine(IEnumerator coroutine)
{
return instance.StartCoroutine(coroutine);
}
/// <summary>
/// 启动一个协程序并且绑定某个对象
/// </summary>
public static Coroutine Start_Coroutine(object obj, IEnumerator coroutine)
{
Coroutine _coroutine = instance.StartCoroutine(coroutine);
if (!instance.coroutineDic.TryGetValue(obj, out List<Coroutine> coroutineList))
{
coroutineList = poolModule.GetObject<List<Coroutine>>();
if (coroutineList == null) coroutineList = new List<Coroutine>();
instance.coroutineDic.Add(obj, coroutineList);
}
coroutineList.Add(_coroutine);
return _coroutine;
}
/// <summary>
/// 停止一个协程序并基于某个对象
/// </summary>
public static void Stop_Coroutine(object obj, Coroutine routine)
{
if (instance.coroutineDic.TryGetValue(obj, out List<Coroutine> coroutineList))
{
instance.StopCoroutine(routine);
coroutineList.Remove(routine);
}
}
/// <summary>
/// 停止一个协程序
/// </summary>
public static void Stop_Coroutine(Coroutine routine)
{
instance.StopCoroutine(routine);
}
/// <summary>
/// 停止某个对象的全部协程
/// </summary>
public static void StopAllCoroutine(object obj)
{
if (instance.coroutineDic.Remove(obj, out List<Coroutine> coroutineList))
{
for (int i = 0; i < coroutineList.Count; i++)
{
instance.StopCoroutine(coroutineList[i]);
}
coroutineList.Clear();
poolModule.PushObject(coroutineList);
}
}
/// <summary>
/// 整个系统全部协程都会停止
/// </summary>
public static void StopAllCoroutine()
{
// 全部数据都会无效
foreach (List<Coroutine> item in instance.coroutineDic.Values)
{
item.Clear();
poolModule.PushObject(item);
}
instance.coroutineDic.Clear();
instance.StopAllCoroutines();
}
#endregion
}
}
1.2.1 单例模式
通过私有构造函数和静态实例instance
,确保MonoSystem
类在游戏中只有一个实例。在Init
方法中,从JKFrameRoot.RootTransform
获取该实例,并初始化事件委托。这样做的好处是,整个游戏中只有一个MonoSystem
实例来管理生命周期函数和协程,避免了多个实例可能带来的冲突。
1.2.2 生命周期函数管理
提供了AddUpdateListener
、RemoveUpdateListener
等方法,允许不继承MonoBehaviour
的其他类注册和移除对Update
、LateUpdate
和FixedUpdate
方法的监听。在对应的生命周期方法中,通过事件委托调用注册的方法。例如,其他类可以通过调用MonoSystem.AddUpdateListener
方法,将自己的Update
逻辑注册到MonoSystem
中,从而实现不继承MonoBehaviour
也能使用Update
函数。
1.2.3 协程管理
使用Dictionary<object, List<Coroutine>>
来管理协程,允许协程与某个对象绑定。提供了启动、停止单个协程以及停止某个对象的全部协程和整个系统的全部协程的方法。这使得协程的管理更加灵活,方便开发者根据不同的需求控制协程的执行。
2. CoroutineTool类:高效协程工具🛠️
2.1 概述
CoroutineTool
类是一个静态类,提供了多种等待时间和帧的方法,旨在避免协程中产生不必要的垃圾回收(GC)。
2.2 代码解析
using System.Collections;
using UnityEngine;
namespace JKFrame
{
/// <summary>
/// 协程工具,避免GC
/// </summary>
public static class CoroutineTool
{
private struct WaitForFrameStruct : IEnumerator
{
public object Current => null;
public bool MoveNext() { return false; }
public void Reset() { }
}
private static WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
private static WaitForFixedUpdate waitForFixedUpdate = new WaitForFixedUpdate();
public static WaitForEndOfFrame WaitForEndOfFrame()
{
return waitForEndOfFrame;
}
public static WaitForFixedUpdate WaitForFixedUpdate()
{
return waitForFixedUpdate;
}
public static IEnumerator WaitForSeconds(float time)
{
float currTime = 0;
while (currTime < time)
{
currTime += Time.deltaTime;
yield return new WaitForFrameStruct();
}
}
public static IEnumerator WaitForSecondsRealtime(float time)
{
float currTime = 0;
while (currTime < time)
{
currTime += Time.unscaledDeltaTime;
yield return new WaitForFrameStruct();
}
}
public static IEnumerator WaitForFrame()
{
yield return new WaitForFrameStruct();
}
public static IEnumerator WaitForFrames(int count = 1)
{
for (int i = 0; i < count; i++)
{
yield return new WaitForFrameStruct();
}
}
}
}
2.2.1 避免GC的设计
通过使用静态的WaitForEndOfFrame
和WaitForFixedUpdate
对象,避免每次调用时创建新的对象,从而减少GC。同时,自定义WaitForFrameStruct
结构体作为等待帧的返回值,也有助于减少内存分配。
2.2.2 多种等待方法
提供了WaitForSeconds
、WaitForSecondsRealtime
、WaitForFrame
和WaitForFrames
等方法,满足不同的等待需求。
3. TestMonoSystem类:测试脚本🚀
3.1 概述
TestMonoSystem
类是一个测试脚本,演示了如何使用MonoSystem
类和CoroutineTool
类,实现不继承MonoBehaviour
的Update
和协程逻辑。
3.2 代码解析
using System.Collections;
using System.Collections.Generic;
using JKFrame;
using UnityEngine;
public class TestMonoSystem : MonoBehaviour
{
private TestMono testMono;
void Start()
{
testMono = new TestMono();
testMono.Init();
}
// Update is called once per frame
void Update()
{
}
}
public class TestMono
{
public void Init()
{
//注册Update逻辑
MonoSystem.AddUpdateListener(Update);
}
private Coroutine coroutine;
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
MonoSystem.RemoveUpdateListener(Update);
}
//开启关闭协程
if (Input.GetKeyDown(KeyCode.S))
{
Debug.Log("按下s键 开始使用协程");
coroutine = this.StartCoroutine(Do());
}
if (Input.GetKeyDown(KeyCode.A))
{
Debug.Log("按下a键 关闭使用协程");
this.StopCoroutine(coroutine);
}
}
private IEnumerator Do()
{
//协程工具
//原版
//yield return null; //null 会被包装,也会产生GC
while (true)
{
yield return CoroutineTool.WaitForSeconds(2f);
Debug.Log("又过了2帧");
}
}
}
3.2.1 注册Update逻辑
在TestMono
类的Init
方法中,通过MonoSystem.AddUpdateListener
方法注册Update
逻辑。
3.2.2 协程控制
在Update
方法中,通过按键W
移除Update
监听,按键S
启动协程,按键A
停止协程。协程中使用CoroutineTool.WaitForSeconds
方法等待2秒,并输出日志。
总结🌟
通过这三个脚本,我们实现了一个高效的游戏循环和协程管理系统。MonoSystem
类让不继承MonoBehaviour
的类也能使用Unity的生命周期函数,CoroutineTool
类提供了避免GC的协程等待方法,TestMonoSystem
类则演示了如何使用这些功能。希望这篇文章能帮助你更好地理解和应用Unity中的协程和游戏循环管理😘!