1. 程式人生 > >unity中的一個簡單易用的A*尋路演算法類

unity中的一個簡單易用的A*尋路演算法類

以前專案中用到了尋路,就寫了個A*尋路的演算法類,感覺還不錯,這裡分享出來。

A*演算法的原理網上有很多,這裡只簡單說一下我這個演算法的邏輯:

*我的這個演算法裡面沒有關閉列表,因為我會根據地圖資料建立一個對應的節點資料的陣列,每個節點資料記錄自己當前的狀態,是開啟還是關閉的。節點資料只有在尋找周圍點被找到的時候才會建立並放到陣列中,這樣在每次新增節點時,只要在陣列中取出對應位置的節點資料,就能知道節點是開啟的還是關閉的或者還沒有被新增,不用去遍歷open和close列表。

*開放列表直接使用List來儲存,為了使列表保持有序,每次新增節點時直接把節點按照從小到大的順序插入到對應位置。這裡可以使用其它資料結構或者排序演算法來進行優化。

*路徑可以行進的方向使用一個數組來表示:

//附近的格子 4方向
    int[,] nearArray = new int[,]{
        {0, 1},
        {1, 0},
        {0, -1},
        {-1, 0},
    };
    //附近的格子 8方向
    int[,] nearArray= new int[,]{
        {0, 1},   
        {1, 1},   
        {1, 0},   
        {1, -1},  
        {0, -1},  
        {-1, -1}, 
        {-1, 0},  
        {-1, 1}   
    };

這裡可以修改成任意個方向和距離,我只對1個距離以內的移動進行過測試。其它距離不能保證正確性。

*G和H值的計算可以使用曼哈頓距離或者歐式距離,曼哈頓距離效率上會更高。

    float GetNodeG(MapNode parent,MapNode node){
        //曼哈頓距離
        float dis = Mathf.Abs(parent.p.x - node.p.x) + Mathf.Abs(parent.p.y - node.p.y);
        //歐式距離
        //float dis = Vector2.Distance(parent.p, node.p);
        return parent.g + dis;
    }

    float GetNodeH( MapNode node)
    {
        //曼哈頓距離
        return Mathf.Abs(endPosition.x - node.p.x) + Mathf.Abs(endPosition.y - node.p.y);
        //歐式距離
        //return Vector2.Distance(endPosition,node.p);
    }

最終的結果只有在4方向時表現上會有些許不同:

   

左圖為曼哈頓距離的結果,右圖為歐式距離的結果,雖然移動距離是一樣的,但是很明顯歐式距離更符合我們的習慣。

*使用方式:

呼叫AStar類的StratAStar方法即可

    /// <summary>
    /// 開始尋路
    /// </summary>
    /// <returns>路徑點資料,從起始點到結束點路徑的有序vector陣列.</returns>
    /// <param name="map">地圖資料 二維陣列,0為可移動路徑,1為不可移動路徑.</param>
    /// <param name="startPosition">開始位置.</param>
    /// <param name="endPosition">結束位置.</param>
    public List<Vector2> StratAStar(int[,] map,Vector2 startPosition,Vector2 endPosition)

*示例:

定義一個二維資料作為我們的地圖資料:

int[,] map = 
    { 
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,1,1,1,1,1,1,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,1,1,1,1,1,1,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    };

設定起始和結束位置:

        Vector2 startPosition = new Vector2(5,6);
        Vector2 endPosition = new Vector2(8, 12);

建立AStar類並呼叫開始方法:

        AStar astar = new AStar();
        List<Vector2> path = astar.StratAStar(map,startPosition,endPosition);

看下效果:


下面使用隨機陣列:

int[, ] map = new int[mapWidth,mapHight];

設定起始點和結束點,然後隨機向陣列中填充資料

        Vector2 startPosition = new Vector2(Random.Range(0, mapWidth),Random.Range(0, mapHight));
        Vector2 endPosition = new Vector2(Random.Range(0, mapWidth), Random.Range(0, mapHight));

        for (int i = 0; i < mapWidth;i ++){
            for (int j = 0; j < mapHight; j++)
            {
                //如果是起點或者終點 跳過
                if((i == (int)startPosition.x && j == (int)startPosition.y) ||
                   (i == (int)endPosition.x && j == (int)endPosition.x) ){
                    continue;
                }
                if (Random.Range(0f, 1f) < 0.2f){
                    map[i, j] = 1;
                }
            }
        }

看下效果:


完整的AStar類:

using System.Collections.Generic;
using UnityEngine;

public class AStar
{
    //節點
    private class MapNode{
        //節點狀態 
        public enum enNodeState{
            normal,//待機
            open,//開啟
            close //關閉
        }

        public MapNode parent = null;//父節點
        public float g, h, f;
        public Vector2 p;//位置
        public enNodeState state = enNodeState.normal;
    }

    //附近的格子 8方向
    int[,] nearArray= new int[,]{
        {0, 1},   
        {1, 1},   
        {1, 0},   
        {1, -1},  
        {0, -1},  
        {-1, -1}, 
        {-1, 0},  
        {-1, 1}   
    };
    //附近的格子 4方向
    //int[,] nearArray = new int[,]{
    //    {0, 1},
    //    {1, 0},
    //    {0, -1},
    //    {-1, 0},
    //};
    Vector2 startPosition, endPosition;//起始點和結束點

    //開放列表,在插入時根據MapNode的f值進行排序,即優先佇列
    List<MapNode> openList = new List<MapNode>();

    //所有點
    MapNode[,] mapList;

    //向開放列表中加入節點,這裡需要進行排序
    void PushNode(MapNode node)
    {
        node.state = MapNode.enNodeState.open;
        for (int i = 0; i < openList.Count; i++){
            if (openList[i].f > node.f){
                openList.Insert(i,node);
                return;
            }
        }
        openList.Add(node);
    }

    //建立節點時即對節點進行估價
    MapNode CreateNode(Vector2 p,MapNode parent){
        MapNode node = new MapNode();
        node.parent = parent;
        node.p = p;

        //f = g+h
        //g和h直接使用曼哈頓距離
        //--------g------
        if(parent != null){
            node.g = GetNodeG(parent,node);
        }else {
            node.g = 0;
        }

        //--------h------
        node.h =GetNodeH(node);

        //--------f------
        node.f = node.g + node.h;

        //建立的節點加入到節點列表中
        mapList[(int)node.p.x,(int)node.p.y] = node;

        return node;
    }

    float GetNodeG(MapNode parent,MapNode node){
        //曼哈頓距離
        //float dis = Mathf.Abs(parent.p.x - node.p.x) + Mathf.Abs(parent.p.y - node.p.y);
        //歐式距離
        float dis = Vector2.Distance(parent.p, node.p);
        return parent.g + dis;
    }

    float GetNodeH( MapNode node)
    {
        //曼哈頓距離
        //return Mathf.Abs(endPosition.x - node.p.x) + Mathf.Abs(endPosition.y - node.p.y);
        //歐式距離
        return Vector2.Distance(endPosition,node.p);
    }

    /// <summary>
    /// 開始尋路
    /// </summary>
    /// <returns>路徑點資料,從起始點到結束點路徑的有序vector陣列.</returns>
    /// <param name="map">地圖資料 二維陣列,0為可移動路徑,1為不可移動路徑.</param>
    /// <param name="startPosition">開始位置.</param>
    /// <param name="endPosition">結束位置.</param>
    public List<Vector2> StratAStar(int[,] map,Vector2 startPosition,Vector2 endPosition){

        mapList = new MapNode[map.GetLength(0),map.GetLength(1)];

        //附近可移動點的數量
        int nearcount = nearArray.GetLength(0);

        this.startPosition = startPosition;
        this.endPosition = endPosition;

        //起始點加入開啟列表
        MapNode startNode = CreateNode(startPosition,null);
        PushNode(startNode);

        MapNode endNode = null;//目標節點

        //開始尋找路徑
        while(openList.Count > 0){
            //取出開啟列表中f值最低的節點,由於我們在向開啟列表中新增節點時已經進行了排序,所以這裡直接取第0個值即可
            MapNode node = openList[0];
            //如果node為目標點則結束尋找
            if(node.p.x == endPosition.x && node.p.y == endPosition.y){
                endNode = node;
                break;
            }

            //設定為關閉狀態並從開啟列表中移除
            node.state = MapNode.enNodeState.close;
            openList.RemoveAt(0);

            //當前點座標
            //相鄰格子加入到開啟列表
            for (int i = 0; i < nearcount; i ++){
                Vector2 nearPosition = node.p - new Vector2(nearArray[i,0], nearArray[i,1]);

                //位置是否超出範圍
                if((nearPosition.x < 0 || nearPosition.x >= map.GetLength(0)) ||
                   (nearPosition.y < 0 || nearPosition.y >= map.GetLength(1))){
                    continue;
                }
                //該位置是否可以移動
                if(map[(int)nearPosition.x,(int)nearPosition.y] != 0){
                    continue;
                }

                //是否已經建立過這個點
                MapNode nearNode = mapList[(int)nearPosition.x, (int)nearPosition.y];
                if(nearNode != null){
                    //該節點已經建立過

                    //節點是否關閉
                    if(nearNode.state == MapNode.enNodeState.close){
                        continue;
                    }

                    //重新計算g
                    float newg = GetNodeG(node, nearNode);
                    if(newg < nearNode.g){
                        nearNode.parent = node;
                        nearNode.g = newg;
                        nearNode.f = nearNode.g + nearNode.h;

                        Debug.Log("==" + openList.Count);
                        //重新對開放列表排序
                        openList.Remove(nearNode);
                        Debug.Log(openList.Count);
                    }else{
                        continue;
                    }
                }else{
                    //建立節點
                    nearNode = CreateNode(nearPosition, node);
                }

                PushNode(nearNode);
            }
        }

        //路徑資料
        List<Vector2> ret = new List<Vector2>();

        if (endNode == null)
        {
            Debug.Log("no path!");
            return ret;
        }

        //將路徑儲存到陣列中
        while(endNode.parent != null){
            ret.Insert(0,endNode.p);
            endNode = endNode.parent;
        }

        return ret;
    }
}

相關推薦

unity一個簡單A*演算法

以前專案中用到了尋路,就寫了個A*尋路的演算法類,感覺還不錯,這裡分享出來。A*演算法的原理網上有很多,這裡只簡單說一下我這個演算法的邏輯:*我的這個演算法裡面沒有關閉列表,因為我會根據地圖資料建立一個對應的節點資料的陣列,每個節點資料記錄自己當前的狀態,是開啟還是關閉的。節

一個簡單的Http訪問工具for Android

    前言    去年(2017)參加服務外包省賽的時候,負責App開發的我遇到了一個小難題——Http請求。雖說已經有成熟的HttpUrlConnection庫供使用,但依然感到有些不方便:進行一次簡單的請求並對結果進行處理,需要敲下不少程式碼;多次請求之間其實有很多重複

php 一個簡單的資料庫---Medoo

參照: https://packagist.org/packages/catfan/medoo https://medoo.in/doc php關於資料庫操作的工具類有很多, 現在介紹一種相容性較好,比較簡單,上手容易的sql類,Medoo。 對於一些小專案來說完全夠用。 使用M

vue-lazyload :一個簡單的 Vue 圖片延遲載入外掛

介紹: vue-lazyload主要應用於圖片延遲載入。包含如下的特點: 小巧輕便,功能強大,易於使用 可以用於載入任何影象型別 支援Vue 1.0和Vue 2.0 理解圖片延遲載入: 當某個網頁中呈現的圖片較多時,由於網路等原因,訪問該網頁時,所有的圖片不會立馬全部

如何實現一個簡單且可靠的訊息佇列框架?

作者:李豔鵬 編輯:Gary 訊息佇列在網際網路領域裡得到了廣泛的應用,它多應用在非同步處理、模組之間的解偶和高併發的消峰等場景,訊息佇列中表現最好的當屬Apache開源專案Kafka,Kafka使用支援高併發的Scala語言開發,利用作業系統的快取原理達到高效能,並且天生具有可分割槽,分散式的特

easyopen——一個簡單的介面開放平臺

easyopen介紹一個簡單易用的介面開放平臺,平臺封裝了常用的引數校驗、結果返回等功能,開發者只需實現業務程式碼即可。easyopen的功能類似於淘寶開放平臺,它的所有介面只提供一個url,通過引數來區分不同業務。這樣做的好處是介面url管理方便了,平臺管理者只需維護好介面引數即可。由於引數的數量是可知的,

分享一個簡單的python並行模組【PP模組】

目前個人計算機大都是多核的,但是在執行python程式的時候會發現實際上只有一個核心(CPU)在跑程式碼,另外幾個核心都在偷懶呢,如下圖  平行計算的目的是將所有的核心都執行起來以提高程式碼的執行速度,在python中由於存在全域性直譯器鎖(GIL)如果使用預設的python多執行緒進行平行計算可能會發現程

頭像截圖上傳兩種方式(SWFUpload、一個簡單的flash外掛)

1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http:

FineBI:一個簡單的自助BI工具

過去,有關企業資料分析的重擔都壓在IT部門,傳統BI分析更多面向的是具有IT背景的人員。但隨著業務分析需求的增加,很多公司都希望為業務使用者提供自助分析服務,將分析工作落實到業務人員手中。但同時,分析工

一個簡單的Linux文字編輯器:nano的安裝與使用

許多狀況下咱們都必要編纂雲主機裡的文字情節,而現時用的比力多的文字編纂器是vim,這個估量許多人都以為很難用,這邊伏筆VPS引薦一

淺談遊戲A*演算法

本文為銳亞教育原創文章,轉載請註明轉載自銳亞教育 A*尋路 A*演算法基本原理 A*(唸作A星)演算法對初學者來說的確有些難度。本文並不試圖對這個話題作權威性的陳述,取而代之的是描述演算法的原理,使你可以在進一步的閱讀中理解其他相關的資

一個高效的a *演算法(八方向)

http://blog.csdn.net/onafioo/article/details/41089579 這種寫法比較垃圾,表現在每次搜尋一個點要遍歷整個地圖那麼大的陣列,如果地圖為256 * 256,每次搜尋都要執行65535次,如果遍歷多個點就是n * 65

A*演算法的C++簡單實現

2007/8/13 17:26:59 #include <iostream> #include <ctime> #include <list> #include <algorithm> #include <cassert&

Html5遊戲框架Craftyjs入門簡單RPG及A*

Crafty.js是一個比較簡單輕量的Html5遊戲框架,個人比較喜歡,是因為它足夠簡便(如果你只需要製作簡單的小遊戲,例如微信h5中的各種遊戲)。 遺憾的是,Crafty.js的社群活躍的人越來越少,文件和新的版本也對不上號,所以有的API只能是從原始碼中獲取使用方法了。

Unity A*演算法(人物移動AI

最近閒來無事,從網上看了一些大神的心得,再融合自己的體會,寫了一個簡單點的尋路演算法,廢話不多說,直接上程式碼 <Grid> usingUnityEngine; usingSystem.Collections; usingS

A*演算法C++簡單實現

參考文章: A*尋路演算法是遊戲中常用的AI演算法,這裡用C++簡單實現了一下演算法,便於理解。 搜尋區域 如圖所示簡易地圖, 其中綠色方塊的是起點 (用 A 表示), 中間藍色的是障礙物, 紅色的方塊 (用 B 表示) 是目的地. 為了可以用一個二

理解A*演算法過程

   這兩天研究了下 A* 尋路演算法, 主要學習了這篇文章, 但這篇翻譯得不是很好, 我花了很久才看明白文章中的各種指代. 特寫此篇部落格用來總結, 並寫了尋路演算法的程式碼, 覺得有用的同學可以看看. 另外因為圖片製作起來比較麻煩, 所以我用的是原文裡的圖片. &n

A*演算法補充知識

A*演算法 輔助知識 unity視覺化輔助工具Gizmos Gizmos.color =Color.red;//設定畫筆的顏色 Gizmos.DrawWireCube(位置,Vector3(長寬高));//畫出一個範圍框: Gizmos.DrawCube(位置,三維屬性長寬高); /

A*演算法之解決目標點不可達問題

在遊戲世界的尋路中,通常會有這樣一種情況:在小地圖上點選目標點時,點選到了障礙物或者建築上,然後遊戲會提示我們目標地點無法到達。玩家必須非常小心的在小地圖上點選目標區域的空白部分,才能移動到目標地點。那麼,有沒有辦法來改進一下這種不友好的體驗呢? 下面給出兩種方法: 最近

A*演算法的優化與改進

提要通過對上一篇A*尋路演算法的學習,我們對A*尋路應該有一定的瞭解了,但實際應用中,需要對演算法進行一些改進和優化。 Iterative Deepening Depth-first search- 迭代深化深度優先搜尋在深度優先搜尋中一個比較坑爹情形就是在搜尋樹的一枝上沒有