Unity_JK框架【4】MonoSystem 和 协程工具类 的剖析与实践

发布于:2025-05-08 ⋅ 阅读:(27) ⋅ 点赞:(0)

在Unity游戏开发中,高效地管理游戏循环和协程是至关重要的。今天将深入探讨三个相关的脚本,它们分别实现了单例模式的MonoSystem类、协程工具类以及一个测试脚本,让我们一起来揭开它们的神秘面纱😎!

1. MonoSystem类:实现不继承MonoBehaviour使用Unity生命周期函数🎮

1.1 概述

在Unity开发里,通常只有继承了MonoBehaviour的类才能使用诸如UpdateLateUpdateFixedUpdate等生命周期函数。而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 生命周期函数管理

提供了AddUpdateListenerRemoveUpdateListener等方法,允许不继承MonoBehaviour的其他类注册和移除对UpdateLateUpdateFixedUpdate方法的监听。在对应的生命周期方法中,通过事件委托调用注册的方法。例如,其他类可以通过调用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的设计

通过使用静态的WaitForEndOfFrameWaitForFixedUpdate对象,避免每次调用时创建新的对象,从而减少GC。同时,自定义WaitForFrameStruct结构体作为等待帧的返回值,也有助于减少内存分配。

2.2.2 多种等待方法

提供了WaitForSecondsWaitForSecondsRealtimeWaitForFrameWaitForFrames等方法,满足不同的等待需求。

3. TestMonoSystem类:测试脚本🚀

3.1 概述

TestMonoSystem类是一个测试脚本,演示了如何使用MonoSystem类和CoroutineTool类,实现不继承MonoBehaviourUpdate和协程逻辑。

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中的协程和游戏循环管理😘!


网站公告

今日签到

点亮在社区的每一天
去签到