首先怪物ai是什么?

答:怪物ai就是怪物与玩家的互动,比如玩家进入怪物可视范围内,我们怪物可以说一句话,或者进入战斗攻击玩家..等等!

实现怪物ai的方法通常为有限状态机和行为树这这两种方式。

不过我们这里不介绍,有兴趣的朋友可以自己去了解一下。我们这里只介绍我最近怎么实现的我所维护的游戏的怪物ai。

ai配置表

怪物ai配置表
unit 怪物id 怪物名称 描述 标号 初始激活 condition(条件) action

间隔时间

优先级

几率

8001 1 守城士兵 守城士兵控制AI 101 1 {14} {11 0,1}&{11,15000,102}&{12,0,101} 2000
8002 1 守城士兵 守城士兵释放技能普通攻击 1 0 {13,400,0.100,-100,1,2 0} {1,0,8120,1,3} 200
8003 1 守城士兵 守城士兵控制AI 102 0 {14} {12,0,2}&{11,0,3}&{11,15000,103}&{12,0,102} 2000
8004 1 守城士兵 守城士兵择放技能满王鞭石 2 0 {13,400,0.100,-100,1,2 0} {1,0,8121,1,3} 3000
8005 1 守城士兵 守城士兵控制AI 103 0 {14} {12,0,2}&{11,0,3}&{11,15000,101}&{12,0,103} ....
8006 1 守城士兵 守城士兵释放技能前溶翻 3 0 {13,400,0.100,-100,1,2 0} {1,0,8122,1,3} ...
8007 1 守城士兵 守城士兵控制AI 104 1 {13,1,99} {12,0,1}&{12,0,2}&{12,0,3}&{12,0,101}&{12,0,102}&{11,0......} ...
8008 1 守城士兵 守城士兵释旅技能后滚翻 4 0 {13,400,0.100,-100,1,2 0} {1,0,8122,1,3} ....
8009 1 守城士兵 ...... ...... ...... ...... ...... .........
9001 2 野狼 1

首先ai是需要配置,ai配置表就是关卡策划,需要配置怪物行为的表。

逻辑很简单,我么表内的每一行都是一个ai单元,当ai单元内的条件触发,那么相应的就要做出一个反馈(也可以是多个反馈)。

下面介绍一下ai条件说明表,和ai反馈说明表,结合这两个表上面ai配置表大家就应该可以理解了。

ai条件表说明表

ai条件说明表
条件id 说明 paran1 paran2 paran3 paran4 paranl5 paran6 paran7
1 当hp小于/等于/大于x 1=小于.2-大于,3=等于 整数
2 当hp每下降的数大于/等于/小于x 1=小于.2=大于,3=等于 整数
3 当hp等于/小于/大于百分之X 1=小于.2=大于,3-等于 百分数
4 当hp每下降的数大于百分之x 2=大于 百分比
5 身上拥有某个基因 基因ID(可以理解为增益减益buff)
6 当xl~x2,z1~z2范围内某个Id的怪物数量大于/等于/小于一个整数  x1(范国左侧的坐标) x2(范围右侧的坐标) z1(范围z轴上里侧的坐标) z2(范围z轴上里侧的坐标) 怪物ID 1=小于,2-大于,3=等于 怪物数量
7 在一定时间类时间内受到某种类型(由paran2~paran3决定)伤害次数大于/等于/小于某个数时 时间 1=物理近程欢击,2=法数攻击,3=物理远程攻击,-1=任意攻击 0=无属性攻击,1=火厘性攻击,3=光属性攻击,4=暗属性攻击,-1=任意属性攻击 1=小于.2=大于,3=等于 伤害次数
8 在一定时间类时间内受到某种类型(由paran2~paran3决定)伤害数值大于/等于/小于某个数时 时间 1=物理近程欢击,2=法数攻击,3=物理远程攻击,-1=任意攻击 0=无属性攻击,1=火厘性攻击,3=光属性攻击,4=暗属性攻击,-1=任意属性攻击 1=小于.2=大于,3=等于 伤害数值
9 x1~x2,Zl ~Z2范围内某个ID的怪物有某个基因 x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 怪物ID 基因ID
10 x1~x2,Zl ~Z2范围内某个ID的怪物死亡时 x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 怪物ID
11 出生一段时间之后 时间
12 仇恨列表拥有一定时间之后 时间
13 x1~x2,Zl ~Z2范围内某个阵营对象数量大于等于小于 x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 阵营类型 1=小于,2-大于,3=等于 怪物数量
14 条件永远为真 有仇恨目标
15 范围内某个ID的怪物血量大于等于小于多少百分比x x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 1=小于,2-大于,3=等于 怪物ID 百分数
16 范围内某个怪物id处于某个状态 x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 状态id 怪物ID
17 范围内某个id怪物hp每下降数大于等于小于百分比 x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 1=小于,2-大于,3=等于 怪物ID 百分数
18 当某个技能执行完毕之后 技能id
19 范围内某个id怪物处于某个状态
 
x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 阵营类型 状态集
20 当x1x2,z1 z2范围内某个阵营的对正在译放某个技能
 
x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 阵营类型 技能
21 当某个技能击中敌对阵营
 
技能id
22 当x1 ×2.z1z2范围内某个阵营的对象拥有某个基因
 
x1(范围左侧的坐标) x2范围右侧的坐标) Z1(范围里侧的坐标) z2(范围外侧的坐标) 阵营类型 基因id
23 条件永远为真 无仇恨目标

ai条件说明表

id 说明 延迟时间(毫秒) paran1 paran2 paran3 paran4 paranl5 paran6 paran7
1 释放技能 必填,立刻触发写0 技能id 技能级别 对象类型 连续释放技能时间间隔 连续释放技能次数 是否排队 后面还有参数,是表示范围
2 说话 必填,立刻触发写0 话语id
3 移动 必填,立刻触发写0 移动类型 持续时间 循环次数 间隔时间
4 做动作 必填,立刻触发写0 动作id
5 做表情 必填,立刻触发写0 表情id
6 将某个对象设置成目标 必填,立刻触发写0 对象类型
7 清空仇恨列表 必填,立刻触发写0
8 自杀 必填,立刻触发写0
9 消失 必填,立刻触发写0
10 说话(聊天框+头顶泡泡) 必填,立刻触发写0 话语id
11 打开ai 必填,立刻触发写0 ai标号
12 关闭ai 必填,立刻触发写0 ai标号
还有其他反馈 当然这些反馈是根据游戏需求才会创建

了解了ai条件表和ai反馈表,我们应该其实大体了解ai配置表要做一些什么。

接下来我们通过代码层面,来说说怪物我的怪物ai是如何实现的。

代码层面

首先,这套ai逻辑是判断条件如果条件为真那么触发反馈。

那么我们什么时候判断这些条件呢?而且我们的ai是采取类似行为树的模式,我们什么时候遍历这颗树呢?

其实这个很简单,我们游戏服务器他都会有游戏帧这个概念,当游戏服务器经过多少毫秒,就会调用各个模块的帧函数去更新数据。

当玩家进入副本,我们会生成怪物实体,这时我们会有一个monsterAI类对象(一个ai管理器)。

这里我们就只介绍简单的。

monster会有三个链表(当然不止是三个列表,这里只介绍最主要的),

第一个链表里面保存将要执行的ai单元,我们取名叫runailist。

第二个链表是排队ai链表,存储我们没有执行的ai单元,我们取名为sortailist,里面的ai单元根据优先级进行排序

第三个链表不排队ai链表存储移动ai。(这个移动ai单独拎出来放进,后续可能有其他ai需要放这里,所以开发中需要预留一下)

玩家进入副本,我们怪物monsterAI类对象的帧函数就会触发,以目前我维护的游戏为例子,他的服务器是25ms触发一次,也就是40帧。

update()
{
    //1.检测runailist表内是否有数据,如果有数据就执行反馈,不走下面逻辑了。
    if(!_checkRunAiList())
    {
        return;
    }
    //2.判断是否需要执行对sortailist进行排序
    if(needSort)
    {
        sort(sortaiList);//根据权重进行排序
    }
    ite=sortaiList.begin();
    for(..sortaiList...)
    {
        //遍历sortailist单元的数据
        if(*ite->checkCondition())
        {
            //ai条件成立
            //这里需要将ai单元的优先级进行重置

            //将ai单元从这个sortailistpop出来然后加入到runailsit列表内
        }else
        {
            //如果条件不成,那就将sortailist的ai单元的优先级+1
        }
    }
    //这里执行移动ai,也就是不排队ai列表内的ai
    moveai();
}

//
_checkRunAiList()
{
    bool isrunend=true;
    for(runailist)
    {
        if()//这里就是执行ai反馈,并且判断反馈是否成功
        {
            //成功那么久将ai单元重新放入sortailist中,并将needsort置为真
        }else
        {
            isrunend=false;
        }
    }
    return isrunend;
}

这里讲的是大概得逻辑,当然一些细节要根据游戏需求进行处理。

这里有人可能问了,这个优先级是干嘛的?你不是说行为树嘛,行为树体现在哪?

这里我说一下优先级,优先级就是保证我们ai可以经量有序进行,通过给条件不同过的ai单元加优先级,可以保证下一次如果条件通过,我们这个ai单元可以率先执行反馈。

然后行为树呢?这要说一下ai单元!

ai单元,Auint类,他里面的有着两个链表一个是就是条件链表(Condition),一个反馈链表(action)。

{13,400,0.100,-100,1,2 0}这是ai配置表内的ai单元条件,其实他还可以这么写,{13,400,0.100,-100,1,2 0}&{其他条件}&{其他条件}。这样我们可以设置多个条件,并且用&字符串起来,我们条件列表就会保存多个条件。

当然我们反馈也是这样,actionlist会有多个反馈执行动作。

checkCondition遍历条件链表,当所以条件为真的时候才会执行ai反馈动作。

这些条件和反馈都是独立的,我们都要为其编写代码进行判断和处理。

这里就有大家问为什么要设计这个怪物ai模块,为什么不直接采取行为树的方式判断成立直接触发呢。

这里是为了解耦合,解判断条件和触发反馈的耦合。让所以执行反馈动作都执行完毕,在去判断条件,让程序逻辑更加清晰更加简单。

当然我这是提供了一下怪物ai制作的思路,具体的游戏ai思路多少都有些不一样。

还有这个其实还优化,比如我这个游戏服务器是一秒四十帧帧函数会触发四十次,其实这个触发频率其实蛮高的,可以降低触发频率,还有就是一个地图实体很多,可以给这些怪物实体设置多个组,让特定帧去更新具体怪物组

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐