注意:本文纯属是本人从研究魔兽争霸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:第一次写这种文章,写得不好请见谅!
如果本文对你的开发有所帮助,并且你手头恰好有零钱。
不如打赏我一杯咖啡,鼓励我继续分享优秀的文章。