##EasyReadMore##

2009年12月11日 星期五

[教程] 如何創建一個魔獸RPG的AI系統(轉)







[教程] 如何創建一個魔獸RPG的AI系統(轉)

文章貼在:http://bbs.wow8.org/viewthread.php?tid=72575&extra=page%3D1%26amp%3Bfilter%3Dtype%26amp%3Btypeid%3D52

如何創建一個魔獸RPG的AI系統

作者:Blade.dk

翻譯:通魔作坊‧onlyxuyang

譯文:
這篇文章將幫助你製作一個簡單但是十分酷的英雄對戰地圖的人工智能。
這個你將學習的人工智能系統不是非常完美。我們將創建的是一個可以攻擊其它英雄、可以自己揀物品、學習和使用技能的人工智能系統,但是還是無法與人類玩家相比。
但是,當你學習了基礎的知識以後你應該可以自己改進它。

前提需要:
JASS基礎----這篇文章使用JASS來製作示例,所以你必須瞭解JASS。在理論上它也可以在T中做出來,但是我不推薦那樣做,因為用T來製作可能導致內存洩露、大量不必要的代碼以及在T中是無法使用JASS的返回值BUG和遊戲緩存系統的。如果你不熟悉JASS,請預先補充一下你自己的JASS知識。你同樣必須知道什麼是代碼行,如果你不知道的話,請補充自己的知識。

基於遊戲緩存以及返回值BUG的系統
注意事項:
-我們將要製作的AI系統達不到人類的水平,但是比什麼都沒有強。而且我認為當你理解了基礎以後可以自己改進它。
-你不用完全按照我說的做;我按做我的想法做,但是如果你的想法更好或者你覺得自己的做法更舒服,請按照你自己的想法做。我並不完美,這篇文章也不可能完美,但是我希望它可以對你有所幫助。
-你可以使用在我的演示地圖裡面的AI系統而不自己動手(如果你那樣做了,請告訴我一聲),但是我建議你自己動手寫,因為地圖可能很複雜而且你可以自己動手寫一個AI系統中學到更多的知識。

初始化部分:
首先在WE中創建一個觸發條件為"玩家1-玩家1(紅色)離開遊戲"的觸發器,然後把它轉換為JASS。我們需要這個觸發器來監視玩家離開遊戲,那樣我們才能為這個玩家開啟人工智能。現在它只監視一號玩家離開遊戲,所以我們在正式地圖中需要使用一個循環來監視從0-11號的玩家。

我們希望這個AI系統可以使用技能。聽起來似乎很難,其實很簡單。我們只需要使英雄學習技能,那麼他們就可以自己使用。

注意:電腦控制的英雄釋放自定義技能的情況總是和它釋放這個自定義技能的基礎技能的情況相同(這裡翻譯的有點含糊不清,自定義技能的基礎技能的意思是....基礎技能是遊戲本身帶有的技能,自定義技能都是以某個基礎技能為基礎的...這樣說做過圖的大大應該可以明白吧?).所以如果你的自定義技能是以沉默為基礎技能的,電腦控制的英雄就會在對戰地圖中應該使用沉默的情況使用這個技能。千萬不要將技能以"通魔(Channel)"為基礎,因為電腦從來不會使用它們,即使改變技能的OrderString也沒有什麼用。

為了知道每個英雄都擁有什麼技能,我們創建了一個遊戲緩存(game cache)來保存它。

在演示地圖中我的觸發器在地圖的初始化部分創建了一個遊戲緩存並將它保存在全局變量 udg_GameCache 中。需要注意的是緩存必須在我們使用它之前初始化,所以我在地圖的初始化時間中創建了它。

在我的地圖中我寫了一個函數SetupSkills.在這個AI觸發器的InitTrig函數中我使用了庫函數ExecuteFunc來開啟另外一個線程執行這個函數。這是為了防止地圖的初始化時間太長。

jass: Copy code

我的SetupSkills函數如下:
function SetupSkills takes nothing returns nothing
local string h // Create a local string variable
// Paladin // Here we’ll initialise the Paladin’s skills, repeat this for all other heroes
set h = UnitId2String('Hpal') // Store the returned value of UnitId2String(『Hpal』) in the local
call StoreInteger(udg_GameCache, h, "BaseSkill1", 'AHhb') // One of his base skills is Holy Light, store it as 「BaseSkill1」
call StoreInteger(udg_GameCache, h, "BaseSkill2", 'AHds') // Store ine Shield as 「BaseSkill2」
call StoreInteger(udg_GameCache, h, "BaseSkill3", 'AHad') // Store Devotion Aura as 「BaseSkill3」
call StoreInteger(udg_GameCache, h, "UltimateSkill", 'AHre') // Store Resurrection as his 「UltimateSkill」
… // Repeat for each Hero.
endfunction

接著是我的AI觸發器的InitTrig部分:
function InitTrig_AI takes nothing returns nothing
local integer i = 0
set gg_trg_AI = CreateTrigger( )
loop
exitwhen i > 11
call TriggerRegisterPlayerEventLeave( gg_trg_AI, Player(i) )
set i = i + 1
endloop
call TriggerAddAction( gg_trg_AI, function PlayerLeaves )
call ExecuteFunc("SetupSkills")
endfunction

為英雄開啟AI系統
為了控制AI我們使用了一個定時器(timer).我寫了一個函數StartAI來獲取一個單位的類型:英雄(請在演示地圖中查看這個函數)。這個函數只是創建一個定時器,並且"綁定"在這個英雄身上,並且開啟這個定時器。

這是演示地圖中的空的AILoop函數和StartAI函數(這裡給的只是一個框架,等下我們將展示一些動作函數,但是你起碼必須先把function和endfunction寫上去以保證WE不報錯) :

jass: Copy code

function AILoop takes nothing returns nothing
endfunction

function StartAI takes unit hero returns nothing
local timer m = CreateTimer()
call AttachObject(m, "hero", hero)
call TimerStart(m, 0, false, function AILoop)
set m = null
endfunction

注意,我的這個StartAI函數通過將periodic參數設置為false來達到使定時器只執行一次的目的(以後我們還會來討論它的).

現在,你就可以在你的英雄選擇系統中當由電腦控制的玩家選擇英雄時調用這個函數,並且在玩家離開遊戲的時候執行這個函數。檢測玩家是否擁有一個英雄,如果它擁有,調用這個函數來開啟那個英雄的AI系統。例如:

jass: Copy code

function PlayerLeaves takes nothing returns nothing
local player p = GetTriggerPlayer()
call DisplayTextToForce(bj_FORCE_ALL_PLAYERS, GetPlayerName(p)+" has left the game.")
if udg_Hero[GetPlayerId(p)] != null then
call StartAI(udg_Hero[GetPlayerId(p)])
endif
set p = null
endfunction

注意:這個函數將使AI系統控制離開的玩家的英雄,但是這也不是必要的,你也可以做別的事情。

使這個AI做些什麼

當定時器終止的時候我們希望它做了這些事情:
●如果英雄死亡,等待他復活。
●如果英雄將要死亡,命令他移動到地圖中心的生命泉水。
●如果英雄狀態良好,檢測是否有敵人在附近。如果有,則命令英雄攻擊它。 否則就檢測是否有物品在附近,如果有的話,發送一個巧妙 的命令讓英雄揀起它。然後命令英雄巡邏到地圖的一個隨機坐標。
●如果英雄是活著的而且有未使用的技能點,學習一個技能。

我們由變量的聲明開始。注意在我函數里面的實變量"e",它定義了在定時器再次啟動前所經過的時間,這樣我們就可以在英雄死亡的時候等待短一點的時間,而在他攻擊的時候等待長一點的時間。這個變量初始化值為5。

jass: Copy code

局部變量的聲明:
function AILoop takes nothing returns nothing
local string a = GetAttachmentTable(GetExpiredTimer())
local unit h = GetTableUnit(a, "hero")
local rect i
local location r
local real x = GetUnitX(h)
local real y = GetUnitY(h)
local group g
local boolexpr b
local boolexpr be
local unit f
local string o = OrderId2String(GetUnitCurrentOrder(h))
local real l = GetUnitState(h, UNIT_STATE_LIFE)
local real e = 5

我們由檢測英雄是否死亡開始,如果他死亡了,設置"e"為1.5(因為在復活以後等待5秒的時間太長了,我們並不想這樣).

當英雄的生命值"l"為0時,設置"e"為1.5來使定時器更加頻繁的檢測英雄是否復活.
if l <= 0 then
set e = 1.5
endif

接著我檢測英雄的生命是否低於最大生命值的20%.如果是的,命令英雄移動到生命泉並且設置"e"為3.
當英雄的生命值少於最大生命值的20%時,命令英雄移動到生命泉的位置。
if l <>
call IssuePointOrder(h, "move", GetUnitX(gg_unit_nfoh_0001), GetUnitY(gg_unit_nfoh_0001))
set e = 3

如果英雄的狀態良好,檢測他是否處在一個普通命令中(防止它打斷了通魔技能).如果是一個標準命令,我們再檢測在500的半徑內是否有敵人存在.如果存在敵人,簡單的發出一個攻擊命令(不要改變"e"的值,5秒對於這個情況剛剛好).

jass: Copy code

function AIFilterEnemyConditions takes nothing returns boolean
return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 and IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(GetAttachedUnit(GetExpiredTimer(), "hero")))
endfunction

else
if ((o == "smart") or (o == "attack") or (o == "patrol") or (o == "move") or (o == "stop") or (o == "hold") or (o == null)) then
set g = CreateGroup()
set b = Condition(function AIFilterEnemyConditions)
call GroupEnumUnitsInRange(g, x, y, 500, b)
set f = FirstOfGroup(g)
if f == null then
else
call IssueTargetOrder(h, "attack", f)
endif
call DestroyGroup(g)
call DestroyBoolExpr(b)
endif

如果沒有敵人存在,再檢測物品.如果發現物品,再檢測是否為一個提升狀態的物品.如果不是,檢測英雄物品欄是否有空欄,有的話就命令英雄將它揀起來.

jass: Copy code

function AISetItem takes nothing returns nothing
set bj_lastRemovedItem=GetEnumItem()
endfunction

function AIItemFilter takes nothing returns boolean
return IsItemVisible(GetFilterItem()) and GetWidgetLife(GetFilterItem()) > 0
endfunction

function AIHasEmptyInventorySlot takes unit u returns boolean
return UnitItemInSlot(u, 0) == null or UnitItemInSlot(u, 1) == null or UnitItemInSlot(u, 2) == null or UnitItemInSlot(u, 3) == null or UnitItemInSlot(u, 4) == null or UnitItemInSlot(u, 5) == null
endfunction

if f == null then
set i = Rect(x-800, y-800, x+800, y+800)
set be = Condition(function AIItemFilter)
set bj_lastRemovedItem=null
call EnumItemsInRect(i, be, function AISetItem)
if bj_lastRemovedItem != null and (GetItemType(bj_lastRemovedItem) == ITEM_TYPE_POWERUP or AIHasEmptyInventorySlot(h)) then
call IssueTargetOrder(h, "smart", bj_lastRemovedItem)
else
endif
call RemoveRect(i)
call DestroyBoolExpr(be)

如果物品欄沒有空位,或者沒有發現物品,則命令英雄到一個隨機地點尋找新的目標.
else
set r = GetRandomLocInRect(bj_mapInitialPlayableArea)
call IssuePointOrderLoc(h, "patrol", r)
call RemoveLocation(r)

【WoW8製圖】: 群體技能系統
現在我們需要檢測的是英雄是否有未使用的技能點(將這個函數與進攻/揀取物品/前進到隨機地點等模塊分開).
如果英雄有未使用的技能點,調用函數來使英雄學習技能.在我的演示地圖中,我是用一個函數來保存將要讓英雄學習的技能的,使用的是下面這個模式:

jass: Copy code

function AILearnSkill takes unit h, string a returns nothing
local integer i = GetTableInt(a, "LearnSkillOrder")+1
if i == 1 or i == 4 or i == 8 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill1"))
elseif i == 2 or i == 5 or i == 9 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill2"))
elseif i == 3 or i == 7 or i == 10 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill3"))
elseif i == 6 then
call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "UltimateSkill"))
endif
call SetTableInt(a, "LearnSkillOrder", i)
endfunction

if GetHeroSkillPoints(h) > 0 and l > 0 then
call AILearnSkill(h, a)
endif

現在所需要做的是使定時器在"e"秒之後再次開啟:
call TimerStart(GetExpiredTimer(), e, true, function AILoop)

最後我們將局部變量設置為空:
set h = null
set i = null
set r = null
set g = null
set b = null
set f = null
set be = null

最後需要注意的事情
這些就是英雄AI系統的基礎,它並不完美,但是它可以做為你的起點.
這個系統一點都不複雜,這樣可以讓你更加徹底的明白我的意思.
當你完成了一個屬於你自己的AI系統時,嘗試一下在你的系統中加入一個或者多個以下特徵:
--嘗試使它可以尋找周圍最虛弱的敵人.
--嘗試在殺死特殊的敵人時讓不同的AI玩家合作.
--當大部分戰鬥都以生命泉為中心的時候,讓英雄離開生命泉.
--讓AI玩家根據情況的不同說出不同的話(比如在殺死你的時候AI玩家會說"死吧~可憐的孩子")

2)線程(Threads)
(這部分屬於AI部分, 作為入門者做一般性瞭解就行了, 因為AI都是純JASS寫的, 也沒有真正好的AI EDITOR. 本人也對此一知半解, 關於AI的文章也不多, 沒什麼好參考的.)
線程只應用於AI腳本(AI JASS), 不能用於觸發器腳本(Trigger Jass). 通常, 當AI腳本開始運行時只創建一個線程, 創建更多的線程可以用comman.j的本地函數:
+ Shingo Jass Highlighter 0.41
native StartThread takes code func returns nothing

調用 call StartThread(function myfunc) 將創建一個從函數myfunc開始執行的線程.
每個玩家最多可以擁有6個線程, 包括一開始執行的主線程. 當一個玩家有6個線程數時, 調用StartThread()的語句將被忽略. 線程不能回收, 當你為某玩家創建了5個自定義線程, 將無法為該玩家創建更多的線程.
當新線程創建時, 線程立即生效. 當線程讓步執行時, 創建此線程的父線程將繼續執行.
在同一玩家中的所有線程都共享全局狀態(包括變量). 即是修改某個全局變量, 修改後的值在此玩家的所有線程中都是可見的.
線程在以下的情況讓步執行, 返回父線程
a) 當線程中的操作碼(opcode)超出限制, 線程會自動休眠 1 秒
b) 當線程中用使用 Sleep(n), 線程將休眠 n 秒, 然後繼續執行.
線程在以下情況會中止, 返回父線程
a) 如果 call StartThread(null)中, 線程中止
b) 當線程的主函數返回, 線程中止.
(StartThread()中之間調用的函數就是主函數.)
c) 當線程中使用沒有聲明的變量, 線程中止. 在使用之前, 變量必須聲明.
d) 當線程中出現零為被除數時, 線程中止
e) 線程主函數出現語法錯誤.
注意: 雖然AI腳本可以使用大部分common.j的庫函數, 但有些類型的函數在AI不能正常工作, 如:
a) 返回字符串類型(string)的本地函數, 如I2S(), SubString()等
b) 需要以code, trigger, boolexpr 等類型數據為參數的本地函數, 如觸發器函數, 隊列函數(ForGroup, 等)
注意: AI中不可以使用Blizzard.j的函數, 觸發器中也不可以使用common.ai的函數, AI和觸發器都可以使用common.j的函數(當然, 對於AI, 還受上面所說的限制)
common.ai和common.j是寫AI時可以調用和參考庫文件, 要研究AI, 先去讀這2個文件.
3) 跨腳本通訊(Inter-Script Communication)
在遊戲中, 可能會有多個獨立的Jass腳本文件同時運行. 比如在對戰地圖中的遊戲, 運行觸發器腳本文件的同時, 也可能運行了每個電腦玩家的AI腳本文件. 每個腳本文件之間的全局變量不是共享的. 所以, 一個電腦玩家的AI腳本中設置的全局變量不會影響另一個電腦玩家的AI腳本的執行.
觸發器腳本也不可以和AI腳本共享全局變量. 但可以用傳遞命令的方法進行腳本之間的數據交換. 命令由一對數值型數據(integer)組成: 命令值(command value)和數據值(data value).

+ Shingo Jass Highlighter 0.41
從觸發器腳本向AI腳本發出通訊命令, 可以使用common.j中定義的以下本地函數:
native CommandAI takes player num,
integer command, integer data returns nothing
參數:
player num //玩家
integer command //命令
integer data //命令數據
以下是AI中使用的common.j函數, 注意: 每個電腦玩家都會有獨立的AI腳本, 所以, 以下函數都沒有要求玩家作為函數參數.
每個電腦玩家都有命令堆來堆放接受到的命令. 想知道有多數個命令堆放在命令堆, 可以用下面的函數:
native CommandsWaiting takes nothing returns integer
參數: 無
返回: 命令堆的命令數(integer)
獲得存放在命令堆中最頂端的命令():
//返回命令
native GetLastCommand takes nothing returns integer
//返回命令數據
native GetLastData takes nothing returns integer
上面2個函數都不會移除命令堆中的命令, 要移除堆中的命令, 可以用:
native PopLastCommand takes nothing returns nothing


4) 隊列(Enumerations)
雖然JASS不能自定義數據結構(因為JASS缺少指針操作符), 但API庫中提供了一些實現隊列操作的函數. 如一組單位為單位組(group), 一組玩家為勢力(force), 雖然一組可破壞物沒有明確定義它的數據類型, 但也可以用API函數來操作.
單位組和勢力的操作函數很類似.
單位組處理函數
// 初始化單位組
native CreateGroup takes nothing returns group
// 在指定單位組中增加指定單位
native GroupAddUnit takes group whichGroup, unit whichUnit returns nothing
// 在指定單位組中移除指定單位
native GroupRemoveUnit takes group whichGroup, unit whichUnit returns nothing
勢力處理函數
// 初始化勢力
native CreateForce takes nothing returns force
// 在指定勢力中增加指定玩家
native ForceAddPlayer takes force whichForce, player whichPlayer returns nothing
// 在指定勢力中移除指定玩家
native ForceRemovePlayer takes force whichForce, player whichPlayer returns nothing
JASS不能直接操作隊列裡面的元素, 它是通過callback類型的函數來實現對隊列的操作:
// 對指定單位組中的每個單位都運行指定callback函數callback
// (對應GUI語言的For Each Unit in
native ForGroup takes group whichGroup, code callback returns nothing
// 對指定勢力中的每個玩家都運行指定callback函數callback
// (對應GUI語言的For Each Player in
native ForForce takes force whichForce, code callback returns nothing
輸入上面兩個函數的callback函數必須是無參數無返回值函數(takes nothing returns nothing)
同樣, 操作可破壞物也可以用在區域內的可破壞物作為隊列, 可以以用類似的方法:
// 在指定區域r內符合指定過濾器filter的都運行指定callback函數actionFunc
// (過濾器見下節的講解)
native EnumDestructablesInRect takes rect r,
boolexpr filter, code actionFunc returns nothing
在callback函數, 可以用下面的函數獲得隊列中的下一個元素:
// 獲得單位組中的下一個單位
// (對應GUI語言的Pick Every Unit in
constant native GetEnumUnit takes nothing returns unit
// 獲得勢力中的下一個玩家
// (對應GUI語言的Pick Every Player in
constant native GetEnumPlayer takes nothing returns player
// 獲得可破壞物組中的下一個可破壞物
// (對應GUI語言的Pick Every Destructables in
constant native GetEnumDestructable takes nothing returns destructable
注意: AI中不支持隊列函數的使用.
這是殺死單位組中所有單位的實例:
// 這是callback函數, 無參數並無返回值
function KillGroupCallback takes nothing returns nothing
// 獲得單位組中的下一個單位
local unit nextUnit = GetEnumUnit()
// 殺死該單位
call KillUnit(nextUnit)
endfunction
// 調用ForGroup
// 對單位組groupToKill中的每個單位都運行函數KillGroupCallback
call ForGroup(groupToKill, function KillGroupCallback)
另一個經常是用的例子是在隊列中查找特定條件的元素. 不幸的是, 因為JASS只支持callback函數來處理隊列中的元素, 所以只有用全局變量來保存不同單位的屬性. 下面是找出單位組裡生命最高的單位的例子:
//定義全局變量
globals
//用於儲存兩單位比較後較高的生命值, 初始化為 0
real mostLifeSoFar
//用於儲存兩單位比較後有較高生命值的單位, 初始化為 null
unit unitWithMostLifeSoFar
endglobals
//比較單位生命值的callback函數
function MostLifeCallback takes nothing returns nothing
//獲得單位組中的下一個單位
local unit nextUnit = GetEnumUnit()
//獲得單位屬性 - 生命
//UNIT_STATE_LIFE是common.j中定義的常量
local real life = GetUnitState(nextUnit, UNIT_STATE_LIFE)
//比較生命值
if life > mostLifeSoFar then
//把較大的生命值儲存
set mostLifeSoFar = life
//把有較大生命的單位儲存
set unitWithMostLifeSoFar = nextUnit
endif
endfunction
...
//初始化全局變量的值為空值
set mostLifeSoFar = 0
set unitWithMostLifeSoFar = null
//調用ForGroup
//對單位組myGroup中的每個單位都運行函數MostLifeCallback比較生命
call ForGroup(myGroup, function MostLifeCallback)
//上句運行後, 全局單位類型變量unitWithMostLifeSoFar便指向單位組myGroup中最高生命的單位, 或:
//如果單位組myGroup是空組, 那麼unitWithMostLifeSoFar便是空值null
...

當然, 實現隊列操作, 也可以用數組的方法來處理. 但, 數組不能使用緊接著要說的隊列過濾器, 也不能定義數組中包含數組. 這些都是隊列所擁有的優勢, 如可以有數組型的單位組(相當於數組中包含數組), 也可以用隊列過濾器.

5)隊列過濾器(Filters)
隊列過濾器用於在隊列中增加符合條件的元素. 比如, 在創建一個法力小於20的單位組時, 便可以用隊列過濾器(Filters)來創建.
+ Shingo Jass Highlighter 0.41

//在單位組中增加指定單位名為unitname, 並符合隊列過濾器filter的單位
native GroupEnumUnitsOfType takes group whichGroup, string unitname,
boolexpr filter returns nothing
//在單位組中增加指定玩家為whichPlayer, 並符合隊列過濾器filter的單位
native GroupEnumUnitsOfPlayer takes group whichGroup, player whichPlayer,
boolexpr filter returns nothing
//在單位組中增加指定玩家為whichPlayer, 並符合隊列過濾器filter的單位
native GroupEnumUnitsOfTypeCounted takes group whichGroup, string unitname,
boolexpr filter, integer countLimit returns nothing
//在單位組中增加指定區域為r, 並符合隊列過濾器filter的單位
native GroupEnumUnitsInRect takes group whichGroup, rect r, boolexpr filter
returns nothing
//在單位組中增加countLimit個指定區域為r, 並符合隊列過濾器filter的單位
native GroupEnumUnitsInRectCounted takes group whichGroup, rect r,
boolexpr filter, integer countLimit returns nothing
//在單位組中增加在指定點坐標範圍之內, 並符合隊列過濾器filter的單位
native GroupEnumUnitsInRange takes group whichGroup, real x, real y,
real radius, boolexpr filter returns nothing
//在單位組中增加在指定點範圍之內, 並符合隊列過濾器filter的單位
native GroupEnumUnitsInRangeOfLoc takes group whichGroup,
location whichLocation, real radius, boolexpr filter returns nothing
//在單位組中增加指定個數, 在指定點坐標範圍之內, 並符合隊列過濾器filter的單位
native GroupEnumUnitsInRangeCounted takes group whichGroup, real x, real y,
real radius, boolexpr filter, integer countLimit returns nothing
//在單位組中增加指定個數, 在指定點範圍之內, 並符合隊列過濾器filter的單位
native GroupEnumUnitsInRangeOfLocCounted takes group whichGroup,
location whichLocation, real radius, boolexpr filter,
integer countLimit returns nothing
//在單位組中增加被指定玩家選中, 並符合隊列過濾器filter的單位
native GroupEnumUnitsSelected takes group whichGroup, player whichPlayer,
boolexpr filter returns nothing
類似地, 對於勢力也有相應的操作函數
//在勢力中增加符合隊列過濾器filter的玩家
native ForceEnumPlayers takes force whichForce, boolexpr filter returns nothing
//在勢力中增加指定個數, 並符合隊列過濾器filter的玩家
native ForceEnumPlayersCounted takes force whichForce, boolexpr filter,
integer countLimit returns nothing
// Add all units that are allies of 'whichPlayer' that satisfy 'filter'
//在勢力中增加和指定玩家同盟, 並符合隊列過濾器filter的玩家
native ForceEnumAllies takes force whichForce, player whichPlayer,
boolexpr filter returns nothing
//在勢力中增加和指定玩家敵對, 並符合隊列過濾器filter的玩家
native ForceEnumEnemies takes force whichForce, player whichPlayer,
boolexpr filter returns nothing

以上函數中boolexpr filter在本章第1)節觸發器中有提到, 通常可以使用過濾器(filterfunc).過濾器跟觸發器的條件函數(conditionfunc)類似. 創建過濾器可以用以下語句:
native Filter takes code func returns filterfunc
其中參數函數code func必須是無參數返回值為布爾值的函數(takes nothing returns boolean), 過濾器用於在創建隊列時增加額外的條件. 在過濾器中, 可以使用下面的函數獲得下一個待查的單位/玩家/不可破壞物:
//獲得下個待查單位
constant native GetFilterUnit takes nothing returns unit
//獲得下個待查玩家
constant native GetFilterPlayer takes nothing returns player
//獲得下個待查可破壞物
constant native GetFilterDestructable takes nothing returns destructable

我們來看個創建一個法力小於20的單位組例子:
//過濾函數, 是無參數返回值為布爾值的函數
function LessThan20ManaCallback takes nothing returns boolean
//獲得下個檢查的單位
local unit nextUnit = GetFilterUnit()
//檢查待查單位的法力是否小於20
//小於20則返回true, 否則返回false
return GetUnitState(nextUnit, UNIT_STATE_MANA) <>
endfunction
...
//創建過濾器, 過濾函數是LessThan20ManaCallback
local filterfunc myFilter = Filter(function LessThan20ManaCallback)
//在單位組中增加指定區域, 符合過濾條件的單位
call GroupEnumUnitsInRect(myGroup, someRect, myFilter)
// Destroy the filter if we are not going to use it again
//不再使用過濾器, 銷毀過濾器, 避免內存洩漏
call DestroyFilter(myFilter)




沒有留言:

張貼留言

※怎麼下載?Xuite硬碟MediaFireBadongoSendSpace
※文章可以轉貼嗎?可以,不過要註明出處、標示本站連結。
※載點可以轉貼嗎?不可以,不能盜連。
※建議瀏覽器:Chrome 或 Firefox 或 Opera 或 IE7以上
!請按+1來支持本站!