注意:本文純屬是本人從研究魔獸爭(zhēng)霸III地圖編輯器的過(guò)程中的一種猜想,所以,不當(dāng)之處,還請(qǐng)高手指出。謝謝!
魔獸爭(zhēng)霸III我以前玩得比較多,也聽(tīng)說(shuō)他的地圖編輯器非常牛X,以前也曾經(jīng)想編輯個(gè)地圖出來(lái)。但是,限于當(dāng)時(shí)得水平問(wèn)題,沒(méi)有成功。直到最近在研究如何制作游戲的時(shí)候,打開(kāi)魔獸爭(zhēng)霸III的地圖編輯器來(lái)看,突然有一種擴(kuò)然開(kāi)朗的感覺(jué)!哦!原來(lái)地圖編輯器是這樣出來(lái)的!閑話不多說(shuō)!馬上進(jìn)入正題!
魔獸爭(zhēng)霸III的地圖編輯器使用了一個(gè)文件來(lái)代表一個(gè)地圖,地圖里包含了什么東西?我無(wú)從得知,但是,從他的編輯器上看,看到編輯器能對(duì)地圖修改的東西,就可以大概猜想到有哪些東西。仔細(xì)看地圖編輯器,看到有那么幾個(gè)模塊:地形編輯器、開(kāi)關(guān)編輯器、聲音編輯器、物體編輯器、戰(zhàn)役編輯器、AI編輯器、物體管理器、輸入管理器。我把我的理解逐一說(shuō)來(lái)。
1.地形編輯器
主要用來(lái)編輯地形,例如某個(gè)地方擺放什么地形,什么地方擺放什么物體、英雄、燈光等等東西。那么,這些東西在地圖文件中我想是以數(shù)據(jù)的形式來(lái)存放的,而不是腳本!因?yàn)椋覝y(cè)試過(guò),放了一個(gè)英雄到地圖中,然后導(dǎo)出腳本,但是腳本里沒(méi)有任何關(guān)于這個(gè)英雄的資料!那就證明了是用某種格式保存在地圖文件中。整個(gè)地形都是這么存放。
2.開(kāi)關(guān)編輯器
開(kāi)關(guān)編輯器,也就是觸發(fā)事件編輯器。這個(gè)東西在地圖編輯器中比較的高級(jí),人們都說(shuō)魔獸爭(zhēng)霸III的地圖編輯器是萬(wàn)能編輯器,很大的原因就是因?yàn)橛辛诉@個(gè)東西!這個(gè)東西是如何實(shí)現(xiàn)的呢?說(shuō)起來(lái)很簡(jiǎn)單!就是腳本實(shí)現(xiàn)!我分析了一下,一個(gè)觸發(fā)器,分成三個(gè)部分:發(fā)生事件、觸發(fā)條件、執(zhí)行動(dòng)作。那么三個(gè)東西在腳本中和程序中是如何實(shí)現(xiàn)?
讓我在編輯器中新建一個(gè)觸發(fā)器解釋一下。新建一個(gè)觸發(fā)器,叫做TestTrigger,在這個(gè)觸發(fā)器下新建一個(gè)事件“玩家 - 玩家1 (紅色) leaves the game”,新建一個(gè)條件“TRUE 等于TRUE”,新建一個(gè)動(dòng)作“Do nothing”。然后導(dǎo)出腳本,看看腳本如何:
//===========================================================================
//
// 只是另外一張魔獸爭(zhēng)霸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
// 我們的觸發(fā)器保存成一個(gè)全局變量了!!
trigger gg_trg_TestTrigger = null
endglobals

function InitGlobals takes nothing returns nothing
endfunction

//***************************************************************************
//*
//* Triggers
//* 觸發(fā)器!
//***************************************************************************

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

// 我們的觸發(fā)器的執(zhí)行動(dòng)作!!
function Trig_TestTrigger_Actions takes nothing returns nothing
// DoNothing ?? 不正是我們?cè)O(shè)置的“Do nothing”嗎?
call DoNothing( )
endfunction

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

// 翻譯:“觸發(fā)器:添加觸發(fā)條件”,然后參數(shù)就是上面那個(gè)觸發(fā)條件的函數(shù),函數(shù)里就是我們?cè)O(shè)置的條件
call TriggerAddCondition( gg_trg_TestTrigger, Condition( function Trig_TestTrigger_Conditions ) )

// 翻譯:“觸發(fā)器:添加執(zhí)行動(dòng)作”,然后參數(shù)就是上面那個(gè)執(zhí)行動(dòng)作的函數(shù),函數(shù)里就是我們?cè)O(shè)置的動(dòng)作!
call TriggerAddAction( gg_trg_TestTrigger, function Trig_TestTrigger_Actions )
endfunction

//===========================================================================
// 地圖初始化的時(shí)候都會(huì)調(diào)用這個(gè)函數(shù)初始化所有自定義的觸發(fā)器,當(dāng)然也有我們的觸發(fā)器了。InitTrig_TestTrigger不就是剛才那個(gè)函數(shù)了嗎
function InitCustomTriggers takes nothing returns nothing
call InitTrig_TestTrigger( )
endfunction

//***************************************************************************
//*
//* Main Initialization
//* main啊!這么熟悉!一定是此腳本文件的入口函數(shù)了!在C++主程序中調(diào)用的!(我暫且認(rèn)為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( )
// 上面執(zhí)行的這幾個(gè)函數(shù)主要是設(shè)置一些環(huán)境變量,什么攝像頭、日晝幾何模型、背景音樂(lè)等等
// 這個(gè)函數(shù)就是初始化我們的觸發(fā)器啊!
call InitCustomTriggers( )
endfunction

//***************************************************************************
//*
//* Map Configuration
//* 地圖配置,估計(jì)也是在C++主程序中調(diào)用的!
//***************************************************************************

function config takes nothing returns nothing
call SetMapName( "只是另外一張魔獸爭(zhēng)霸III的地圖" )
call SetMapDescription( "沒(méi)有描述" )
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


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