|
为什么我一直不让jass初学者使用set bj_wantDestroyGroup = true ?
一来~~手动销毁单位组在我看来只有更正式更具可读性更易理解~~二来呢这东西看似方便~~本质上还是DestroyGroup()~~而且无端增加多余的代码~~所以比直接销毁单位组效率低得多~~最关键的问题是~这玩意儿存在不少的害人之bug
BUG在哪里?全局变量冲突。
让我们来看看set bj_wantDestroyGroup = true的实现原理
打开Blizzard.j搜索bj_wantDestroyGroup字样
[jass]
function IsUnitGroupDeadBJ takes group g returns boolean
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
set bj_isUnitGroupDeadResult = true
call ForGroup(g, function IsUnitGroupDeadBJEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(g) //这里
endif
return bj_isUnitGroupDeadResult
endfunction
function IsUnitGroupEmptyBJ takes group g returns boolean
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
set bj_isUnitGroupEmptyResult = true
call ForGroup(g, function IsUnitGroupEmptyBJEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(g) //这里
endif
return bj_isUnitGroupEmptyResult
endfunction
function ForGroupBJ takes group whichGroup, code callback returns nothing
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
call ForGroup(whichGroup, callback)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(whichGroup) //这里
endif
endfunction
function GroupAddGroup takes group sourceGroup, group destGroup returns nothing
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
set bj_groupAddGroupDest = destGroup
call ForGroup(sourceGroup, function GroupAddGroupEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(sourceGroup) //这里
endif
endfunction
function GroupRemoveGroup takes group sourceGroup, group destGroup returns nothing
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
set bj_groupRemoveGroupDest = destGroup
call ForGroup(sourceGroup, function GroupRemoveGroupEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(sourceGroup) //这里
endif
endfunction
function GroupPickRandomUnit takes group whichGroup returns unit
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
set bj_groupRandomConsidered = 0
set bj_groupRandomCurrentPick = null
call ForGroup(whichGroup, function GroupPickRandomUnitEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(whichGroup) //这里
endif
return bj_groupRandomCurrentPick
endfunction
function CountUnitsInGroup takes group g returns integer
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //这里
set bj_wantDestroyGroup = false //这里
set bj_groupCountUnits = 0
call ForGroup(g, function CountUnitsInGroupEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then //这里
call DestroyGroup(g) //这里
endif
return bj_groupCountUnits
endfunction
[/jass]
以上是所有可以应用bj_wantDestroyGroup的函数~~可见bj_wantDestroyGroup的作用原理是~用局部变量wantDestroy记录bj_wantDestroyGroup的值~~然后将bj_wantDestroyGroup设为false~~当函数运行完毕以后再判断wantDestroy是否为真~~为真则销毁单位组~~
也许你看完以后会说~~它明明是用局部变量保存的~~哪里冲突了?~~
hmmm~~我当然没有说冲突发生在函数体以内~~实际上~~在另外一个地方~~单是盯着上面的源码看可能看一个钟头也想不到~~但是你在实际应用中不可避免地会碰到~~
考查以下几个实际例子~~
[trigger]
XXX
Events
Player - Player 1 (Red) skips a cinematic sequence
Conditions
Actions
Custom script: set bj_wantDestroyGroup=true
Set i = (Number of units in (Units of type Footman))
[/trigger]
[trigger]
YYY
Events
Player - Player 1 (Red) skips a cinematic sequence
Conditions
Actions
Set UG = (Units owned by Player 2 (Blue))
Custom script: set bj_wantDestroyGroup=true
Unit Group - Pick every unit in (Random 4 units from UG) and do (Actions)
Loop - Actions
Unit - Change ownership of (Picked unit) to Player 1 (Red) and Change color
[/trigger]
以上2个触发都是比较常见的BUG触发实例~~
显然触发XXX想要的结果是在屏幕上输出地图上步兵的个数~~而由于会生成一个地图上所有步兵集合的单位组~~于是在前面加了一条Custom script: set bj_wantDestroyGroup=true ~~意图在输出完成后销毁这个单位组~~
而YYY想要的结果显然是随机选出属于玩家2的4个单位~~然后将它们交给玩家1并改变颜色~~考虑到"随机的4个单位"也是个多余的单位组~~于是自然就会想用set bj_wantDestroyGroup=true来销毁之~~
我们可以在地图上放上12个玩家2的步兵进行测试~~理论上按下esc后应该显示12~~并且其中有4个被转移给玩家1~~
不过~~事实是~~运行地图按下esc后~~屏幕上显示的是0~~而玩家2的步兵也一个都没有转移所属~~
现在我们就来展示其中的bug~~
将上面2个触发的动作转为jass
[jass]
function Trig_XXX_Actions takes nothing returns nothing
set bj_wantDestroyGroup=true
set udg_i = CountUnitsInGroup(GetUnitsOfTypeIdAll('hfoo'))
call DisplayTextToForce( GetPlayersAll(), I2S(udg_i) )
endfunction
[/jass]
[jass]
function Trig_YYY_Func004A takes nothing returns nothing
call SetUnitOwner( GetEnumUnit(), Player(0), true )
endfunction
function Trig_YYY_Actions takes nothing returns nothing
set udg_UG = GetUnitsOfPlayerAll(Player(1))
set bj_wantDestroyGroup=true
call ForGroupBJ( GetRandomSubGroup(4, udg_UG), function Trig_YYY_Func004A )
endfunction
[/jass]
也许一时半会还看不出问题~~那么现在开始~~想象自己是单步调试机~~我们开始执行XXX触发器的动作了~~
先运行第一条代码~~全局变量bj_wantDestroyGroup值被设为真~~
运行第二条代码~~我们先运行CountUnitsInGroup()么?~~不对~~先得计算参数GetUnitsOfTypeIdAll('hfoo')~~那么就要跳入函数GetUnitsOfTypeIdAll()进行计算了~~该函数是一个BJ函数~~那么我们列出代码~~可以看出该函数的作用原理是遍历所有玩家~~然后将每个玩家的步兵组g分别投入到result中~~最后返回result~~不过我们现在还是继续单步执行比较好~~
[jass]
function GetUnitsOfTypeIdAll takes integer unitid returns group
local group result = CreateGroup()
local group g = CreateGroup()
local integer index
set index = 0
loop
set bj_groupEnumTypeId = unitid
call GroupClear(g)
call GroupEnumUnitsOfPlayer(g, Player(index), filterGetUnitsOfTypeIdAll)
call GroupAddGroup(g, result)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call DestroyGroup(g)
return result
endfunction
[/jass]
继续~~我们加快点速度~~创建单位组result,创建单位g,创建局部整型变量index作为循环变量,设index初值,循环开始~~
给全局变量bj_groupEnumTypeId赋值~~用来作为枚举过滤~~清空g~~再开始将Player(0)的所有步兵装入单位组g~~
下面就是关键了~~放慢速度~~
call GroupAddGroup(g, result)
将单位组g整个装入result单位组~~
我们继续跳入GroupAddGroup()这个函数~~如果你记性不错~~那么会记得之前我列出的所有应用bj_wantDestroyGroup的BJ函数中~~已经包括了该函数的源码在内了~~不过这里为了展示方便~~不妨多copy一遍~~
[jass]
function GroupAddGroup takes group sourceGroup, group destGroup returns nothing
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup
set bj_wantDestroyGroup = false
set bj_groupAddGroupDest = destGroup
call ForGroup(sourceGroup, function GroupAddGroupEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then
call DestroyGroup(sourceGroup)
endif
endfunction
[/jass]
好了~~我们将2个单位组g和result作为参数传递给GroupAddGroup()的参数sourceGroup和destGroup~~为GroupAddGroup()创建局部变
布尔量wantDestroy~~值为bj_wantDestroyGroup~~而现在我们检查我们大脑中记忆体中的bj_wantDestroyGroup的值罢~~好吧~~是true~~那么wantDestroy自然也被赋值为true了~~
赋值完成后~~bj_wantDestroyGroup的使命似乎就完成了~~于是我们将其设为false~~注意我这里用“似乎”~~一般来说~~稍微有点基础的同学看到这里就应该已经完全明白问题在哪里了~~不过我们不妨继续调试~~
于是继续~~我们加快点速度~~接下去是枚举sourceGroup也就是前一个函数中的单位组g~~将其中所有单位逐一地添加给单位组bj_wantDestroyGroup也就是前一个函数中的result单位组~~
然后判断wantDestroy为真~~于是我们调用DestroyGroup()来将sourceGroup也就是前一个函数中的g销毁~~
遇到endfunction跳出函数~~回到前一个函数GetUnitsOfTypeIdAll()继续执行~~
现在循环变量index自增为1~~而没有达到跳出循环条件(等于bj_MAX_PLAYER_SLOTS~~也就是等于16)~~于是循环继续~~
继续给bj_groupEnumTypeId~~不过可惜~~当我们执行到GroupClear(g)的时候~~嘿嘿g不是已经被销毁了么?~~
那么下一步~~将Player(1)的所有步兵都添加给g也毫无意义~~不过我们现在是调试机~~乖乖地做一个向不存在的单位组添加单位的操作罢~~
下一步将g中所有单位装入result~~既然g不存在~~那么就是白装~~result中依然还是只有上一次添加的单位~~
继续~~循环~~加快速度~~反正接下去所有15次循环全部都是一样结果~~什么也没有给result添加~~所以整个循环下来~~只有第一次成功地执行了单位添加~~也就是仅仅加入了玩家1的所有步兵~~现在你知道为何这个触发会在屏幕上显示0了~~因为我们只给玩家2放了12个步兵~~
bug就这样完了?~~才不是呢~~继续调试~~
销毁g~~毫无意义的操作~~
遇到return跳出函数~~并返回result~~就这个例子来说~~就是返回玩家1的所有步兵~~(要记得我们的最初目的是返回地图上所有的步兵~~而不单单是玩家1的)
回到触发器XXX的动作函数Trig_XXX_Actions()~~开始执行CountUnitsInGroup()~~
于是我们再copy一遍CountUnitsInGroup()的源码~~
[jass]
function CountUnitsInGroup takes group g returns integer
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup
set bj_wantDestroyGroup = false
set bj_groupCountUnits = 0
call ForGroup(g, function CountUnitsInGroupEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then
call DestroyGroup(g)
endif
return bj_groupCountUnits
endfunction
[/jass]
将之前的result单位组赋给CountUnitsInGroup()的参数g~~跳入函数~~
跳入函数后第一步~~将bj_wantDestroyGroup的值赋给局部变量wantDestroy~~检查我们脑内的记忆体~~发现bj_wantDestroyGroup已经被设为false了~于是wantDestroy的值也是假~~
加快速度~~将全局变量bj_wantDestroyGroup的值再设为假~~bj_groupCountUnits的值设为0~~枚举单位组g~~也就result~~计算单位总数~~在这个例子里由于玩家1一个步兵也没有~~所以结果自然是0~~
继续~~检查wantDestroy~~假~~于是不销毁result单位组~~返回单位组单位总数~~也就是0~~
跳回到回到触发器XXX的动作函数Trig_XXX_Actions()~~执行写屏操作~~将0显示在屏幕上~~
好了~~现在让我们回过头去看看这一句set bj_wantDestroyGroup=true给这个触发带来了多少bug~~
第一~~本应该返回所有的步兵~~却只返回了玩家1的步兵~~
第二~~本应该将(所有步兵)这个计数用的临时单位组销毁~~结果却根本没有销毁~~
XXX的问题是这样~~那么触发器YYY呢?~~
相比这次也不用像刚才那样详细地调试了~~
单只就列出关键部分即可~~
[jass]
function Trig_YYY_Actions takes nothing returns nothing
set udg_UG = GetUnitsOfPlayerAll(Player(1))
set bj_wantDestroyGroup=true //关键
call ForGroupBJ( GetRandomSubGroup(4, udg_UG), function Trig_YYY_Func004A ) //跳入函数GetRandomSubGroup()
endfunction
function GetRandomSubGroup takes integer count, group sourceGroup returns group
local group g = CreateGroup()
set bj_randomSubGroupGroup = g
set bj_randomSubGroupWant = count
set bj_randomSubGroupTotal = CountUnitsInGroup(sourceGroup) //跳入函数CountUnitsInGroup()
if (bj_randomSubGroupWant <= 0 or bj_randomSubGroupTotal <= 0) then
return g //g已经被销毁!
endif
set bj_randomSubGroupChance = I2R(bj_randomSubGroupWant) / I2R(bj_randomSubGroupTotal)
call ForGroup(sourceGroup, function GetRandomSubGroupEnum)
return g //g已经被销毁!
endfunction
function CountUnitsInGroup takes group g returns integer
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //关键
set bj_wantDestroyGroup = false //关键
set bj_groupCountUnits = 0
call ForGroup(g, function CountUnitsInGroupEnum)
// If the user wants the group destroyed, do so now.
if (wantDestroy) then
call DestroyGroup(g) //关键,计数用的单位组g已经被销毁。
endif
return bj_groupCountUnits
endfunction
function ForGroupBJ takes group whichGroup, code callback returns nothing
// If the user wants the group destroyed, remember that fact and clear
// the flag, in case it is used again in the callback.
local boolean wantDestroy = bj_wantDestroyGroup //bj_wantDestroyGroup已经为假
set bj_wantDestroyGroup = false
call ForGroup(whichGroup, callback) //跳入Trig_YYY_Func004A()~~执行枚举动作
// If the user wants the group destroyed, do so now.
if (wantDestroy) then
call DestroyGroup(whichGroup) //由于wantDestroy为假~~没有销毁!
endif
endfunction
function Trig_YYY_Func004A takes nothing returns nothing
call SetUnitOwner( GetEnumUnit(), Player(0), true ) //实际上单位组已经被销毁~~因此一个单位也无法被改变所属~~
endfunction
[/jass]
好了~~总结下来~~这小小的几句动作也出现了二个大BUG~~
第一~~随机出来的4个步兵组成的单位组已经被销毁~~所以枚举该组内单位~~并改变他们所属的玩家成了一句空话~~
第二~~本来打算销毁这4个单位组成的临时单位组~~却没有正确地销毁~~
看了以上两个小例子~~你应该对bj_wantDestroyGroup的废材之处有了更多了解了~~如果你还是jass新手~~希望你从现在起就杜绝这种写法以免养成坏习惯~~乖乖地使用DestroyGroup()这个很有前途的写法罢~~ |
评分
-
查看全部评分
|