天天看点

Gameframework架构思路

前言

俗话说得好不想懂底层代码的程序猿,不是好程序猿(那里来的俗话,我也不知道...),Unity引擎如何搭建的,不懂倒是没有关系,毕竟代码没有开源(就算开源了,也不会去读的,毕竟在下智商有限),但是GF代码是E大设计基于Unity框架,用着封装好的十八大金刚,敲着简单的代码,良心不会痛嘛,而且万一想要往框架封装新模块,@作者去添加模块也不太好,我们也不是这样的人,所以看下作者设计框架时的简单思路。
  • 如何下手?

接触GF框架差不多一个月了,本来准备拿它马上练手的,尝试去写个小游戏,但是突然思考良久,不行!作为优秀的程序猿,连框架代码原理都不了解,这么可以如此匆忙的下手(手里已经拿着省力杠杆,但还是用手去翘石头,这样也不太好...),所以准备把每个模块仔仔细细了解清楚,然后把搭建GF框架的思路也明明白白给大脑安排一遍,那个时候再去使用省力杠杆,岂不是甚好?知识都是高浓度转向低浓度的,所以只要把脑袋紧贴着电脑屏幕就可以把知识转移到脑子里。在学习GF框架尽量要做到一个目标:你的框架就是我的框架,我的框架还是我的框架。

Gameframework架构思路

文字水了这么多了,现在应该讨论一下框架应该如何下手学习?当场捕获两个脚本(GameFrameworkEntry和GameEntry),各位可以先从GameFrameworkEntry脚本,毕竟作者把它备注成游戏框架入口,看看游戏框架入口到底写了什么鬼?具体代码如下:

using System;
using System.Collections.Generic;

namespace GameFramework
{
    /// <summary>
    /// 游戏框架入口。
    /// </summary>
    public static class GameFrameworkEntry
    {
        private static readonly GameFrameworkLinkedList<GameFrameworkModule> s_GameFrameworkModules = new GameFrameworkLinkedList<GameFrameworkModule>();

        /// <summary>
        /// 所有游戏框架模块轮询。
        /// </summary>
        /// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
        /// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
        public static void Update(float elapseSeconds, float realElapseSeconds)
        {
            foreach (GameFrameworkModule module in s_GameFrameworkModules)
            {
                module.Update(elapseSeconds, realElapseSeconds);
            }
        }

        /// <summary>
        /// 关闭并清理所有游戏框架模块。
        /// </summary>
        public static void Shutdown()
        {
            for (LinkedListNode<GameFrameworkModule> current = s_GameFrameworkModules.Last; current != null; current = current.Previous)
            {
                current.Value.Shutdown();
            }

            s_GameFrameworkModules.Clear();
            ReferencePool.ClearAll();
            GameFrameworkLog.SetLogHelper(null);
        }

        /// <summary>
        /// 获取游戏框架模块。
        /// </summary>
        /// <typeparam name="T">要获取的游戏框架模块类型。</typeparam>
        /// <returns>要获取的游戏框架模块。</returns>
        /// <remarks>如果要获取的游戏框架模块不存在,则自动创建该游戏框架模块。</remarks>
        public static T GetModule<T>() where T : class
        {
            Type interfaceType = typeof(T);
            if (!interfaceType.IsInterface)
            {
                throw new GameFrameworkException(Utility.Text.Format("You must get module by interface, but '{0}' is not.", interfaceType.FullName));
            }

            if (!interfaceType.FullName.StartsWith("GameFramework."))
            {
                throw new GameFrameworkException(Utility.Text.Format("You must get a Game Framework module, but '{0}' is not.", interfaceType.FullName));
            }

            string moduleName = Utility.Text.Format("{0}.{1}", interfaceType.Namespace, interfaceType.Name.Substring(1));
            Type moduleType = Type.GetType(moduleName);
            if (moduleType == null)
            {
                throw new GameFrameworkException(Utility.Text.Format("Can not find Game Framework module type '{0}'.", moduleName));
            }

            return GetModule(moduleType) as T;
        }

        /// <summary>
        /// 获取游戏框架模块。
        /// </summary>
        /// <param name="moduleType">要获取的游戏框架模块类型。</param>
        /// <returns>要获取的游戏框架模块。</returns>
        /// <remarks>如果要获取的游戏框架模块不存在,则自动创建该游戏框架模块。</remarks>
        private static GameFrameworkModule GetModule(Type moduleType)
        {
            foreach (GameFrameworkModule module in s_GameFrameworkModules)
            {
                if (module.GetType() == moduleType)
                {
                    return module;
                }
            }

            return CreateModule(moduleType);
        }

        /// <summary>
        /// 创建游戏框架模块。
        /// </summary>
        /// <param name="moduleType">要创建的游戏框架模块类型。</param>
        /// <returns>要创建的游戏框架模块。</returns>
        private static GameFrameworkModule CreateModule(Type moduleType)
        {
            GameFrameworkModule module = (GameFrameworkModule)Activator.CreateInstance(moduleType);
            if (module == null)
            {
                throw new GameFrameworkException(Utility.Text.Format("Can not create module '{0}'.", moduleType.FullName));
            }

            LinkedListNode<GameFrameworkModule> current = s_GameFrameworkModules.First;
            while (current != null)
            {
                if (module.Priority > current.Value.Priority)
                {
                    break;
                }

                current = current.Next;
            }

            if (current != null)
            {
                s_GameFrameworkModules.AddBefore(current, module);
            }
            else
            {
                s_GameFrameworkModules.AddLast(module);
            }

            return module;
        }
    }
}
           

通过脚本可以获取到需要的管理器,并且也有框架清理释放函数。还有Update函数可以轮询每个管理器的更新函数,有没有发现这个类是静态的,而且在动态链接库里的。所以大胆猜测一定是某个脚本继承了Mono脚本里然后去调用更新函数(虽然查看一下Update函数所有引用就知道了),这样动态链接库里的每个管理器都接入了更新方案。GameFrameworkModule列表是各位在游戏框架入口类里可以看到的,所以如果有什么和游戏相关的管理器需要扩展时,就必须要继承GameFrameworkModule才可以,可以看一下框架有哪些类是继承了模块抽象类的,具体截图如下:

Gameframework架构思路

目测十八大金钢都在,为什么名字有如此多重复?如果有这种疑问的童靴,可以去看一下partial限定字作用,这样就可以知道如何做到把类代码分开实现的。至于为什么作者把这些管理器脚本分成这么多份,因为管理器下面有分成一些功能,如果把代码全部集中在一起太low,而且类代码可能又长又宽, 导致看起来很不清晰。像我这种菜鸟以前都是集中起来,怂什么?干就完事了。还有一点要知道,如果往框架添加游戏模块时,管理器代码集中写问题倒是不大。但是添加新模块时不能引用Unity相关的库,如果发现添加新模块时引用了Unity相关库,在下就锤爆在座各位的狗头🙄。

Gameframework架构思路

这样怎么办呢?动态链接库代码如何做到与Unity中代码进行对接,其实每个管理器可以有很多个代理类,比如资源管理器可以有很多个资源加载代理类,下载管理器可以有很多下载代理类,命名差不多是xxxHelper,通俗的说它们的领导就是这些管理器,各位只需要在动态链接库里拟定一下接口,在Unity中具体实现接口即可,具体是什么意思?各位参照设置管理器(SettingManager)就知道了,首先设置代理接口代码如下:

namespace GameFramework.Setting
{
    /// <summary>
    /// 游戏配置辅助器接口。
    /// </summary>
    public interface ISettingHelper
    {
        /// <summary>
        /// 加载游戏配置。
        /// </summary>
        /// <returns>是否加载游戏配置成功。</returns>
        bool Load();

        /// <summary>
        /// 保存游戏配置。
        /// </summary>
        /// <returns>是否保存游戏配置成功。</returns>
        bool Save();

        /// <summary>
        /// 检查是否存在指定游戏配置项。
        /// </summary>
        /// <param name="settingName">要检查游戏配置项的名称。</param>
        /// <returns>指定的游戏配置项是否存在。</returns>
        bool HasSetting(string settingName);

        /// <summary>
        /// 移除指定游戏配置项。
        /// </summary>
        /// <param name="settingName">要移除游戏配置项的名称。</param>
        void RemoveSetting(string settingName);

        /// <summary>
        /// 清空所有游戏配置项。
        /// </summary>
        void RemoveAllSettings();

        /// <summary>
        /// 从指定游戏配置项中读取布尔值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的布尔值。</returns>
        bool GetBool(string settingName);

        /// <summary>
        /// 从指定游戏配置项中读取布尔值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的布尔值。</returns>
        bool GetBool(string settingName, bool defaultValue);

        /// <summary>
        /// 向指定游戏配置项写入布尔值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的布尔值。</param>
        void SetBool(string settingName, bool value);

        /// <summary>
        /// 从指定游戏配置项中读取整数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的整数值。</returns>
        int GetInt(string settingName);

        /// <summary>
        /// 从指定游戏配置项中读取整数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的整数值。</returns>
        int GetInt(string settingName, int defaultValue);

        /// <summary>
        /// 向指定游戏配置项写入整数值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的整数值。</param>
        void SetInt(string settingName, int value);

        /// <summary>
        /// 从指定游戏配置项中读取浮点数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的浮点数值。</returns>
        float GetFloat(string settingName);

        /// <summary>
        /// 从指定游戏配置项中读取浮点数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的浮点数值。</returns>
        float GetFloat(string settingName, float defaultValue);

        /// <summary>
        /// 向指定游戏配置项写入浮点数值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的浮点数值。</param>
        void SetFloat(string settingName, float value);

        /// <summary>
        /// 从指定游戏配置项中读取字符串值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的字符串值。</returns>
        string GetString(string settingName);

        /// <summary>
        /// 从指定游戏配置项中读取字符串值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的字符串值。</returns>
        string GetString(string settingName, string defaultValue);

        /// <summary>
        /// 向指定游戏配置项写入字符串值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的字符串值。</param>
        void SetString(string settingName, string value);

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <typeparam name="T">要读取对象的类型。</typeparam>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的对象。</returns>
        T GetObject<T>(string settingName);

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <param name="objectType">要读取对象的类型。</param>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的对象。</returns>
        object GetObject(Type objectType, string settingName);

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <typeparam name="T">要读取对象的类型。</typeparam>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultObj">当指定的游戏配置项不存在时,返回此默认对象。</param>
        /// <returns>读取的对象。</returns>
        T GetObject<T>(string settingName, T defaultObj);

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <param name="objectType">要读取对象的类型。</param>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultObj">当指定的游戏配置项不存在时,返回此默认对象。</param>
        /// <returns>读取的对象。</returns>
        object GetObject(Type objectType, string settingName, object defaultObj);

        /// <summary>
        /// 向指定游戏配置项写入对象。
        /// </summary>
        /// <typeparam name="T">要写入对象的类型。</typeparam>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="obj">要写入的对象。</param>
        void SetObject<T>(string settingName, T obj);

        /// <summary>
        /// 向指定游戏配置项写入对象。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="obj">要写入的对象。</param>
        void SetObject(string settingName, object obj);
    }
}
           

这里设置代理接口就是在要打包成动态链接库的解决方案里拟定的,因为每个管理器都需要持有它们的代理类,所以设置管理器类里一定有的参数就是ISettingHelper SettingHelper,继续?看看Unity中如何实现ISettingHelper接口的,部分代码如下:

public abstract class SettingHelperBase : MonoBehaviour, ISettingHelper
           

所以我们需要添加其他设置方案时(比如将本地设置保存成byte文件),继承接口并且去实现即可,作者这里默认实现的设置代理类是对Unity的PlayerPrefs的一层封装,如此去设计GF简直是天才之作(先把作者吹一波),这样就可以具体实现功能的插拔,比如有天不想使用PlayerPrefs去实现设置模块保存数据,这样再额外实现一个设置代理类即可。

Gameframework架构思路
  •  各大管理器组件又是干嘛的?

故事还是从GameEntry脚本开始吧!盯着电脑屏幕半天,感觉这个脚本和上面开始说的那个脚本简直神似???只不过脚本在Unity解决方案里的,多了注册模块的函数,用来将管理器组件添加到列表里,缺少了更新函数(想想也对的,都是在Unity中的解决方案了,还要什么更新函数?)。因为组件是挂载到Unity实际对象上的,所以GF框架在Awake时直接去注册了,而动态链接库里是获取管理器时,如果无法找到管理器的话就马上创建管理器然后保存到列表里,还是来看看组件们是如何被注册吧,具体代码如下:

namespace UnityGameFramework.Runtime
{
    /// <summary>
    /// 游戏框架组件抽象类。
    /// </summary>
    public abstract class GameFrameworkComponent : MonoBehaviour
    {
        /// <summary>
        /// 游戏框架组件初始化。
        /// </summary>
        protected virtual void Awake()
        {
            GameEntry.RegisterComponent(this);
        }
    }
}
           

只要管理器组件继承了组件抽象类时,就可以在Awake时注册到列表里,当然如果要重写Awake函数时也是没有问题的 ,但是要记住在Awake开头调用写上以下代码:

base.Awake();
           

可能各位会说这个就不必多说了,这个不是基础知识嘛,那个那个...,就随便提一嘴而已,各位接下来就来分析一下各个管理器组件脚本的具体的用处是什么?动态链接库里的管理器总需要有去调用和初始化参数的脚本,这个时候就需要像月老牵线一样,天下情侣都是一对的(如果你们敢说国外有些地方都可以一夫多妻,我只能对各位说渣男!),所以一个管理器组件对应上一个管理器,来看看设置管理器组件到底做了什么事情,具体代码如下:

//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2020 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:[email protected]
//------------------------------------------------------------

using System;

namespace GameFramework.Setting
{
    /// <summary>
    /// 游戏配置管理器。
    /// </summary>
    internal sealed class SettingManager : GameFrameworkModule, ISettingManager
    {
        private ISettingHelper m_SettingHelper;

        /// <summary>
        /// 初始化游戏配置管理器的新实例。
        /// </summary>
        public SettingManager()
        {
            m_SettingHelper = null;
        }

        /// <summary>
        /// 游戏配置管理器轮询。
        /// </summary>
        /// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
        /// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
        internal override void Update(float elapseSeconds, float realElapseSeconds)
        {
        }

        /// <summary>
        /// 关闭并清理游戏配置管理器。
        /// </summary>
        internal override void Shutdown()
        {
            Save();
        }

        /// <summary>
        /// 设置游戏配置辅助器。
        /// </summary>
        /// <param name="settingHelper">游戏配置辅助器。</param>
        public void SetSettingHelper(ISettingHelper settingHelper)
        {
            if (settingHelper == null)
            {
                throw new GameFrameworkException("Setting helper is invalid.");
            }

            m_SettingHelper = settingHelper;
        }

        /// <summary>
        /// 加载游戏配置。
        /// </summary>
        /// <returns>是否加载游戏配置成功。</returns>
        public bool Load()
        {
            return m_SettingHelper.Load();
        }

        /// <summary>
        /// 保存游戏配置。
        /// </summary>
        /// <returns>是否保存游戏配置成功。</returns>
        public bool Save()
        {
            return m_SettingHelper.Save();
        }

        /// <summary>
        /// 检查是否存在指定游戏配置项。
        /// </summary>
        /// <param name="settingName">要检查游戏配置项的名称。</param>
        /// <returns>指定的游戏配置项是否存在。</returns>
        public bool HasSetting(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.HasSetting(settingName);
        }

        /// <summary>
        /// 移除指定游戏配置项。
        /// </summary>
        /// <param name="settingName">要移除游戏配置项的名称。</param>
        public void RemoveSetting(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.RemoveSetting(settingName);
        }

        /// <summary>
        /// 清空所有游戏配置项。
        /// </summary>
        public void RemoveAllSettings()
        {
            m_SettingHelper.RemoveAllSettings();
        }

        /// <summary>
        /// 从指定游戏配置项中读取布尔值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的布尔值。</returns>
        public bool GetBool(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetBool(settingName);
        }

        /// <summary>
        /// 从指定游戏配置项中读取布尔值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的布尔值。</returns>
        public bool GetBool(string settingName, bool defaultValue)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetBool(settingName, defaultValue);
        }

        /// <summary>
        /// 向指定游戏配置项写入布尔值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的布尔值。</param>
        public void SetBool(string settingName, bool value)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.SetBool(settingName, value);
        }

        /// <summary>
        /// 从指定游戏配置项中读取整数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的整数值。</returns>
        public int GetInt(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetInt(settingName);
        }

        /// <summary>
        /// 从指定游戏配置项中读取整数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的整数值。</returns>
        public int GetInt(string settingName, int defaultValue)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetInt(settingName, defaultValue);
        }

        /// <summary>
        /// 向指定游戏配置项写入整数值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的整数值。</param>
        public void SetInt(string settingName, int value)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.SetInt(settingName, value);
        }

        /// <summary>
        /// 从指定游戏配置项中读取浮点数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的浮点数值。</returns>
        public float GetFloat(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetFloat(settingName);
        }

        /// <summary>
        /// 从指定游戏配置项中读取浮点数值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的浮点数值。</returns>
        public float GetFloat(string settingName, float defaultValue)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetFloat(settingName, defaultValue);
        }

        /// <summary>
        /// 向指定游戏配置项写入浮点数值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的浮点数值。</param>
        public void SetFloat(string settingName, float value)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.SetFloat(settingName, value);
        }

        /// <summary>
        /// 从指定游戏配置项中读取字符串值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的字符串值。</returns>
        public string GetString(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetString(settingName);
        }

        /// <summary>
        /// 从指定游戏配置项中读取字符串值。
        /// </summary>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultValue">当指定的游戏配置项不存在时,返回此默认值。</param>
        /// <returns>读取的字符串值。</returns>
        public string GetString(string settingName, string defaultValue)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetString(settingName, defaultValue);
        }

        /// <summary>
        /// 向指定游戏配置项写入字符串值。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="value">要写入的字符串值。</param>
        public void SetString(string settingName, string value)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.SetString(settingName, value);
        }

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <typeparam name="T">要读取对象的类型。</typeparam>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的对象。</returns>
        public T GetObject<T>(string settingName)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetObject<T>(settingName);
        }

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <param name="objectType">要读取对象的类型。</param>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <returns>读取的对象。</returns>
        public object GetObject(Type objectType, string settingName)
        {
            if (objectType == null)
            {
                throw new GameFrameworkException("Object type is invalid.");
            }

            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetObject(objectType, settingName);
        }

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <typeparam name="T">要读取对象的类型。</typeparam>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultObj">当指定的游戏配置项不存在时,返回此默认对象。</param>
        /// <returns>读取的对象。</returns>
        public T GetObject<T>(string settingName, T defaultObj)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetObject(settingName, defaultObj);
        }

        /// <summary>
        /// 从指定游戏配置项中读取对象。
        /// </summary>
        /// <param name="objectType">要读取对象的类型。</param>
        /// <param name="settingName">要获取游戏配置项的名称。</param>
        /// <param name="defaultObj">当指定的游戏配置项不存在时,返回此默认对象。</param>
        /// <returns>读取的对象。</returns>
        public object GetObject(Type objectType, string settingName, object defaultObj)
        {
            if (objectType == null)
            {
                throw new GameFrameworkException("Object type is invalid.");
            }

            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            return m_SettingHelper.GetObject(objectType, settingName, defaultObj);
        }

        /// <summary>
        /// 向指定游戏配置项写入对象。
        /// </summary>
        /// <typeparam name="T">要写入对象的类型。</typeparam>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="obj">要写入的对象。</param>
        public void SetObject<T>(string settingName, T obj)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.SetObject(settingName, obj);
        }

        /// <summary>
        /// 向指定游戏配置项写入对象。
        /// </summary>
        /// <param name="settingName">要写入游戏配置项的名称。</param>
        /// <param name="obj">要写入的对象。</param>
        public void SetObject(string settingName, object obj)
        {
            if (string.IsNullOrEmpty(settingName))
            {
                throw new GameFrameworkException("Setting name is invalid.");
            }

            m_SettingHelper.SetObject(settingName, obj);
        }
    }
}
           

看来组件做的事情也不少,动态链接库里管理器持有代理对象,而管理器组件持有着管理器对象,可能有些人会说为什么就一定要使用动态链接库里去调用,就不能在Unity解决方案里直接写嘛?可以是可以,但是主要的代码写到动态链接里,性能方面会更加好,管理器组件持有管理器去调用方法即可。给大家画个比较简单的GF架构图,让各位更好的理解设计思路。结构思路图如下:

Gameframework架构思路

这样需要添加新管理器时,就可以按照这个思路去设计了,武林秘籍已经倾囊传授给各位了,接下来各位去看看十八模块的事件是如何触发起来的,比如设置成功以后需要有一个成功的回调函数。

  • 管理器事件触发机制

每个管理器都需要有一定回调函数,通知它是否成功的回调,成功或失败之后总需要干点什么事情,在管理器组件里寥寥无几的事件代码,只需要在Awake里给它添加到一个多播事件里即可,以场景管理器组件(SceneComponent)作为例子,具体代码如下:

m_SceneManager.LoadSceneSuccess += OnLoadSceneSuccess;
            m_SceneManager.LoadSceneFailure += OnLoadSceneFailure;
           

查找所有引用看一下到底那里调用这个多播事件,先给各位截个图吧,毕竟这个算是一个转折点了,诸君请看下图:

Gameframework架构思路

可以看到事件的回调时放到这个函数里了,但是具体这个函数在那里调用呢?让我们继续查找一下引用,发现一个事件管理器把所有回调函数全部封装到一个对象里了,具体代码段如下:

m_LoadSceneCallbacks = new LoadSceneCallbacks(LoadSceneSuccessCallback, LoadSceneFailureCallback, LoadSceneUpdateCallback, LoadSceneDependencyAssetCallback);
           

所以需要找到m_LoadSceneCallbacks对象引用即可,最后的最后!!!可以发现的是把对象交给任务池管理器了,添加到任务管理池里面,大概是放到类似列表的数据结构里进行轮回,先给各位看一下跳转到的添加到任务池的代码段。

public void LoadScene(string sceneAssetName, int priority, LoadSceneCallbacks loadSceneCallbacks, object userData)
            {
                ResourceInfo? resourceInfo = null;
                string[] dependencyAssetNames = null;

                if (!CheckAsset(sceneAssetName, out resourceInfo, out dependencyAssetNames))
                {
                    string errorMessage = Utility.Text.Format("Can not load scene '{0}'.", sceneAssetName);
                    if (loadSceneCallbacks.LoadSceneFailureCallback != null)
                    {
                        loadSceneCallbacks.LoadSceneFailureCallback(sceneAssetName, LoadResourceStatus.NotReady, errorMessage, userData);
                        return;
                    }

                    throw new GameFrameworkException(errorMessage);
                }

                LoadSceneTask mainTask = LoadSceneTask.Create(sceneAssetName, priority, resourceInfo.Value, dependencyAssetNames, loadSceneCallbacks, userData);
                foreach (string dependencyAssetName in dependencyAssetNames)
                {
                    if (!LoadDependencyAsset(dependencyAssetName, priority, mainTask, userData))
                    {
                        string errorMessage = Utility.Text.Format("Can not load dependency asset '{0}' when load scene '{1}'.", dependencyAssetName, sceneAssetName);
                        if (loadSceneCallbacks.LoadSceneFailureCallback != null)
                        {
                            loadSceneCallbacks.LoadSceneFailureCallback(sceneAssetName, LoadResourceStatus.DependencyError, errorMessage, userData);
                            return;
                        }

                        throw new GameFrameworkException(errorMessage);
                    }
                }

                m_TaskPool.AddTask(mainTask);
            }
           

至于TaskPool里代码确实是在Update里执行,任务池不断的更新,执行任务以后就把任务对象从列表里移除,给各位看一下任务池的Update里是如何执行的,调用截图如下:

Gameframework架构思路

 进入任务队列以后,实际调用的函数是通过ProcessRunningTasks,这里又有一个概念就是任务代理类,说起来有点繁琐,这里就先简单的画龙点睛一下,具体的调用原理可以看一下在下写的任务池原理分析,首先给各位看看ProcessRunningTasks函数到底做了那些事情吧!具体代码如下:

private void ProcessRunningTasks(float elapseSeconds, float realElapseSeconds)
        {
            LinkedListNode<ITaskAgent<T>> current = m_WorkingAgents.First;
            while (current != null)
            {
                T task = current.Value.Task;
                if (!task.Done)
                {
                    current.Value.Update(elapseSeconds, realElapseSeconds);
                    current = current.Next;
                    continue;
                }

                LinkedListNode<ITaskAgent<T>> next = current.Next;
                current.Value.Reset();
                m_FreeAgents.Push(current.Value);
                m_WorkingAgents.Remove(current);
                ReferencePool.Release(task);
                current = next;
            }
        }
           

到这里的current.Value.Update就是调到管理器需要执行的回调函数(m_LoadSceneCallbacks),各位是不是慢慢忘记了标题...好像这么调着调着,代码就讲到这里了,不要惊慌!只需要看过任务池文章,你的思路就会渐渐清楚了(虽然写这篇文章时,任务池文章还没有开始写,哈哈哈哈哈哈)。

Gameframework架构思路

这里Update函数具体调用代码给各位看一下,具体是在DefaultLoadResourceAgentHelper里,如图所示:

Gameframework架构思路

到这里算是结束了...还有一点差点忘记了,就是调用半天了,其实回调函数里只是调用m_EventComponent.Fire函数,这个函数又是什么作用?各位可以到以下传送门:

https://blog.csdn.net/m0_37920739/article/details/104723210