SCAR–Scripting at Relic |
1.SCAR的第一課 |
1.SCAR的第一課 |
SCAR(SCript At Relic)是一種線性的腳本語言(Script Language),用來建構一個規則導向(Rule Based)的系統,類似ICPC所使用的觸發導向(Trigger Based)系統.SCAR是以LUA這種 script 語言為基礎,包含了LUA大部分的內建功能.程式設計師透過一些函式來提供一個系統,用以存取遊戲中特定項目的狀態-例如下指令-並且修改這些項目的狀態. 規則(Rule)是一個使用者定義的函式,一但加入後,遊戲會每隔一段間隔作一次評估的動作.加入規則的方式是利用Rule_Add("name of Rule")函式,藉以傳遞你要加入的規則的名稱.每次評估規則的間隔時間預設值是一個frame的大小,也可以在使用Rule_Add的地方使用Rule_AddInterval函式來自訂間隔時間為幾秒.你可以給每個規則可以各自不同的評估時間間隔. 像是若我們想要加入一個每個frame都會在控制台(console)印出"Hello world!"字樣的規則,我們可能會有下列這幾行程式: -- 將 Rule_HelloWorld 加入要被評估的規則的序列之中 如以一來,每個frame都會在主控台印出「Hello world!」字樣 一但規則被加入,遊戲每隔一個間隔就會執行一次此規則.如果此規則的第一行敘述是條件敘述,那就能確保這個規則只有在條件適當的時候才會被執行. 讓我們利用條件敘述,讓「Hello world!」只有在玩家是西加拉人時才會被印出: Rule_Add("Rule_HelloWorld") 注意我們在這裡呼叫了 Rule_Remove .這會把規則從序列中移除,所以下一個間隔之後這個規則就不會被執行.如果你不再規則被執行後將之移除,那他將會繼續在每個時間間隔過評估一次.藉由 Rule_Remove 我們可以確保規則只有在條件符合的時候被執行一次. 規則可以在你的程式的任何地方被移除,但是一直要到下一個間隔過後才會真正的被移出.如果你再規則的中間加上一除規則的敘述,規則剩下的部分還是會被執行,直到下個間隔為止. 看起來好像沒什麼不好,不過要是玩家的ID並不是0呢?或者在多人遊戲中我們希望能夠檢查每個玩家的話要怎麼做?我們可以把敘述包入一個 for 迴圈中,於是就會跑過每一個玩家來檢查玩家是不是西加拉人. function Rule_HelloWorld() 在這個規則中, for 迴圈會檢查遊戲中每個玩家,看看是不是西加拉人,是的話就印出「Hello world!」.當迴圈的 i 的值等於玩家的數目時,這條規則就會被移除 看到了這個簡單的規則很快的變的非常有用了嗎?這就是SCAR內藏的力量.在最基礎的層級上也能相當有用.而藉著更進階的Script技巧,它可以成為非常強大而且多用途的工具. 如同你所見的,規則只是一個函式.讓他成為規則的就只是我們在前面加上 Rule_ (這只是為了辨識方便),以及我們將他加到規則列表中被評估,而不是直接從script中呼叫.這表示你也可以在你的script中創造一些簡單的函式來作重複的動作,而不用建立一堆執行相同功能的規則. |
2.事件表 |
SCAR的另外一個功能是事件表(Event Table).事件表是一個項目清單,這些項目發生時會持續一段時間,事件表記載何時從某一段(segment)跳到下一段.他的流程是完全直線的,但是幾乎可以包含任何可以從規則中去呼叫的東西.僅有的例外是條件判斷式(像是 if)或是迴圈(像是 for). 事件最典型的應用IntelEvent或Autofocus,或是其他你希望一連串的事情在你的控制之下一個接一個發生的場合.下面是一個事件的實例. -- 建立事件表 事件是藉由 Event_Start( ) 函式來呼叫. Event_Start 函式會傳遞你想呼叫的事件.然後你呼叫的事件就會從頭開始執行到尾.讓我們來看這個如何和我們的 HelloWorld 規則一起運作.我們來把沒什麼用處的列印敘述改成呼叫上面這個事件. Rule_Add("Rule_HelloWorld") 事件看起來是有點複雜,不過其實是很直覺的.讓我們把事件拆成幾個部分來看,以對整個架構有更深一步的認識. 2.1建立事件 Events = {} 一定要在你的事件開始時去作呼叫,只會呼叫一次,而且名字一定要叫做"Events".因為遊戲會去查這個名字的表格.如果表格不是這個名字的話,你所有的事件都沒辦法運作.所以當你遇到問題時,記得檢查有沒有漏了這一行. 接下來是為你的事件取名.也就是是下面這一行: Events.intelevent_constructinterceptors = 於是我們有了一個稱作"intelevent_constructinterceptors"的事件.等下我們會用這個名字來開始執行事件以及檢查事件是否已經完畢.因此事件的名字必須是獨一無二的,不能和其他事件重複. 接下來的就是事件的本體了,呼叫函試的區塊會決定接下來會發生什麼事情.如妥我們可以看到的,事件表被拆成好幾段.每一段都用大括號 { 和 } 包起來.每個在相同括號裡面的呼叫都會同時被執行.所以接下來的: } 會同時啟動skipping,告訴聲音部分我們進入了一個Intelevent,讓艦隊指揮停止說話,打開Letterbox,並且等待2秒鐘.關鍵在於HW2_Wait( 2 ).這相當於一個控制器,控制我們何時會進入到事件列表的下一個段落.以這裡來說是兩秒鐘後. 事件被分成好幾個分明的段落.所以我們可以在某個特定的段落中暫停一段時間,讓這個段落能在進入下個段落前跑完. 如同你所見,段落的內容不過就是函式的呼叫而已.注意到 Universe_EnableSkip(1) 和 HW2_Wait(2) 之間的不同.前者被包在大括號裡面,後面還有一個空的欄位,而後者是函式的呼叫. 這兩者之間的差別是,HW2_Wait 是一個helper函式會回傳下列的值: {"wID = Wait_Start( 2 )","Wait_End( wID )"}, 注意到第二個元素是如何去包含其他的函式呼叫了嗎? The second element specifies the function that, when it returns true, ensures this Controller will step to the next segment of the Event list. 在上面的例子中,第一個項目是呼叫 Wait_Start ,這會回傳一個唯一的ID,第二個項目以這個 ID 作為參數呼叫 Wait_End 函式.當指定的時間經過之後, Wait_End 會傳回 ture,讓這個控制器去允許進入下一個段落. 如果第二個項目是空的,那表示第一個函式只會單純的被執行,而不會作用為一個控制器. 所以,若是想要讓這個段落中間不包含helper函式,那看起來就會像這樣: { |
3.特殊函式 |
如果你已經看過萬艦二的關卡的SCAR script,你會注意到一些共通之處.像是 OnInit( ) 函式. OnInit( ) 函式會在關卡第一次被起動時執行,通常包含了加入 Rule_Init( ) 的呼叫. OnInit( ) 和 Rule_Init( ) 之間的差別在於,當關卡被遊戲載入時,遊戲會自動尋找 OnInit( ). 這是遊戲用來開始關卡的進入點.如果 OnInit( ) 裡面沒有東西,那你的任務什麼也不會做! 注意 OnInit( ) 函式跟 OnStartOrLoad( ) 函式的差別. OnInit( ) 只會在關卡第一次被載入時執行,如果這個關卡是因為讀取遊戲存檔而被載入,那麼 OnInit( ) 不會被執行.這就是為什麼要有 OnStartOrLoad( ).如果你有每次載入時-甚至從遊戲存檔中-都需要去啟動的東西(例如某些需要不斷重複的特效),把需要用到的呼叫放在OnStartOrLoad( ) 函式裡面. |
4.建立任務 |
這裡我們會概略描述為萬艦齊發二建立任務的基本步驟.首先我們要為這個教學建立一個新的戰役.然後為這個戰役建立一個基本的任務. 一但目錄結構已經正確的被建立好後,建立任務需要三個步驟.第一,建立並且匯出 mission.level 檔,第二,建立一個基本的 .lua 檔案,第三,將這個任務加進 .campaign 檔裡面. 為了要執行這些步驟,你會需要:
4.1 建立戰役結構 一個戰役中的所有任務都會存放在 data\LevelData\Campaign 資料夾中.在這個資料夾裡面有許多的子目錄,每一個都存放了更多的子目錄,其中存放著真正的任務檔案本體.我們現在要建立一個"Postmortem"(分析用)戰役. 首先,在 data\LevelData\Campaign 資料夾下面建立一個"Postmortem"資料夾,這是我們新戰役的名稱(是沒什麼想像力,不過很適合我們的目的).全部的任務都會放在這裡面. 你可能已經注意到在 Campaign (戰役)資料夾裡面有幾個 .campaign 檔.他們定義了在戰役中會進行哪些任務,以及其他像是過場動畫之類的東西.我們等下在來看這些檔案. 4.1.1 建立 .level
檔案
由於我們正要建立的任務是我們的戰役的第一關,在你的 .level 檔案裡面放入這些項目:
一但你已經把這些項目都放到你的地圖中,在萬艦二目錄下建立 "datasrc\LevelData\Campaign\Postmortem\Mission_01"資料夾,並且把MAYA產生的 .ma 檔案存放在 Mission_01 資料夾中,而且檔名要和資料夾相同(也就是 Mission_01.ma ) 接下來,把這個檔案匯出到你的"Postmortem"戰役資料夾中的 Mission_01 目錄,並且把 .level 檔案取名為 Mission_01.level .現在你的 .level 檔案被存到了 data\LevelData\Campaign\Postmortem\Mission_01 資料夾裡面. 4.2 建立 .lua 檔案
給任務用的 .lua 檔案裡面包含了所有任務中會用到的規則.他的檔名必須和 .level 檔的檔名一樣.以這裡來說,他會是 Mission_01.lua .他們必須放在同一個目錄裡面. 4.2.1 範例 Mission.lua
Script -- 匯入 library 檔案,其中包含了所有的 helper 函式 |
5.設定 .campaign 檔案 |
你需要為你的新戰役建立一個戰役檔案.戰役檔案指出了戰役的關卡存放在哪裡,他們的順序,動畫的一些資訊,諸如此類的東西. 為你的"分析用"戰役建立一個 Postmortem.campaign 檔案,放在 data\LevelData\Campaign 資料夾裡.這個檔案的內容應該如下: -- =============================================================================
當你想要增加新的任務的時候,你需要在檔案中增加新的任務項目. |
6.雜項檔案 |
你可能能會想做一些額外的動作,像是方言化( Localization ),增加特定的AI script ,建立任務載入畫面,或者為任務中的每一個玩家設定隊伍顏色.這些檔案放在任務資料夾裡面,包括了 .dat 檔, Teamcolour.lua, AI.lua, ReferenceFleet.lua, datfiles.lua,以及 Mission.tga. 6.1 DAT 檔案 42999 This is the localized text that would appear in the game 而這個是會去查找這行文字的的事件呼叫: { 以這裡來說,這行Script會顯示 Actor Fleet Command 並且顯示這行字幕:"This is the localized text that would appear in the game". 5標示出了文字會在螢幕上停留多久.以這裡來說是五秒鐘. 如果你有要用到音效檔案,遊戲會到 Data\Sound\english\Speech\MISSIONS 資料夾中尋找檔名和文字號碼相同的音效檔案.以這裡來說,音效檔案的名稱會是42999.fda 6.1.1 將你的任務方言化(Localize) 接下來,你需要建立一個 datafiles.lua 檔案,和你的 mission.level 放在同一個資料夾裡面.這個檔案看起來會像這樣: Dictionaries = { { name = "locale:leveldata/campaign/postmortem/mission_01.dat", }, } 6.2 Teamcolour.lua 要自定任務中玩家的隊伍顏色,建立 teamcolor.lua 檔案並且把他和你的任務放在同一個資料夾中. teamcolor.lua 的範例如下: -- [玩家索引號碼] = {{底色 R, G, B}, {條紋顏色 R, G, B}, "艦徽檔案.tga",
{尾流顏色 R, G, B}, "尾流檔案.tga"}, 6.3 AI.lua AIX.lua的內容已經超過了這份文件所預期要涵蓋的部分. 6.4 ReferenceFleet.lua 6.5 Datfiles.lua 6.6 Mission.tga |
7.附錄 |
•想要取得更多有關於 LUA 的資訊,請參考 http://www.lua.org
的線上文件 •推薦的 Lua 編輯程式? 你可以到 http://www.scintilla.org/SciTE.html 去取得 Scite |
8.聲明 |
本文件主要是翻譯自Relic Developers Network所釋出的"HW2_SCAR.pdf". 翻譯者為西加拉宇宙載具改裝中心的CQD.原始文件版權為Relic所有. |