注意:本文純屬是本人從研究魔獸爭霸III地圖編輯器的過程中的一種猜想,所以,不當之處,還請高手指出。謝謝!
魔獸爭霸III我以前玩得比較多,也聽說他的地圖編輯器非常牛X,以前也曾經想編輯個地圖出來。但是,限于當時得水平問題,沒有成功。直到最近在研究如何制作游戲的時候,打開魔獸爭霸III的地圖編輯器來看,突然有一種擴然開朗的感覺!哦!原來地圖編輯器是這樣出來的!閑話不多說!馬上進入正題!
魔獸爭霸III的地圖編輯器使用了一個文件來代表一個地圖,地圖里包含了什么東西?我無從得知,但是,從他的編輯器上看,看到編輯器能對地圖修改的東西,就可以大概猜想到有哪些東西。仔細看地圖編輯器,看到有那么幾個模塊:地形編輯器、開關編輯器、聲音編輯器、物體編輯器、戰役編輯器、AI編輯器、物體管理器、輸入管理器。我把我的理解逐一說來。
1.地形編輯器
主要用來編輯地形,例如某個地方擺放什么地形,什么地方擺放什么物體、英雄、燈光等等東西。那么,這些東西在地圖文件中我想是以數據的形式來存放的,而不是腳本!因為,我測試過,放了一個英雄到地圖中,然后導出腳本,但是腳本里沒有任何關于這個英雄的資料!那就證明了是用某種格式保存在地圖文件中。整個地形都是這么存放。
2.開關編輯器
開關編輯器,也就是觸發事件編輯器。這個東西在地圖編輯器中比較的高級,人們都說魔獸爭霸III的地圖編輯器是萬能編輯器,很大的原因就是因為有了這個東西!這個東西是如何實現的呢?說起來很簡單!就是腳本實現!我分析了一下,一個觸發器,分成三個部分:發生事件、觸發條件、執行動作。那么三個東西在腳本中和程序中是如何實現?
讓我在編輯器中新建一個觸發器解釋一下。新建一個觸發器,叫做TestTrigger,在這個觸發器下新建一個事件“玩家 - 玩家1 (紅色) leaves the game”,新建一個條件“TRUE 等于TRUE”,新建一個動作“Do nothing”。然后導出腳本,看看腳本如何:
//===========================================================================
//
// 只是另外一張魔獸爭霸III的地圖
//
// Warcraft III map script
// Generated by the Warcraft III World Editor
// Date: Sat Nov 18 23:35:12 2006
// Map Author: 李錦俊
//
//===========================================================================

//***************************************************************************
//*
//* Global Variables
//* 全局變量
//***************************************************************************

globals
// Generated
// 我們的觸發器保存成一個全局變量了!!
trigger gg_trg_TestTrigger = null
endglobals

function InitGlobals takes nothing returns nothing
endfunction

//***************************************************************************
//*
//* Triggers
//* 觸發器!
//***************************************************************************

//===========================================================================
// Trigger: TestTrigger 我們的觸發器的觸發條件
//===========================================================================
function Trig_TestTrigger_Conditions takes nothing returns boolean
// 如果true == true ?? 不正是我們設置的“TRUE等于TRUE”嗎
if ( not ( true == true ) ) then
return false
endif
return true
endfunction

// 我們的觸發器的執行動作!!
function Trig_TestTrigger_Actions takes nothing returns nothing
// DoNothing ?? 不正是我們設置的“Do nothing”嗎?
call DoNothing( )
endfunction

//===========================================================================
// 初始化我們的觸發器
function InitTrig_TestTrigger takes nothing returns nothing
// 創建一個觸發器,保存在一個全局變量里
set gg_trg_TestTrigger = CreateTrigger( )
// 看這個英文的函數名。。我翻譯一下應該是“觸發器:注冊玩家單位簡單事件”
// 再看看參數
// 第一個參數是我們的觸發器的全局變量
// 第二個參數是Player(0)啊,不就是我們設置的“玩家1”嗎?(語言上從0開始,顯示上從1開始,習慣了)。
// 第三個參數EVENT_PLAYER_UNIT_DEATH,翻譯一下應該是“玩家單位死亡事件”,哈哈!很明顯又是我們設置的
call TriggerRegisterPlayerUnitEventSimple( gg_trg_TestTrigger, Player(0), EVENT_PLAYER_UNIT_DEATH )

// 翻譯:“觸發器:添加觸發條件”,然后參數就是上面那個觸發條件的函數,函數里就是我們設置的條件
call TriggerAddCondition( gg_trg_TestTrigger, Condition( function Trig_TestTrigger_Conditions ) )

// 翻譯:“觸發器:添加執行動作”,然后參數就是上面那個執行動作的函數,函數里就是我們設置的動作!
call TriggerAddAction( gg_trg_TestTrigger, function Trig_TestTrigger_Actions )
endfunction

//===========================================================================
// 地圖初始化的時候都會調用這個函數初始化所有自定義的觸發器,當然也有我們的觸發器了。InitTrig_TestTrigger不就是剛才那個函數了嗎
function InitCustomTriggers takes nothing returns nothing
call InitTrig_TestTrigger( )
endfunction

//***************************************************************************
//*
//* Main Initialization
//* main啊!這么熟悉!一定是此腳本文件的入口函數了!在C++主程序中調用的!(我暫且認為WarCraftIII是用C++寫的了)
//***************************************************************************

//===========================================================================
function main takes nothing returns nothing
call SetCameraBounds( -3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), -3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), -3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), -3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM) )
call SetDayNightModels( "Environment\\DNC\\DNCLordaeron\\DNCLordaeronTerrain\\DNCLordaeronTerrain.mdl", "Environment\\DNC\\DNCLordaeron\\DNCLordaeronUnit\\DNCLordaeronUnit.mdl" )
call NewSoundEnvironment( "Default" )
call SetAmbientDaySound( "LordaeronSummerDay" )
call SetAmbientNightSound( "LordaeronSummerNight" )
call SetMapMusic( "Music", true, 0 )
call InitBlizzard( )
call InitGlobals( )
// 上面執行的這幾個函數主要是設置一些環境變量,什么攝像頭、日晝幾何模型、背景音樂等等
// 這個函數就是初始化我們的觸發器啊!
call InitCustomTriggers( )
endfunction

//***************************************************************************
//*
//* Map Configuration
//* 地圖配置,估計也是在C++主程序中調用的!
//***************************************************************************

function config takes nothing returns nothing
call SetMapName( "只是另外一張魔獸爭霸III的地圖" )
call SetMapDescription( "沒有描述" )
call SetPlayers( 1 )
call SetTeams( 1 )
call SetGamePlacement( MAP_PLACEMENT_USE_MAP_SETTINGS )

call DefineStartLocation( 0, -1409.3, 219.2 )

// Player setup
call InitCustomPlayerSlots( )
call SetPlayerSlotAvailable( Player(0), MAP_CONTROL_USER )
call InitGenericPlayerSlots( )
endfunction


我加的注釋里已經寫得很清楚了,很明顯我們在地圖編輯器里面設置的所有觸發器的東西,都會以一種腳本的形式生成,然后程序在根據地圖數據初始化地圖之后調用這個腳本的main函數和config函數。在游戲進行過程中,TriggerRegister開頭的函數注冊了一些事件開端,簡單的實現就是維護一個列表而已。然后游戲進行到這個事件的時候(例如剛才的例子里是TriggerRegisterPlayerUnitEventSimple,則玩家單位發生一些簡單事件的時候就會執行),就遍歷這個列表的每一個元素調用他們用TriggerAddCondition注冊的條件,如果為true,則執行TriggerAddAction函數的內容!觸發器的實現原理基本上就是這樣了!當然,真正實現起來還是有很多東西要做的,例如要有很多很多的函數要綁定到腳本引擎中!
3.聲音編輯器
這個我沒研究過啊,待續....
4.物體編輯器
里面就是所有單位、建筑物、物品(簡稱Items吧,中文翻譯過來就變味了,呵呵!)的一些屬性設置,這些屬性都是以數據的形式保存在地圖中的,程序運行的時候就以這些數據來基礎運行起來,例如某某英雄聲明值是多少云云....
5.戰役編輯器
沒搞過,不多作評論
6.AI編輯器
我只知道AI不是腳本引擎實現的!他只是一些數據,至于為何不用腳本來實現?我想應該還是執行效率問題吧!(猜想而已...)
7.物體管理器
用來管理場景上出現的物體,也是數據保存。
8.輸入管理器
實現一個簡單的單文件系統,就是說在一個地圖文件里包含多個附件進去,例如:模型、貼圖等等。那么就可以實現一個自定義的東西了(例如自定義一個以我的形象做出來的英雄!哈哈!)。然后程序可以直接調用這個模型。
另外,說說C++程序中應該如何導入綁定函數到腳本中,綁定的方法很多文章都有說,我的BLOG上LUA那部分也有幾篇這樣的文章。我現在要說的是應該綁定什么函數進去腳本引擎中。我覺得,腳本引擎主要是實現一些速度要求不高,但是邏輯性又極強的代碼。那么,相反,對速度要求高,邏輯性比較固定的,就不應該寫到腳本引擎中。
舉幾個應該在腳本中實現的例子:注冊觸發器、觸發器的執行條件、觸發器執行的事件、地圖初始化之后應該設置的信息(例如攝像機的位置,Load一些東西),這些東西都是邏輯性比較強,也就是說變化是比較大的,如果用數據來驅動,估計很難驅動得了(就好像,N個條件組合,還有邏輯判斷的東西,如何能用數據來表示?例如:3D中的可編程流水線(Shader腳本驅動)就有很多固定流水線(渲染狀態數據驅動)無法實現的)。
舉幾個不應該在腳本中實現的例子:尋路算法、讀取文件等等需要比較多系統資源的操作。AI,是一個特例,他執行速度要求比較高,但是他的邏輯性又比較強。暴雪公司的選擇是速度,那么他們就需要作一個比較龐大的數據來驅動這些AI(這里編程估計比較復雜了)。我還沒學過AI的算法呢,只知道復雜。不多作評論了,還請高手們給指點指點。
舉了幾個比較有代表意義的例子。簡單的說,就是應該把一些固定功能的函數盡量用C++來實現,然后綁定到腳本引擎中,然后由腳本引擎來實現相應的邏輯。
好了,大的東西基本上都說完了。好像也沒說什么,主要就是分析了腳本的實現了。可能說得不明白啊,大家回帖討論一下。
PS:第一次寫這種文章,寫得不好請見諒!
如果本文對你的開發有所幫助,并且你手頭恰好有零錢。
不如打賞我一杯咖啡,鼓勵我繼續分享優秀的文章。
