Posted on 2008-12-14 21:55
Condor 閱讀(603)
評論(0) 編輯 收藏 引用
Nebula2的腳本系統實現了一個面向C++的腳本接口, 它把腳本命令直接映射到了C++方法. 從技術角度來說, 這是一個簡捷的思路, 但是對于需要把游戲邏輯和行為腳本化的關卡設計師來說, Nebula2的腳本系統太底層和透明了.
關卡邏輯腳本一般來說構架于比C++接口更高級的層次上, 直接把腳本命令映射到C++方法會把腳本層次弄得錯綜復雜. Bug甚至會比同樣的C++代碼更多, 因為腳本語言一般缺少強類型檢查和”編譯時”的錯誤檢測, 所以在本應在C++編譯時發現的Bug會在腳本運行時才發現(這對于不同的腳本語言有所不同). 這是我們從Project Nomads中得出的經驗, 它就是用Nebula2的腳本系統驅動的.
所以教訓就是: 把你的腳本架構在一個正確的抽象層上, 并且: 把你的C++接口映射到一種腳本語言是沒有意義的, 因為那樣你不如從一開始直接用C++來做這些東西.
相應的, 新的Nebula3腳本哲學為關卡設計師提供一些在”正確的抽象層”的(大多是限于特定應用)積木. 當然, “正解的抽象層” 很難來定義, 因為這要在靈活性跟易用性之間找到一個平衡( 例如, 一個”Pickup” 命令是不是應該把角色移動到拾取范圍內呢? )
除了太底層以外, Nebula2的腳本系統也有一些其它的缺點:
- C++方法必須遵循可以轉化為腳本的原則( 只有簡單數據類型才可以做為參數 )
- 給程序員帶來麻煩. 每個C++方法都需要額外的腳本接口代碼( 每個方法幾行 )
- 只有派生自nRoot的類可以腳本化
- 對象關聯到腳本系統( 思路簡單, 但是增加的依賴性會使重構非常困難 )
下面是Nebual3的底層腳本的大概:
- 腳本系統的基礎是Script::Command類
- Script::Command是一個完全腳本語言無關的, 它包含了一個命令名稱, 一些輸入參數的集合還有一些輸出參數的集合.
- 一個新的腳本命令通過派生Script::Comand類來創建, 腳本的C++功能代碼可以寫入子類的OnExecute()方法
- ScriptServer類是腳本系統中僅有一個腳本語言相關的類, 它會把Command對象注冊成新的腳本命令, 并且把命令參數在腳本語言和C-API之間做翻譯.
這個觀念比Nebula2更為簡單, 最重要的是, 它不會跟Nebula3的其它部分交織在一起. 甚至可以通過改變一個#define來編譯一個沒有腳本支持的Nebula3.
當然, 書寫腳本命令的C++代碼跟Nebula2一樣煩人, 這是NIDL的由來. NIDL的是全稱是”Nebula Interface Definition Language”. 基本思想是通過為腳本命令定義一個簡單的XML schema并把XML描述編譯成派生了Script::Command的C++代碼, 來盡量減少書寫腳本命令的重復性工作.
對于一個腳本命令必不可少的信息有:
- 命令的名稱
- 輸入參數的類型和名稱
- 輸出參數的類型和名稱
- 對應的C++代碼( 通常只有一行 )
還有一些非必須, 但是可以帶來便利性的信息:
- 關于命令的作用和每個參數的意義的描述, 這可以作為運行時的幫助系統
- 一個唯一的FourCC(四字符碼), 可以更快的通過二進制通道傳輸
大部分的腳本命令翻譯成了大約7行的XML-NIDL代碼. 這些XML文件再用”nidlc”NIDL編譯器工具編譯為C++代碼. 這個預處理是VisualStudio完全集成的, 所以使用NIDL文件不會為程序員代來任何困難.
為了減少亂七八糟的文件(編譯生成的), 相關的腳本命令被組織到一個叫作庫的集合中. 一個庫由一個單獨的NIDL-XML文件表示, 并且它只會被翻譯一個C++頭文件和一個C++源代碼文件. 腳本庫可以在程序啟動時注冊到ScriptServer, 所以如果你的應用程序不需要腳本訪問文件的話, 僅僅不注冊IO腳本庫就可以了. 這會減小可執行文件的體積, 因為連接器會把沒有用到的腳本庫丟棄掉.
最后, Nebula3放棄了TCL作為標準的腳本語言, 而采用了運行時代碼更加小巧的LUA. LUA已經成為游戲腳本的準規范, 這也使得尋找熟練的LUA關卡設計師更加容易.