|
如何安全有效的编码
前言
这篇教程是针对有一定WE编辑基础的人. 在阅读之前你需要了解JASS和VJASS的语法,至少要知道什么是泄漏而什么不是.
目录
安全性摘要内容
单位组
- 创建 / 删除
- 选取
关联
单位索引
动态触发
- 安全性考虑
- 动作与条件
正文
.....................: 安全性摘要内容 :.....................
使用UnitDamagePoint(BJ)或者UnitDamagePointLoc(CJ)将会造成玩家游戏不同步.
当游戏被保存和重新加载后,所有类型变量数组的第8192位后都会丢失.
所以使用数组的安全范围在索引[0]到[8190]之间.
等待时间条件 (非等待游戏时间)无视玩家暂停游戏或者玩家掉线造成的等待时间.
当销毁计时器后设置其值为NULL,有时会造成冲突.
无返回值的函数转到筛选/条件后会造成游戏不同步.
.....................: 单位组 :.....................
-- 创建/删除 --
在处理局部单位组时,我们为了避免泄漏必须删除它们.这个做法十分正确
,问题是创建单位组效率很低.假如在要大量使用单位组时,这个问题就会
突显出来.
而且在某些情况下删除单位组可能造成泄漏.我们可以使用全局组来代替:
[jass]
scope GroupEx
globals
private group tempGroup
endglobals
function DoSomething takes nothing returns nothing
// 使用前清空组
call ClearGroup(tempGroup)
// 使用tempGroup来做你想要的
// *** 例子 ***
call GroupEnumUnitsInRange(tempGroup,X,Y,500,null)
call GroupIssuePointOrder(tempGroup,0,0,"attack")
endfunction
public function InitTrig takes nothing returns nothing
//....
set tempGroup=CreateGroup()
endfunction
endscope [/jass]
不过大多数情况下仅仅使用一个组远远不能满足需求,用结构体就可以完美的处理大堆的组:
[jass]
// 不用删除组
// 只需要清空组,然后交给下个结构体.
struct Data
group g
method create takes nothing returns Data
local Data d=Data.allocate()
if d.g==null then
set d.g=CreateGroup()
endif
return d
endmethod
method onDestroy takes nothing returns nothing
call GroupClear(.g)
endmethod
endstruct
[/jass]
-- 选取 --
ForGroupBJ()函数能选取组里的所有单位做动作,是ForGroup()的封装.
最快速选取组里单位的方法就是使用ForGroup()了.而且最好使用临时的
全局变量.
[jass]
globals
private player temp
endglobals
private function Loop takes nothing returns nothing
call SetUnitOwner(GetEnumUnit(),temp,true)
endfunction
function GroupChangeOwner takes group g, player newOwner returns nothing
set temp=newOwner
call ForGroup(g,function Loop)
endfunction
[/jass]
假如只需要选取一次组内单位,就没有必要用ForGroup了.这和制作技能的
时候一样,直接调用GroupEnum就能直接选取了.
[jass]
globals
private unit tempUnit
private integer tempInt
private group tempGroup
private boolexpr cond
endglobals
//=================================================
private function Loop takes nothing returns boolean
if IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(tempUnit))
then
call UnitDamageTarget(tempUnit, GetFilterUnit(), tempInt, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
endif
return false
endfunction
function AOEDamage takes unit attacker, real x, real y, integer
damage returns nothing
set tempUnit = attacker
set tempInt = damage
call GroupEnumUnitsInRange(tempGroup,x,y,RADIUS,cond)
endfunction
//=================================================
private function Init takes nothing returns nothing
set tempGroup=CreateGroup()
set cond=Condition(function Loop)
endfunction
[/jass]
.....................: 关联 :.....................
使用H2I函数可以把任意类型数值转换为一个整数。
[jass]
function H2I takes handle h returns integer
return h
return 0
endfunction
[/jass]
这用来把数据和实物联系起来(方法不止一种).出于安全性考虑我强烈建议你只关联整数和结构体。假如你关联(或者明确的恢复)了结构体或整数外的东西,那么魔兽争霸将会出现各种搞笑的问题。
因此我们需要避免使用:
Handle Vars(句柄变量)
CSCache(详见caster system)
现在有更多关于关联的系统,它们更稳定,效率更高。“Timer Utils”就是一个比较受欢迎的系统,不过它只能和计时器关联。
附录:
最初Vexorian对于TimerUtils的帖子
对于计时器和触发来说,关联非公有数值是非常好的办法。 我们不需要动态触发器 / the mantra is that dynamic triggers are
evil. So we just need quick private attaching for timers? Well,
maybe we could use private attaching for ... units or items but
they already got user data, there are also dialog buttons but
speed is not important there.
澄清: when he says speed is not important, he means that
you should go for another solution than custom attaching
(such as gamecache / table). If you do need fast attaching to
something else than timers, look for HSAS, HAIL, or old CSData
(for units, use indexing).
使用TimerUtils关联:
[jass]
library Blah needs TimerUtils
struct EffectWrapper
effect fx
endstruct
private function CET_2 takes nothing returns nothing
local EffectWrapper ew=GetTimerData(GetExpiredTimer())
call DestroySpecialEffect(ew.fx)
call ew.destroy()
call ReleaseTimer(GetExpiredTimer())
endfunction
public function TimedEffect takes effect fx, real time returns
nothing
local timer t=NewTimer()
local EffectWrapper ew = EffectWrapper.create()
set ew.fx=fx
call SetTimerData(t,ew)
call TimerStart(t,time,false,function CET_2)
set t=null
endfunction
endlibrary
[/jass]
请多多注意TimerUtils是如何为“CSSafety”的旧ReleaseTimer(timer)和NewTimer()函数做功夫的. 这些比直接创建/销毁计时器更安全更快捷。
.....................: 单位索引 :.....................
Attaching to units is troublesome. A unit can die / decay / be
removed from the game, and then we must to remove the
attached data. However, there are no Unit is Removed - events.
For this reason, you should use an Indexing System. These
systems keep track on the units, and also make the process of
"attaching" pretty straightforward and fast. Both cohadar's PUI
and Strilanc's DUI give you a function which returns a unique
index for each unit. You can then use that number for storing
things in arrays. DUI also offers a nice function callback when it
has detected a unit is removed.
A simplified kill-counter:
[jass]
// If using DUI, you should add a callback for resetting the kill
counter when the unit is removed.
// If using PUI, read the tutorial to find out what to do :)
function CountKills_Actions takes nothing returns nothing
// GetUnitIndex returns a unique number for the unit.
local integer i = GetUnitIndex( GetTriggerUnit() )
// So, we use that number to keep track on data!
// This would be the kill count of the triggering unit.
set killCount = 1 + killCount
endfunction
[/jass]
.....................: 动态触发 :.....................
-- 安全性考虑 --
A trigger which has it's attributes (event/condition/action)
modified by a script not running at map init is called a dynamic
trigger. If you do not know about triggers in JASS, I'd
recommend Vexorian's tutorial.
Since you can create triggers on-the-fly, you will also want to
destroy the triggers, in order to prevent memory leaks. This is
bad because destroying the trigger under certain conditions
(having waits in any code used by, or called by the trigger) may
cause two handles to get the same id.
Usually you can work your way around using dynamic triggers
in your scripts. For Damage Detection, you have to use a
dynamic trigger. The solution here is to never destroy the
trigger, and accept the fact that it will leak a bit. After all, you
would rather have a leaking trigger than a corrupted game. For
auras and other enter range events, you can use periodical
GroupEnums.
-- 动作与条件 --
触发条件比触发动作的效率要高,不过在条件中无法使用等待.
[jass]
// 自定义技能.
private function Actions takes nothing returns boolean
if GetSpellAbilityId() == 'char' then
call SetUnitOwner(GetSpellTargetUnit(),GetOwningPlayer (GetSpellAbilityUnit()),true)
endif
return false
enfunction
public function InitTrig takes nothing returns nothing
local trigger trg=CreateTrigger()
call TriggerRegisterAnyUnitEvent (trg,PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trg, Condition(function Actions))
set trg=null
endfunction
[/jass] |
|