02 一切从管理器出发

万事开头难!

想了半天,应该从哪儿开始呢,算了还是别开始了…

哈哈开玩笑的让我们先新建一个Unity项目,并给咱们这个框架取一个响当当的名字:SimpleGameFramework,简称SGF(好吧我承认名字很随便,你随意

项目结构:

SimpleGameFramework

好吧这只是一个毫无看点的空项目…那么我们该从哪儿开始学习呢?

在上一篇的结尾我曾经提出过一个想法,即:把系统分为很多不同的模块,每个模块只关心自己的事情。那么很自然的会有一个问题,如果我们的项目有很多模块,那我们要怎么统一管理这些模块呢?当然我们也可以不统一管理…随便管理就好,但后果就是当模块数量越来越多的时候,我们就开始难以控制他们,不知道什么模块在什么时候被什么调用…

可能大家还不太能体会到统一管理所有模块的好处,但没关系,就假设这是我们的一个需求好了!即,我们需要有一个地方能够管理我们所有的模块,那就叫这个类为SGFEntry好了,即框架的入口,所有模块都从这里开始产生效果。

那我再问个问题,咱们这个SGFEntry类,可以是普普通通的随便一个类吗?答案当然是否定的…试想一下,如果这个类很普通,你可以随便的new,那我们项目里就可以存在很多的入口,哪怕不考虑代码细节,光从逻辑上想这都是很搞笑的,入口当然有且只有一个!那该怎么实现呢?

从单例模式开始

单例模式是很好用的一个模式,他可以保证我们的类在整个项目的运行期间都是唯一的,因此不管我们从什么地方调用,进入的都是唯一的那个类,也就是唯一的入口!Perfect!完美符合我们的想法!

我们可以直接把SGFEntry这个类写成单例类,当然这就可以满足我们的要求,但在这里我提供另一个思路

我们先写一个单例模板类,继承这个模板的类会是一个单例类

namespace SimpleGameFramework.Core
{
    /// <summary>
    /// 脚本的单例模板基类
    /// </summary>
    public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
    {
        /// 保存实际的对象
        protected static T _instance;
        /// 对外提供访问接口
        /// 即我们可以使用Singleton<T>.Instance来访问_instance,而当_instance不存在时,我们会对其初始化
        /// 注意,这不会在程序运行的时候初始化这个类,只有我们访问时,才可能进行初始化
        public static T Instance
        {
            get
            {
                // 如果不存在实例,尝试进行初始化
                if (_instance == null)
                {
                    //从场景中找T脚本的对象,这会找到所有T对象,并返回最后一个加入场景中的那个
                    _instance = FindObjectOfType<T>();

                    // 如果T对象的数量比1大,那很明显场景里出现了多个单例类,必然是有错误的
                    if (FindObjectsOfType<T>().Length > 1)
                    {
                        Debug.LogError("场景中的单例脚本数量 > 1:" + _instance.GetType().ToString());
                        return _instance;
                    }

                    // 场景中找不到的情况,那就要进行初始化了
                    if (_instance == null)
                    {	
                        
                        string instanceName = typeof(T).Name;
                        GameObject instanceGO = GameObject.Find(instanceName);
						// 我们会生成一个空的gameobject并挂在对应脚本,如果场景里有同名的物体,自然就会有问题 
                        if (instanceGO == null)
                        {
                            instanceGO = new GameObject(instanceName);
                            DontDestroyOnLoad(instanceGO);
                            _instance = instanceGO.AddComponent<T>();
                            // 删除这一行代码并运行游戏可以很明显的发现变化,他的作用是切换场景的时候不销毁指定对象
                            DontDestroyOnLoad(_instance);
                        }
                        else
                        {
                            //场景中已存在同名游戏物体时就打印提示
                            Debug.LogError("场景中已存在单例脚本所挂载的游戏物体:" + instanceGO.name);
                        }
                    }
                }

                return _instance;
            }
        }

        void OnDestroy()
        {
            _instance = null;
        }
    }
}

并新建一个SimpleGameFramework,再新建一个Core文件夹,把单例模板放入其中,在这个文件夹中,我们会存放我们的核心类

SimpleGameFramework

  • Core

    Singleton.cs

管理器

继续我们的思维探索,我们现在有很多模块,每一个模块都需要有自己的管理器,并且所有的模块会被SGFEntry统一管理,即总管理器,这种设计模式我愿称其为Manager Of Managers

那很明显,虽然管理器是不同的,但他们都是管理器,都需要被统一管理,那自然他们都需要继承自同一个基类,这应该是很好理解的吧,所以我们来写一个管理器的基类,定义管理器的一些核心属性和方法~

namespace SimpleGameFramework.Core
{
    /// <summary>
    /// 模块管理基类
    /// </summary>
    public abstract class ManagerBase
    {
        /// 模块优先级,优先级高的模块会被先SGFEntry处理
        public abstract int Priority { get; }

        /// 初始化模块
        public abstract void Init();

        /// 模块更新
        public abstract void Update(float time);

        /// 关闭模块
        public abstract void ShutDown();
    }
}

我先不多解是其中的具体含义,但提醒同学们一点,大家发现这个类竟然只是一个普通的C#类而没有继承Monobehaviour!这说明什么?这说明这个类是没法被Unity所Update的!那么我们要怎么更新我们的模块呢?嘿嘿,卖个关子,看完下面的内容大家肯定都能理解了~

好了我们现在写我们的框架总入口:SGFEntry!

首先,我们的框架入口需要继承我们的单例模板,表示我们的的入口是全局唯一的!

namespace SimpleGameFramework.Core
{
    /// 框架入口,管理所有的模块
    public class SGFEntry : Singleton<SGFEntry>
    {
        
    }
}

定义我们的管理器链表,这里维护了我们所有的管理器,并按照优先级排序

namespace SimpleGameFramework.Core
{
    public class SGFEntry : Singleton<SGFEntry>
    {
        /// 维护了所有的管理器,并按照管理器的优先级由大到小排序
		private LinkedList<ManagerBase> m_Managers = new LinkedList<ManagerBase>();
    }
}

定义获取和新建管理器的方法

using System;
using System.Collections.Generic;
using UnityEngine;

namespace SimpleGameFramework.Core
{
    public class SGFEntry : Singleton<SGFEntry>
    {
        /// 从管理器链表中获取指定的管理器,如果没有,那会创建一个对应的管理器,并加入管理器链表 
        public TManager GetManager<TManager>() where TManager : ManagerBase
        {
            Type managerType = typeof(TManager);
            // 检查是否存在对应管理器
            foreach (var manager in m_Managers)
            {
                if (manager.GetType() == managerType)
                {
                    return manager as TManager;
                }
            }
            // 不存在就创建
            return CreateManager(managerType) as TManager;
        }
        
        /// 创建一个管理器 
        private ManagerBase CreateManager(Type managerType)
        {
            ManagerBase manager = Activator.CreateInstance(managerType) as ManagerBase;

            if (manager == null)
            {
                throw new Exception("创建管理器失败...");
            }
            
            // 根据模块优先级决定它在链表里的位置
            LinkedListNode<ManagerBase> current = m_Managers.First;
            while (current != null)
            {
 
                if (manager.Priority < current.Value.Priority)
                {
                    break;
                }
 
                current = current.Next;
            }
            // 如果存在current,那么会在他前面插入manager
            if (current != null)
            {
 
                m_Managers.AddBefore(current, manager);
            }
            // 如果不存在current,说明所有节点的优先级都比当前manager高,那就插入到末尾
            else
            {
 
                m_Managers.AddLast(manager);
            }
 
            // 初始化管理器
            manager.Init();
            return manager;
        }
    }
}

SGFEntry是继承自单例脚本的,而单例模板是继承了Monobehaviour的,所以咱们的入口是存在Update等Mono方法的~

添加更新管理器的方法

using System;
using System.Collections.Generic;
using UnityEngine;


namespace SimpleGameFramework.Core
{
    public class SGFEntry : Singleton<SGFEntry>
    {
        /// 依次更新所有的管理器
        private void Update()
        {
            foreach (var manager in m_Managers)
            {
                manager.Update(Time.deltaTime);
            }
        }

        /// 倒序销毁所有的管理器
        private void OnDestroy()
        {
            for (var manager = m_Managers.Last; manager != null; manager = manager.Previous)
            {
                manager.Value.ShutDown();
            }
            m_Managers.Clear();
        }
    }
}

好啦,核心类暂时就是这些,但就凭这些我们还没办法测试…因为我们压根还没有任何一个管理器…

举个例子,假如我们写了一个UI管理器UIManager,那我们就可以这么调用

public class UIManager : ManagerBase
{
    public void OpenUI(UI ui);
}

public class TestMain : MonoBehaviour
{
    public void Start()
    {
    	SGFEntry.Instance.GetManager<UIManager>.OpenUI(ui);    
    }
}

目前咱们的结构是这样的:

SimpleGameFramework

  • Core

    SIngleton.cs

    ManagerBase.cs

    SGFEntry.cs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值