Gameframework(Config初探篇)

前言

聽過遊戲數值策劃崗位的,都知道他們是爲了遊戲數值的平衡和制定,遊戲中各種公式的設計,以及整個系統平衡的搭建。當然策劃可以不必懂編程或需要編程,但配置信息程序猿是不需要干涉的,所以遊戲需要可配置的(爲了熱更新、程序便於維護和分工明確),思考一下如果直接把數值寫死在代碼中感覺好傻的(別的不說了,策劃每次要改數值,還要告訴程序去改),程序和數值分開是很好的習慣,接下來看看GF框架的配置模塊做了些什麼?

1 默認配置輔助器

看過網站上GF架構思路文章的童靴,應該知道學習GF模塊時,可以從默認輔助器人手,畢竟模塊管理器持有模塊輔助器去實現各種功能的,然後模塊組件持有模塊管理器去實際的調用(阿勒,這麼又說一遍了…),所以直接來看看DefaultConfigHelper(自定義的配置輔助器),以後在配置擴展篇裏會考慮實現xml或json的輔助器(個人比較偏向於xml,因爲execl比json可視化軟件更加普遍和便於修改),工程下的默認配置輔助器是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或回車鍵表示數據的分割,只不過同行數據不要回車分割(txt沒有xml格式直觀,畢竟xml可以用execl打開),這樣看起來會很累。

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> 

解決問題的思路差不多就這樣了,具體的實現方案將在以後文章完成~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章