找回密码
 点一下
查看: 1250|回复: 1

我有学创建一个魔兽RPG的AI系统文字但是我看不懂想请想做个视频教程给我

[复制链接]
发表于 2008-8-3 13:31:59 | 显示全部楼层 |阅读模式
这篇文章将帮助你制作一个简单但是十分酷的英雄对战地图的人工智能。
    这个你将学习的人工智能系统不是非常完美。我们将创建的是一个可以攻击其它英雄、可以自己拣物品、学习和使用技能的人工智能系统,但是还是无法与人类玩家相比。
    但是,当你学习了基础的知识以后你应该可以自己改进它。

---------------------------------------------------------------------------------------------------------------------------

前提需要:
JASS基础 ---- 这篇文章使用JASS来制作示例,所以你必须了解JASS。在理论上它也可以在T中做出来,但是我不推荐那样做,因为用T来制作可能导致内存泄露、大量不必要的代码以及在T中是无法使用JASS的返回值BUG和游戏缓存系统的。如果你不熟悉JASS,请预先补充一下你自己的JASS知识。你同样必须知道什么是代码行,如果你不知道的话,请补充自己的知识。

基于游戏缓存以及返回值BUG的系统

---------------------------------------------------------------------------------------------------------------------------



演示地图 ----一 个演示地图,很重要,因为我的文章中很多处引用了里面的代码。
点击下载演示地图:

AI Tutorial Arena.rar


---------------------------------------------------------------------------------------------------------------------------




注意事项:
-我们将要制作的AI系统达不到人类的水平,但是比什么都没有强。而且我认为当你理解了基础以后可以自己改进它。
-你不用完全按照我说的做;我按做我的想法做,但是如果你的想法更好或者你觉得自己的做法更舒服,请按照你自己的想法做。我并不完美,这篇文章也不可能完美,但是我希望它可以对你有所帮助。
-你可以使用在我的演示地图里面的AI系统而不自己动手(如果你那样做了,请告诉我一声),但是我建议你自己动手写,因为地图可能很复杂而且你可以自己动手写一个AI系统中学到更多的知识。
-这个演示地图可能有BUG,而且它也并不是一个好玩的地图。请记住它只是一个展示AI系统的演示地图,如果你想观赏更好的有AI的地图,试试下面这个地图。


---------------------------------------------------------------------------------------------------------------------------



初始化部分:
首先在WE中创建一个触发条件为"玩家1-玩家1(红色)离开游戏"的触发器,然后把它转换为JASS。我们需要这个触发器来监视玩家离开游戏,那样我们才能为这个玩家开启人工智能。现在它只监视一号玩家离开游戏,所以我们在正式地图中需要使用一个循环来监视从0-11号的玩家。

我们希望这个AI系统可以使用技能。听起来似乎很难,其实很简单。我们只需要使英雄学习技能,那么他们就可以自己使用。

注意:电脑控制的英雄释放自定义技能的情况总是和它释放这个自定义技能的基础技能的情况相同(这里翻译的有点含糊不清,自定义技能的基础技能的意思是....基础技能是游戏本身带有的技能,自定义技能都是以某个基础技能为基础的...这样说做过图的大大应该可以明白吧?).所以如果你的自定义技能是以沉默为基础技能的,电脑控制的英雄就会在对战地图中应该使用沉默的情况使用这个技能。千万不要将技能以"通魔(Channel)"为基础,因为电脑从来不会使用它们,即使改变技能的OrderString也没有什么用。

为了知道每个英雄都拥有什么技能,我们创建了一个游戏缓存(game cache)来保存它。

在演示地图中我的触发器在地图的初始化部分创建了一个游戏缓存并将它保存在全局变量 udg_GameCache 中。需要注意的是缓存必须在我们使用它之前初始化,所以我在地图的初始化时间中创建了它。

在我的地图中我写了一个函数SetupSkills.在这个AI触发器的InitTrig函数中我使用了库函数ExecuteFunc来开启另外一个线程执行这个函数。这是为了防止地图的初始化时间太长。

---------------------------------------------------------------------------------------------------------------------------



我的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 Divine 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不报错) :
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系统。
例如:
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。

局部变量的声明:
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 < GetUnitState(h, UNIT_STATE_MAX_LIFE)/5 then
        call IssuePointOrder(h, "move", GetUnitX(gg_unit_nfoh_0001), GetUnitY(gg_unit_nfoh_0001))
        set e = 3


---------------------------------------------------------------------------------------------------------------------------

如果英雄的状态良好,检测他是否处在一个普通命令中(防止它打断了通魔技能).如果是一个标准命令,我们再检测在500的半径内是否有敌人存在.如果存在敌人,简单的发出一个攻击命令(不要改变"e"的值,5秒对于这个情况刚刚好).

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


---------------------------------------------------------------------------------------------------------------------------

如果没有敌人存在,再检测物品.如果发现物品,再检测是否为一个提升状态的物品.如果不是,检测英雄物品栏是否有空栏,有的话就命令英雄将它拣起来.

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)

---------------------------------------------------------------------------------------------------------------------------

现在我们需要检测的是英雄是否有未使用的技能点(将这个函数与进攻/拣取物品/前进到随机地点等模块分开).
如果英雄有未使用的技能点,调用函数来使英雄学习技能.在我的演示地图中,我是用一个函数来保存将要让英雄学习的技能的,使用的是下面这个模式:
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玩家会说"死吧~可怜的孩子")
发表于 2008-8-3 13:55:35 | 显示全部楼层
用触发做AI么..
还以为用AI函数呢...

以上的东西 .... 学了J就知道了 ..其实一般学J就不涉及AI函数...(我也不会啊...这方面的资料又少)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-11-24 19:56 , Processed in 0.062453 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表