《游戏人工智能编程 案例精粹》阅读心得

        最近读完了这本《游戏人工智能编程 案例精粹》,感觉获益匪浅,在对游戏人工智能的设计上有了更深的感悟。

        这本书既适合初学者学习,因为次书会从最基础的数学物理公式推导一步一步介绍到完整的人工智能开发;同时也适合进阶程序员,因为其设计思路、解决方案能给人以很大启发。

要注意的是:这里讲的人工智能,并非现在大火的强人工智能(AI),而是游戏中的行为体智能控制(例如控制 NPC 行动的状态机)。

层级状态机

        目前的游戏AI开发,一般是状态机模式或者行为树模式。这里我就不介绍状态机、行为树的基本概念了,有不清楚的可以参考以下文章的介绍。

  • 游戏AI游戏研究(二):状态机与行为树_状态机和行为树-CSDN博客

  • 行为树VS状态机 - 卧沙的猫眼螺 知乎专栏

        书中除了对常规的状态机进行了运用和讨论之外,特别提到了状态机的 “崩溃” 问题:即在存在大量状态节点时,维护状态机就变得特别复杂(复杂度为 n*n)。

不分层的状态机

        作者给出的解决方案是分层状态机:将部分状态统一整合成一个层级,与其他状态区分开来,从而大量减少维护难度。具体的介绍参考以下链接:

  • FSM(状态机)、HFSM(分层状态机)、BT(行为树)的区别-CSDN博客

  • 层次化状态机的讨论 – AI分享站

层次化状态机

         这个解决方案也很简单,但很少听人提起过(可能是我孤陋寡闻)。在我看来,层次化状态机极大减少了AI系统的复杂度,且相似的状态机层级可以复用,减少代码量,增加了扩展性。

        例如游戏中常见的矿工 NPC,他的行为有以下类型:

  • 饿了吃饭,渴了喝水,困了睡觉等基本行为需求;

  • 当遇到敌人攻击时,选择攻击敌人,或者逃跑;

  • 生病了需要找医生、娱乐值低了需要找酒馆等;

  • 无所事事时,会去街上散步,去广场上找人聊天等;

        如果按照传统状态机的写法,那将会出现数十种状态,每个状态都要能互相切换。此时状态机的控制将会非常复杂,且每增加一种行为都会导致控制难度呈几何增长。于是我们对状态机进行分层,例如按照马斯洛需求层次理论将状态机分到不同层级,并按照优先级排序。

        通过对状态机进行分层,将大量状态整合,控制起来更为清晰,同时也更能符合玩家的通常认知。

        除AI外,有限状态机的一大应用场景就是 Unity 里面的动画状态机(Animation Controller),这套系统甚至能支持多层级状态机,可以说非常完整了。在制作状态机编辑器时,最方便的就是直接复用这套系统(参考 Unity -> 用动画状态机来做行为树 - 哔哩哔哩)或者使用 Vitrual Scripting 来做状态机:About Visual Scripting | Visual Scripting | 1.9.5。

        值得一提的是,状态机和行为树并不是互斥的,没有说法用了一个就不能用另一个。我们在开发时完全可以对 AI 的各个层级进行抽象,内部既可以使用状态机也可以使用行为树。考虑到 AI 一般是程序开发可视化编辑工具,策划进行配置,所以不论实现上多复杂,抽象后的行为逻辑也应简洁明了。

自治智能体

        自治智能体,简单来说,就是 AI 不会精确控制到智能体的准确行为,而由智能体自己决定。例如上层 AI 只会命令智能体从 A 点移动到 B 点,至于搜索到的路径、中途如何避让障碍物这些行为则并不关心,交由智能体自行处理(底层的寻路算法、RVO等)。        

        在实际游戏中,通常遇到的情景是智能体常常会被各种系统影响,例如避障、物理碰撞(模拟),以及自身的行走(寻路),以图中所示:

  • 蓝色箭头:寻路指引的方向,也是智能体自身期望的行进方向。

  • 浅红色:墙体的阻挡对当前智能体施加的碰撞;

  • 浅黄色:其他智能体单位对目标智能体施加的挤压碰撞力;

        这么多的力结合起来,最终形成一个合力(图中绿色箭头),便是最终的行进方向。但并不是简单地加和,而是带优先级的加权截断(Weighted Truncated Running Sum wtih Prioritization)。

        在实际游戏中,我们通常会尽量避免穿模发生。所以我们总是会把墙体、障碍物等静态阻挡视为第一优先级,所以一旦有来自此方向的力就会只考虑这一种力,而把其他的力计算截断。当然也可以不这么粗暴,而是给其赋予极高的权重。这就是是带优先级的加权截断(Weighted Truncated Running Sum wtih Prioritization)

        如果游戏 CPU 开销太高,在 Reynolds 的论文中也建议了一种计算合力的方法,成为带优先级的抖动(Prioritized Dithering)。简单地说就是给每一种力增加一个概率,每次计算时根据概率决定此时应该计算哪一种力,其他的力本帧便不予考虑。这种方法的 CPU 开销会显著降低,但会损失一些精度。

        此外,对于之前提到的 “确保无重叠” 需求,其实单凭群组分离、碰撞推挤等其实并无法绝对避免重叠。书中还提到可以主动进行避障(也就是 RVO),通过主动+被动实现避免单位重叠。但说实话,其实即便使用了各种方案,也并不能避免穿模的发生(我忘了在哪里看见的一个说法,就是游戏中的穿模无论如何都避免不了的)。所以我个人看法,就是我们将穿模提到一个较高的优先级即可,但不必为不穿模而弄出很多特殊处理、不合系统设计的代码。轻微、短暂的穿模,其实玩家也是能接受的,谁还没在游戏里见过穿模啊!

寻路的答案

        其实寻路不是游戏AI开发的重点(虽然有一章专门在讲寻路),其作为底层的算法只是 AI 行为的一个辅助。但由于我我在寻路上思考过很多方案,且距离我的终极目标都有一定距离(时间复杂度低、同时支持海量起点终点并发寻路且寻路地图可以实时重建),所以看这一章时不免精神了一些。


按照我之前的研究学习,对主流的几种寻路方案总结如下:

寻路方案

搜索效率

分步寻路*

地图重建代价

多起点/目标*

最优路径

A Star

JPS/JPS+

FlowFiled/Dijkstra

极高

NavMesh

较高

蚁群算法

  • 分步寻路:这里指是否可以不进行完整搜索就返回部分路径,且部分路径与最终结果相同。例如 FlowFiled 就可以先返回一小段路径,后续再补全;显然 A* 这类的寻路不行,其没有完成搜索时无法返回路径。

  • 多起点/目标*:这里的多起点/目标的前提条件是只使用一张地图。FlowFiled/Dijkstra、蚁群算法理论上是支持多起点多终点的寻路的,但每一个路径都需要一个新的地图(内存开销成倍增长)。

  • A Star(A*):这个大家都非常熟悉了,这里就不讲算法原理了。他的缺点就是搜索效率低,极差情况下会遍历大量格子。了解这个算法的原理就知道,A* 是不能返回部分路径的,一定要完全搜索之后才能返回结果。

  • JPS/JPS+ :这两个相当于 A* 的加速,通过设置跳点、缓存最近跳点的方式能在搜索时直接跳过大量的格子。但是代价就是地图重建代价很大,需要对地图进行不止一次的遍历。如果是那种不会更改地图的游戏,离线烘焙就很优雅,但如果是地图实时重建的话,就不美好了。

  • FlowFiled/Dijkstra :FlowFiled 的原理就是 Dijkstra,这个算法会生成一张流场图,搜索时直接查图,运行时开销忽略不计。但可惜的是,这个算法是对多单位、单目标支持最好的(比如RTS游戏),对多目标的情况就很难支撑了(每增加一个目标就要多生成一张流场图)。

  • NavMesh:网格导航是将空间按照凸多边形划分,然后按照图搜索寻路。目前 Unity 内置了这一套寻路,而且非常好用。但是其缺点在于其重建地图有一定开销,而且不能很好支持战棋类游戏(例如文明系列,每一个地格都有单独的开销)。

  • 蚁群算法:蚁群算法我了解得不对,它比较特殊,是派出了很多小蚂蚁去搜索目标,每个小蚂蚁每次移动都会留下信息素,之后并全局更新信息素地图。一旦有小蚂蚁找到目标,就直接能通过信息素最强烈的路径直达。这个算法的特点是他无论如何都能找到一条路(虽然不一定会达到终点),其搜索结果可能不是最优路径,但在多次迭代下会收敛。和 FlowFiled 寻路一样,他的信息素地图也是一个目标一份,因此同样不适合多目标搜索。


        现有的各种寻路方案,总体上来讲都各有优缺点,只能在特定使用环境选择最适合的,而没有一个通解。之前我一直在思考,对于海量单位的开放世界寻路,应该从哪个方向入手,但无果。这次学习这本书有点茅塞顿开的意思:A* 其实一直都带有启发因子,但是我之前一直把启发因子当做了优化项,而没有想过会对这个算法产生质变。所以有结果了,能提供良好启发因子的的 A* 寻路其实就是版本答案。因为如果启发因子(G值)取得够好,就能直接让 A* 用最小代价搜索出结果。

通过调整 G 值,A* 搜索效率上有显著变化

        但关键是如何设定每一个格子的 G 值。

        其实方案有很多,比如根据地图生成一份热力图,或者烘培一张低精度的 NavMesh 图,再或者根据 Dijkstra 或者蚁群算法进行迭代等。根据不同项目,总会有一些处理方案。其实当明确了最终答案就是 A* 之后,反而思路明确了很多,之后再据此进行深入研究。

目标驱动(关系依赖图)

        所谓目标驱动,即是让一个 AI 行为体决定自己要做什么。听起来似乎上面的状态机已经解决了这个问题,但在战略层面并没有给出解决方案。

        举个例子:当一名勇士想要打造一把高级武器(传说之剑),需要材料(铁、木头)、技能、金钱以及一个铁匠朋友,那如何给勇士设定 AI 呢?如果我们将目标设置更复杂一些,例如结交朋友需要送礼,送礼需要特殊道具,特殊道具也需要打造……似乎进入了一个不休止的轮回。

        一般游戏开发者都是偷懒,要么就直接设定NPC之间的关系,要么到了特定时间(或条件)直接获得对应道具。虽然这样也能实现需求,但显然 AI 的可能性就大大降低了。而且,随着游戏内容的增加,AI 的配置内容就会越来越多,越来越难以维护。

        书中给出的方案是制作一个关系依赖图,画出每个目标所有依赖:

        当 AI 产生这个需求时,就根据传说之剑的依赖树进行搜索,将一个大任务分解成一个一个的小任务,在之后的 AI 逻辑就简单了:伐木、交易或者出门打怪。

        关系依赖图不用特别配置,根据配置表(例如物品合成表)直接自动生成即可。当然实际上会有很多优化工作可以做,例如某些行为不让 AI 去做,例如在检测到关系依赖图中存在环则需要验证配置等。而这样的关系依赖图属于这个游戏的 “常识” ,所有的 AI 都可以共用。

        通过关系依赖图来确定 AI 的战略决策,显然使 AI 变得更像玩家了。当然并不是所有的 NPC 都需要使用这个,有的 NPC 就是游戏里的 “工具人” 。当我们需要在游戏里添加更多生命力时,需要让每个 AI “活起来” 时,关系依赖图则必不可少。

模糊逻辑

        在实际游戏开发中,我们会给智能体施加各种行为逻辑,有的甚至是互斥的:

  • 当自身血量较少时,要寻找医院治疗;

  • 当敌人攻击时,要奋勇反击,驱除敌人;

  • 当智能体手上没有武器,第一要务是去装备一件武器去准备战斗;

  • 当敌人已经残血,应该立马给他一拳送他去轮回。

        平时游戏开发怎么处理这些问题呢?一般是策划给配置一个参数,例如血量低于20%时就要找医院,敌人血量低于5%时,视为残血……诸如此类。这种情况就会导致智能体出现行为突变:例如血量从80%掉到20%的时候智能体行为如常,但从20%掉到19%时(明明变化值更少)却立马逃跑;要是此时恰好遇到敌人的血量从6%掉到4%……如此种种的行为突变,给玩家的感觉就是莫名其妙。更有甚者,部分玩家会测试出这个边界值,然后在边缘来回横跳来戏弄智能体。总而言之,这些行为都会导致玩家更能确定智能体是一段程序(虽然玩家一开始就知道智能体是程序,但还是期待着智能体能像人一样思考)。

        书中推荐了使用模糊逻辑来处理这种问题:将血量映射出一个治疗欲望的期望值(线性或是分段都可以),敌人的血量映射出一个攻击欲望的期望值,诸如此类。这一步就叫做模糊化,将各种明确的逻辑转换成 “模棱两可” 的参数(治疗的欲望为 0.4,攻击敌人的欲望有 0.2 ……)。再之后,根据映射出的参数决定智能体当前的逻辑,这一步叫做去模糊化。

        模糊逻辑实际上能更好地帮助智能体通过图灵测试,行为没有那么明确的边界值。在模糊逻辑下,智能体在做决策时,综合考虑了多个变量,行为也会变得更合理。当然,这样设计 AI 的缺点就是无法精准控制其每时每刻在做什么,智能体似乎有他自己的想法。

推荐阅读

  • 全部文章 – AI分享站

  • 云风的 BLOG: 模糊逻辑在 AI 中的应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/22585.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑马点评_商品信息缓存模块

保证缓存不要有空档期 删除后马上要写入中间不能插入任何阶段(如查询数据库) 对于单体系统1,将缓存与数据库操作放在同一个事务中(当前项目就是一个单体项目,所以选择这种方式) 对于分布式系统2,利用TCC(Tr…

OnlyOffice:前端编辑器与后端API实现高效办公

OnlyOffice:前端编辑器与后端API实现高效办公 一、OnlyOffice概述二、前端编辑器:高效、灵活且易用1. 完善的编辑功能2. 实时协作支持3. 自动保存与版本管理4. 高度自定义的界面 三、后端API:管理文档、用户与权限1. 轻松集成与定制2. 实时协…

面阵工业相机提高餐饮业生产效率

餐饮行业是一个快节奏、高要求的领域,该领域对生产过程中每一个阶段的效率和准确性都有很高的要求。在食品加工、包装、质量控制和库存管理等不同生产阶段实现生产效率的优化是取得成功的关键步骤。面阵工业相机能够一次性捕捉对象的二维区域图像,并支持…

现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能

现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能,每个人都可以通过手机实时拍照上传到大屏幕上,同时还可以发布留言内容,屏幕上会同步滚动播放展示所有人的照片和留言。相比校传统的照片直播功能更加灵活方便,而…

【多线程】线程安全

目录 一、初识线程安全 什么是线程安全问题 理解线程不安全的原因 原因总结 二、解决线程不安全 加锁🔐 锁对象 synchronized几种使用方式 死锁🔏 死锁的三个场景 (1)一个线程针对一把锁连续加锁两次 (2)两个线程两把锁 (3)N个线程M个锁 如…

传统文旅+AI构建数字文旅新生态

传统文旅AI构建数字文旅新生态 前言: 当前许多旅游景区在旅游管理和旅游基础设施配套上都下足了功夫,在一定程度上也给旅客和消费者带来了舒适的体验感。但是针对于我们游客而言,似乎只有欣赏沿途风景、了解景区历史文化、拍照打卡和品尝特色…

VSCode - VSCode 切换自动换行

VSCode 自动换行 1、基本介绍 在 VSCode 中,启用自动换行可以让长行代码自动折行显示,避免水平滚动条频繁使用,提升代码阅读体验 如果禁用自动换行,长行代码就需要手动结合水平滚动条来阅读 2、演示 启用自动换行 禁用自动换…

解锁音频新境界:LALAL.AI 与 Audo Studio 深度解析

在音频处理的世界里,噪音常常是困扰我们的一大难题。无论是专业的音频工作者,还是普通的音频爱好者,都渴望拥有一款强大的工具来解决这个问题。今天,就为大家介绍两款来自 AI 工具导航(AIDH.NET)的 AI 语音…

线上死锁问题排查和处理

Java 死锁排查 通过 jps jstack 来定位和排查 如果线程长时间处于阻塞,就需要考虑是否是死锁了。 模拟死锁 public class DeadlockDemo {private static final Object lock1 new Object();private static final Object lock2 new Object();public static vo…

5 分钟用满血 DeepSeek R1 搭建个人 AI 知识库(含本地部署)

最近很多朋友都在问:怎么本地部署 DeepSeek 搭建个人知识库。 老实说,如果你不是为了研究技术,或者确实需要保护涉密数据,我真不建议去折腾本地部署。 为什么呢? 目前 Ollama 从 1.5B 到 70B 都只是把 R1 的推理能力…

MT7628基于原厂的SDK包, 修改ra1网卡的MAC方法。

1、在/etc/config/wireless文件添加多个WIFI网卡的方法。 2、修改WIFI驱动,在src/embedded/ap/ap.c文件里面,从系统文件信息来修改ra1网卡的MAC内容,添加红色部分源代码。 RTMP_IO_WRITE32(pAd, RMAC_RMACDR, Value); if (idx > 0) …

brew Nushell mac升级版本

运行命令: brew upgrade nushell 国内更新比较慢建议架个梯子。 如果没有更新则先更新一下brew brew update 更新后看下版本是否死最新的了

windows怎样查看系统信息(处理器等)

首先打开命令行工具 win R 输入 cmd, 输入 msinfo32 ,然后回车 这个页面就可以看到 电脑的锐龙版就是 AMD 芯片 酷睿版就是 intel 芯片

mysql之Innodb数据页

Innodb数据页结构 InnoDB数据页结构一、数据页基础概念二、数据页核心结构1. 头部控制区2. 数据存储区3. 尾部与目录区 三、关键机制详解1. 记录链表与删除优化2. 页目录与二分查找3. 空间复用与碎片管理4. 数据页的合并与分裂 四、应用与性能影响1. 索引效率2. 插入优化3. 事务…

1200沿指令和取反指令的应用。

以下是关于西门子S7-1200 PLC中沿指令(边沿检测指令)和取反指令的详细解析及应用示例,结合其工作原理、编程方法和典型场景: 一、沿指令(边沿检测指令) 1. 功能说明 沿指令用于检测信号状态的变化&#x…

three.js之特殊材质效果

*案例42 创建一个透明的立方体 <template><div ref"container" className"container"></div> </template><script setup> import * as THREE from three; import WebGL from three/examples/jsm/capabilities/WebGL.js // 引…

三格电子上新了——PLC 数据采集网关

型号&#xff1a;SG-PLC-Private 第一章 产品概述 PLC 转 Modbus 网关型号 SG-PLC-Private &#xff08; PLC 私有协议网关&#xff09;&#xff0c;是三格电子推出的工业 级网关&#xff08;以下简称网关&#xff09;&#xff0c;主要用于 在不需要对 PLC 编程的情况…

算法日记25:01背包(DFS->记忆化搜索->倒叙DP->顺序DP->空间优化)

对于01背包这类DP入门的问题&#xff0c;新手应该是去了解如何一步步得出所谓的状态转移方程&#xff0c;而不是直接去看答案所给予的方程过程应该为&#xff1a;DFS->记忆化搜索->倒序递推->循序递推->二维->一维 一、DFS暴力搜索 O ( 2 n ) O(2^n) O(2n) 1…

Spring AutoWired与Resource区别?

大家好&#xff0c;我是锋哥。今天分享关于【Spring AutoWired与Resource区别?】面试题。希望对大家有帮助&#xff1b; Spring AutoWired与Resource区别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Spring 中&#xff0c;Autowired 和 Resource 都是用于…

【知识】深度学习中,应该先zero_grad还是先backward?

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 抛出问题 各大GPT的回答 ChatGPT-4o ChatGPT-o3-mini-high Kimi-长思考 Deepseek-R1 Grok3 Pytorch官方教程中 抛出问题 以下哪种方式是…