游戏后端开发,说说我是怎么制作怪物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条件表说明表
| 条件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思路多少都有些不一样。
还有这个其实还优化,比如我这个游戏服务器是一秒四十帧帧函数会触发四十次,其实这个触发频率其实蛮高的,可以降低触发频率,还有就是一个地图实体很多,可以给这些怪物实体设置多个组,让特定帧去更新具体怪物组
更多推荐

所有评论(0)