图论期末论文
摘要
游戏中的AI控制角色进行寻路操作,一直都是一个比较困难的问题,为此从事游戏行业的很多从业者们也提出了不少的解决方案,诸如A算法,B算法,以及广度优选寻路算法,深度优选寻路算法,还有就是本文中使用的Dijkstra算法。本文主要就是讲述了通过将游戏中的位置信息化为一个图,应用Dijstra算法到游戏智能寻路中,解决游戏中AI控制角色选取前往目标位置最短最优的路径问题。
关键词:游戏智能,Dijkstra,寻路算法
目录
一 介绍 4
二 Dijkstra算法 4
三 在游戏中的应用 6
四 总结 13
参考文献 15
一 介绍
游戏智能寻路就是希望能够使游戏中的AI通过初始点和目标点间的路径搜索[1],计算出一条路径。并且这条路径必须能够躲避障碍物,最好能够尽可能的短与合适。
寻路算法是游戏人工智能的重要组成部分[4],其在实际的游戏应用,从根本深化游戏场景的真实性及游戏的可玩性[2]。但并不能直接使用游戏数据生成游戏路径,而且需要先将其转换为一种特别的数据结构:有向非负权重图。于是游戏智能寻路的问题就转换为图论的问题。
图其本身是一个数学概念,由两种部分组成:节点和线。以此我们通常由点来表示游戏关卡中的一个区域,由线来连接两个区域,表示可通行。
权重图则是在图的基础上,在每条线上加了一个权重值,,而在游戏中它通常被称作花费。寻路图中的花费通常代表时间或者距离。不过也可能是时间和距离的混合或者其他因素。
二 Dijkstra算法
Dijkstra算法是求从一点到网络其它各点之间最短路的重要算法[3]。而在这里就可以将其运用起来,使用其找出从起始点到任意其他位置的最短路径(包括目标点)。
Dijkstra算法从起始点向它的四周进行扩散,随着它向更远的节点扩散,它记录前面的节点,最终将抵达目标点并根据记录来生成完整的路径。
下面开始演示Dijstra算法的计算过程。
如有1,2,3,4四个节点,其节点,线,权重如图2-1:
图2-1
以下是使用Dijstra算法计算1到所有点的最短路径的过程(无穷大由@表示):
次数
节点 1 2 3
1 @ @ @
2 2 @ @
3 6 5 5
4 4 4 @
路径 {1->2} {1->4} {1->2->3}
第一次迭代,直接从节点1开始寻找其指向的节点,在将去往该节点的权重填到与之对应的方框中,如果不能去往该点的路径则用无穷大填入与之对应的方框中。然后再比较第一次迭代中每一个节点的权重,由此得出从节点1到节点2的最短的路径为{1->2},权重为2。
第二次迭代,在上一次迭代的基础上,先将不能去往该点的路径和已经找到最短路径的节点对应位置的方框中填入无穷大。再以当前的覆盖的节点向四周发散,寻找最短的路径。由此发现由{1->2->3}的路径的权重为5,比原来直接由1->2的权重值要小,于是便将该权重值写入与3对应的方框中。最后比较第二次迭代中剩余的每个节点的权重值,由此得出从节点1到节点4的最短路径为{1->4},权重为4。
第三次迭代,由于本图只有四个节点,所以本次迭代为最后一次迭代。在前面的基础上,再以当前覆盖的节点向四周发散,寻找更短的路径。可是并没能发现更短的路径,而且可以选择的节点也只剩下节点,所以由此得出从节点1到节点3的最短的路径为{1->2->3},权重为5。
最后,得出了从节点1到每一个节点的最短路径,对于其他节点更多的图,同样可以由这些步骤解答问题。
三 在游戏中的应用
将Dijkstra算法应用到游戏智能寻路中具体的应用思路是:
1先通过把地图添加虚拟的横竖相交的直线做成类似棋盘网格的形式的规格网络,将两直线的交点或者棋盘格的中心作为一个节点[5]。并且AI控制的角色只能进行向上走,向下走,向左走,向右走这四个邻近自身的操作,所以该图的邻接矩阵只在每个节点的相邻四个方向上进行赋值,并且超出节点个数范围的不给予赋值,其他的节点赋值为无穷大。
2由于还需要AI控制角色完成对于碰撞体的绕行,因此将碰撞体所在位置的邻接矩阵行列全部置为无穷大,也就是断掉连接碰撞体的线,以此来达到绕行的操作。
本文中主要是使用Unity以及C#语言来进行模拟游戏智能寻路的实现。
其中的Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。
其中的C#语言是微软公司发布的一种面向对象的、运行于.NET Framework和.NET Core(完全开源,跨平台)之上的高级程序设计语言。
接下来是实现游戏智能寻路的具体过程。
第一步,在Hierarchy面板中鼠标右键->3Dobject->Plane,创建一个Plane,并调整Plane的Inspector面板中的Scale,将其中的X和Z改为4。
第二步,在Hierarchy面板中鼠标右键->3Dobject->Cube,创建三个Cube,分别取名Player,Wall,Target。将Player的Inspector面板中的Position属性调整为0,0,0。将Target的Inspector面板中的Position属性调整为2,0,0。Wall则将其拖出相机的视野。
第三步,调整Hierarchy中的Main Camera的Inspector面板中的Rotation属性中的x为90,然后调整摄像机正对玩家。
第四步,在Assets面板中鼠标右键->Create->Material,创建三个Material,将三个Material的名字分别改为black,red,green。然后将black中的Albedo调整为黑色,red中的Albedo调整为红色。将green中的Albedo调整为绿色,并将green的透明度调整为100,Rendering Mode调整为Fade。
第五步,将名为black的Material拖拽到Player上,名为red的Material拖拽到Wall上,名为green的Material拖拽到Wall上,然后就得到了如图3-1的Game视图,Hierarchy视图如图3-2:
。
图3-1
图3-2
第六步,在Assets面板中鼠标右键->Create->C#script,取名为Dijstra,然后编辑该代码,具体的实现代码如代码块3-1。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Dijstra : MonoBehaviour
{
const int dotNum = 25;//点的个数
static int[,] graph = new int[dotNum, dotNum];//图
static int[] S = new int[dotNum];//最短路径的顶点集合
public GameObject cube;//预制体
const int infinity = 60000;//无穷
static int[,] positions = new int[dotNum, dotNum];//路径
static int[] walls = new int[dotNum];//墙
int endPos = 2;//结束位置
int startPos=0;//开始位置
public static int IsContain(int m)//判断元素是否在mst中
{
int index = -1;
for (int i = 1; i < S.Length; i++)
{
if (S[i] == m)
{
index = i;
}
}
return index;
}
/// <summary>
/// Dijkstrah实现最短路算法
/// </summary>
static void ShortestPathByDijkstra(int startPos, int endPos)
{
int min;
int next;
for (int f = S.Length; f > 0; f--)
{
//置为初始值
min = 1000;
next = 0;//第一行最小的元素所在的列 next点
//找出第一行最小的列值
for (int j = 1; j < S.Length; j++)//循环第0行的列
{
if ((IsContain(j) == -1) && (graph[startPos, j] < min))//不在S中,找出第一行最小的元素所在的列
{
min = graph[startPos, j];
next = j;
}
}
//将下一个点加入S
S[next] = next;
if (next == endPos)
{
break;
}
// 重新初始start行所有列值
for (int j = 1; j < S.Length; j++)//循环第start行的列
{
if (IsContain(j) == -1)//初始化除包含在S中的
{
if ((graph[next, j] + min) < graph[startPos, j])//如果小于原来的值就替换
{
graph[startPos, j] = graph[next, j] + min;
int k = 0;
for (k = 0; positions[next, k] != 0; k++)
{
positions[j, k] = positions[next, k];
}
positions[j, k] = next;//路径
}
}
}
}
}
static void SetInfinity(int index)//设置为无穷大
{
for (int i = 0; i < S.Length; i++)
{
graph[index, i] = infinity;
graph[i, index] = infinity;
}
walls[index] = infinity;
}
// Start is called before the first frame update
void Start()
{
//初始化
for (int i = 0; i < S.Length; i++)
{
S[i] = 0;
walls[i]= 0;
for (int j = 0; j < S.Length; j++)
{
graph[i, j] = infinity;
positions[i, j] = 0;
}
Random ran = new Random();
if (i - 1 > 0&& i % Mathf.Sqrt(dotNum) != 0)
graph[i, i - 1] = Random.Range(0,50) ;
if (i + 1 < 20 && i % Mathf.Sqrt(dotNum) != 4)
graph[i, i + 1] = Random.Range(0, 50);
if (i - 5 > 0)
graph[i, i - 5] = Random.Range(0, 50);
if (i + 5 < 20)
graph[i, i + 5] = Random.Range(0, 50);
}
SetInfinity(1);//设置墙体
SetInfinity(6);
SetInfinity(11);
for (int i = 0; i <dotNum; i++)
{
if(walls[i]==infinity)
{
Instantiate(cube, new Vector3((int)i % 5,0,i / 5 ), Quaternion.identity);
Debug.Log(new Vector3((int)i / 5, 0, i % 5));
}
}
for (int i = 0; i < Mathf.Sqrt(dotNum)+2; i++)
{
for (int j = 0; j < Mathf.Sqrt(dotNum)+2; j++)
{
if(i==0||i==Mathf.Sqrt(dotNum)+1||j==Mathf.Sqrt(dotNum)+1||j==0)
{
Instantiate(cube, new Vector3(i-1,0, j-1), Quaternion.identity);
}
}
}
//获取路径
ShortestPathByDijkstra(startPos, endPos);
int k = 0;
for ( k = 0; positions[endPos, k]!=0; k++)
{
Debug.Log(positions[endPos, k]);
}
positions[endPos, k] = endPos;
}
int p = 0;
// Update is called once per frame
void Update()
{
//运动代码
if (Vector3.Distance( transform.position , new Vector3(positions[2, p] % 5,0,positions[2, p]/5 ))>0.05f)
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(positions[2, p] % 5, 0, positions[2, p] / 5), Time.deltaTime);
}
else
{
if(positions[2, p+1]!=0)
{
p = p + 1;
}
}
}
}
代码块3-1
第七步,将创建的Dijstra代码拖拽到Player上,然后再把Wall拖拽到Player的Inspector面板中的Dijstra(Script)组件下的Cube输入框中。
第八步,点击播放按钮,运行游戏,得到如图3-3的一个Game视图
。
图 3-3
其中的黑色方块是Player,红色方块是生成的墙体,绿色半透明方块是Target。代码要做的就是控制Player绕过右边的墙体,以最短最优的路径前往Target。
四 总结
最终的运行效果如图3-4到图3-7:
图3-4 图3-5
图3-6 图3-7
由图3-4到图3-7,可以看出AI控制Player选择了最短,最快的路径。因此将Dijstra算法应用到游戏智能寻路中,算是基本完成。
只是在本文的实现的过程中,用到了大量的邻接矩阵,而且这些邻接矩阵为稀疏矩阵,并且每增加一个可以导航的点,就需要矩阵的长宽就要增加一,非常浪费内存。所以其实可以将邻接矩阵换成邻接链表的形式,以此来减少稀疏矩阵的浪费的内存。
而且本文中的算法只能进行直线的路径导航,并不能进行斜线的路径导航,毕竟两点之间直线最短,由此该算法其实并不能算是优秀的算法。之后的优化路径就可以从减少内存消耗和寻找更短更优的路径。