創(chuàng)建一個(gè)角色扮演游戲項(xiàng)目時(shí),會(huì)發(fā)現(xiàn)在源碼中編寫游戲相關(guān)信息十分困難(這樣做也是非常魯莽的)。最好的辦法就是使用外部數(shù)據(jù)源(類似于程序的代碼),稱之為游戲腳本(例如對(duì)話)。以這種方式,可以控制游戲的流程并節(jié)省寶貴的時(shí)間,因?yàn)椴恍枰诿看巫龀龈淖兒笾匦逻M(jìn)行編譯。
理解腳本
當(dāng)創(chuàng)建游戲時(shí),游戲腳本與所編寫的程序代碼非常類似,只是游戲腳本相對(duì)于游戲引擎而言是外部的。正因?yàn)樗鼈兪峭獠康模圆趴梢匝杆俚貙?duì)腳本做出更改,而不用重新編譯整個(gè)游戲引擎。否則對(duì)于一個(gè)超過100萬行代碼的項(xiàng)目,僅僅為了改變一個(gè)對(duì)話行就要重新編譯整個(gè)項(xiàng)目。
腳本的使用并不會(huì)非常困難,而且游戲的每個(gè)方面都可以從腳本的運(yùn)用中獲益,比如導(dǎo)航菜單、戰(zhàn)斗控制、處理玩家的物品清單,都可以使用腳本。舉個(gè)例子,進(jìn)行游戲開發(fā)時(shí),把自己想象成處于戰(zhàn)斗的用戶,他們有規(guī)律地使用一系列的法術(shù)發(fā)起攻擊。但在游戲開發(fā)過程中可能決定改變部分法術(shù),如果法術(shù)的資料是硬編碼的話,將面臨一個(gè)非常麻煩的問題,必須更改控制法術(shù)的那些程序代碼的每個(gè)實(shí)例,更不用說去調(diào)試和檢驗(yàn)?zāi)切┐a直到正確為止。為什么要花費(fèi)如此多的時(shí)間去做這些改變呢?
相反,可以將法術(shù)以及它們對(duì)游戲中人物的影響編寫到幾個(gè)小小的腳本里。每當(dāng)戰(zhàn)斗打響時(shí),這些腳本被加載,并顯示可供選擇的法術(shù)。一旦該法術(shù)被施展,一個(gè)腳本將發(fā)揮自己的作用,從造成損傷到產(chǎn)生運(yùn)動(dòng)或法術(shù)圖形的動(dòng)畫。
有兩種類型的腳本系統(tǒng)可供使用,第一種涉及到使用某種腳本編程語言,在腳本文件中輸入命令,編譯該文件,并在游戲中執(zhí)行編譯好的腳本文件。第二種是第一種的簡(jiǎn)化版本,將命令輸入到一個(gè)文件中,系統(tǒng)通過從一個(gè)預(yù)先定義好的命令集里選擇命令來創(chuàng)建腳本。
為了簡(jiǎn)化問題的處理,我們使用第二種腳本系統(tǒng)來創(chuàng)建自己的腳本命令集,稱之為Mad Lib
Scripting(MLS)系統(tǒng),它使用一個(gè)預(yù)先定義好的命令集合(稱為行為action),同時(shí)每個(gè)定義好的命令都與一個(gè)游戲功能相關(guān)聯(lián)。
下圖是一個(gè)腳本命令集示例:

使用這樣一個(gè)有限的行為集合,就不再需要復(fù)雜的可編譯腳本語言了。相反,只需要告訴腳本系統(tǒng)要使用哪些行為,以及這些行為將使用怎樣的選項(xiàng)以實(shí)現(xiàn)游戲的功能。對(duì)于這種方法,最大的好處就是不再需要為指定一個(gè)簡(jiǎn)單的行為而羅列代碼行,可以通過編號(hào)來引用行為和選項(xiàng)。
舉個(gè)例子,Play Sound行為的編號(hào)為4,而且該行為僅要求一個(gè)輸入,即播放聲音的編號(hào)。在腳本中只存儲(chǔ)兩個(gè)數(shù)值:一個(gè)對(duì)應(yīng)于行為,另一個(gè)代表了聲音。使用數(shù)值表示行為(代替文本)的方法可以使這種類型腳本的處理既快速又簡(jiǎn)單。
Mad Lib Scripting系統(tǒng)的設(shè)計(jì)
創(chuàng)建在游戲中想到的行為,可以通過創(chuàng)建或編輯腳本來填充那些空白點(diǎn)(稱之為條目entries)。對(duì)于每個(gè)行為,請(qǐng)明確提供一個(gè)可供空白條目填充的選項(xiàng)列表,它的類型可以從一行文本到一串?dāng)?shù)字。接著將行為和空白條目進(jìn)行編號(hào),以便腳本系統(tǒng)可以引用它們,以下是一些行為列表的范例:
1. Character (*NAME*) takes (*NUMBER*) damage.
2. Print (*TEXT*).
3. Play sound effect titled (*SOUND_NAME*).
4. Play music titled (*MUSIC_NAME*).
5. Create object (*OBJECT_NAME*) at coordinates (*XPOS*),(*YPOS*).
6. End script processing.
在這6種行為中,都有0個(gè)或多個(gè)空白條目位于括號(hào)內(nèi),每個(gè)空白條目包含了一個(gè)文本字符串或者一個(gè)數(shù)字,這個(gè)行為與可能條目(以及條目的類型)的列表被稱之為行為模板(action
template),如下圖所示:

一旦使用了行為模板,就可以使用它們的編號(hào)而不是行為的文本進(jìn)行引用(文本的存在只是為了使用戶能夠更容易理解每個(gè)行為所實(shí)現(xiàn)的功能)。
MLS系統(tǒng)的編寫
為了使MLS系統(tǒng)功能盡可能強(qiáng)大,需要設(shè)計(jì)它以便可以支持多重行為的模板,而且每個(gè)行為模板都包含不受數(shù)量限制的行為。以這種方式,就可以將系統(tǒng)復(fù)用到任何想要的項(xiàng)目中。當(dāng)一個(gè)腳本完成時(shí),將腳本讀入到引擎中,并處理各自的行為,為每個(gè)由腳本編輯器所輸入的行為使用指定的條目。
一個(gè)行為模板需要保存行為的列表,包括文本、條目編號(hào)以及每個(gè)條目的數(shù)據(jù)。每個(gè)行為按它們?cè)诹斜碇械乃饕颠M(jìn)行編號(hào),同時(shí)每個(gè)行為中的空白條目也被加以編號(hào)。可以為每個(gè)條目指定一種類型(文本型、整數(shù)型、浮點(diǎn)型、布爾型、多重選擇型),如下所示:
0. No entry type
1. Text entry
2. Boolean value
3. Integer number
4. Float number
5. Multiple choice (a choice from a list of text selections)
每個(gè)條目類型都有一個(gè)獨(dú)特的特征,字符串類型的長(zhǎng)度是可以變化的,數(shù)字型可以是兩個(gè)數(shù)字范圍之間的任何數(shù)值,而布爾值可以是TRUE或者FALSE。至于多重選項(xiàng)型,每個(gè)選項(xiàng)都有它自己的文本字符串(腳本從一個(gè)列表中獲取選項(xiàng),而且所選選項(xiàng)的索引編號(hào)比它的文本更適用)。
行為可以采用如下格式:
Action #1: Spell targets (*MULTIPLE_CHOICE*).
Possible choices for blank entry #1:
1. Player character
2. Spell caster
3. Spell target
4. Nobody
我們通過創(chuàng)建結(jié)構(gòu)體ENTRY_RULE和ACTION來處理?xiàng)l目規(guī)則與行為。
enum ENTRY_TYPE { ENTRY_NONE = 0, ENTRY_TEXT, ENTRY_BOOL, ENTRY_INT, ENTRY_FLOAT, ENTRY_CHOICE };
typedef char* char_ptr;
typedef int BOOL;
//============================================================================
// Structures to store information about a single blank entry.
//============================================================================
typedef struct ENTRY_RULE
{
long type; // type of blank entry (ENTRY_TEXT, ENTRY_BOOL,
)
// The following two unions contain the various information about a single blank entry,
// from the min/max values (for int and float types), as well as the number of choices
// in a multiple choice entry.
union
{
long long_min; // min value of long type
float float_min; // min value of float type
long num_choices; // number of choices in list
};
union
{
long long_max; // max value of long type
float float_max; // max value of float type
char_ptr* choices; // choice text array
};
// structure constructor to clear to default values
ENTRY_RULE()
{
memset(this, 0, sizeof(*this));
}
// structure destructor to clean up used resources
~ENTRY_RULE()
{
// special case for choice type
if(type == ENTRY_CHOICE && choices != NULL)
{
for(long i = 0; i < num_choices; i++)
delete[] choices[i];
delete[] choices;
}
}
} *ENTRY_RULE_PTR;
//============================================================================
// Structure that store a single action.
//============================================================================
typedef struct ACTION
{
long index; // action index [0, number of action - 1]
char text[256]; // action text
short num_entries_rule; // number of entries in action
ENTRY_RULE_PTR entries_rule; // array of entry structures
ACTION* next; // next action in linked list
ACTION()
{
memset(this, 0, sizeof(*this));
}
~ACTION()
{
delete[] entries_rule;
delete next;
}
} *ACTION_PTR;
行為模板被存儲(chǔ)為文本文件,同時(shí)每個(gè)行為的文本被包括在括號(hào)中。每個(gè)包含條目的行為(標(biāo)記為文本中的波浪字符)緊跟著是條目數(shù)據(jù)的列表。每個(gè)條目由一個(gè)描述條目類型(文本型、布爾型、整型、浮點(diǎn)型或選項(xiàng)型)的單詞開始。對(duì)于文本類型而言并沒有更多的需要信息,對(duì)于布爾類型來說也是如此。而作為整數(shù)和浮點(diǎn)型,則要求一個(gè)最小值和最大值。最后,選項(xiàng)類型條目后跟著的是可供選擇的編號(hào)以及每個(gè)選項(xiàng)的文本(
包括在引號(hào)里)。如下所示:
"If flag #~ is ~ then"
INT 0 255
BOOL
"Else"
"Endif"
"Set flag #~ to ~"
INT 0 255
BOOL
"Print ~"
TEXT
"Move character to ~, ~, ~"
FLOAT 0.0 2048.0
FLOAT 0.0 2048.0
FLOAT 0.0 2048.0
"Character ~ ~ ~ ~ points"
CHOICE 3
"Main Character"
"Caster"
"Target"
CHOICE 2
"Gains"
"Looses"
INT 0 128
CHOICE 2
"Hit"
"Magic"
"Engage in battle sequence #~"
INT 0 65535
"End Script"