摘要:腳本技術在游戲引擎中的應用是近年來游戲開發一個新的趨勢。本文探討了腳本技術在游戲引擎中的作用,并以一種腳本語言Lua為例,詳細分析了腳本與引擎通信的原理。
關鍵詞:游戲引擎;腳本;集成;Lua
中圖分類號:TP311文獻標識碼:A文章編號:1009-3044(2008)19-30167-03
The Application of Scripting Technology in Game Engine
YU Zao-bo
(College of Software Engineer, Southeast University, Nanjing 210018, China)
Abstract: Scripting Technology applied in game Engine is a new trend in nowadays. In this paper, we described the importance of Scripting Technology in Game Engine, and analysis the principle of communication between script and engine with the Lua Language.
Key words: Game Engine; Script; Integration; Lua
1 引言
游戲引擎的產生從根本上講是為了代碼的可復用,早期的游戲開發效率很低,幾乎每款游戲都要從頭編寫代碼,造成了大量的重復勞動; 然而每款類似題材的游戲基本框架是相對穩定的,這種特性使得可以把這部分相對穩定的代碼從整個游戲中分離出來, 并且可以在多款游戲中重復使用。 現在的商業游戲99%以上都是這樣工作的:首先開發一個游戲引擎,接著在虛擬機的基礎上使用腳本語言系統創建一個引擎接口。游戲開發者,甚至游戲設計者,用腳本語言來創建游戲的真正邏輯以及整個游戲的行為。
2 游戲引擎
游戲引擎的發展到現在已經日益成熟,游戲的畫面達到了一個瓶頸的高度了,引擎以后的發展方向難以朝著圖形圖像的改良而進行下去了。然而,引擎的作用絕不僅僅局限在游戲畫面中,它會直接影響到游戲的整體風格。所以游戲開發者不得不從其它方面尋求突破,目前業界主要在關注以下兩個領域:
1) 在引擎中引入腳本的技術,這種技術可以讓游戲以合理的故事來觸動游戲整體架構上的變化,使得玩家可以真實的體驗到游戲情節的發展;
2) 在人工智能算法上的改進,在游戲中,敵人的行動與以前相類似的游戲來看,他們明顯有了更多狡猾的為,而不再只是單純的沖向玩家所在的位置。
這兩方面的特點明顯的突破了以往的引擎架構。現在越來越多的游戲開發者開始將游戲中的重點有單純的視覺效果逐漸的轉變成具有更豐富變化性的游戲內容,這也成功的說明了故事內容與人工智能對于游戲的重要性。是否能夠支持更好的游戲故事內涵和敵人的反映特性己成為衡量引擎優劣的另一項標準。
3 腳本技術
3.1 概述
腳本是一種通常可以編輯和運行、具有極高抽象級別的編程語言,而腳本技術就是與此相關的技術的總稱。由于其強大的自定義功能,腳本技術正在被越來越多的軟件供應商使用。
腳本語言作為一類開發語言,主要有以下優點:
1) 快速開發。它們大大縮短了“開發、部署、測試、調試”周期;
2) 部署簡便。大多提供即時部署能力,而無需花費大量時間在編譯和打包周期上;
3) 與已有技術集成。它們大都構建在已有的組件技術,以便有效重復利用現有代碼;
4) 易于學習和使用。技術門檻很低,可以輕松找到大量的使用者;
5) 動態代碼。腳本語言能夠被即時生成和執行,這在某些應用程序中是非常必要和有用的高級特性。
3.2 腳本在引擎中的作用
實現腳本編程系統能夠的最根本原因是要避免硬編碼,將游戲內容與游戲引擎相分離。這樣,就可以在不需要重新編譯整個工程的情況下調整、測試和修改游戲運行的機制和特性。這種模塊化的結構促成了游戲開發工作量的劃分,游戲編程人員負責引擎等核心技術的開發,而游戲設計者可以專注游戲本身的邏輯與策劃。
從系統架構方面講, 使用腳本可以減少與底層的耦合度。把游戲邏輯相關的部分放進腳本中,這部分模塊實際上可以被多個游戲項目復用。因為腳本是基于虛擬機的,只要運行時環境接口一致,腳本部分代碼便可以實現真正意義上的跨平臺,這一點與java語言很相似。另外,腳本語言相對C++來說要簡單的多,經過簡單的培訓,甚至策劃人員也可以參與編寫,大大降低了開發門檻。
4 腳本系統與游戲引擎的集成
4.1 原理分析
集成(Integration)是將兩個或者更多個分離的、通常情況下不相關的實體連接在一起,使得它們為了一個共同的目標相互通信、協調工作。將兩個實體集成最大的挑戰就是如何在它們之間建立某種類型的通道,從而使得兩者可以進行方便而又可靠的通信。
Lua是基于關聯數組和可擴展語法結構設計的語言,具有變量無類型、動態定義類型、面向對象結構、編譯產生中間代碼和內存自動回收等特點,常被作為一種腳本嵌入于其它主系統中。但是Lua作為一種腳本語言,不是直接編譯為機器碼,而是在運行時通過運行環境或虛擬機執行源代碼或中間代碼。相比較編寫引擎使用的C/C++語言代碼,引擎與腳木的運行環境不同,從而導致了它們之間不能直接通信,所以集成的關鍵在于要在兩者間建立一個抽象層,也就是接口,能夠解釋和傳遞它們的輸入輸出。一般來說,腳本系統的運行是通過使用靜態庫的方式實現的,這個庫包括了兩個關鍵部分:第一部分是運行環境,也稱為虛擬機,用來把腳本語言翻譯成機器語言;另一部分是與主程序連接的函數接口,提供把腳本嵌入到主程序的接口。它與主程序的關系如圖1所示:
■
圖1 lua與c通信
由圖1可以看到,引擎與lua腳本之間的調用是雙向的,即lua可以調用引擎函數,引擎也可以調用lua函數。Lua中用狀態(State)這個概念描述運行時環境相關的某個具體實例的信息,每個狀態在任意時刻都可以包含一個腳本,這個腳本已經被裝載到內存當中以供使用。同時,由于lua運行時環境是運行在引擎上的,也就是說引擎能夠訪問lua的運行時環境,因此lua與引擎都能操作運行時環境中的堆棧。把棧作為兩者通信的中間層,通過把全局變量、函數引用、參數、返回值等壓入堆棧,以達到信息共享的作用。如圖2所示:
■
圖2 Lua堆棧
4.2 Lua調用引擎函數
為了使在引擎中定義的函數能夠被Lua腳本調用,需要傳遞一個函數指針至Lua的運行環境,Lua提供了注冊函數名的接口:
lua_register (lua_State *pLuaState, const char *pstrFunctionName, lua_CFunction pFunc);
只需要提供一個函數名稱字符串,當前函數指針以及當前函數應導出到的具體Lua狀態,函數lua_register()就會注冊這個函數,這就是游戲腳本可以像訪問其它函數一樣來調用這個函數的原因。需要注意的是,如果第三個參數pFunc未被導出,會導致運行時錯誤。 正確的定義格式應該是這樣:
int FuncName(lua_State *pLuaState);
由于Lua腳本對函數參數及返回值處理與C語言不同,Lua是把參數和返回值都壓入當前State棧中,通過訪問棧中的變量達到傳遞的作用,所以在lua_register()中沒有對函數參數、返回值個數的說明。在函數調用時,所有對堆棧的訪問都是相對與索引值1開始的,參數個數可以用lua_gettop()函數獲得, 并用lua_to*()訪問每一種類型的變量。例如一個函數參數表如下:
(int x, float y, string z)
需要采用下面的方式讀取這3個參數:
int x = (int) lua_tonumber(pLuaState, 1);
float y = lua_tonumber(pLuaState, 2);
char *z = lua_tostring(pLuaState, 3);
函數返回值采用相反的方式進行返回,需要在C函數返回前將這些數值壓入堆棧。假設要返回3個數值2、4、6,則代碼應該如下:
lua_pushnumber(pLuaState, 2);
lua_pushnumber(pLuaState, 4);
lua_pushnumber(pLuaState, 6);
return 3;
注意由于lua可以返回多個值,所以最后要加上return 3。
4.3 引擎調用Lua函數
引擎調用Lua函數正好是與上節相反的過程,在lua中所有函數都可以看作是全局范圍的,沒有作用域的概念。當調用一個函數時,需要完成的第一件事就是將一個對該函數的引用壓入堆棧,可以使用lua_getglobal()來完成這個工作:
lua_getglobal(pLuaState, \"FuncName\");
在這里,FuncName是一個字符串值,在腳本內部對應于函數的名稱。緊接著要將函數參數壓入堆棧,對于函數原型為:
function FuncName(int Param1, string Param2);
假設按照以下方式調用這個函數:
FuncName(256, \"Hello!\");
參數應該以下面的方式壓入堆棧:
lua_pushnumber(pLuaState, 256);
lua_pushstring(pLuaState, \"Hello!\");
然后采用如下方式來調用函數:
Lua_call(pLuaState, 2, 1);
其中,2表示參數個數為2個,1表示返回值個數為1個。
最后是獲取腳本函數返回值,該返回值同樣是保存在堆棧之中。假設腳本函數返回一個整形值,那么應該用以下代碼獲取這個結果:
int iResult = (int) lua_tonumber(pLuaState, 1);
lua_pop(pLuaState, 1);
即取出棧中索引1的雙精度值并強制轉換為整形, 然后從棧中刪除該值。
5 結束結
本篇文章探討了腳本技術在游戲引擎中的地位與作用,并從原理層面分析了腳本與引擎集成的可行性,最后結合Lua這種腳本語言詳細介紹了如何實現游戲引擎與腳本系統間的通信。腳本技術在游戲引擎中的應用是應游戲產業發展而產生的,它的發展大大提高了游戲開發的靈活性與高效性,也必然會得到越來越多的關注。
參考文獻:
[1] Alex Varanese. Game Scripting Mastery[M]. 清華大學出版社.
[2] Diego Garces. 腳本語言總述 游戲編程精粹6[M].
[3] 云風. 游戲之旅——我的編程感悟.電子工業出版社.
[4] Roberto Ierusalimschy. Programming in Lua[EB/OL].http://www.lua.org 2005.
[5] 房曉溪. 游戲引擎教程[M].中國水利水電出版社.
注:本文中所涉及到的圖表、注解、公式等內容請以PDF格式閱讀原文