===============
---(實作篇)---
===============
《7》引數與回傳
引數與回傳就跟數學的方程式一樣,像這條:
function Test1 takes integer A,integer B,integer C returns integer
return A+B+C
endfunction
我如果打 set A = Test1(1,2,3)就會獲得6的回傳值
第二條:
function Test2 takes unit UnitA,unit UnitB,unit UnitC returns group
local group G = CreateGroup()
call GroupAddUnit(G,UnitA)
call GroupAddUnit(G,UnitB)
call GroupAddUnit(G,UnitC)
return G
endfunction
我如果打 set G = Test2(U1,U2,U3)就會獲得一個包含U1 U2 U3 三個部隊的群組
第三條:
function Test3 takes unit UnitA,unit UnitB,unit UnitC returns nothing
call RemoveUnit(UnitA)
call RemoveUnit(UnitB)
call RemoveUnit(UnitC)
endfunction
我如果打 call Test3(U1,null,U3)會發生甚麼事呢?
這時候要擺脫數學的概念了,因為我們寫的是程式碼而不是數學
U1與U3經過Test3函數的處理之後,被我們刪除了
中間傳入一個null值,所以 call RemoveUnit(UnitB)這段不會發生任何事情
那如果是這樣呢?
function Test4A takes integer A,integer B returns integer
return A+(B*2)
endfunction
function Test4B takes integer A,integer B returns integer
loop
set B = B-1
exitwhen B == 0
set A = Test4A(A,B)
endloop
return A
endfunction
我們打 set A = Test4B(3,3)會發生甚麼事?
我們會在Test4B獲得Test4A回傳的數值
第一次迴圈獲得A=7,然後獲得A=9,第三次則跳出迴圈
Test4B回傳的9是我們最後獲得的結果
所以再來一題吧:
function Test5A takes nothing returns nothing
call RemoveUnit(GetEnumUnit())
endfunction
function Test5B takes group G returns nothing
call ForGroup(G,function Test5A)
endfunction
ForGroup會挑取群組裡的每一個單位各自執行動作
是我們在GUI常常會用到的一個功能
而現在他們將執行的動作就是function Test5A
GetEnumUnit()也就是GUI常看到的(挑取的部隊)
所以當我們輸入call Test5B(G)的時候,這個群組會怎麼樣呢?
答案是,裡面的所有部隊將被刪除!
再舉個例子,這是阿扁害人TD新增科技的觸發
只要取得玩家、科技類型,就可以幫玩家新增相對應的科技
若不使用引數功能,要製作數十條相同的觸發才有辦法達到目的
tekes <玩家>要新增科技的玩家、<整數>科技的ID編號
returns <無>
function TechNLG takes player P ,integer T returns nothing
if ( GetPlayerTechCountSimple( T, P) == 0 ) then
call KillUnit( GetTriggerUnit() )
call SetPlayerTechResearchedSwap( T, 1, P )
else
call DisplayTextToForce( GetForceOfPlayer(P), "You have already this technologie !!!" )
endif
endfunction
這是呼叫用的function
只要更改每個點的科技類型引數,就可以輕鬆的完成整個新增科技的系統
function Fire takes nothing returns boolean
call TechNLG(GetOwningPlayer(GetTriggerUnit()) ,'R001')
return false
endfunction
不過這系統的做法實在點爛,連續抓了GetTriggerUnit()兩次
如何讓這系統變得更有效率呢? 留給大家思考看看!
最後順便一提,做為code的function不可導入引數,否則將導致錯誤
常見的code如Action、Condition、Filter、ForGroup、TimerStart
必須使用全域變數或return bug做為資料的傳遞媒介
《8》BJ函數研究
blizzard.j函數可以將GUI指令轉為common.j指令,裡面包括了一些化繁為簡的功能,讓初學者可以更輕鬆的使用GUI調整參數,但裡面也有許多不必要的多層呼叫,所以在JASS裡面盡量直接使用common.j指令撰寫,會讓程式碼更有效率,完整的blizzard.j與common.j指令在NewGen(VJ)或JASS Craft都可以查詢,這邊只列出幾個比較實用且容易轉換的函數。
直接丟CJ系列
function DestroyEffectBJ takes effect whichEffect returns nothing
call DestroyEffect(whichEffect)
endfunction
function DestroyLightningBJ takes lightning whichBolt returns boolean
return DestroyLightning(whichBolt)
endfunction
function DestroyTimerBJ takes timer whichTimer returns nothing
call DestroyTimer(whichTimer)
endfunction
function IssueTargetOrderBJ takes unit whichUnit, string order, widget targetWidget returns boolean
return IssueTargetOrder( whichUnit, order, targetWidget )
endfunction
--(其他略,實在太多了...)--
看到上面這串,如果你了解前幾篇所講的「引數」
你會發現這些BJ函數是多麼的累贅
它們對引數完全沒有做任何的處理
只是把引數再丟給common.j而已
下次使用這些函數的時候,直接把BJ刪掉,跳過無用的BJ函數吧!
call DestroyEffectBJ(whichEffect)
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
call DestroyEffect(whichEffect)
調換位置系列
function SetUnitAbilityLevelSwapped takes integer abilcode, unit whichUnit, integer level returns integer
return SetUnitAbilityLevel(whichUnit, abilcode, level)
endfunction
function GetUnitAbilityLevelSwapped takes integer abilcode, unit whichUnit returns integer
return GetUnitAbilityLevel(whichUnit, abilcode)
endfunction
--(其他略,實在太多了...)--
看完有種 Orz 的感覺
不就是把引數抓來換位置,再丟給CJ處理嗎?
一樣是毫無意義的函數,下次看到就直接調換引數位置,呼叫CJ吧!
隱藏功能系列
function TriggerRegisterEnterRectSimple takes trigger trig, rect r returns event
local region rectRegion = CreateRegion()
call RegionAddRect(rectRegion, r)
return TriggerRegisterEnterRegion(trig, rectRegion, null)
endfunction
function TriggerRegisterLeaveRectSimple takes trigger trig, rect r returns event
local region rectRegion = CreateRegion()
call RegionAddRect(rectRegion, r)
return TriggerRegisterLeaveRegion(trig, rectRegion, null)
endfunction
function TriggerRegisterPlayerUnitEventSimple takes trigger trig, player whichPlayer, playerunitevent whichEvent returns event
return TriggerRegisterPlayerUnitEvent(trig, whichPlayer, whichEvent, null)
endfunction
--(其他略,實在太多了...)--
其實這些事件都可以加上Filter做篩選動作
因為GUI由Condition負責篩選
所以Blizzard把Event的Filter功能全部隱藏起來,不讓GUI新手們使用
到了JASS因為把Action移到Condition
所以Condition自然要移到Filter裡面
在某些情況下你也可以直接把Action移到Filter
不過必須注意的是Filter只能抓GetFilterUnit()跟少量的資訊
使用上也有諸多限制,只能執行篩選的動作,無法完全取代Condition
《9》BJ函數研究之二
以下是傳說中的ForGroupBJ函數
function ForGroupBJ takes group whichGroup, code callback returns nothing
local boolean wantDestroy = bj_wantDestroyGroup
set bj_wantDestroyGroup = false
call ForGroup(whichGroup, callback)
if (wantDestroy) then
call DestroyGroup(whichGroup)
endif
endfunction
ForGroupBJ與ForGroup只差在要不要依據bj_wantDestroyGroup把做為引數的群組刪除
但必須注意的是,它套用的布林bj_wantDestroyGroup是個bj定義的全域變數
還有很多函數會使用到它
也因此,使用bj_wantDestroyGroup可能造成許多問題
你可以這樣寫:
function Start takes nothing returns nothing
local group G = GetUnitsOfPlayerAndTypeId(Player(12), 'nkot')
call ForGroupBJ(G ,function RefereeIndex)
call DestroyGroup(G)
endfunction
不過,因為不需要使用ForGroupBJ刪除引數的功能
所以乾脆這樣寫,直接下達common.j指令再把群組刪除
function Start takes nothing returns nothing
local group G = GetUnitsOfPlayerAndTypeId(Player(12), 'nkot')
call ForGroup(G ,function ABC)
call DestroyGroup(G)
endfunction
以下是創造N個單位於某地點的BJ函數
function CreateNUnitsAtLoc takes integer count, integer unitId, player whichPlayer, location loc, real face returns group
call GroupClear(bj_lastCreatedGroup)
loop
set count = count - 1
exitwhen count < 0
call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face)
call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
endloop
return bj_lastCreatedGroup
endfunction
可以發現,它是以迴圈的方式不斷的重複創造部隊
然後再把創造好的部隊加入bj_lastCreatedGroup(最後創造的群組)
而繼續深入分析會發現
function CreateUnitAtLocSaveLast takes player id, integer unitid, location loc, real face returns unit
if (unitid == 'ugol') then
set bj_lastCreatedUnit = CreateBlightedGoldmine(id, GetLocationX(loc), GetLocationY(loc), face)
else
set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face)
endif
return bj_lastCreatedUnit
endfunction
這函數會先判定要創造的單位是不是鬧鬼金礦(unitid == 'ugol')
然後再不斷的把單位紀錄成bj_lastCreatedUnit(最後創造的單位)
BJ的運作方式實在讓人傻眼,一大堆不必要的操作,為的是甚麼呢?
當然是為了方便新手使用(最後創造的單位)與(最後創造的群組)這些變數
但是,當我們不需要使用這些變數的時候
BJ的功能就成了拖慢執行效率的罪魁禍首
所以,該怎麼辦呢?
當我們要創造N個單位,並命令單位移動至某地點的時候
我們可以這樣寫
function WB_CreateNMove takes integer count ,player Pl ,integer ID, location locA ,location locB returns nothing
loop
set count = count - 1
exitwhen count < 0
call IssuePointOrderLoc(CreateUnitAtLoc(Pl,ID,locA,270), "move", locB )
endloop
endfunction
或是這樣寫
function WB_CreateNOrder takes integer count ,player Pl ,integer ID, location locA ,location locB,string order returns nothing
loop
set count = count - 1
exitwhen count < 0
call IssuePointOrderLoc(CreateUnitAtLoc(Pl,ID,locA,270), order, locB )
endloop
endfunction
再來看看BJ挑取範圍內部隊的函數
function GetUnitsInRangeOfLocAll takes real radius, location whichLocation returns group
return GetUnitsInRangeOfLocMatching(radius, whichLocation, null)
endfunction
很顯然這又是一個毫無用途的函數
它呼叫了挑取範圍內部隊包含比對條件的函數,並且把條件設定為null
function GetUnitsInRangeOfLocMatching takes real radius, location whichLocation, boolexpr filter returns group
local group g = CreateGroup()
call GroupEnumUnitsInRangeOfLoc(g, whichLocation, radius, filter)
call DestroyBoolExpr(filter)
return g
endfunction
其實負責挑取部隊的只有GroupEnumUnitsInRangeOfLoc這個CJ函數
只要給它群組、地點、範圍、Filter,它就會把符合條件的部隊加入群組
這時候可以來談談一開始提到的問題,為什麼GUI的挑取部隊會沒有效率呢?
當你要挑取範圍內的敵方部隊時,會創造一個filter的function在JASS腳本內
每次挑取部隊都會拿這個filter的function創造一個filter
然後再創造一個群組g,並且把群組與filter傳給GroupEnumUnitsInRangeOfLoc運作
這個BJ函數設計的還不錯,在使用完畢之後,會自動幫你把filter刪除
所以只要有做好基本的記憶體漏失處理,並不影響效能
不過,假如要用同一個條件挑取多次部隊呢?
這時候會變成以下情況:
創造Filter與群組 → 刪除 → 創造Filter與群組 → 刪除 → ....(迴圈)
我們可以這樣寫:
function WE_UnitInRange takes group g ,real radius, location whichLocation, boolexpr filter returns group
call GroupClear(g)
call GroupEnumUnitsInRangeOfLoc(g, whichLocation, radius, filter)
return g
endfunction
重複利用群組g與filter,避免創了又刪,刪了又創的狀況
在製做支援多重施展的挑取動作時,可以大幅提升挑取效率
BJ函數有著相當高的泛用性與相容性
卻犧牲了執行效率
純JASS的優勢就在於,可以創造自己需要的函數來運用
下面我們會看到更多例子