Unity 游戲配置表代碼自動(dòng)生成術(shù)

說起游戲配置表,不論是游戲程序還是游戲策劃,都是每天都在打交道的、最普通不過的東西。

相信不少游戲程序員,擼過大量這樣的代碼:

class SkillSetting
{
    public int Id;
    public string Name;
    public string Desc;
    public int Arg1;
    public int Arg2;
    public int Arg3;
    // .....
    // .....
}

class SkillSettingManager
{
    // .....
    public void Init ()
    {
        var table = TableReader.Read("jineng.txt")
        for (var line = 0; line <= table.RowsCount; line++)
        {
            Id = table.GetRow(line, "id");
            Name = table.GetRow(line, "name");
            Desc = table.GetRow(line, "desc");
            Arg1 = table.GetRow(line, "arg1");
            Arg2 = table.GetRow(line, "arg2");
            Arg3 = table.GetRow(line, "arg3");
            // ......
        }
    }
}
// ....
// .....
// ......
class BuffSettingManager { .... }
class TaskSettingManager { .... }
class MissionSettingManager { .... }

// .... 接近上百個(gè)XXX SettingManager

相信不少游戲策劃,都遇到過這樣的配置表:

id name desc arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8
1 降龍十八掌 哼哼哈嘿 0 0 1 1 2 4 5 6
......

好吧。大家都或多或少遇過這些問題:

  • 大量的配置表代碼需要手工擼,枯燥繁雜
  • 大量的手工配置表代碼跟隨著大量的BUG
  • 策劃配置表跟程序代碼經(jīng)常命名不一
  • 策劃新人看不懂配置表的這一列究竟是干嘛的
  • 策劃同學(xué)想要更方便的工具提升工作體驗(yàn)
  • 配置表加載嚴(yán)重影響游戲啟動(dòng)速度
  • 運(yùn)營(yíng)需求對(duì)游戲配置表修改熱重載,手工代碼沒有支持
  • 配置表相關(guān)聯(lián)的功能和BUG消磨大量的研發(fā)、運(yùn)營(yíng)時(shí)間

嗯,多么痛的領(lǐng)悟。

接下來拋磚引玉,讓我們一起探討一種游戲配置表的優(yōu)化方案。

需求

編輯游戲配置表的用戶首要就是我們的策劃同學(xué)了,而策劃同學(xué)最喜歡最順手的工具非Excel(或WPS表格)莫屬了。 當(dāng)然了,也見過自己開發(fā)編輯器工具的,但我們畢竟不是全職做工具開發(fā),沒必要額外的增大工作量。
因此我們可以在Excel表格設(shè)計(jì)上,動(dòng)動(dòng)手腳。策劃同學(xué)有這樣的需求:

  • 配置表的列頭帶上注釋
  • 策劃同學(xué)可以在任意他們喜歡的地方做注釋
  • 策劃同學(xué)可以在他們的配置表中的添加文檔性東西如圖表、文字框
  • 有時(shí)候同樣的表,可以拆分成多張,方便編輯

拿到這樣的一份配置表后,程序同學(xué)有這樣的需求:

  • 希望配置表的代碼是可以自動(dòng)生成
  • 部分復(fù)雜邏輯的可以自定義擴(kuò)展
  • 為配置表的列定義類型

方案

編輯

針對(duì)這些需求,我們就有了這樣的這個(gè)結(jié)果:

Excel源表
  • 第一行是列名
  • 第二行是程序用途的類型聲明,如int, Dictionary<int, string>
  • 第三行是該列的注釋
  • 列名以#開頭,則這一列為注釋列(忽略該列單元格內(nèi)容忽略)
  • 行的第一個(gè)單元格內(nèi)容以#開頭,則這一行為注釋行(所有行單元格內(nèi)容忽略)
  • 可以加入圖表、第二張工作表作為輔助內(nèi)容

將這樣的一張表,保存為SettingSrc/Test.xls文件。

下面我們使用一個(gè)名為TableML的執(zhí)行程序,可以從https://github.com/mr-kelly/TableML/releases下載測(cè)試,并包含源碼和單元測(cè)試。

TableML.exe --Src SettingSrc --To Setting --CodeFile Code.cs

在SettingSrc目錄下執(zhí)行這個(gè)配置表編譯命令,會(huì)把所有的Excel文件,編譯成Tab分隔的表格純文本(TSV),并生成一個(gè)代碼文件Code.cs。
編譯后的TSV文件Test.tml純文本內(nèi)容,實(shí)質(zhì)就是剝離了注釋的Excel表格。

Excel表編譯結(jié)果

同時(shí)命令生成代碼:

/// <summary>
/// Auto Generate for Tab File: "Test.bytes"
/// Singleton class for less memory use
/// </summary>
public partial class TestSetting : TableRowFieldParser
{
        /// <summary>
        /// ID Column/編號(hào)/主鍵
        /// </summary>
        public string Id { get; private set;}
        
        /// <summary>
        /// Name/名字
        /// </summary>
        public I18N Value { get; private set;}

        // .............
}
public class TestSettings 
{
     IEnumerator GetAll()
     {
          // ...
     }
}

至此,程序同學(xué),把策劃同學(xué)的游戲配置表編譯優(yōu)化成純文本格式,生成的Code.cs文件也導(dǎo)入U(xiǎn)nity工程,使用TestSettings.GetAll來獲取所有的配置表內(nèi)容了。

多語(yǔ)言

標(biāo)注列的類型為I18N

既然表的第二行支持類型說明,那自然而然,我們可以標(biāo)記某一列是可以需要進(jìn)行翻譯的。比如,把這一列標(biāo)記成I18N,我們通過腳本,把所有帶有I18N列中的字符串進(jìn)行收集,自動(dòng)生成一個(gè)翻譯表;而生成的代碼中對(duì)應(yīng)I18N這個(gè)類,則對(duì)翻譯表進(jìn)行處理,來實(shí)現(xiàn)字符串的多語(yǔ)言翻譯。 細(xì)節(jié)不多敘述。

而在游戲做多語(yǔ)言版本的過程中,光字符串翻譯是不夠的,我們有些時(shí)候,不同的版本還有不同的游戲數(shù)值。鑒于我們的表編譯機(jī)制,我們可以加入一些類似預(yù)編譯指令的機(jī)制來處理:

預(yù)編譯指令

拆表

在很多時(shí)候,策劃同學(xué)喜歡將一個(gè)表的東西,分成多個(gè)文件來寫。比如有游戲的道具比較多,一般會(huì)分成SettingSrc/Item/Weapon.xls,SettingSrc/Item/Equipd.xls, SettingSrc/Item/Common.xls等多張表格,盡管他們是不同的文件,但是它們的列頭都是一樣的,這樣讓編輯起來更加的容易,而且多人編輯時(shí),不容易做成沖突。

在程序代碼中,它們也會(huì)被一個(gè)統(tǒng)一的ItemSettingsManager類讀取成統(tǒng)一的配置類型。

我們可以應(yīng)用之前的“#”符號(hào),來對(duì)他們的文件名修改一下:SettingSrc/Item/#Weapon.xls,SettingSrc/Item/#Equipd.xls, SettingSrc/Item/#Common.xls,這樣,來騙過編譯程序,再執(zhí)行剛才的編譯命令:

TableML --Src SettingSrc --To Setting --CodeFile Code.cs

這樣就會(huì)讓代碼生成器,忽略#號(hào)后面的字符串,把它們統(tǒng)一合并成ItemSettings類。

public class ItemSettings
{
    // 把三個(gè)表一起進(jìn)行加載
    public static readonly string[] TabFilePaths = {
       "Item/#Weapon.xls", "Item/#Equipd.xls", "Item/#Common.xls"
   };
   // ...
}

至于還有一些細(xì)節(jié)功能的,如自定義類型、自定義解析、自定義代碼模板(讓C++、Lua都支持)等擴(kuò)展代碼能力的功能,這里不多作解釋。具體的細(xì)節(jié)可以參考命令的源碼中的單元測(cè)試: GitHub: TableML,而一些邏輯細(xì)節(jié)也可以參考這里的文章

常見問題

在TableML嘗試應(yīng)用的過程中,曾經(jīng)遇到過不少疑問:

考慮其它格式讓策劃同學(xué)編輯?如Lua、JSON?

考慮到策劃同學(xué)和其他同學(xué)的使用體驗(yàn)和習(xí)慣,Excel表格是他們最順手、功能強(qiáng)大的工具。

既然編譯,為什么不直接編譯成序列化的字節(jié)?

選擇編譯成純文本格式,更多出于工程考慮的,一考慮文本格式能對(duì)版本管理(如SVN)更友好,二考慮開發(fā)調(diào)試也更容易。性能方面,在我經(jīng)歷的項(xiàng)目上,對(duì)這種Tab分隔的表格文本解析,比序列化還要快。

我更喜歡自己擼,沒必要代碼自動(dòng)生成?

從程序習(xí)慣的角度來說,這種想法無可厚非,畢竟多年的開發(fā)習(xí)慣如此,而且自由度更高,寫起這些代碼來自然是舒暢的 。 但是從工業(yè)角度來想,人工寫出的代碼維護(hù)Bug成本,比自動(dòng)生成代碼維護(hù)成本要高。并且,為自動(dòng)生成的代碼添加功能(比如,運(yùn)行時(shí)動(dòng)態(tài)重載),只需要在生成代碼的代碼中稍微修改,就全局生效。

Excel文件怎么進(jìn)行版本比較?

在我看來這是一個(gè)非常關(guān)鍵的問題。這也是為什么Excel表被編譯成TSV純文本的一個(gè)重要原因。另外除了Excel表,TableML命令中還支持TSV翻譯到TSV,就是直接把Excel文件另存為TSV格式的配置表文件,再進(jìn)行編譯。
另外,Beyond Compare 4支持Excel文件的直接比較;TortoiseSVN中雙擊Excel文件,也會(huì)打開Excel文件顯示差異的地方(但較蹩腳)。

我項(xiàng)目的配置表都是自動(dòng)生成的,程序策劃沒意見、也挺智能的?

恭喜你們項(xiàng)目的質(zhì)量棒棒的,也希望一同分享您的方案!獨(dú)樂樂不如眾樂樂!

所以呢

說這么多,技術(shù)角度來說,自動(dòng)化的配置表編輯和加載,沒有什么技術(shù)含量,更多的是一種思想,但是我相信這對(duì)項(xiàng)目管理、規(guī)范化是起到積極的作用的,減少重復(fù)勞動(dòng)的時(shí)間,增加生產(chǎn)力。做游戲項(xiàng)目,消耗時(shí)間的,往往不是高深難解的問題,而是一些簡(jiǎn)單問題的重復(fù)輪回。

藉著本文拋磚引玉,也希望大家各抒己見,提出一些讓策劃、程序皆大歡喜的方法和技巧,讓更多更好的方案通過思想的交流碰撞出來,“節(jié)約更多時(shí)間,去陪戀人、家人和朋友 :) ”

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容