接上文 游戏开发中的人工智能(十):模糊逻辑

本文内容:技术上而言,有限状态机和模糊逻辑都落在基于规则的方法这个大伞之下。本章将谈这些方法,以及其他变化的方法。


规则式 AI

本章我们要研讨基于规则的 AI 系统。基于规则的 AI 系统可能是真实世界和游戏软件 AI 中最为广泛使用的 AI 系统了。规则系统最简单的形式由一连串的 if-then 规则组成,用来推论或行动决策。从形式上来说,在第九章的有限状态机中,已经看过规则系统的一种形式:我们用规则处理状态的转换问题。第十章谈到模糊逻辑时,也看过另一种规则系统(模糊规则)。

规则系统基础

规则系统有两个主要的部分,一个是工作记忆,另一个是规则记忆

工作记忆储存已知的游戏世界信息,这部分是动态的。规则记忆储存设游戏设计师设计的的规则。当工作记忆符合规则记忆的某一条规则时,相应的行动就会被触发。或者,规则记忆中的规则也能修改工作记忆的内容。

为了说明规则系统,我们举个实时战略模拟游戏中科技树的例子。在实时战略模拟游戏中,玩家必须训练农民,建立设施以及收割农作物。与此同时计算机对手也会追踪玩家当前的科技状态进行评估并推论,更新自己的科技。玩家也可以以同样的方式评估计算机对手的科技状态。因此,玩家和计算机都必须排除侦察兵,收集信息,根据所收集到的信息做推论。(可以利用简单的规则系统达到这种效果)。图11-1 说明了科技树的构成。

例11-1 是实时策略游戏科技树的工作记忆内容。

//例11-1:工作记忆示例enum TMemoryValue(Yes,No,Maybe,Unknown);TMemoryValue peasants;    //农民
TMemoryValue Woodcutter;  //伐木工
TMemoryValue Stonemason;  //石匠
TMemoryValue Blacksmith;  //铁匠
TMemoryValue Barracks;    //兵营
TMemoryValue Fletcher;    //箭工
TMemoryValue WoodWalls;   //木栅栏
TMemoryValue StoneWalls;  //石墙
TMemoryValue Cavalry;     //骑兵
TMemoryValue FootSoldier; //步兵
TMemoryValue Spearman;    //矛兵
TMemoryValue Archer;      //弓箭手
TMemoryValue Temple;      //庙宇
TMemoryValue Priest;      //僧侣
TMemoryValue Crossbowman; //十字弓箭手
TMemoryValue Longbowman;  //长弓箭手

就此例而言,我们让工作记忆里的每个元素都以 TMemoryValue 类型声明,而且可以取下列四个值之一:Yes、No、Maybe 或 Unknown。主要目的是,让计算机对手知道当前玩家对手的科技状态。Yes 表示玩家有某种科技,No 表示没有。如果玩家满足所有获得某种科技的条件,但其状态尚未被侦察兵确认,则其值是 Maybe。如果计算机不知道玩家对某科技的能力,则取值 Unknown。

计算机可以收集玩家当前科技状态的事实,做法是派出侦察兵,并做观察。例如,如果计算机派出一名侦察兵,而侦察兵看见玩家建了庙宇,则 Temple 设为 Yes。不过在此之前,使用一组 if-then 规则,在侦测兵确认之前,计算机能根据既有事实推论玩家的科技状态。例如,看图11-1 ,如果玩家有伐木工和石匠,则有能力建庙宇,则 Temple 的值会是 Maybe。如例11-2 所示。

例11-2:庙宇规则示例if(Woodcutter==Yes && Stonemason==Yes && Temple==Unknown)Temple=Maybe;

推论也可以以反推的方式得到。例如,如果玩家被观察到有僧侣,则计算机可以推论,玩家一定有庙宇,因此,也一定有兵营、伐木工以及石匠。如例11-3 所示。

//例11-3:僧侣规则示例if(Priest==Yes)
{Temple=Yes;Barracks=Yes;Woodcutter=Yes;Stonemason=Yes;
}

根据图11-1 的科技树还可以写出许多规则,例11-4 是可以写出的其他规则。

//例11-4:其他规则示例if(Peasants==Yes && Woodcutter==Unknown)Woodcutter=Maybe;
if(Peasants==Yes && Stonemason==Unknown)Stonemason=Maybe;
…

如前所述,就此例而言能写的规则不止这些,你可以开发更多规则,包含如图11-1 所示的所有可能科技。思路是:你可以写这类规则,并在游戏中不断执行(GameCycle 时),以保持计算机对手看待玩家科技能力的最新图像,以决定如何部署攻防兵力。

此例让你大致了解规则系统的运作方式,实际上就是一组 if-then 规则。但是,注意,开发人员经常不用本节所用的 if 语句建构规则系统,因为直接把 if 语句写在程序里,会让某种推论难以达到。开发人员时常使用描述语言或 shell 语言,使他们能建立规则并予以修改,而不用修改源代码再重新编译。

对战游戏攻击预测

此例中,我们的目标是,在对战游戏中,预测人类对手的下一个招式。我们想让计算机对手,能够利用玩家最近出的招式以及玩家过去所出招式的某些模式,预测玩家下次要出什么招。如果计算机可以预测下一招,就能采取适当的反击、阻挡或闪躲动作,比如往侧边跳或往后退。这会让战斗模拟游戏有更强烈的真实感,给玩家新的挑战。

为了达到这种效果,我们要实现一个有学习能力的规则系统。让每条规则加权,强化某些规则,压抑另外一些规则,借此达到学习的效果。

为了让范例能在讨论的掌控范围内,我们做一些简化工作。假定玩家的招式可以分成挥拳、下踢、上踢。

工作记忆

例11-6 是工作记忆的操作方式。

//例11-6:工作记忆enum TStrikes(Punch,LowKick,HighKick,Unknown);struct TWorkingMemory
{TStrikes strikeA; //前前次攻击TStrikes strikeB; //前次攻击TStrikes strikeC; /预测的下次攻击//可以在这里加上其他元素,比如要怎么反击等
};TWorkingMemory WorkingMemory; //全局工作记忆变量

规则

例11-7 是此例的规则类。这里我们没有直接写出 if-then 规则,我们以 TRule 对象数组表示规则记忆。

//例11-7:规则类class TRule
{public:TRule();void SetRule(TStrikes A,TStrikes B,TStrikes C);TStrikes antecedentA; //前前次攻击TStrikes antecedentB; //前次攻击TStrikes consequentC; //预测的下次攻击bool matched; //工作记忆是否与规则记忆相匹配int weight;   //权值因子
};

TRule 规则类只有两个方法:SetRule( ) 和构造方法。构造方法是把 matched 赋初值 false,weight 赋为 0。我们以 SetRule( ) 设定其他成员:antecedentA、antecedentB、consequentC,由此就可以定义出一条规则。SetRule( ) 方法如例11-8 所示。

//例11-8:SetRule()方法void TRule::SetRule(TStrikes A,TStrikes B,TStrikes C)
{antecedentA=A;antecedentB=B;consequentC=C;
}

此例需要几个全局变量,第一个是 WorkingMemory,如例11-6 所示。例11-9 是其他的全局变量。

//例11-9:全局变量TRule Rules[NUM_RULES]; //存储规则记忆 TRule对象的数组,此例指定为27
int PreviousRuleFired;  //存储上一次游戏循环中启动的规则索引值TStrikes Prediction;    //规则系统中所作的招式预测,技术上而言并不需要,因为预测招式都会存储在工作记忆中
TStrikes RandomPrediction; //存储随机产生的预测招式,用以比较随机和我们预测的成功率int N;                  //存储预测次数
int NSuccess;           //成功预测次数
int NRandomSuccess;     //随机猜测成的次数

初始化

游戏开始时,我们必须对所有规则和工作记忆做初始化。例11-10 的 Initialize( ) 函数会完成此任务。

//例11-10:Initialize()函数void TFom1::Initialize()
{Rules[0].SetRule(Punch,Punch,Punch);…Rules[26].SetRule(HighKick,HighKick,HighKick);WorkingMemory.strikeA=sUnknown;WorkingMemory.strikeB=sUnknown;WorkingMemory.strikeC=sUnknown;PreviousRuleFired= -1;N=0;NSuccess=0;NRandomSuccess=0;UpdateForm();
}

这里我们一共有27条规则,对应出拳、下踢、上踢这三招的所有可能组合模式。例如,第一条规则 Rules[0] 可以理解成这样:

if ( WorkingMemory.strikeA == Punch && WorkingMemory.strikeB == Punch)
then WorkingMemory.strikeC = Punch

检视这些规则可以发现,任何时刻都有一条以上的规则可以吻合工作记忆中的事实。例如,如果招式A 和 B 都是出拳,则前三条规则都吻合,预测的招式可以是出拳、下踢或者上踢。此时我们用加权因子,协助我们找出要启动哪条规则。我们只用权重最高的规则。如果有两条或两条以上的规则有相同的权重,那就用最前面那一条。

预测招式

当游戏开始运行,每次玩家出招之后,我们都必须做招式预测。我们用函数 ProcessMove( ) 处理玩家出的每一招,并预测其下一招。如例11-11 所示。

//例11-11:ProcessMove()TStrikes TForm1::ProcessMove(TStrikes move)
{int i;int RuleToFire= -1;//第一块:if(WorkingMemory.strikeA == sUnknown){WorkingMemory.strikeA=move;return sUnknown;}if(WorkingMemory.strikeB == sUnknown){WorkingMemory.strikeB=move;return sUnknown;}//第二块://先处理前次预测,记录并调整权重N++;if(move==Prediction){NSuccess++;if(PreviousRuleFired != -1)Rules[PreviousRuleFired].weight++;}else{if(PreviousRuleFired != -1)Rules[PreviousRuleFired].weight--;//增加应该启动规则的权重for(i=0;i<NUM_RULES;i++){if (Rules[i].matched && (Rules[i].consequentC == move) ){Rules[i].weight++;break;}}}if(move == RandomPrediction)NRandomSuccess++;//删除旧值WorkingMemory.strikeA=WorkingMemory.strikeB;WorkingMemory.strikeB=move;//第三块://开始做新预测for(i=0;i<NUM_RULES;i++){if(Rules[i].antecedentA == WorikingMemory.strikeA && Rules[i].antecedentB == WorikingMemory.strikeB)Rules[i].matched=true;elseRules[i].matched=false;}//选出权重最高的规则RuleToFire= -1;for(i=0;i<NUM_RULES;i++){if(Rules[i].matched){if(RuleToFire == -1)RuleToFire=i;else if(Rules[i].weight > Rules[RuleToFire].weight)RuleToFire=i;}}//启动规则if(RuleToFire != -1){WorikingMemory.strikeC=Rules[i].antecedentC;PreviousRuleFired=RuleToFire;}else{WorkingMemory.strikeC=sUnknown;PreviousRuleFired= -1;}return WorikingMemory.strikeC;
}

第一块
第一块是填写工作记忆。游戏开始时,工作记忆初始化之后,任何招式出击之前,工作记忆中只有 Unknown 值,这样使无法预测的,所以我们要在玩家开始出招后,从玩家那里搜集资料。

第一招存储在 WorkingMemory.strikeA 中,而 ProcessMove( ) 返回 Unknown。第二招打出后,ProcessMove( ) 再次被调用,第二招存储在 WorkingMemory.strikeB 中,ProcessMove( ) 依旧返回 Unknown。

第二块
ProcessMove( ) 的第一块是处理前次预测,也就是上一次调用 ProcessMove( ) 后所返回的预测招式。

第二块首先要确认前次预测时候有效。ProcessMove( ) 以 move 为参数。move 是玩家最近一次出的招。如果 move 等于存储在 Predicition 的前次预测招式,那么我们的预测就是成功的。我们递增 NSuccess,以更新成功率。然后我们我们强化上次启动的规则即增加该规则的权重。如果前次预测是错的,我们则要递减前次启动的规则权重。

接下来我们查看前次随机预测是否正确,正确就递增 NRandomSuccess。最后,我们更新工作记忆中的招式,以便做新预测,即 WoringMemory.strikeB 变成
WoringMemory.strikeA,而 move 变成 WoringMemory.strikeB 。

第三块
首先我们要找出符合工作记忆中事实的规则(第一个 for 循环)。配对步骤完成后,我们要从那些吻合的规则中挑选一条出来,即冲突解决(第二个 for 循环),循环工作完成之后,选定的规则的索引值会存储在 RuleToFire 中。要实际启动规则,只需要把 Rules[RuleToFire] 的 consequentC 赋值给 WorkingMemory.strikeC 即可。

ProcessMove( ) 把要启动的规则索引值 RuleToFire 存储在 PreviousRuleFired中,下次 ProcessMove( )被调用时,会在第二块使用。最后,ProcessMove( ) 返回预测的招式。

游戏开发中的人工智能(十一):规则式 AI相关推荐

  1. 游戏开发中的人工智能(一):游戏人工智能简介

    本系列文章对<游戏开发中的人工智能>David M.Bourg / Glenn Seemann 一书进行解读. 这本书谈了很多游戏软件 AI 的主题,内容深度适合初涉开发人员.所以,无论你 ...

  2. 游戏开发中的人工智能(十三):不确定状态下的决策:贝叶斯技术

    接上文 游戏开发中的人工智能(十二):概率概论 本文内容:贝叶斯技术是概率技术,本章解释如何运用,以便在游戏中做决策并适应游戏. 不确定状态下的决策:贝叶斯技术 本章要介绍贝叶斯推论和贝叶斯网络,教你 ...

  3. 游戏开发中的人工智能(五):以势函数实现移动

    接上文: 游戏开发中的人工智能(四):群聚 本文内容:靠势能移动在游戏 AI 程序中还算相当新颖.这个方法的最优越的地方在于可以同时处理追逐.闪躲.成群结队和避免碰撞等行为.我们专门研究的这个势函数叫 ...

  4. 游戏开发中的人工智能(十四):神经网络

    接上文 游戏开发中的人工智能(十三):不确定状态下的决策:贝叶斯技术 本文内容:"神经网络"技术让游戏具有学习和适应的能力.事实上,从决策判断到预测玩家的行为,都可以应用.我们会详 ...

  5. 游戏开发中的人工智能(六):基本路径寻找及航点应用

    接上文:游戏开发中的人工智能(五):以势函数实现移动 本文内容:游戏开发人员使用很多技术在游戏环境中寻找路径.本章要谈几种方法,包括航点应用. 基本路径寻找及航点应用 寻找路径的问题有很多不同类型.没 ...

  6. 游戏开发中的人工智能(二):追逐和闪躲

    接上文 游戏开发中的人工智能(一):游戏人工智能简介 本文内容:讨论基本的追逐和闪躲技术,以及进级的拦截技术.我们也谈及这些技术在砖块环境和连续环境中的变化. 追逐和闪躲 本章的焦点是追逐和闪躲,这是 ...

  7. 游戏开发中的人工智能

    前言 今天非常开心,观看cocos官方直播居然在几千人中中奖,可以买彩票了. 言归正传,所谓的人工智能,也就是大家常说的AI(Artificial Intelligence).一说到AI可能就会让人觉 ...

  8. Unity3D游戏开发中的人工智能AI 简单实例

          今天我想说的是游戏中的人工智能.人工智能这个东西在游戏中是非常重要的,人工智能说简单了就是根据随机的数字让敌人执行一些动作或逻辑,说难了TA需要一个非常复杂的算法,本文我主要说说Unity ...

  9. Unity3D研究院之游戏开发中的人工智能AI

        很久没有写Unity3D相关的东西了,是因为这段时间我深陷一款IOS的软件开发中.不过以后我还是会回归Unity3D游戏开发的.什么语言都在用,生活与工作都挺给力的嚯嚯.今天还是打开了久违的U ...

最新文章

  1. 【转】使用Xcode中的iOS SDK给iphone开发出第一个App程序
  2. 用html修改游戏聊天字体,前端使用自定义字体方案
  3. import引入json文件_关于TypeScript中import JSON的正确姿势详解
  4. AI 玩微信跳一跳的正确姿势:跳一跳 Auto-Jump 算法详解
  5. Leetcode题库 144.二叉树的前序遍历(递归 C实现)
  6. word无法打开请去应用商店_word文档打不开的4种解决方法
  7. linux没有interface文件,Linux下interface文件修改
  8. JS 进阶知识点及常考面试题
  9. java图片去掉文字,Java 移除html,图片 链接转文字
  10. 新冠疫情,或加速银行数字化服务转型
  11. (Object detection)目标检测从入门到精通——第五部分YOLO 算法
  12. c语言switch的作用域,你真的懂switch吗?聊聊switch语句中的块级作用域
  13. 老年代的更新机制_魔兽世界:60年代五大“远古”机制,这根胡萝卜,可是当年的神器...
  14. ESET Smart Security 3.0667与WindowsXP Sp3冲突
  15. JAVAFX输入法的实现
  16. Excel 宏编程-使用excel宏编写第一个Hello World程序实例演示!
  17. 《Head First设计模式》中文版 读书笔记
  18. 【转载】win10环境下,利用自带虚拟机hyper-v安装centos7方法详解
  19. 卸载软件失败:“1628:完成基于脚本的安装失败”【已解决】
  20. Android7.0以上 安装Ca证书

热门文章

  1. MySQL之——CentOS6.5_x64安装配置drbd8.4.2
  2. 【luoguP3243】[HNOI2015]菜肴制作--拓扑排序
  3. 嵌入式GUI QT之注意事项
  4. 网站集成QQ号登录-QQ互联审核(附涉及不良信息解决办法)
  5. Flutter学习笔记学习资料推荐,手机端开发工具
  6. 网络DHCP配置简单介绍
  7. PostgreSQL 大象 -- Slonik 的历史
  8. java8注解@Repeatable使用技巧
  9. 全球与中国室内运动地板市场深度研究分析报告
  10. 注册破解加暗桩去除下篇去除暗桩部分