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算法應用到遊戲智能尋路中,算是基本完成。

只是在本文的實現的過程中,用到了大量的鄰接矩陣,而且這些鄰接矩陣爲稀疏矩陣,並且每增加一個可以導航的點,就需要矩陣的長寬就要增加一,非常浪費內存。所以其實可以將鄰接矩陣換成鄰接鏈表的形式,以此來減少稀疏矩陣的浪費的內存。
而且本文中的算法只能進行直線的路徑導航,並不能進行斜線的路徑導航,畢竟兩點之間直線最短,由此該算法其實並不能算是優秀的算法。之後的優化路徑就可以從減少內存消耗和尋找更短更優的路徑。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章