3D游戏作业十 · 游戏智能
3D游戏设计与实现十 · 游戏智能
在本章我们学习了游戏智能的相关知识,因此本次的作业主要是将这一知识在我们的游戏中进行应用。本次的作业主要有三个,我们任选一即可。
-
有趣
AR
小游戏制作 -
坦克对战游戏
AI
设计
从商店下载游戏:“Kawaii” Tank
或 其他坦克模型,构建 AI 对战坦克。具体要求
-
使用
感知-思考-行为
模型,建模AI
坦克 -
场景中要放置一些障碍阻挡对手视线
-
坦克需要放置一个矩阵包围盒触发器,以保证
AI
坦克能使用射线探测对手方位 -
AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
-
实现人机对战
-
P&D
过河游戏智能帮助实现,程序具体要求:- 实现状态图的自动生成
- 讲解图数据在程序中的表示方法
- 利用算法实现下一步的计算
遇事不决最后一个,正好老师这边也给了非常详细的师兄博客,而且我们之前已经实现过简易版本和复杂版本的priests and Devils
,所以我们本次就选择实现第三个游戏,直接在之前简易版本的作业上面给他加上智能自动操作。
为了后面的便于理解,我们将实现状态图的自动生成与讲解图数据在程序中的表现方法调换一个顺序。
一、图数据在程序中的表示方法
我们最后的图会有16个状态节点,而状态图的边我们可以将其归为五类:
- P
- D
- PP
- DD
- PD
我们使用邻接矩阵来进行存储。
public enum Vertex {
P3D3B, P2D2, P3D2, P3D1, P3D2B,P3D1B, P3D0, P1D1, P2D2B, P0D2,P0D3B, P2D1B, P0D1, P1D1B, P0D2B, P0D0
};
public enum Edge { P, D, PP, DD, PD, NONE};
private Edge[,] graph = new Edge[16, 16];
private bool[] isVisited = new bool[16]; //节点是否访问过
private int[] parentIndex = new int[16]; //父节点的index,用于回溯
二、状态图的自动生成
为了方便后续程序的设计,我们将15个状态从左到右、从上到下暂时命名为0 ~15。事实上就是给定两个状态点间的Edge
是什么。
public AINext()
{
for (int i = 0; i < 16; i++)
{
parentIndex[i] = -1;
isVisited[i] = false;
for (int j = 0; j < 16; j++)
{
graph[i, j] = Edge.NONE;
}
}
graph[0, 1] = Edge.PD;
graph[0, 2] = Edge.D;
graph[0, 3] = Edge.DD;
graph[1, 0] = Edge.PD;
graph[1, 4] = Edge.P;
graph[2, 0] = Edge.D;
graph[3, 0] = Edge.DD;
graph[3, 4] = Edge.D;
graph[4, 1] = Edge.P;
graph[4, 3] = Edge.D;
graph[4, 6] = Edge.DD;
graph[5, 6] = Edge.D;
graph[5, 7] = Edge.PP;
graph[6, 4] = Edge.DD;
graph[6, 5] = Edge.D;
graph[7, 8] = Edge.PD;
graph[7, 5] = Edge.PP;
graph[8, 7] = Edge.PD;
graph[8, 9] = Edge.PP;
graph[9, 8] = Edge.PP;
graph[9, 10] = Edge.D;
graph[10, 9] = Edge.D;
graph[10, 12] = Edge.DD;
graph[11, 12] = Edge.PP;
graph[12, 10] = Edge.DD;
graph[12, 11] = Edge.PP;
graph[12, 13] = Edge.P;
graph[12, 14] = Edge.D;
graph[13, 12] = Edge.P;
graph[13, 15] = Edge.PD;
graph[14, 12] = Edge.D;
graph[14, 15] = Edge.DD;
graph[15, 14] = Edge.DD;
graph[15, 13] = Edge.PD;
}
三、利用算法实现下一步的计算
sense-think-act
(感知-思考-行为)范式是我们日常构造agent
、robot
、NPC
的基础概念。自从上世纪80年代提出这个概念以来,我们通常使用这个范式来思考机器人如何工作,并且涉及它们。即使机器人最终设计的方式不一样,但是用这种方式,往往是一个开端。
- 感知(Sense):是
agent
接收感知世界信息的行为,其获取的数据将是思考的输入。- 思考(Think):思考其实就是算法,它的输入是感知的数据,输出是行为(behaviours)。思考的算法,通常就是我们所说的游戏规则的一部分,即
agent
能做什么,该做什么。- 行动(Act):行动就是将思考的结果作为输入,该部分的任务就是使得
agent
的行为更加符合物理世界的规律,使得"心想事成"这样理想的结果变得不确定。
我们这次就直接在作业二——简易牧师过河的基础上直接进行改造,添加一个感知-思考-行为
的模式,具体来说就是我们会根据两岸的牧师/魔鬼数目以及船所在的位置来给出一个下一步可行的办法,如果下一步可行的办法有多个的话,就通过概率选择一个。
我们确定下一步可行方案的程序是:
//根据两岸的P/D数目以及船只方向确定下一步动作方案
public void getNextBoatAction()
{
isAuto = true;
if (priest_right.Count == 3 && devil_right.Count == 3)
{
check();
}
else if (boatPos == 1 && priest_left.Count == 3 && devil_left.Count == 3)
{
float num = Random.Range(0, 1f);//如果有多种可能,我们就直接range随机选择一个
if (num < 0.5f)
{
devilOn();
devilOn();
}
else
{
priestOn();
devilOn();
}
}
else if (boatPos == 2 && priest_left.Count == 2 && devil_left.Count == 2)
{
priestOn();
}
else if (boatPos == 2 && priest_left.Count == 3 && (devil_left.Count == 2 || devil_left.Count == 1 || devil_left.Count == 0))
{
devilOn();
}
else if (boatPos == 1 && priest_left.Count == 3 && devil_left.Count == 2)
{
devilOn();
devilOn();
}
else if (boatPos == 1 && priest_left.Count == 3 && devil_left.Count == 1)
{
priestOn();
priestOn();
}
else if (boatPos == 2 && priest_left.Count == 1 && devil_left.Count == 1)
{
priestOn();
devilOn();
}
else if (boatPos == 1 && priest_left.Count == 2 && devil_left.Count == 2)
{
priestOn();
priestOn();
}
else if (boatPos == 2 && priest_left.Count == 0 && devil_left.Count == 2)
{
devilOn();
}
else if (boatPos == 1 && priest_left.Count == 0 && devil_left.Count == 3)
{
devilOn();
devilOn();
}
else if (boatPos == 2 && priest_left.Count == 0 && devil_left.Count == 1)
{
float num = Random.Range(0, 1f);
if (num < 0.5f)
{
priestOn();
}
else
{
devilOn();
}
}
else if (boatPos == 1 && priest_left.Count == 2 && devil_left.Count == 1)
{
priestOn();
}
else if (boatPos == 1 && priest_left.Count == 0 && devil_left.Count == 2)
{
devilOn();
devilOn();
}
else if (boatPos == 1 && priest_left.Count == 1 && devil_left.Count == 1)
{
priestOn();
devilOn();
}
targetDir = boatPos;
}
然后我们就可以开始自动移动船只和自动让角色下船了,因为篇幅原因,我们只介绍自动移动船只,简单来说,我们需要判断是否可以自动移动船只,如果可以的话,我们就直接调用move
函数就可以了。
public bool autoMove()//判断何时开始移动小船
{
if (isAuto)//如果可以的话你要开启自动
{
if (boatPos == 1)
{
if (Boat[0] != null && Boat[1] != null)
{
if (Boat[0].transform.position.x == -5.2f)
if(Boat[1].transform.position.x == -4.6f)
{
return true;
}
}
else if (Boat[0] != null && Boat[1] == null)
{
if (Boat[0].transform.position.x == -5.2f)
return true;
}
else if (Boat[1] != null && Boat[0] == null)
{
if (Boat[1].transform.position.x == -4.6f)
return true;
}
return false;
}
else if (boatPos == 2)
{
if (Boat[0] != null && Boat[1] != null)
{
if (Boat[0].transform.position.x == -0.3f && Boat[1].transform.position.x == 0.2)
return true;
}
else if (Boat[0] != null && Boat[1] == null)
{
if (Boat[0].transform.position.x == -0.3f)
return true;
}
else if (Boat[1] != null && Boat[0] == null)
{
if (Boat[1].transform.position.x == 0.2f)
return true;
}
return false;
}
return false;
}
}
最后的页面设计图如下: