P&D 过河游戏智能帮助实现


  • 实现状态图的自动生成

  • 讲解图数据在程序中的表示方法

  • 利用算法实现下一步的计算

对于过河游戏,首先需要知道其中各个状态之间的转换关系,绘制状态转移图如下:

其中,P代表出发岸上的牧师,D代表出发岸上的恶魔,加号和减号分别标识船停在出发一侧的岸上还是终点一侧的岸边。则黄色框代表起始的状态,此时出发岸上有三个牧师和三个恶魔,船停在出发一侧的岸上;红色框代表游戏终止时的状态,此时为胜利状态,出发岸上没有牧师和恶魔,船停在终点一侧的岸边。双箭头表示两个状态间可以相互转移。

“sense-think-act” paradigm(范式) 是构造 agent、robot、NPC(Non-Player Character) 的基础概念。自从上世纪80年代提出以来,我们使用 Sense-Think-Act 范例思考机器人如何工作,并设计它们。 即使机器人最终设计的方式不同,STA 通常仍然是一个有用的开始。 随着机器人技术的发展,协同技术越来越重要,机器人之间的 “Communicate”通常也纳入设计之中。本节主要关注 NPC 如何产生看似理性(Rational)的行为(模拟人)的设计方法。

根据以上的状态转移图,首先完成“感知”部分的设计:

感知是 agent 接收世界信息的行为,其获取的数据将是思考的输入。在游戏中,程序是可以获得游戏世界任意信息的,设计一个“干死”玩家的算法通常是比较容易的,因此如何限制信息获取是设计不同级别 agent 的核心问题。

在游戏中,定义获取信息能力通常可以从视觉、听觉和嗅觉等渠道去考虑:

  • 视觉(Vision)

    • 识别“敌人”的位置和属性
    • 识别“障碍物”及其范围
  • 听觉(Sound)
    • 识别事件的方向和距离
  • 嗅觉(Smell)
    • 获得玩家/事件的痕迹

在这里,我使用最简单的方法让游戏智能体感知到应该作出下一步决策,就是增加一个按钮,每当玩家点击这个按钮,游戏智能体就会改变当前的状态并作出对应的改变。

相关的代码如下:

 void OnGUI() {if (result == 0 && GUI.Button(new Rect(Screen.width/2-70, Screen.height/2 - 70, 120, 60), "Next", bStyle))action.nextMove();if (result != 0 && GUI.Button(new Rect(Screen.width/2-70, Screen.height/2 - 130, 120, 60), "Replay", bStyle)) {result = 0;action.replay();}if (result == 1)GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-190, 100, 50), "You lose!", style);else if(result == 2)GUI.Label(new Rect(Screen.width/2-60, Screen.height/2-190, 100, 50), "You win!", style);}

以上代码增加了一个“next”按钮,当游戏未结束且用户点击此按钮时,执行nextMove,完成下一步移动。

接着完成“思考”的部分,需要使游戏智能体作出下一步走向哪一状态的决策:

Think 就是算法,它的输入是感知的数据,输出是行为(behaviours)。 思考的算法,通常就是我们所说的游戏规则的一部分,即 agent 能做什么,该做什么。 通常这不是程序员的工作,而是游戏设计师的工作。游戏 agent 的思考类似人脑的决策过程,这其中的关键不是打造最强大脑,而是建立符合游戏玩家难度曲线,可以控制、且符合社会准则的行为。另一个相关问题,玩家难度曲线在编程阶段是未知的,依赖众多玩家与 agent 的操作与对抗结果。它在游戏测试和运维过程中存在巨大不确定性(在数据驱动的设计和集成脚本引擎等章节中讨论解决方案)。

过程中,比较重要的点就是表示状态。

public enum BoatAct { P, D, PP, DD, PD };public struct Next {public BoatAct boatAct;
}

因为在每一步决策时,智能体只需要决定向船上放若干个人物,所以船的状态就只有五种,用结构体Next来存储船的状态。

        int from_P, from_D, direction = boat.direction;int[] fromNum = fromBank.getNum();from_P = fromNum[0];from_D = fromNum[1];

以上代码用来获取出发岸上的人物数量,可以通过getNum函数即时获取,因此不需要额外的数组来存储,并且因为人物数量恒定,只需一侧岸边的人物数量就可以推出另一边的;diection变量用来表示船的行驶方向,这里也可以直接获取。

船的方向以及岸边的人物数量构成了每一步的状态空间,船上的人物数量构成了每一步的决策状态空间。

为了符合以上说明,使游戏智能体的决策具有一定的不确定性,这里增加一个随机决策的函数:

    private int randomValue() {float a = Random.Range(0f, 1f);if (a <= 0.5f) return 1;else return 2;}

当可行的状态空间有两个时,智能体随机选择其中一个状态转移。

接下来是“思考”部分最关键的步骤,即作出下一步决策:

    public void GetNextPassager() {int from_P, from_D, direction = boat.direction;int[] fromNum = fromBank.getNum();from_P = fromNum[0];from_D = fromNum[1];if (from_P == 3 && from_D == 3 && direction == 1) {int turn = randomValue();if (turn == 1) next.boatAct = BoatAct.PD;else next.boatAct = BoatAct.DD;}else if (direction == -1 && from_P == 2 && from_D == 2) next.boatAct = BoatAct.P;else if (direction == -1 && from_P == 3 && from_D == 2) next.boatAct = BoatAct.D;else if (direction == -1 && from_P == 3 && from_D == 1) next.boatAct = BoatAct.D;else if (direction == 1 && from_P == 3 && from_D == 2) next.boatAct = BoatAct.DD;else if (direction == -1 && from_P == 3 && from_D == 0) next.boatAct = BoatAct.D;else if (direction == 1 && from_P == 3 && from_D == 1) next.boatAct = BoatAct.PP;else if (direction == -1 && from_P == 1 && from_D == 1) next.boatAct = BoatAct.PD;else if (direction == 1 && from_P == 2 && from_D == 2) next.boatAct = BoatAct.PP;else if (direction == -1 && from_P == 0 && from_D == 0) next.boatAct = BoatAct.D;else if (direction == 1 && from_P == 0 && from_D == 3) next.boatAct = BoatAct.DD;else if (direction == -1 && from_P == 0 && from_D == 1) {if (randomValue() == 1) next.boatAct = BoatAct.D;else next.boatAct = BoatAct.P;}else if (direction == -1 && from_P == 0 && from_D == 2) next.boatAct = BoatAct.D;else if (direction == 1 && from_P == 2 && from_D == 1) next.boatAct = BoatAct.P;else if (direction == 1 && from_P == 0 && from_D == 2) next.boatAct = BoatAct.DD;else if (direction == 1 && from_P == 1 && from_D == 1) next.boatAct = BoatAct.PD;}

此函数通过对不同的状态进行判断并作出相应的决策。

最后则要完成“行为”部分。当智能体作出决策时,需要指挥相应的游戏对象完成状态的改变。

行动(Act)将思考(Think)的结果作为输入,该部分的任务就是使得 agent 行为更符合物理世界的规律,使得“心想事成”这样理想的结果变得不确定。

首先增加变量status,用来表示船的状态改变:

   private int status;

status为0时人物上船,为1时船从一侧岸边到另一侧,为2时人物下船。这样可以避免状态间的转换太过生硬,保证转换时仍有人物的动作。

    public void nextMove() {int direction = boat.direction;ICharacterController p, d;if (status == 0) {GetNextPassager();if (direction == 1 && next.boatAct == BoatAct.PP) {p = fromBank.findCharacter(0);moveCharacter(p);p = fromBank.findCharacter(0);moveCharacter(p);}else if (direction == 1 && next.boatAct == BoatAct.P) {p = fromBank.findCharacter(0);moveCharacter(p);}else if (direction == 1 && next.boatAct == BoatAct.PD) {p = fromBank.findCharacter(0);moveCharacter(p);d = fromBank.findCharacter(1);moveCharacter(d);}else if (direction == 1 && next.boatAct == BoatAct.D) {d = fromBank.findCharacter(1);moveCharacter(d);}else if (direction == 1 && next.boatAct == BoatAct.DD) {d = fromBank.findCharacter(1);moveCharacter(d);d = fromBank.findCharacter(1);moveCharacter(d);}else if (direction == -1 && next.boatAct == BoatAct.PP) {p = toBank.findCharacter(0);moveCharacter(p);p = toBank.findCharacter(0);moveCharacter(p);}else if (direction == -1 && next.boatAct == BoatAct.P) {p = toBank.findCharacter(0);moveCharacter(p);}else if (direction == -1 && next.boatAct == BoatAct.PD) {p = toBank.findCharacter(0);moveCharacter(p);d = toBank.findCharacter(1);moveCharacter(d);}else if (direction == -1 && next.boatAct == BoatAct.D) {d = toBank.findCharacter(1);moveCharacter(d);}else if (direction == -1 && next.boatAct == BoatAct.DD) {d = toBank.findCharacter(1);moveCharacter(d);d = toBank.findCharacter(1);moveCharacter(d);}if (direction == 1) direction = -1;else direction = 1;status = 1;}else if (status == 1) {moveBoat();status = 2;}else if (status == 2) {ICharacterController[] pass = boat.getRoles();for (int i = 0; i < pass.Length; i++)if (pass[i] != null) moveCharacter(pass[i]);status = 0;}}

以上函数完成了状态转移相关的动作,通过moveBoat函数和moveCharacter函数来操纵人物和船体的动作。

    public void moveBoat() {int i;for (i = 0; i < boat.roles.Length; i++)if (boat.roles[i] != null) break;if (i == boat.roles.Length) return;boat.Move();int[] fromNum = fromBank.getNum();int[] toNum = toBank.getNum();int[] boatNum = boat.getNum();int direction = boat.direction;GUI.result = gameover.check(fromNum, toNum, boatNum, direction);}public void moveCharacter(ICharacterController role) {BankController bank;Vector3 t, pos;if (role.onBoat) {if (boat.direction == -1) bank = toBank;else bank = fromBank;boat.getOff(role.role.name);t = bank.pos[bank.emptyNum()];t.x *= bank.direction;role.moveable.destination(t);role.getOn(bank);bank.getOn(role);}else {bank = role.bank;if (boat.emptyNum() == -1) return;if (bank.direction != boat.direction) return;bank.getOff(role.role.name);int e = boat.emptyNum();if (boat.direction == -1) pos = boat.to_pos[e];else pos = boat.from_pos[e];role. moveable.destination(pos);role.getOn(boat);boat.getOn(role);}int[] fromNum = fromBank.getNum();int[] toNum = toBank.getNum();int[] boatNum = boat.getNum();int direction = boat.direction;GUI.result = gameover.check(fromNum, toNum, boatNum, direction);}

综上,利用“感知-思考-行为”模型完成了过河游戏的智能实现。

游戏效果如下:

游戏视频

项目传送

【Unity 3D学习笔记】PD 过河游戏智能实现相关推荐

  1. Unity学习之PD 过河游戏智能帮助实现

    Unity学习之P&D 过河游戏智能帮助实现 根据之前设计好的动作分离版过河游戏,我们进行一个简单的状态图AI实现. 转移状态图 状态图老师已经给出: 该状态图只记录了游戏过程中左岸的情况.P ...

  2. unity:PD 过河游戏智能帮助实现

    P&D 过河游戏智能帮助实现 github传送门 状态图 状态图课件有 状态图(Statechart Diagram)是描述一个实体基于事件反应的动态行为,显示了该实体如何根据当前所处的状态对 ...

  3. 3D游戏编程实践——PD 过河游戏智能帮助实现

    P&D 过河游戏智能帮助实现 需求 实现状态图的自动生成 讲解图数据在程序中的表示方法 利用算法实现下一步的计算 实现过程 实现状态图的自动生成&讲解图数据在程序中的表示方法 牧师与魔 ...

  4. Unity3d入门之路-PD 过河游戏智能帮助

    文章目录 P&D 过河游戏智能帮助 状态图 实现方法 图的表示方法 广度优先搜索 P&D 过河游戏拓展 结果展示 P&D 过河游戏智能帮助 本次作业基本要求是三选一,我选择了P ...

  5. Unity 3D学习笔记之一 界面介绍

    因为学校的课程,本学期对Unity 3D有学习的要求,在博客中记录下自己的Unity学习之路(内容摘录自书本和视频,书本为Unity 4.x从入门到精通) 一.Unity界面介绍      首先进入U ...

  6. Unity 3D学习笔记(5)物体的碰撞/触发检测

    前言 在学习了刚体组件后,我们看到了物体的物理效果,比如重力,碰撞等等,那么该如何检测他们之间的碰撞呢? 在Unity中,有碰撞检测和触发检测两种类型,他们的简单概括如下: 一.碰撞检测 1.如何使用 ...

  7. PD 过河游戏智能帮助实现

    github传送门 https://github.com/ddghost/unity3d_n/tree/R%26D%E6%99%BA%E8%83%BD%E5%B8%AE%E5%8A%A9%E6%8F% ...

  8. Unity 3d 学习笔记

    如何应用左手法则决定视图,应用 Field of view 设置场景大小. 1.场景视图的右上角是场景Gizmo,这个显示场景相机的当前方向,并允许你迅速修改视图角度,Unity中默认是左手法则视图. ...

  9. 3D游戏编程与设计 PD(牧师与恶魔)过河游戏智能帮助实现

    3D游戏编程与设计 P&D(牧师与恶魔) 过河游戏智能帮助实现 文章目录 3D游戏编程与设计 P&D(牧师与恶魔) 过河游戏智能帮助实现 一.作业与练习 二.设计简述 1. 状态图基础 ...

最新文章

  1. SELECT 语句与其子句的详解
  2. Jquery v1.3.2 与v1.4.2在andSelf()函数方面的区别
  3. MNN编译android
  4. leetcode算法题--可获得的最大点数
  5. 最全面实用的MySql操作大全。
  6. Tomcat启动脚本startup.sh分析
  7. Linux 创建桌面应用程序图标 (Ubuntu 18.04 16.04、Linux Mint、Deepin、等均适用 )
  8. CodeForces - 1118F1 Tree Cutting (Easy Version)(树形dp)
  9. mysql中的extract()函数
  10. 【STC15库函数上手笔记】3、外部中断
  11. [HNOI2007] 分裂游戏
  12. 【转发】Git本地服务器搭建及使用详解
  13. Spring(16)——使用注解进行bean定义
  14. linux 测试网络端口通不通_能否使用一台矢量网络分析仪来控制多台 E5092A 以增加测试端口数?...
  15. VC与VS的版本对应关系,VC到底是什么?为啥总提示缺少VC
  16. ubuntu12.10 使用lync
  17. 3D图库框架范围与示例
  18. html a标签鼠标聚焦,html怎么实现鼠标悬停提示A标签内容
  19. 2. evaluate-reverse-polish-notation
  20. 设计模式笔记--访问者模式

热门文章

  1. SendMessage消息是否进入消息队列
  2. Java实现导出excel对重复数据进行单元格合并
  3. 计算机视觉英文介绍_计算机视觉介绍
  4. 软件测试转ba需求分析工程师,软件测试工程师考核标准
  5. 倍加福NJ50-FP-N-P1电感式传感器
  6. 计算机用户卡序列号查询系统,MRT用户中心
  7. 斋藤康毅-深度学习入门 学习笔记二
  8. H264/AVC-帧内预测
  9. 校园虚拟服务器设备配置规划,高校数字化校园高性能虚拟化服务器平台建设规划.doc...
  10. Creo 钣金设计视频教程