游戏开发论坛

 找回密码
 立即注册
搜索
查看: 8106|回复: 0

游戏AI研究(一):感知AI

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2018-12-4 12:01:53 | 显示全部楼层 |阅读模式
文/KillerAery

一、游戏AI是什么?

AI=Artifical Intelligence=人工智能

游戏AI和理论AI

但是值得注意的是,但是游戏AI与理论研究的AI那些有所不同。

我们平时所熟悉的人工智能,大多数是指理论AI。(例如深度学习,机器学习什么的)

而游戏AI往往很少应用到理论AI那些神经网络、深度学习等等理论AI所流行的技术。

智能的假象

游戏AI主要是程序员预先定义编写好可能发生的行为,而不具有理论人工智能那种自我“学习”的特性。

因为游戏AI主要职责是模拟出智能行为,而并非学习

(例如:街道上会避让车的人群,天空中乱飞的小鸟,来回巡逻的守卫,兵线上的小兵...)

可以说游戏AI是智能的假象。

要是游戏功能需要自我学习,尽可能做出最优策略,更应该使用理论AI(也是我们熟悉的那个人工智能),通过神经网络,深度学习等流行技术实现。

例如:围棋AI阿法狗,DOTA2的OPENAI

介绍一些游戏AI

4X游戏的AI

1.jpg
《群星》

《群星》《文明》《王国风云》等为代表的4X游戏,战略游戏的一种,其主要的四个游戏目的分别是:

eXplore(探索),eXpand(扩张),eXploit(掠夺),eXterminate(毁灭)。

为了让玩家在4X进程中受到阻挡,4X游戏AI必须得足够聪明做出决策,

但又同时为了不让玩家觉得无法胜利,它往往不是采用最优策略,而是使用更“人性化”的策略,

(例如反应延迟,走的路径稍微扭曲,模糊决策等做法)。

《求生之路》系列

2.png
《求生之路2》

作为一款FPS游戏,很难说《求生之路》里的怪物有多智能——它们本来就该是愚蠢而凶猛的。真正有技术含量的是它的“导演系统”,AI Director作为后台的核心,会根据玩家在游戏中的具体表现调控游戏的节奏。怪物出现的地点、数量,何处刷新道具等等,配合上根据形势动态变换的音乐,给了用户更真实的游戏体验。

角色扮演/沙盒游戏中的NPC

3.png
《巫师3》

在一些自由度较高的游戏中,为了让玩家更好的融入这个世界,游戏会对NPC进行很多详细的设定。比如在《巫师》系列中,每个NPC都有自己的性格设定,包括会话数据库,让他们可以进行丰富的动作和对话。在GTA这种沙盒游戏中更是这样。

游戏AI需要掌握什么

下面列出部分游戏AI所需掌握的知识和简要介绍:

感知

AI所能获取的信息应该总是有限的。引入“感知”的概念,用以模拟智能体的感官,从而获取一定的信息。

黑板

“黑板”简单来说就是可访问的共享数据,用于多模块间的数据共享。

状态机

“状态机”是一种表示状态并控制状态切换的设计模式,常常用于设计某种东西的多个状态。

例如一个人有站立状态,跑动状态,走路状态,蹲下状态,开火状态等...

行为树

“行为树”是一种以树状结构表达的决策模式,也是一种设计模式。这也是现代游戏AI最常用的设计模式。

自治智能体

正如其命,有自治动作的智能体称之为“自治智能体”。在《看门狗》《GTA》里,街上走路的人群或者开动的汽车

就是一种自治智能体。主角开车如果冲向它们,这些自治智能体能够自行判断并做出躲开的动作。

群体智能

如其名,与自治智能体相对,“群体智能”一般用于编队的AI或者集群的AI。例如足球游戏里,AI操控一方所有球员互相配合传球踢球。

又或者射击游戏里,AI操控一支小队通过战术进攻据点。

寻路/搜索/规划

“寻路”是游戏里极为常见的操作,常用的算法有A*算法和B*算法,当然更优化的话则是依赖规划路径的寻路。

“搜索”其实应该跟寻路并在一起,只是游戏AI有可能还会用到图搜索。

在《群星》里,通过一条条“航路”连接千千万万个星球,这时候AI就要每次在决策派遣舰队时,

需要考虑到达这张“图”的各个星球的权值和最优期望。

“规划”则是游戏的预处理,例如提前构建路线,划分区域等,以便游戏进行后利用预先处理好的数据进行高效的算法操作。

模糊逻辑

为了让游戏AI更人性化,模糊逻辑很有必要。做游戏AI往往不是做最优解,而是做像人类的解。

在某些时候,例如:某个AI战斗结束后,判断如果子弹数量少,则回基地取弹药,

“子弹数量少”这个条件就可以做成一种模糊逻辑条件。

此外还有“抖动”,"平滑"等trick可以增强拟人性,让AI愚笨的像个人。

脚本驱动

使用脚本可以随意编写出逻辑代码而无需再次编译,从而极大减少修改逻辑的成本。

此外游戏程序最常用的脚本语言——Lua.

游戏AI基本设计

实际世界的智能:

一个正常的智能体,得先感知到周围的事物,才能思考下一步该做什么,才能做出反应行为。

基于这个便自然而然分出以下三个模块:

  • 感知
  • 决策
  • 行为

感知


一般的游戏AI,首先需要获取到感知的信息。

例如:

  • 一个NPC的视野范围内看到了有一个丧尸。
  • 一个丧尸听到了玩家的脚步声。
  • 足球队队员之间的传达配合信息。


//例如 视野感知
class ViewPerception {
public:
    //进行一次视野感知探测:
    void check();
    //访问感知目标结果
    const std::list<Agent*>& getResult()const;
private:
    std::list<Agent*> mPerceptionResult;   //使用链表存储感知到的目标
};


决策

分析感知信息,并进行计算,输出接下来想要做的行为的结果。

例如:

  • NPC检查了这个丧尸,判断到它是自己的敌人,所以NPC想逃跑。
  • 丧尸检查了这个玩家,判断到它是自己的敌人,所以丧尸想扑过去攻击。
  • 中锋检查了前锋的带球情况,判断到它想传球,所以中锋想要去对应的位置接球。


可以把决策模块想象成一个大脑(为了类比,可以把下面的“决策模块”字样看成“大脑”):

  • 决策模块“寄生”在一个智能体模型上(该智能体模型被称为寄主)。
  • 决策模块的生命周期是跟随智能体模型的生命周期的。
  • 决策模块能够获取智能体模型的感知信息,分析之,并控制智能体模型行动。


class AgentAI{
  public:
    //计算寄主感知信息,并根据信息让寄主执行相应的行为
    void caculate(){
      //获取感知信息
      auto information = mMaster->mPerception.getResult();
      //根据感知信息,让寄主执行行为
      if(information...)mMaster->moveTo(...);
      if(information...)mMaster->attack();
      //....
    }
  protected:
    Agent* mMaster;//寄主
  };

行为

根据决策的结果,行为模块只要按部就班的执行相应行为就行。

一般的游戏模型应该提供相应的行为接口。

//一个例子:智能体类提供 移动和攻击 这两个接口
class Agent{
public:
  //智能体的各种行为接口
  //...
  void moveTo(cosnt Position& pos);
  void attack();
protected:
  //智能体的各种变量
  //....
  Position mHealth;               //当前位置
  int mAttack;                        //攻击力
  ViewPerception mPerception;     //视野感知模块
};

小结

这样,一种简单基本的设计就浮现了:

  • 每个智能体模型含有一个感知模块
  • 每个智能体模型提供行为接口。
  • 每个决策模型"寄生"于一个智能体模型。
  • 决策模型,根据该智能体模型的感知模块得到的信息,分析运算后让该智能体模型执行相应的行为。


二、游戏AI之感知

感知

  • 视觉感知


视觉感知是一种常见的感知。

4.jpg

在许多即时战略游戏或者类DOTA游戏里,一个单位的视觉感知往往是圆形范围的。

当然在其他大部分俯视角游戏里,一个智能体的视觉感知应该是类似现实人眼观看的扇形范围

5.png

对于横板游戏,可以把视野“竖”起来,检测方式无多少差别。
对于空间更加复杂的3D游戏,可能需要视锥体形状(6个平面)检测而不是扇形或者圆锥形。
潜在的优化是照样做成扇形检测,只是再额外增加高度差检测(即看作2.5D处理)。

但是视野实际还需考虑阻挡问题。

这里提供两种解决视野遮挡的思路:

6.png

1.在前方扇形范围发出若干条射线进行检测,若检测到某个射线第一个碰到的物体是目标物体,则感知到该目标。

7.png

2.在所在区域的所有潜在目标进行遍历,每次遍历先判断是否在扇形范围内,

再做一条智能体到目标的射线,若射线碰到的第一个物体是该目标,则感知到该目标。

第一个思路比较容易实现,第二个则算法效率比较高。

第二个思路要是预先“规划”好区域,尽可能过滤不必要的目标,缩小所在区域的潜在目标数量

(例如屋外看不到房内的人,也就可以过滤掉房内的人),那么检测速度就非常快。

  • 听力感知


听力感知一般比较简单粗暴:一个圆形/球形范围检测,

而且一般还无需考虑阻挡问题(现实中的声音传播可近似看作无阻挡)。

9.png

另外的,听力感知一般需要得到的信息:

  • 声音来源(例如发出声音的生物)
  • 声音大小和距离


通过简单的线性计算,由声音大小和距离可以计算出实际接受声音的大小。

将这个信息作为额外数据交由决策使用。

(例如一个警卫,听到太大的声音就进入敌对状态,小的声音则进入警戒状态)

  • 其它感知


这个其实应该叫杂项感知或者根据需求随便取名的感知。

一般来说,视觉感知和听力感知已经足够一个基本的智能体所需感知了。

但极少情况还可能一些智能体需要知道各种杂项信息

(例如队长给警卫发送了一条无线电消息,要求警卫赶往队长所在位置支援)

实现

感知可以做成一种类,提供检测函数和结果访问接口。

下面提供一种大致的示例(C++):



//视野感知
class ViewPerception {
public:
    //进行一次视野感知探测:
    void check(Vector3 position) {
        //先清理结果
        perceptionResult.clear();
        //逐个潜在目标检测
        for (Biology* target : targets) {
            //运用简单的数学运算判断点是否在扇形范围:
            //先进行距离判断是否在半径内。
            //look向量和射线单位向量的数量积 若小于 数量积限制,
            //则证明该射线离look向量的角度 超出数量积限制的对应角度。
            Vector3 offset = target.getPosition() - position;
            float distanceSq = offset.lengthSquare();
            if (distanceSq < radiusSq)continue;
            float  dotproduct = offset.normalize().dot(look);
            if (dotproduct < dotproductlimit)continue;
            perceptionResult.emplace_back(target);
        }
    }
    //访问感知目标结果
    const std::list<Biology*>& getResult()const {
        return perceptionResult;
    }
private:
    float radiusSq;             //扇形半径的平方
    Vector3 look;               //朝前的单位向量
    float dotproductlimit;  //数量积限制
    std::list<Biology*> perceptionResult;   //使用链表存储感知到的目标
};





//听力感知
class ListenPerception {
public:
    //进行一次听力感知探测:

    void check(Vector3 position) {
        perceptionResult.clear();
        //逐个潜在声源检测
        for (Voice& voice : voices) {
            //判断目标点是否在圆形范围,即距离是否在半径内。
            Vector3 offset = voice.getPosition() - position;
            float distanceSq = offset.lengthSquare();
            if (distanceSq < radiusSq)continue;
            perceptionResult.emplace_back(voice.getBiology(),voice.getVolume()/distanceSq);
        }
    }
    //访问感知目标结果
    const std::list<std::pair<Biology*, float>>& getResult()const {
        return perceptionResult;
    }
private:
    float radiusSq;         //范围半径
    std::list<std::pair<Biology*,float>> perceptionResult;  //使用链表存储感知到的目标+实际声音大小
};




TIP:

判断点在圆形范围应->比较距离的平方和半径的平方,每次判断就可以减少一次开方的运算。


博客地址:https://www.cnblogs.com/KillerAery/p/10003678.html


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-11-16 10:39

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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