IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> A*寻路实例项目实践笔记 -> 正文阅读

[游戏开发]A*寻路实例项目实践笔记

前言

好久不见!今天使用Unity照着Sebastian Lague大佬的视频做一个A寻路算法的实例项目,包括A寻路的介绍,网格系统的创建,以及一些实际使用案例。

A*的算法原理

简单来说,A是一种寻路算法,通过A可以找到一系列网格中从A到B的最短路线。假如说,我们想得到下图所示A到B的最短路线,第一步我们需要得到关于A节点的所有相邻节点和这些相邻节点的的一些参数。第一个参数是相邻节点到A的距离,叫做G cost。第二个参数是该相邻节点到B节点的距离,叫做H cost,基本上可以说H cost是G cost的反面。最后一个参数为G cost和H cost的和,叫做F cost。

将这些相邻节点存入一个集合,然后算法看一圈,并拿到F cost最低的节点,对拿到的点重复上诉过程。

直到在相邻节点中找到了B点,算法就完成工作了。在没有障碍加入的情况下,路径向着终点就去了。

当然,如果你的游戏里面没有障碍物,还需要什么寻路算法,接下来我们看看加入障碍物以后的情况。如下图所示,在执行了一步操作以后,出现了三个F cost相同的相邻节点,这时候我们选择H cost,也就是离B点最近的节点

然后,依旧是选择F cost最小的两个中的一个,这一次两个节点从参数上看完全一样,所以随便选一个,反正如果选错的话得到的相邻节点的F cost最后都不可能大于另一个选择。

现在我们可以看到,鼠标指向的54是下一个选择,但是这里有个小细节,他左边相邻节点的G cost为38,而不是最短距离30,这是因为我们第一次将该节点考虑进来是通过鼠标指针上面的48和其相邻。所以说G cost得到的是根据最近一次路径运算得到的最小值,而如果我们马上考虑鼠标指向的54,我们就会发现到达这个左边相邻节点的G cost出现更小值,所以我们进行更新。这个节点现在G cost为更小值30,F cost为60。总结一下:G cost当前的值不一定是最小值,而是当前所以算过的路径下的最小值。其实说到这里,我们就知道节点还需要存储一个父节点,表明目前这个最小的G cost是从与哪个节点相邻得来的。

说句题外话,既然G cost不一定是最短, H cost呢?H cost一定是最短,H cost通过某节点先直线到B节点的同一横向或纵向,再直线到B得来的,所以一定是最短。

我们继续,现在更新后的60是最短,我们选60。

就这样一直选下去,最后我们就能得到这条路径,对了,别忘了前面说的每个节点需要记录自己是通过哪个父节点走到的,这样才能得到路径

保存父节点,就像这样

来看一下A* 算法的伪代码。
在这里插入图片描述
可以将OPEN理解为上图中的绿色节点,意味着候选的路径节点,在算法起始时起点为OPEN中的唯一选择,CLOSED理解为红色的点过的节点,意味着算法已经算出了到这个点的最短距离了,以后也不需要再看他了。每次循环开始时从OPEN里面找到F cost最小的作为当前节点,对于当前节点的相邻节点,如果这个节点不可移动,或者已经找到最短路径了,就跳过,否则查看这个相邻节点是否不在OPEN里面或者能得到一个更短的G cost,一个更短的G cost意味着找到了新的到这个相邻节点的更短路径,需要更新这个节点的G cost F cost以及设置当前节点为该相邻节点新的父节点,而不在OPEN意味着该相邻节点从未被考虑,现在要被考虑进来。如此循环往复,当某次循环发现当前节点就是目标节点时,寻路结束。
接下来要做的就是找到目标节点的父节点,再找到这个父节点的父节点,直到找回起始点,得到路径。

实现网格系统

要在项目中使用A*我们首先需要网格,这里大佬直接教我们自制一套网格系统。我们的网格包含一个节点类,一个网格类。节点负责定义网格中的一个位置以及该位置的信息(目前只有unwalkable)。节点将由网格负责创建。可以自定网格整体大小,单个节点大小,可以显示在Scene面板(OnDrawGizmos),可以通过给定(网格中的)某个位置获得网格中的单个节点,由此可以获得玩家所在的网格节点。

节点类

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

public class Node
{
    // node能走吗?会根据是否与障碍物重合判断
    public bool walkable;
    // node中心的世界坐标位置
    public Vector3 worldPosition;

    // 构造函数
    public Node(bool _walkable, Vector3 _worldPos)
    {
        walkable = _walkable;
        worldPosition = _worldPos;
    }
}

网格类

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

// Grid的X、Y轴在场景正中
public class Grid : MonoBehaviour
{
    // 用来存储unwalkable的layermask
    public LayerMask unwalkableMask;
    // grid的大小
    public Vector2 gridWorldSize; // Vector2的y对应世界坐标中的z轴
    // grid中的node的半径(node立方体边长的一半)
    public float nodeRadius;
    // 玩家的位置
    public Transform player;

    // grid是二位的node数组
    Node[,] grid;
    // grid中的node的直径
    float nodeDiameter;
    // Grid中的node数量
    int gridSizeX, gridSizeY;


    private void Start()
    {
        // 根据grid的尺寸和node的尺寸计算node的数量并填入二维数组
        nodeDiameter = nodeRadius * 2;
        gridSizeX = Mathf.RoundToInt(gridWorldSize.x / nodeDiameter);
        gridSizeY = Mathf.RoundToInt(gridWorldSize.y / nodeDiameter);
        CreateGrid();
    }

    // 创建grid实例
    private void CreateGrid()
    {
        grid = new Node[gridSizeX, gridSizeY];
        // 计算得到grid(从上往下看)左下角的世界坐标位置
        Vector3 worldButtomLeft = transform.position - Vector3.right * gridWorldSize.x / 2 - Vector3.forward * gridWorldSize.y / 2; //forward没错,y对应node的z坐标

        for (int x = 0; x < gridSizeX; x++)
        {
            for (int y = 0; y < gridSizeY; y++)
            {
                // 计算每一个node的世界坐标位置
                Vector3 worldPoint = worldButtomLeft +
                    Vector3.right * (x * nodeDiameter + nodeRadius) +
                    Vector3.forward * (y * nodeDiameter + nodeRadius);

                // 判断是否有obstacles,如果有就将node设置为unwalkable
                bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, unwalkableMask));
                // 创建每个node实例并给成员赋值
                grid[x, y] = new Node(walkable, worldPoint);
            }
        }
    }

    // 通过世界坐标获得Node
    public Node GetNodeFromWorldPoint(Vector3 worldPosition)
    {
        // 通过将坐标换算为Grid中的百分比位置来获取Node
        float percentX = (worldPosition.x + gridWorldSize.x / 2) / gridWorldSize.x;
        float percentY = (worldPosition.z + gridWorldSize.y / 2) / gridWorldSize.y; //grid的y长对应世界坐标系的z

        percentX = Mathf.Clamp01(percentX);
        percentY = Mathf.Clamp01(percentY);

        int x = Mathf.RoundToInt((gridSizeX - 1) * percentX); //减一是因为gridSize是1开始,我们需要index
        int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
        return grid[x, y];
    }

    // 在Scene面板中显示grid
    private void OnDrawGizmos()
    {
        Gizmos.DrawWireCube(transform.position, new Vector3(gridWorldSize.x, 1, gridWorldSize.y));

        if (grid != null)
        {
            Node playerNode = GetNodeFromWorldPoint(player.position);
            foreach(Node node in grid)
            {
                Gizmos.color = node.walkable ? Color.white : Color.red;
                if(playerNode == node)
                {
                    Gizmos.color = Color.cyan;
                }
                Gizmos.DrawCube(node.worldPosition, Vector3.one * (nodeDiameter - 0.1f));
            }
        }
    }
}

效果
在这里插入图片描述

######## Work In Progress

######## Work In Progress

######## Work In Progress

######## Work In Progress

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-11-23 12:41:48  更:2021-11-23 12:43:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 22:43:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码