為什麼在遊戲中使用指令碼
在早期的一些遊戲中,大部分的遊戲邏輯都直接寫入遊戲代碼,例如計算公式、遊戲流程等。但隨著遊戲產業的不斷髮展,遊戲開發本身也變得越來越複雜,遊戲企劃需要更多的時間來對遊戲進行調整,如果遊戲邏輯還寫在代碼中,則企劃對遊戲的每次修改都要通過程式進行,而且還需要重新編譯以及重啟程式,這樣工作效率就大大降低了。
在遊戲中使用指令碼,就正好能解決上述問題,小到一些計算公式,大到遊戲的控制流程程都可以通過指令碼完成,而且目前的指令碼系統大多是解釋執行的,因此都可以支援運行時動態修改,這樣一來可以立即看到修改結果,非常方便。
如何使用指令碼
在遊戲中使用指令碼主要有兩種方式。一種方式是主程式使用一門進階語言,比如C++進行編寫,然後對其嵌入一個指令碼解譯器,在運行時動態執行一些指令碼函數;另一種方式則是整個程式全部使用指令碼編寫,例如一些泥巴遊戲就是直接用LPC指令碼編寫的。
本文主要研究嵌入式指令碼使用方法,因為目前大部分的指令碼都不能提供如VC++那樣方便的調試環境,如果程式全部用指令碼編寫,當指令碼多達幾萬行甚至十幾萬行的時候,調試會成為一件非常痛苦的事。而且嵌入式使用時可以將一些非常耗時的代碼用C++編寫,以保持較好的運行效率。
程式從C++的main( )函數開始啟動,然後進入主迴圈,在一些C++函數中會直接呼叫指令碼函數,在指令碼函數的運行過程中,又可能調用C++的擴充函數。C++擴充函數的主要功能有兩個:一是用來增加指令碼無法直接編寫的功能,二是用來替換指令碼中運行速度過慢的函數。
上述過程的關鍵點就在於C++和指令碼如何相互調用函數,以及如何傳遞參數與結果。一般的解決方案是在程式啟動時利用指令碼的API向指令碼註冊C++的擴充函數,將函數指標傳遞給指令碼系統以便將來調用,呼叫指令碼函數則使用指令碼系統的API將調用參數壓進棧,取得運行結果也要通過API進行。
Python 指令碼簡介
目前有許多第三方指令碼語言可供直接使用,例如Tcl、Lua等等,本文要介紹的是Python指令碼。Python已經有超過十年的曆史,是一種解釋性的、物件導向的指令碼語言。Python的解譯器在大部分的作業系統上都可以運行,如Windows、Linux、Solaris、Mac 等。
1. 安裝與配置
Python的首頁在http://www.python.org,目前最新版是2.3.2,本文採用2.2.2,你可以在其首頁上下載安裝包http://www.python.org/ftp/python/2.2.2/Python-2.2.2.exe。運行安裝程式後,它會將Python解譯器、文檔、擴充模組等安裝到你的電腦上。
安裝完成後在開始菜單中會有Python的圖形化編輯器(IDLE,但目前版本不支援中文字元),Python的命令列解譯器以及使用者手冊。
為了在C++程式中調用Python的API函數,需要將標頭檔與lib路徑添加到VC++的搜尋目錄中,標頭檔路徑是本地Python安裝目錄下的include目錄,lib路徑是本地Python安裝目錄下的libs目錄。這裡需要注意的是安裝包只提供了release版本的lib與dll,如果需要調試運行,則必須自己下載Python的原始碼以編譯debug版本的lib與dll,源碼可在這裡下載http://www.python.org/ftp/python/2.2.2/Python-2.2.2.tgz。
2. 文法簡介
詳細的文法說明請參考Python安裝包內建的文檔,這裡我只介紹一些常用的關鍵字與注意事項。
Python沒有C++中的 { 和 } ,它使用縮排來代替。變數不需要單獨聲明,但不能引用未經賦值的變數。
Python中引入了模組的概念,類似C++中Library的概念。模組可以包含函數、變數、類。一個指令檔就是一個模組,模組在使用前需要匯入。
Python中沒有switch,使用if判斷代替:
if ( num==1 ):
print "1"
elif ( num==2 ):
print "2"
else:
print "unknown"
while 是Python的一個迴圈語句。在while迴圈內可以使用continue跳到下個迴圈,使用break可以跳出整個迴圈:
cnt = 5
while ( cnt > 0 ):
print cnt
cnt -= 1
for 迴圈:
list = ["test1", "test2", "test3"]
for str in list:
print str
詞典是Python的一種映射資料類型,它能從一個索引值(key)映射到實際內容(value):
accounts = {'tom':'123456', 'mike':'654321'}
print accounts['tom']
print accounts['mike']
3.API 介紹
Python提供了大量的C API,C++與Python的互動都是通過這些API進行。下面介紹幾個比較重要的API函數:
void Py_Initialize( )
在使用Python系統前,必須使用Py_Initialize對其進行初始化。它會載入Python的內建模組並添加系統路徑到模組搜尋路徑中。這個函數沒有傳回值,檢查系統是否初始化成功需要使用Py_IsInitialized。
int PyRun_SimpleString(char *command)
把輸入的字串作為Python代碼直接運行,返回0表示成功,-1表示有錯。大多時候錯誤都是因為字串中有語法錯誤。
PyObject* Py_BuildValue(char *format, ...)
把C++的變數轉換成一個Python對象。當需要從C++傳遞變數到Python時,就會使用這個函數。此函數有點類似C的printf,但格式不同。常用的格式有s表示字串,i表示整型變數,f表示浮點數,O表示一個Python對象。
PyObject* PyObject_CallObject(PyObject*callable_object, PyObject *args)
調用一個callable_object指向的Python函數,args為調用參數。在使用此函數前可以用PyCallable_Check來檢測callable_object是否為一個可被調用的Python對象。
PyObject* PyImport_Import(PyObject *name)
載入一個n a m e 指定的模組。可以先使用PyString_FromString將模組名轉換為Python對象,再使用PyImport_Import載入。
void Py_Finalize()
關閉Python系統,一般在程式退出時調用此函數。
【小知識】
更多Python 簡介
Python 使用一種優雅的程式設計文法,它非常接近自然語言,這使得它具有很好的可讀性。
Python 是一種靈活的程式設計語言,程式易於運行。這使得它成為進行原型開發和特殊程式設計任務的理想化語言;用P y t h o n 做程式設計,你甚至可以不太考慮你的程式的可維護性很差。
Python 是支援類和多繼承的物件導向程式設計。
Python 代碼可以被打包為模板和包。
Python 支援異常處理追蹤並能夠列出比較清晰、詳細的錯誤提示。
Python 包含了一些進階的程式設計特性,例如代碼產生器和解譯器。自動垃圾收集功能使你從記憶體管理的爭戰中解脫出來。
Python 龐大的標準庫支援很多一般的程式設計任務,如與網路伺服器串連,Regex,檔案操作。
Python 的互動式模式使得調試小段的程式非常便捷;另外,處理大型程式時,它還具備一個捆綁式的開發環境—— IDLE。
Python 編譯器很容易擴充,可以將C 或者C++ 編譯後的模板作為新的模板加入到其中。
Python 編譯器可以被嵌入到另外一個應用程式中以提供一個可程式化的介面。
Python 可以在很多不同種類的電腦和作業系統上運行:比如Windows,Mac OS,OS/2,Unix,Linux 等。
Python 語言的編譯器是開源項目,擁有著作權但可以免費使用和免費發布,甚至可以應用在商業項目中。
在C++中使用python最簡單的方式就是進階應用程式
在使用時要初始化python語言的解譯器
Py_Initialize();
在使用完成時要中止python的語言解譯器
Py_Finalize();
這種應用最簡單的就是直接執行一斷指令碼
PyRun_SimpleString("import sys/n"
"print 100+200/n");
這就是最簡單的應用了
如果想執行一段儲存在檔案中的指令碼那就調用
int PyRun_SimpleFile( FILE* fp , const char* filename)
這種方式的調用是最簡單的一種調用了。
函數調用終於可以傳回值了前幾天的BCB調用Python,沒有直接在C++裡面調用Python函數,只是簡單的使用了PyRun_SimpleString()這個函數進行指定字串的執行。這種調用,對於沒有輸出結果,或者是輸出結果在檔案之類時還是可以使用的,但是通常情況下,我們調用函數是需要返回一個結果給被調用者的。針對這種情況,使用解釋字串方式可行性不大了(可以實現,使用輸出資料流重新導向,但是處理起來複雜),只能是調用函數,然後接受函數的傳回值。
這種調用也是比較簡單的(因為我只要求返回字串就可以了:)),首先使用PyImport_ImportModule初始化你要調用的模組(一般指檔案名稱),然後使用PyObject_CallMethod調用你的Python函數,當然了,調用函數的參數是需要設定的。
參考代碼:
-----------------------------------
AnsiString ScriptPath = ExtractFilePath(Application->ExeName)+"script";
AnsiString PyStr;
PyObject *pName, *pOs, *pArg, *pResult, *pCall;
Py_Initialize();
InitLogger();
pOs = PyImport_ImportModule("os.path");
PyObject_CallMethod(pOs, "join", "(s)", ScriptPath.c_str());
pCall = PyImport_ImportModule("BOMandXY");
pResult = PyObject_CallMethod(pCall, "ReadBOM", "(s)", FileName.c_str());
ShowMessage(PyString_AsString(pResult));
Py_Finalize();