Dijkstra算法在游戏智能寻路中的应用

图论期末论文

摘要
游戏中的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算法应用到游戏智能寻路中,算是基本完成。

只是在本文的实现的过程中,用到了大量的邻接矩阵,而且这些邻接矩阵为稀疏矩阵,并且每增加一个可以导航的点,就需要矩阵的长宽就要增加一,非常浪费内存。所以其实可以将邻接矩阵换成邻接链表的形式,以此来减少稀疏矩阵的浪费的内存。
而且本文中的算法只能进行直线的路径导航,并不能进行斜线的路径导航,毕竟两点之间直线最短,由此该算法其实并不能算是优秀的算法。之后的优化路径就可以从减少内存消耗和寻找更短更优的路径。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
网格路是一种常用的找最短路径的算法,用于在网格中找到起点到终点的最短路径。而Dijkstra算法是其中一种经典的算法,可以在有障碍的网格中找最短路径。 Dijkstra算法的基本思想是,从起点开始,逐步扩展搜索范围,直到找到终点或者无法再找到可行路径为止。在每次扩展时,算法会根据当前节点的距离值和周围节点的权重,更新周围节点的距离值。重复这个过程,直到终点被标记为已访问或者所有可达节点都被访问过。 网格路中的障碍物会给算法带来额外的复杂性。当算法在扩展节点时,需要判断当前节点是否可行,即是否可以通过或绕过障碍物到达。如果当前节点是障碍物,则将其标记为不可行,并且不再对它进行扩展。通过这种方式,Dijkstra算法可以在有障碍的网格中找到最短路径。 在实现Dijkstra算法时,可以使用优先队列(Priority Queue)来存储待扩展的节点,并按照节点的距离值进行排序。这样可以确保每次扩展的节点都是距离起点最近的节点,提高算法的效率和准确性。 总之,Dijkstra算法是一种用于网格路的经典算法,可以在有障碍的网格中找到最短路径。通过不断地扩展搜索范围,根据节点的距离值和权重来更新节点的距离值,算法可以从起点到终点找到一条最短路径。在实现时,可以使用优先队列来提升算法的效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值