Gameframework(Config初探篇)

前言

听过游戏数值策划岗位的,都知道他们是为了游戏数值平衡和各种公式设计,以及整个系统平衡。策划可不必懂编程,但配置文件都是靠策划修改的,游戏需要高度配置的(为了热更新、程序便于维护和分工明确),思考如果把数值写在代码中,其实会好傻的,接下来学习下GF的配置模块做了些什么?

1 默认配置辅助器

看过GF架构思路文章的童靴,应该知道学习GF模块时,先从默认辅助器入手,毕竟模块管理器持有模块辅助器实现功能的,模块组件持有模块管理器去实际的调用(阿勒,这么又说一遍了…),所以直接学习下DefaultConfigHelper,以后配置模块文章会考虑实现xlsx、csv、json辅助器(个人偏向于csv),默认配置辅助器是txt格式的,接下来先展示代码:

using GameFramework;
using GameFramework.Config;
using System;
using System.IO;
using System.Text;
using UnityEngine;

namespace UnityGameFramework.Runtime
{
    /// <summary>
    /// 默认全局配置辅助器。
    /// </summary>
    public class DefaultConfigHelper : ConfigHelperBase
    {
        private static readonly string[] RowSplitSeparator = new string[] { "\r\n", "\r", "\n" };
        private static readonly string[] ColumnSplitSeparator = new string[] { "\t" };
        private const int ColumnCount = 4;

        private ResourceComponent m_ResourceComponent = null;
        private IConfigManager m_ConfigManager = null;

        /// <summary>
        /// 解析全局配置。
        /// </summary>
        public override bool ParseConfig(string text, object userData)
        {
            try
            {
                string[] rowTexts = text.Split(RowSplitSeparator, StringSplitOptions.None);
                for (int i = 0; i < rowTexts.Length; i++)
                {
                    if (rowTexts[i].Length <= 0 || rowTexts[i][0] == '#')
                    {
                        continue;
                    }

                    string[] splitLine = rowTexts[i].Split(ColumnSplitSeparator, StringSplitOptions.None);
                    if (splitLine.Length != ColumnCount)
                    {
                        Log.Warning("Can not parse config '{0}'.", text);
                        return false;
                    }

                    string configName = splitLine[1];
                    string configValue = splitLine[3];
                    if (!AddConfig(configName, configValue))
                    {
                        Log.Warning("Can not add raw string with config name '{0}' which may be invalid or duplicate.", configName);
                        return false;
                    }
                }

                return true;
            }
            catch (Exception exception)
            {
                Log.Warning("Can not parse config '{0}' with exception '{1}'.", text, exception.ToString());
                return false;
            }
        }

        /// <summary>
        /// 解析全局配置。
        /// </summary>
        public override bool ParseConfig(byte[] bytes, object userData)
        {
            using (MemoryStream memoryStream = new MemoryStream(bytes, false))
            {
                return ParseConfig(memoryStream, userData);
            }
        }

        /// <summary>
        /// 解析全局配置。
        /// </summary>
        public override bool ParseConfig(Stream stream, object userData)
        {
            try
            {
                using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8))
                {
                    while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length)
                    {
                        string configName = binaryReader.ReadString();
                        string configValue = binaryReader.ReadString();
                        if (!AddConfig(configName, configValue))
                        {
                            Log.Warning("Can not add raw string with config name '{0}' which may be invalid or duplicate.", configName);
                            return false;
                        }
                    }
                }

                return true;
            }
            catch (Exception exception)
            {
                Log.Warning("Can not parse config with exception '{0}'.", exception.ToString());
                return false;
            }
        }

        /// <summary>
        /// 释放全局配置资源。
        /// </summary>
        public override void ReleaseConfigAsset(object configAsset)
        {
            m_ResourceComponent.UnloadAsset(configAsset);
        }

        /// <summary>
        /// 加载全局配置。
        /// </summary>
        protected override bool LoadConfig(string configName, object configAsset, LoadType loadType, object userData)
        {
            TextAsset textAsset = configAsset as TextAsset;
            if (textAsset == null)
            {
                Log.Warning("Config asset '{0}' is invalid.", configName);
                return false;
            }

            bool retVal = false;
            switch (loadType)
            {
                case LoadType.Text:
                    retVal = m_ConfigManager.ParseConfig(textAsset.text, userData);
                    break;

                case LoadType.Bytes:
                    retVal = m_ConfigManager.ParseConfig(textAsset.bytes, userData);
                    break;

                case LoadType.Stream:
                    using (MemoryStream stream = new MemoryStream(textAsset.bytes, false))
                    {
                        retVal = m_ConfigManager.ParseConfig(stream, userData);
                    }
                    break;

                default:
                    Log.Warning("Unknown load type.");
                    return false;
            }

            if (!retVal)
            {
                Log.Warning("Config asset '{0}' parse failure.", configName);
            }

            return retVal;
        }

        /// <summary>
        /// 增加指定全局配置项。
        /// </summary>
        protected bool AddConfig(string configName, string configValue)
        {
            bool boolValue = false;
            bool.TryParse(configValue, out boolValue);

            int intValue = 0;
            int.TryParse(configValue, out intValue);

            float floatValue = 0f;
            float.TryParse(configValue, out floatValue);

            return AddConfig(configName, boolValue, intValue, floatValue, configValue);
        }

        /// <summary>
        /// 增加指定全局配置项。
        /// </summary>
        protected bool AddConfig(string configName, bool boolValue, int intValue, float floatValue, string stringValue)
        {
            return m_ConfigManager.AddConfig(configName, boolValue, intValue, floatValue, stringValue);
        }

        private void Start()
        {
            m_ResourceComponent = GameEntry.GetComponent<ResourceComponent>();
            if (m_ResourceComponent == null)
            {
                Log.Fatal("Resource component is invalid.");
                return;
            }

            m_ConfigManager = GameFrameworkEntry.GetModule<IConfigManager>();
            if (m_ConfigManager == null)
            {
                Log.Fatal("Config manager is invalid.");
                return;
            }
        }
    }
}

看到配置辅助器需要实现两个函数:AddConfig(解析成功的数值保存到字典中)和LoadConfig(读取配置文件),也就是说添加xml配置辅助器时主要重写这两个方法,打开工程下txt配置文件查看格式是什么样子的,DefaultConfig配置文件如下所示:

#   默认配置        
#   配置项 策划备注    配置值
    Game.Id     Star Force
    Scene.Menu      1
    Scene.Main      2

默认配置辅助器比较麻烦,想使用GF的默认配置辅助器就按照以上格式定义txt,空格、Tab、回车表示数据分割,同行数据尽量不要使用回车分割,这样看起来会更累。

2.如何使用配置模块?

数据保存到字典应该如何取用呢?可以先看一下数据是如何保存就到字典的,就可以知道数据取用的方式(虽然有封装好的接口),具体保存的代码如下:

    public bool AddConfig(string configName, bool boolValue, int intValue, float floatValue, string stringValue)
    {
        if (HasConfig(configName))
        {
            return false;
        }

        m_ConfigDatas.Add(configName, new ConfigData(boolValue, intValue, floatValue, stringValue));
        return true;
    }

configName是key,ConfigData(多数据组合类)是value,把读出来的数据这样保存感觉怪怪的,思考了一下好像确实没有更好的解决方案,比如在配置时这个key只是表示int型的,却多保存了其他数据,取用时还需要告诉它具体调用的函数类型,做法感觉不太智能,比如取用Int数据时需要调用这种代码:

    GameEntry.Config.GetInt("Scene.Menu")

写框架目的就是提供给使用者大量便捷途径,但是配置模块用着确实不太理想(在下不是处女座的,不要误会。en…处女座的也不要误会),小节三来考虑一下比较好的处理方式,当然有更好的想法希望各位可以传授给我(ありがとうございます)。

3.如何修改配置模块?

首先需要分析问题,知道具体的敌人是谁才可以击败敌人,所以第一个问题就是如何实现保存不同值类型集合的功能?第二个问题就是取用数据时如何统一接口去调用?(不需要使用取用int型时调用getint,取用string型时调用getstring这样子)。

  • 如何保存不同值类型到字典?
    首先需要知道的就是使用泛型是不可以的,因为每个数据都可能不相同(不可能t,t1,t2这样子去搞,在下也不是这样的人),所以只能用到拆箱装箱的方式,也就是object,这样就解决了第一个问题,具体实现将在进阶篇里实现,字典将调整成这样:
   Dictionary<string,dynamic> 
  • 如何统一接口?
    需要智能识别数据类型的话,就必须在读取数据时就确定它的类型,这个东西如何确定呢?其实仔细思考以后知道数据类型无非就是字符串(string)、数值(int,double…)、布尔(bool),应该没有更多了。这样我们需要制定一个规矩,比如被"“包含起来就是string类型,false或true就是布尔类型的(配置成前面类型中间_后面数值也是可以的,就是太憨憨了,比如int_111111),然后按照规定就可以确定值类型了,简直完美哎…,但是这样还是不行的,最后还需要保存每个数据的type取用时可以用到,经过分析后就可以知道字典的value要改成这样子。
    public class ConfigData
    {
        dynamic data;
        Type type;
    }

刚接触编程的可能会有疑问,为什么不定义成结构体反而定义成了类?(结构体性能不是比类好嘛),建议少侠去百度字典的value保存成值类型还是引用类型比较好,这里就不多废话了,字典的value改成如下所示:

   Dictionary<string,ConfigData> 

解决问题的思路差不多就这样了,具体的实现方案将在以后文章完成~

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值