【Unity 3D学习笔记】PD 过河游戏智能实现
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 过河游戏智能实现相关推荐
- Unity学习之PD 过河游戏智能帮助实现
Unity学习之P&D 过河游戏智能帮助实现 根据之前设计好的动作分离版过河游戏,我们进行一个简单的状态图AI实现. 转移状态图 状态图老师已经给出: 该状态图只记录了游戏过程中左岸的情况.P ...
- unity:PD 过河游戏智能帮助实现
P&D 过河游戏智能帮助实现 github传送门 状态图 状态图课件有 状态图(Statechart Diagram)是描述一个实体基于事件反应的动态行为,显示了该实体如何根据当前所处的状态对 ...
- 3D游戏编程实践——PD 过河游戏智能帮助实现
P&D 过河游戏智能帮助实现 需求 实现状态图的自动生成 讲解图数据在程序中的表示方法 利用算法实现下一步的计算 实现过程 实现状态图的自动生成&讲解图数据在程序中的表示方法 牧师与魔 ...
- Unity3d入门之路-PD 过河游戏智能帮助
文章目录 P&D 过河游戏智能帮助 状态图 实现方法 图的表示方法 广度优先搜索 P&D 过河游戏拓展 结果展示 P&D 过河游戏智能帮助 本次作业基本要求是三选一,我选择了P ...
- Unity 3D学习笔记之一 界面介绍
因为学校的课程,本学期对Unity 3D有学习的要求,在博客中记录下自己的Unity学习之路(内容摘录自书本和视频,书本为Unity 4.x从入门到精通) 一.Unity界面介绍 首先进入U ...
- Unity 3D学习笔记(5)物体的碰撞/触发检测
前言 在学习了刚体组件后,我们看到了物体的物理效果,比如重力,碰撞等等,那么该如何检测他们之间的碰撞呢? 在Unity中,有碰撞检测和触发检测两种类型,他们的简单概括如下: 一.碰撞检测 1.如何使用 ...
- 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% ...
- Unity 3d 学习笔记
如何应用左手法则决定视图,应用 Field of view 设置场景大小. 1.场景视图的右上角是场景Gizmo,这个显示场景相机的当前方向,并允许你迅速修改视图角度,Unity中默认是左手法则视图. ...
- 3D游戏编程与设计 PD(牧师与恶魔)过河游戏智能帮助实现
3D游戏编程与设计 P&D(牧师与恶魔) 过河游戏智能帮助实现 文章目录 3D游戏编程与设计 P&D(牧师与恶魔) 过河游戏智能帮助实现 一.作业与练习 二.设计简述 1. 状态图基础 ...
最新文章
- SELECT 语句与其子句的详解
- Jquery v1.3.2 与v1.4.2在andSelf()函数方面的区别
- MNN编译android
- leetcode算法题--可获得的最大点数
- 最全面实用的MySql操作大全。
- Tomcat启动脚本startup.sh分析
- Linux 创建桌面应用程序图标 (Ubuntu 18.04 16.04、Linux Mint、Deepin、等均适用 )
- CodeForces - 1118F1 Tree Cutting (Easy Version)(树形dp)
- mysql中的extract()函数
- 【STC15库函数上手笔记】3、外部中断
- [HNOI2007] 分裂游戏
- 【转发】Git本地服务器搭建及使用详解
- Spring(16)——使用注解进行bean定义
- linux 测试网络端口通不通_能否使用一台矢量网络分析仪来控制多台 E5092A 以增加测试端口数?...
- VC与VS的版本对应关系,VC到底是什么?为啥总提示缺少VC
- ubuntu12.10 使用lync
- 3D图库框架范围与示例
- html a标签鼠标聚焦,html怎么实现鼠标悬停提示A标签内容
- 2. evaluate-reverse-polish-notation
- 设计模式笔记--访问者模式