草庐IT

Unity中实现A*寻路

军酱不是酱 2023-03-28 原文

前言:最近没事儿没工作,计划每天写一篇博客,防止对Unity生疏,也可以记录学习的点点滴滴。

A*寻路在很多面试里都会问到,但实际工作中根本用不着自己写,网上有成熟的插件,不容易错还方便。

思路:我们将地块切成大小均匀的格子,格子分成普通(可通行)、起点、终点、阻挡类型(不可通行)。每次循环时,查找open列表中综合代价最低的为当前格子,查找当前格子的八个方向(也可以查找四个方向)的邻格,计算综合代价并加入到open列表中去,当前格子就加入到close列表里,并从open列表中移除。当open列表内容为空时,或者当前格子已经为结束点时,结束循环。F代表综合代价,也就是起始距离 + 结束距离 = 综合距离;H代表结束距离(忽视阻挡);G代表起始距离

示例:

第一步,先实现Cell对象

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class Cell : MonoBehaviour, IComparable
{
    private CellType m_type;
    public CellType myType
    {
        get => m_type;
        set
        {
            m_type = value;
            switch (m_type)
            {
                case CellType.Normal:
                    SetColor(Color.white);
                    break;
                case CellType.Start:
                    SetColor(Color.green);
                    break;
                case CellType.End:
                    SetColor(Color.red);
                    break;
                case CellType.Block:
                    SetColor(Color.black);
                    break;
            }
        }
    }
    public Vector2Int pos;//坐标
    public int F, G, H;//综合代价、起始代价、结束代价
    public Cell parent;//父物体,为方便查找上一个节点,类似链表
    private MeshRenderer render;

    private void Awake()
    {
        render = GetComponent<MeshRenderer>();
    }


    public void SetColor(Color color)
    {
        render.material.color = color;
    }

    /// <summary>
    /// 两个cell对象排序,需要实现icompareable接口,指定它俩对比,是指对比F
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public int CompareTo(object obj)
    {
        Cell cell = (Cell)obj;
        if (cell.F > F)
        {
            return -1;
        }
        else if (cell.F == F)
        {
            return 0;
        }
        else
        {
            return 1;
        }
    }
}

第二步,实现查找具体方法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 这就是格子类型
/// </summary>
public enum CellType
{
    Normal = 1,
    Start = 2,
    End = 3,
    Block = 4
}
public class AStar : MonoBehaviour
{
    /// <summary>
    /// 这是生成地图数据
    /// </summary>
    public int[,] map =
    {
        {2, 1, 4, 1, 1 },
        {1, 1, 4, 1, 1 },
        {1, 1, 4, 1, 1 },
        {1, 1, 4, 1, 1 },
        {1, 1, 1, 1, 3 }
    };
    /// <summary>
    /// 预制体
    /// </summary>
    public GameObject prefab;

    /// <summary>
    /// 所有格子对象
    /// </summary>
    private Cell[,] cells;

    /// <summary>
    /// 开始点坐标和结束点坐标
    /// </summary>
    private Vector2Int startPos, endPos;

    /// <summary>
    /// open列表和close列表
    /// </summary>
    private List<Cell> openList, closeList;
    /// <summary>
    /// 存储路径的栈
    /// </summary>
    private Stack<Cell> path;


    private void Start()
    {
        cells = new Cell[map.GetLength(0), map.GetLength(1)];
        openList = new List<Cell>();
        closeList = new List<Cell>();
        path = new Stack<Cell>();

        //生成地图
        Vector3 pos = Vector3.zero;
        for (int i = 0; i < map.GetLength(0); i++)
        {
            for (int j = 0; j < map.GetLength(1); j++)
            {
                pos.Set(i * 1.5f, 0, j * 1.5f);
                GameObject obj = GameObject.Instantiate<GameObject>(prefab);
                obj.transform.parent = transform;
                obj.transform.position = pos;
                Cell cell = obj.AddComponent<Cell>();
                CellType temp = (CellType)map[i, j];
                if (temp == CellType.Start)
                {
                    startPos.Set(i, j);
                }
                else if (temp == CellType.End)
                {
                    endPos.Set(i, j);
                }
                cell.myType = temp;
                cell.pos.Set(i, j);
                cells[i, j] = cell;
            }
        }
    }

    private void Update()
    {
        if (Input.GetMouseButton(0))
        {
            Find();
            StartCoroutine(Draw());
        }
    }

    private void Find()
    {
        openList.Add(cells[startPos.x, startPos.y]);
        Cell currentCell = openList[0];
        while (openList.Count > 0 && currentCell.myType != CellType.End)
        {
            //通过排序找到综合代价最小的
            openList.Sort();
            currentCell = openList[0];

            //这里已经找到了
            if (currentCell.myType == CellType.End)
            {
                while (currentCell.parent != null)
                {
                    if (currentCell.parent.myType != CellType.Start)
                    {
                        path.Push(currentCell.parent);
                    }
                    currentCell = currentCell.parent;
                }
                return;
            }

            //查找八个邻格
            for (int i = -1; i <= 1; i++)
            {
                for (int j = -1; j <= 1; j++)
                {
                    //增量为0,代表这个坐标指自己,所以直接跳过
                    if (i == 0 && j == 0)
                    {
                        continue;
                    }
                    //如果想只获取四个方向的邻格,就需要排除增量i = 增量j的情况
                    //if (i == j)
                    //{
                    //    continue;
                    //}
                    int x = currentCell.pos.x + i;
                    int y = currentCell.pos.y + j;
                    if (x < 0 || y < 0 || x >= cells.GetLength(0) || y >= cells.GetLength(1) || cells[x,y].myType == CellType.Block || closeList.Contains(cells[x,y]))
                    {
                        //这里判断当前邻格的坐标是否合法?是否为阻塞格子?是否已经存在于close列表中?
                        continue;
                    }
                    //重新计算起始距离,乘10为了方便计算。当前坐标的格子的起始距离 = CurrenCell的起始距离 + 当前坐标的格子与CurrenCell的距离
                    int g = (int)(currentCell.G + Mathf.Sqrt(Mathf.Abs(i) + Mathf.Abs(j))* 10);
                    if (cells[x,y].G == 0 || g < cells[x,y].G)
                    {
                        //若当前坐标的格子并未被查找过,或者当前坐标的格子的起始代价大于新算的起始代价,则更新
                        cells[x, y].G = g;
                        cells[x, y].parent = currentCell;
                    }
                    cells[x, y].H = (Mathf.Abs(x - endPos.x) + Mathf.Abs(y - endPos.y)) * 10;//计算结束距离
                    cells[x, y].F = cells[x, y].G + cells[x, y].H;//综合代价
                    if (!openList.Contains(cells[x,y]))
                    {
                        openList.Add(cells[x, y]);
                    }
                }
            }
            openList.Remove(currentCell);
            closeList.Add(currentCell);
            if (openList.Count == 0)
            {
                //这里是指open列表都已经没有内容了,但是仍未查找到结束点,因此可认为无路可达
                Debug.LogWarning("穷途末路");
            }
        }
    }

    private IEnumerator Draw()
    {
        while (path.Count > 0)
        {
            Cell cell = path.Pop();
            cell.SetColor(Color.blue);
            yield return new WaitForSeconds(0.2f);
        }
    }
}

第三步,测试并查看正确性

这是八个方向的

 

 

 这是四个方向的

 

总结:

以前同事做推箱子的时候,就用过A*算法,还教过我,不过我当时并没懂,最近又挨着学了一次,发现还是自己动手牢靠,记得比较清晰。当时的我的误区在于,我以为close列表就是路径,其实close列表仅仅代表“这个格子我已经检查过啦,不必再检查了”,真正的路径是通过结束点,一步步获取它的parent直到起始点(起始点的parent为空),这样才是完整路径。还需要注意的是,我们每次循环都会对open列表排序,选择综合代价最小的格子,作为本次循环的“CurrentCell”,所以我们必须为Cell对象实现排序接口,指定排序是通过对比F综合代价。

有关Unity中实现A*寻路的更多相关文章

  1. ruby - 在 Ruby 中实现 `call_user_func_array` - 2

    我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)

  2. ruby-on-rails - 如何在 Ruby on Rails 中实现无向图? - 2

    我需要在RubyonRails中实现无向图G=(V,E)并考虑构建一个Vertex和一个Edge模型,其中Vertex有_多条边。由于边恰好连接两个顶点,您将如何在Rails中执行此操作?您是否知道任何有助于实现此类图表的gem或库(对重新发明轮子不感兴趣;-))? 最佳答案 不知道有任何现有库在ActiveRecord之上提供图形逻辑。您可能必须实现自己的Vertex、EdgeActiveRecord支持的模型(请参阅Rails安装的rails/activerecord中的vertex.rb和edge.rb/test/fixtur

  3. ruby-on-rails - 如何在 Ruby on Rails 中实现由 JSF 2.0 (Primefaces) 驱动的 UI 魔法 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道ruby​​onrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim

  4. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  5. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  6. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  7. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  8. ruby - 在 Ruby 中实现 to_int 和 to_str 的后果 - 2

    我haveaclass它公开了一个字符串值和一个int值(分别是命令输出和退出代码)。除了通过to_s和to_i公开它们之外,我还使用to_str和to_int,如下所示:classStatusdefto_s@outputendalias:to_str:to_sdefto_i@status.exitstatusendalias:to_int:to_iend我的想法是能够在尽可能多的情况下使用这个对象。将其强制转换为字符串或整数会增加可用性。例如,我可以将对象与字符串连接起来:a_string="Outputwas:"+results(我想用这个作为int强制转换的例子,但是Fixnum

  9. ruby - 在 Ruby 中实现二叉树 - 2

    我一直在尝试在Ruby中实现BinaryTree类,但我得到了stackleveltoodeep错误,尽管我似乎没有在该特定代码段中使用任何递归:1.classBinaryTree2.includeEnumerable3.4.attr_accessor:value5.6.definitialize(value=nil)7.@value=value8.@left=BinaryTree.new#stackleveltoodeephere9.@right=BinaryTree.new#andhere10.end11.12.defempty?13.(self.value==nil)?true:

  10. ruby - 如何在 Ruby 中实现私有(private)内部类 - 2

    来自Java,我正在尝试在Ruby中实现LinkedList。我在Java中实现它的通常方法是有一个名为LinkedList的类和一个名为Node的私有(private)内部类,其中LinkedList的每个对象都作为Node对象。classLinkedListprivateclassNodeattr_accessor:val,:nextendend我不想将Node类暴露给外部世界。然而,通过Ruby中的这个设置,我可以使用这个访问LinkedList类之外的私有(private)Node类对象-node=LinkedList::Node.new我知道,在Ruby1.9中,我们可以使用

随机推荐