0.概述
LUA系统是一套由事件触发到函数执行的代码体系,是在常规的Behavior、OCL路线之外,实现神秘游戏效果的利器,这篇文章将由浅入深地向大家介绍LUA系统的关键信息,帮助各位理解LUA系统的结构和用法。阅读本文需要用到SDK中LUA文件夹中的文件(主要是ScriptEvents.xml和Scripts.lua)
1.事件列表(EventList)
所谓的LUA系统到底说了一个什么事呢?其实用一句通俗的话概括就是“当_____的时候,就做_____的事情”,比如“当肚子饿的时候,就做吃饭的事情”,“当没弹药的时候,就做发动技能的事情”,“当重伤的时候,就做播放声效的事情”之类的。这个句子的条件状语,“当_____的时候”,在ScriptEvents.xml里的名字叫事件(Event),而这个句子后面的动词,“就做_____的事情”就是Scripts.lua里的函数(Function)。当然,SDK的源码本身也自带很多“当_____的时候,就做_____的事情”这样的句子,比如铁锤坦克有“当造好的时候,就做隐藏副炮模型的事情”这样的描述,都写在ScriptEvents.xml的事件列表(EventList)里。
这是我在测试LUA功能的时候写的一个EventList,它其实就是记录了这样的一句话:“当ReallyDamaged的时候,就做OnJapanAntiVehicleVehicleTech1ReallyDamaged的事情”。
<EventHandler
EventName="ReallyDamaged
ScriptFunctionName="OnJapanAntiVehicleVehicleTech1ReallyDamaged"DebugSingleStep="false"/>2.LUA函数(LUAFunction)
“当Event的时候,就做Function的事情”,那到底做了什么事情呢?这时候我们就需要看看这个函数(Function)里到底有些什么东西了。用记事本或者UE啥的打开Scripts.lua,发现有一大堆function开头end结尾的东西,和我们通常接触的xml完全不同,这些就是LUA函数。到底做了什么事情呢?就是做了这些事情。这些“function”单词后面的一串就是这个函数的名字,我们在EventList里用到某个函数的时候,就要把函数的名字一字不漏地对应上。函数函数,谁是谁的函数?变量是什么?其实就是函数名字括弧里的东西,例如function kill(self) end,函数名字叫做kill,变量名字叫做self,这和我们中学学的函数是同一个道理,函数f(x),f就是函数名,x就是变量名。当然函数也可以有多个变量,它们之间会用“,”隔开。
我们费那么大力气去搞一个函数,当然想要它实现相应的功能,在这里,我们通常会调用系统自带的一套函数来实现想要的功能。例如,我想要做“海啸开纳米装甲”这个事情,就可以利用系统自带的ObjectDoSpecialPower函数来实现。我就写了这样的一个函数:
function OnJapanAntiVehicleVehicleTech1ReallyDamaged(self)
ObjectDoSpecialPower(self, "SpecialPower_ToggleEnergizedArmor" )
End
怎么理解这个东西呢?我们知道,函数是一种映射关系,在映射的彼岸,我们需要“海啸开纳米装甲”,那么在映射的此岸,我们就要给函数的变量赋予合适的值。这个函数ObjectDoSpecialPower,它只是描述一种映射,通俗地讲就是一种变化规则,是规则,而并不记录具体值,我们需要给它具体值才能让它动起来。ObjectDoSpecialPower函数有两个变量,第二个变量好说,用什么技能嘛,就是把技能名字写上去就行,不过注意要写在引号里,因为我们是直接写一个值,如果不写引号这就是一个变量名了。而第一变量表示谁用这个技能,这还用问?当然是海啸自己啦。但注意,我们不能直接写“海啸”或者“JapanAntiVehicleVehicleTech1”我们需要写的是【这个海啸】,这个海啸是哪个海啸?这在地图编辑器里很好说,“就是那个海啸!“我们可以这样说,然后在地图上指明一个海啸写进去;然而现在并没有一个具体的海啸,我们做mod的时候需要指涉一个抽象的海啸,怎么办?幸好,在主函数里,我们有一个self变量,这里面恰好就存储着【这个海啸】的信息,我们只需要把它用到ObjectDoSpecialPower函数里就可以了,于是,我们在第一个变量的位置写上self,和主函数的变量self一字不差,这就表示它们是同一个变量,从而给ObjectDoSpecialPower函数说明了抽象的【这个海啸】,到时函数运作起来的时候,该是哪个海啸开技能,哪个海啸就会被赋值到self变量中,进而成为ObjectDoSpecialPower函数的变量,正确地使用技能。需要注意的是,变量的名字和函数的名字一样是可以随意更改的,比如我如果讨厌self这个变量名,我写成
functionOnJapanAntiVehicleVehicleTech1ReallyDamaged(haixiao)
ObjectDoSpecialPower(haixiao, "SpecialPower_ToggleEnergizedArmor" )
End
效果是一样的。
3.事件(Event)
我们知道LUA系统是“当Event的时候,就做Function的事情”。
做什么事情大概已经知道了,那“当Event的时候做”,到底是什么时候呢?这就要看ScriptEvents.xml中的三类事件(Event)了,分别是:
内部事件(InternalEvent),游戏系统自带的事件,简单好用,变量丰富
可编辑事件(ScriptedEvent),用户自定义的事件,要多少有多少,用的时候注册Name(类似AssetID,在这里叫Name)。
状态事件(ModelConditionEvent/ObjectStatusEvent) ,用户自定义的事件,用的时候也要注册Name,它们会根据ModelCondition或ObjectStatus的变化判断是否触发。
怎么理解事件(Event)的触发呢?用一句话来说就是“一旦_____,就有______事件”,第一个空是事件的触发条件,第二个空是事件名(Name)。
例如:一旦被打了,这个单位马上就有OnDamaged事件;一旦打光了弹夹,立刻有OnClipEmpty事件。不过要注意的是,事件描述的是行为,不是状态,是在说“弹夹打空了”而非“弹夹是空的”。一般的内部事件(InternalEvent)所描述的东西都可以从事件的名字里猜出来,比如OnDamaged、OnDestroyed、OnPowerOutage这些都很好理解。
而状态事件也好理解,比如
+REALLYDAMAGED就是一个典型的状态事件,它表示一旦这个单位获得了REALLYDAMAGED的状态时,就视为有了ReallyDamaged这个事件。
里阔着的东西就是事件的触发条件。
而可编辑事件(ScriptedEvent)则并没有什么自动的触发机制,它需要其他机制来赋予,比如我可以设置一个“午时已到”的事件,那究竟什么时候是午时呢?我还要用别的机制来说明什么时候是午时。
所以说“当Event的时候,就做Function的事情”到底什么时候做呢?就是在发生这个Event的时候。那这个Event怎么发生呢?就看这个事件的发生条件了,或者你用别的机制来强制让某个名字的事件发生,这是可以的。
4.事件(Event)附带的信息
上文(2.)提到,ObjectDoSpecialPower中第一个变量【这个海啸】的信息来自于主函数OnJapanAntiVehicleVehicleTech1ReallyDamaged(self)的self变量,但实际上主函数并没有给这个self变量赋值,那主函数self变量的【这个海啸】的信息又来自于哪里呢?
实际上就来自于事件ReallyDamaged。
我们知道,事件的作用是充当启动函数的条件,“当Event的时候,就做Function的事情”,像一个开关一样,但其实函数的变量的初始值也是由事件来的,你不但按了这个开关,按的力、按的时间、按的深度都有意义。每个事件附带的信息是不一样的,这可以在官方的注释里看出来,例如
这就说明OnDamaged这个事件附带了两个信息:被攻击的单位自己,攻击自己的单位。比如一个武士被PK攻击了,那么【这个武士】和【这个PK】的信息都会被记录下来,可以传递到函数中。
内部事件和可编辑事件携带的信息往往比较多,而状态事件则只附带了一个信息:【改变状态的这个单位】。
例如上文+REALLYDAMAGED如果当在这个事件发生时,就要做某个函数的事情,那ReallyDamaged事件只能传递给这个函数【获得了REALLYDAMAGED状态的这个单位】的信息。
5.写码套路
所谓的LUA系统,就是一句话“当Event的时候,就做Function的事情”,当然这句话要转化为xml语言写进mod源码里,一般写在*\Additional\Data\scripts的ScriptEvents.xml文件内,比如:
<EventList
Name="JapanAntiVehicleVehicleTech1Functions"
Inherit="BaseScriptFunctions">
<EventHandler
EventName="ReallyDamaged
ScriptFunctionName="OnJapanAntiVehicleVehicleTech1ReallyDamaged"DebugSingleStep="false"/>
</EventList>
其中<EventHandler
/>里的这便是“当ReallyDamaged的时候,就做OnJapanAntiVehicleVehicleTech1ReallyDamaged的事情”这句话的翻版。那么“当ReallyDamaged的时候”到底是什么时候呢?就是:
<ModelConditionEvent
Name="ReallyDamaged">
<Conditions>+REALLYDAMAGED</Conditions>
</ModelConditionEvent>
这段也要写到ScriptEvents.xml里(实际上官方文件有),这段就是在说“一旦这个单位有REALLYDAMAGED的ModelCondition,就视为发生ReallyDamaged的事件”。
好了,“当……”有了,做什么呢?做OnJapanAntiVehicleVehicleTech1ReallyDamaged,这个函数要写到*\Additional\Data\scripts的Scripts.lua里:
functionOnJapanAntiVehicleVehicleTech1ReallyDamaged(Self)
ObjectDoSpecialPower(Self, "SpecialPower_ToggleEnergizedArmor" )
End
好了,我们来检查一下。做OnJapanAntiVehicleVehicleTech1ReallyDamaged这个函数到底是做什么呢?
就是做ObjectDoSpecialPower这个函数呀。
那这个函数又有什么用呢?
就是使用名为SpecialPower_ToggleEnergizedArmor的技能嘛。
那谁使用技能呢?
Self使用。
谁是Self?
主函数OnJapanAntiVehicleVehicleTech1ReallyDamaged的Self咯。
所以是谁呢?
就是ReallyDamaged事件里说的那个啊!
哦,好了,原来是“当这个单位ReallyDamaged的时候,就做该单位开技能的事情”。这个单位那怎么就是海啸了呢?而不是天狗?不是武士呢?
所以需要在海啸这个单位的Behavior写上这一段呀:
<LUAEventList
id="ModuleTag_LUAEventList"
EventListName="JapanAntiVehicleVehicleTech1Functions"/>
记得这一定要和EventList对应上。
6.设计思想和高级用法
如大家所见,LUA系统十分繁琐,一个微小的改动都需要改变三四个文件。更麻烦的是SDK本身不支持LUA系统的查错,也就是说你任何一个ID/Name只要打错一个标点符号就全盘皆乱,所以在练习的时候应该循序渐进,由小及大,先学会像自动技能、剥夺操作权、播放换弹音效之类的基本操作。
事件(Event)和函数(Function)是LUA系统最重要的两点,利用事件的自发性我们可以用LUA来做许多自动行为,在Kindof/ObjectFilter难以涉及的一些条件下作出游戏效果;而利用函数的灵活性,我们可以执行地图编辑器的脚本,或者实现一些复杂的多单位互动效果,例如给予/剥夺Upgrade、空放/对目标使用技能、伤害/杀死/抹去单位、显示/隐藏模型、改变单位所属/阵营色、播放音乐/音效/影片、调节音量、杀死/踢出玩家、裁判胜负、计数器等功能。这里有一份SAGE全LUA命令文件,大家可作参考,具体函数的变量如何选择可以参考地图编辑器脚本。
关于事件的触发,出了自带的触发之外,武器也可以触发事件,例如这种Nugget:
<LuaEventNugget
id="2"
EventName="EASB"
SendToEnemies="true"
SendToAllies="true"
SendToNeutral="true">
</LuaEventNugget>
此外,LUA函数中ObjectBroadcastEvent系列函数也可以触发事件,例如:
ObjectBroadcastEventToUnits(self,EASB,200)
LUA函数中有许多判断类函数,例如ObjectHasUpgrad,以及所有的EvaluateCondition系列函数。它们有一个返回值,如果判断成立则返回1,判断不成立则返回0,这样的判断能弥补通常用ObjectFilter所带来的不足。例如下面的函数就可以让某个单位在升级后自杀。
function Upgradekill(self)
if ObjectHasUpgrade(self,“upgrade_killyourself”)==1
then ExecuteAction("NAMED_KILL",self)
elseif return
end
end
7.总之
LUA能绕过Behavior、OCL、Weapon等传统代码,实现复杂而神奇的功能,希望这个教程能够帮助大家读懂LUA函数、拓展设计思路、优化复杂代码、发掘引擎功能。谢谢各位。
星火战队-Ngen
2018年8月14日