游戏开发实战:A*算法在Unity寻路中的高效实现(附完整C#代码)

在Unity中实现Astar寻路算法 在游戏中,从一点到另一点的操作有时需要游戏系统自动完成,在一些带有rpg元素的游戏中,敌人在发现玩家位置后会自动向玩家的位置移动。这些移动的路线是如何自动确定的?本文将介绍寻路算法中的A*算法,并在unity中用C#脚本来实现寻路功能。 阅读详情

游戏开发实战:A*算法在Unity寻路中的高效实现(附完整C#代码)

在游戏世界里,无论是开放世界的探索,还是策略游戏的单位调度,让角色或单位智能地找到从A点到B点的路径,是开发者必须解决的核心问题之一。你或许尝试过Unity自带的NavMesh系统,它在处理静态场景时表现出色,但对于动态变化的网格、需要高度自定义寻路逻辑,或是追求极致性能的2D/3D项目,手动实现一个寻路算法往往能带来更大的灵活性和控制力。A*(A-Star)算法,正是这个领域里那颗璀璨的明珠,它巧妙地在计算效率和路径最优性之间找到了平衡,成为游戏开发中应用最广泛的寻路算法。

这篇文章不是一篇枯燥的算法论文,而是一份面向Unity开发者的实战手册。我们将从零开始,一步步构建一个高效、可复用的A*寻路系统。我会分享在实际项目中踩过的坑,比如如何处理不同地形代价、如何优化大网格下的性能瓶颈,以及如何将算法优雅地集成到你的游戏循环中。无论你是正在开发一款Roguelike地牢探险游戏,还是一个需要复杂单位调度的RTS游戏,这里提供的思路和代码都能直接为你所用。让我们暂时抛开复杂的数学证明,聚焦于如何让算法在Unity引擎中“跑起来”,并跑得又快又好。

1. 理解A*:从理论到游戏世界的映射

在深入代码之前,我们需要清晰地理解A算法在游戏上下文中的核心思想。简单来说,A是一种“启发式”的搜索算法。它不像无头苍蝇一样盲目探索所有方向,而是像一个手持地图和指南针的探险家,每走一步都会评估两个关键信息:我已经走了多远(G代价),以及我估计离终点还有多远(H代价)。将两者相加,就得到了当前节点的总评估代价(F代价)。算法总是优先探索F代价最小的节点,从而高效地导向目标。

1.1 核心概念的游戏化解读

让我们把这些抽象概念映射到游戏开发的具体元素上:

  • 节点 (Node): 在2D网格地图中,一个节点就是一个格子(Tile)。在3D导航网格(NavMesh)或无网格系统中,一个节点可能是一个多边形(Polygon)或一个路点(Waypoint)。我们的寻路就是在这些节点构成的图(Graph)中进行的。
  • G代价 (G Cost): 从起点移动到当前节点的实际累积代价。在平地上,移动一格代价可能是1;穿越沼泽地,代价可能是3;翻越墙壁?代价可能是无穷大(不可通行)。
  • H代价 (H Cost): 从当前节点到目标节点的估算代价,这就是“启发式”部分。它引导搜索方向。常用的估算方法有:
    • 曼哈顿距离: 适用于只能上下左右移动的网格(如经典2D游戏)。H = |dx| + |dy|。
    • 欧几里得距离: 适用于可以任意方向移动的场景(如大多数3D游戏)。H = sqrt(dx*dx + dy*dy)。
    • 切比雪夫距离: 适用于可以八方向(包括对角线)移动的网格。H = max(|dx|, |dy|)。
  • F代价 (F Cost): F = G + H。这是决定节点探索优先级的关键。开放列表(Open Set)会根据F代价对节点进行排序。

提示: 选择正确的启发函数(H Cost计算方式)至关重要。曼哈顿距离计算快,但在允许对角线移动时可能会高估距离,导致寻路不是“最直观”的直线。欧几里得距离更准确,但计算平方根开销稍大。在大多数情况下,欧几里得距离的平方(H = dx*dx + dy*dy)是一个很好的折中方案,因为它保持了大小关系且避免了开方运算。

1.2 A*与Dijkstra、Floyd的实战视角对比

你可能会在资料中看到Dijkstra和Floyd算法。它们在游戏寻路中扮演什么角色?

特性A* 算法Dijkstra 算法Floyd 算法
核心目标找到两点间最短路径找到单点到所有其他点的最短路径计算所有点对之间的最短路径
启发式有,使用H(n)引导搜索无,均匀向所有方向扩展无,基于动态规划
游戏应用场景实时单位寻路(主角移动、AI移动)地图全图可达性分析、影响力扩散计算极小规模固定图的预计算(如房间内点位),极少用于实时寻路
性能特点高效,因目标导向,搜索节点少较慢,需探索大量无关节点极慢(O(N³)),仅适合预处理

简单来说,Dijkstra是不知道终点在哪里的A*。如果你需要让一个单位探索整个地图并记录下到每个点的最短距离(比如战略游戏中的“视野”或“控制区域”扩散),Dijkstra更有用。而Floyd算法在游戏实时寻路中基本不用,它的立方级时间复杂度无法承受动态变化的游戏世界。A*因其在单次点对点寻路上的高效性,成为游戏开发中的事实标准。

2. 构建Unity中的A*寻路系统:基础框架

理论足够清晰了,现在让我们动手搭建。我们将创建一个基于2D网格的A*系统,但其核心逻辑可以轻松扩展到3D导航网格。

2.1 定义节点类:存储寻路信息

首先,我们需要一个类来代表网格中的每一个节点。这个类需要存储所有A*算法所需的信息。

using System.Collections.Generic;
using UnityEngine;

public class PathNode
{
    // 节点在网格中的坐标
    public int x;
    public int y;

    // A* 核心代价
    public int gCost; // 从起点到本节点的实际代价
    public int hCost; // 到终点的估算代价
    public int fCost { get { return gCost + hCost; } } // 总代价,由G和H计算得出

    // 是否为障碍物
    public bool isWalkable = true;

    // 用于回溯路径:当前节点是由哪个节点走过来的
    public PathNode cameFromNode;

    // 构造函数
    public PathNode(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    // 重写ToString,方便调试
    public override string ToString()
    {
        return $"({x},{y}) G:{gCost} H:{hCost} F:{fCost}";
    }
}

这个PathNode类是寻路数据的载体。cameFromNode属性是路径回溯的关键,当算法找到终点后,我们可以像链表一样从终点节点一路回溯到起点,从而得到完整的路径。

2.2 创建网格管理系统:世界的抽象

接下来,我们需要一个管理所有节点的网格系统。这个系统负责创建节点、根据世界坐标获取节点、设置障碍等。

public class PathfindingGrid : MonoBehaviour
{
    [SerializeField] private int width = 20;
    [SerializeField] private int height = 20;
    [SerializeField] private float cellSize = 1f;
    [SerializeField] private LayerMask obstacleLayer; // 用于检测障碍物的图层
    [SerializeField] private bool showGridGizmos = true; // 是否在Scene视图显示网格

    private PathNode[,] gridArray;

    void Start()
    {
        CreateGrid();
    }

    // 初始化网格,创建所有节点并检测障碍
    private void CreateGrid()
    {
        gridArray = new PathNode[width, height];
        Vector3 worldBottomLeft = transform.position - Vector3.right * width * cellSize / 2 - Vector3.forward * height * cellSize / 2;

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * cellSize + cellSize * 0.5f) + Vector3.forward * (y * cellSize + cellSize * 0.5f);
                // 检测该世界位置是否有障碍物(使用BoxCast或OverlapSphere等)
                bool walkable = !Physics.CheckSphere(worldPoint, cellSize * 0.4f, obstacleLayer);
                gridArray[x, y] = new PathNode(x, y) { isWalkable = walkable };
            }
        }
    }

    // 根据世界坐标获取对应的节点
    public PathNode GetNodeFromWorldPoint(Vector3 worldPosition)
    {
        Vector3 localPos = worldPosition - transform.position;
        float percentX = (localPos.x / (width * cellSize)) + 0.5f;
        float percentY = (localPos.z / (height * cellSize)) + 0.5f; // 注意:在Unity中,forward对应世界坐标的Z轴

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

        int x = Mathf.RoundToInt((width - 1) * percentX);
        int y = Mathf.RoundToInt((height - 1) * percentY);
        return gridArray[x, y];
    }

    // 获取一个节点的所有邻居节点(四方向或八方向)
    public List<PathNode> GetNeighbourNodes(PathNode currentNode)
    {
        List<PathNode> neighbours = new List<PathNode>();
        // 四方向移动
        int[] dx = { 0, 1, 0, -1 };
        int[] dy = { 1, 0, -1, 0 };

        for (int i = 0; i < 4; i++)
        {
            int checkX = currentNode.x + dx[i];
            int checkY = currentNode.y + dy[i];

            if (checkX >= 0 && checkX < width && checkY >= 0 && checkY < height)
            {
                neighbours.Add(gridArray[checkX, checkY]);
            }
        }
        return neighbours;
    }

    // 在Scene视图中绘制调试网格
    private void OnDrawGizmos()
    {
        if (!showGridGizmos || gridArray == null) return;

        foreach (PathNode node in gridArray)
        {
            Gizmos.color = (node.isWalkable) ? Color.white : Color.red;
            Vector3 worldPos = GetWorldPosition(node.x, node.y) + Vector3.up * 0.1f;
            Gizmos.DrawCube(worldPos, Vector3.one * (cellSize * 0.9f));
        }
    }

    private Vector3 GetWorldPosition(int x, int y)
    {
        Vector3 worldBottomLeft = transform.position - Vector3.right * width * cellSize / 2 - Vector3.forward * height * cellSize / 2;
        return worldBottomLeft + Vector3.right * (x * cellSize + cellSize * 0.5f) + Vector3.forward * (y * cellSize + cellSize * 0.5f);
    }
}

这个PathfindingGrid脚本挂载在一个空物体上,它定义了寻路发生的“舞台”。GetNeighbourNodes方法目前只返回上下左右四个邻居,你可以轻松扩展它来支持八方向移动(包括对角线),但需要注意对角线移动的代价计算(通常是sqrt(2) ≈ 1.4倍)。

3. 实现A*核心算法:从寻找到路径

有了网格和节点,现在可以实现A*算法的主逻辑了。我们将创建一个静态的Pathfinding类来执行寻路计算。

3.1 算法步骤的代码实现

A*算法的流程可以概括为以下几个步骤,我们将用代码逐一实现:

  1. 将起点加入开放列表(待检查节点集合)。
  2. 进入循环,只要开放列表不为空: a. 从开放列表中取出F代价最小的节点作为当前节点。 b. 将当前节点移入关闭列表(已检查节点集合)。 c. 如果当前节点就是终点,重构路径并返回。 d. 遍历当前节点的所有邻居: i. 如果邻居不可通行或在关闭列表中,跳过。 ii. 计算从起点经过当前节点到该邻居的新G代价。 iii. 如果新G代价更小,或者邻居不在开放列表中: - 更新邻居的G、H代价。 - 设置邻居的cameFromNode为当前节点。 - 如果邻居不在开放列表中,将其加入。
  3. 如果循环结束仍未找到终点,说明路径不存在。

下面是具体的C#实现:

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

public static class Pathfinding
{
    // 主寻路函数
    public static List<Vector3> FindPath(Vector3 startWorldPos, Vector3 targetWorldPos, PathfindingGrid grid)
    {
        PathNode startNode = grid.GetNodeFromWorldPoint(startWorldPos);
        PathNode targetNode = grid.GetNodeFromWorldPoint(targetWorldPos);

        if (startNode == null || targetNode == null || !targetNode.isWalkable)
        {
            Debug.LogWarning("起点或终点无效!");
            return null;
        }

        // 开放集合(待检查节点),使用List并手动排序,对于性能要求高的项目可改用优先队列(Heap)
        List<PathNode> openSet = new List<PathNode>();
        // 关闭集合(已检查节点),使用HashSet实现快速查找
        HashSet<PathNode> closedSet = new HashSet<PathNode>();

        openSet.Add(startNode);

        while (openSet.Count > 0)
        {
            // 1. 从开放集中找到F代价最小的节点
            PathNode currentNode = openSet[0];
            for (int i = 1; i < openSet.Count; i++)
            {
                if (openSet[i].fCost < currentNode.fCost || 
                   (openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost))
                {
                    currentNode = openSet[i];
                }
            }

            // 2. 将当前节点移入关闭集,从开放集移除
            openSet.Remove(currentNode);
            closedSet.Add(currentNode);

            // 3. 找到目标,重构路径
            if (currentNode == targetNode)
            {
                return RetracePath(startNode, targetNode, grid);
            }

            // 4. 遍历邻居
            foreach (PathNode neighbour in grid.GetNeighbourNodes(currentNode))
            {
                if (!neighbour.isWalkable || closedSet.Contains(neighbour))
                {
                    continue;
                }

                // 计算从当前节点到邻居的新G代价(假设移动代价为1)
                int newMovementCostToNeighbour = currentNode.gCost + GetDistance(currentNode, neighbour);
                if (newMovementCostToNeighbour < neighbour.gCost || !openSet.Contains(neighbour))
                {
                    neighbour.gCost = newMovementCostToNeighbour;
                    neighbour.hCost = GetDistance(neighbour, targetNode);
                    neighbour.cameFromNode = currentNode;

                    if (!openSet.Contains(neighbour))
                    {
                        openSet.Add(neighbour);
                    }
                }
            }
        }

        // 开放集为空,未找到路径
        return null;
    }

    // 回溯路径:从终点节点根据cameFromNode反向追溯到起点
    private static List<Vector3> RetracePath(PathNode startNode, PathNode endNode, PathfindingGrid grid)
    {
        List<PathNode> path = new List<PathNode>();
        PathNode currentNode = endNode;

        while (currentNode != startNode)
        {
            path.Add(currentNode);
            currentNode = currentNode.cameFromNode;
        }
        path.Add(startNode);
        path.Reverse(); // 反转列表,得到从起点到终点的顺序

        // 将节点列表转换为世界坐标列表(可在此进行路径平滑优化)
        List<Vector3> waypoints = new List<Vector3>();
        foreach (PathNode node in path)
        {
            waypoints.Add(grid.GetWorldPosition(node.x, node.y)); // 需要为PathfindingGrid添加GetWorldPosition方法
        }
        return waypoints;
    }

    // 计算两个节点之间的估算距离(启发函数H),此处使用曼哈顿距离
    private static int GetDistance(PathNode nodeA, PathNode nodeB)
    {
        int dstX = Mathf.Abs(nodeA.x - nodeB.x);
        int dstY = Mathf.Abs(nodeA.y - nodeB.y);
        return 10 * (dstX + dstY); // 乘以10是为了避免使用浮点数,同时为对角线移动(14)留出整数空间
    }
}

3.2 关键细节与优化点

上面的代码是A*最直接的实现,但有几个地方值得深入讨论:

  • 开放列表的数据结构: 我们使用了List<PathNode>并通过线性搜索查找最小F代价节点。这在节点数量少时没问题,但当网格很大时(如100x100),这会成为性能瓶颈。生产环境强烈建议使用二叉堆(Binary Heap)实现优先队列,可以将“查找并移除最小值”的操作从O(n)优化到O(log n)。这是A*算法最重要的优化之一。
  • 启发函数的选择: 代码中GetDistance使用了曼哈顿距离并乘以10。如果你要实现八方向移动,需要修改这个函数。例如,对角线移动一格的实际距离是sqrt(2)≈1.414,为了保持整数运算,我们可以设定水平/垂直移动代价为10,对角线移动代价为14(≈10*1.414)。
  • 路径平滑: RetracePath返回的是网格中心点的路径,这会导致角色走“锯齿形”路线。一个常见的优化是进行路径后处理,比如使用漏斗算法(Funnel Algorithm) 或简单的射线投射(Raycasting) 来剔除不必要的拐点,让路径更平滑、更自然。

4. 高级优化与实战技巧

一个基础的A*系统已经搭建完成,但要将其投入真正的游戏项目,我们还需要考虑更多现实问题。

4.1 性能优化策略

当处理大规模地图或大量单位同时寻路时,性能至关重要。

  • 使用二叉堆(优先队列): 如前所述,这是必须的优化。你可以自己实现一个Heap<T>类,或者使用C#的SortedSet(需注意处理相同F值的情况)。
  • 分帧/异步寻路: 不要让寻路计算阻塞主游戏线程。可以将寻路请求放入队列,在Update或协程中每帧只处理一部分计算。Unity的Job System和Burst Compiler也为高性能多线程寻路提供了可能。
  • 网格分区与层次化A*: 对于超大型地图,不要使用一个巨大的网格。可以将地图划分为多个区块(Chunk),先在高层次的区块图上寻路,再在具体的区块内进行精细寻路。这能极大减少需要搜索的节点数量。
  • 缓存与重用: 如果很多单位需要前往同一个目标点(比如所有士兵冲向一个据点),可以只计算一次从目标点到全图各点的距离场(这类似于Dijkstra算法),然后每个单位只需根据距离场“下山”即可找到路径。这种技术称为流场寻路(Flow Field),在RTS游戏中非常高效。

4.2 处理复杂地形与动态障碍

游戏世界是动态的。一堵墙可能被炸毁,一扇门可能被打开。

  • 动态更新网格: 当游戏中的障碍物状态改变时,需要调用PathfindingGrid的方法更新对应节点的isWalkable状态。为了避免频繁的全局重计算,可以只标记受影响的区域。
  • 代价系统(Weighted A)*: 不是所有可通行区域都一样。草地、道路、沼泽应有不同的移动代价。我们可以在PathNode中增加一个movementPenalty字段,在计算G代价时加上它。这样寻路就会自动偏好道路,避开沼泽。
    // 在PathNode类中增加
    public int movementPenalty = 0;
    // 在计算G代价时
    int newCostToNeighbour = currentNode.gCost + GetDistance(currentNode, neighbour) + neighbour.movementPenalty;
    
  • 单位碰撞与局部避障: A*找到的是宏观路径,但单位之间可能会互相阻挡。常见的做法是结合局部避障算法,如RVO(Reciprocal Velocity Obstacles)或简单的力导向模型,让单位在遵循全局路径的同时,能动态避开移动中的其他单位。

4.3 集成到游戏对象:一个移动示例

最后,让我们创建一个简单的脚本,让一个游戏对象使用我们的A*系统进行移动。

public class UnitMovement : MonoBehaviour
{
    public PathfindingGrid grid; // 拖拽赋值
    public float speed = 5f;
    private List<Vector3> path;
    private int targetIndex;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit))
            {
                RequestPath(transform.position, hit.point);
            }
        }

        if (path != null)
        {
            FollowPath();
        }
    }

    void RequestPath(Vector3 start, Vector3 end)
    {
        // 在实际项目中,这里应该使用异步或分帧寻路
        path = Pathfinding.FindPath(start, end, grid);
        targetIndex = 0;
        if (path == null || path.Count == 0)
        {
            Debug.Log("无法到达目标点!");
            path = null;
        }
    }

    void FollowPath()
    {
        if (targetIndex >= path.Count)
        {
            path = null;
            return;
        }

        Vector3 currentWaypoint = path[targetIndex];
        Vector3 direction = (currentWaypoint - transform.position).normalized;
        transform.position += direction * speed * Time.deltaTime;

        if (Vector3.Distance(transform.position, currentWaypoint) < 0.1f)
        {
            targetIndex++;
        }
    }

    // 在Scene视图中绘制路径(调试用)
    private void OnDrawGizmos()
    {
        if (path != null)
        {
            for (int i = targetIndex; i < path.Count; i++)
            {
                Gizmos.color = Color.black;
                Gizmos.DrawCube(path[i], Vector3.one * 0.2f);
                if (i == targetIndex)
                {
                    Gizmos.DrawLine(transform.position, path[i]);
                }
                else
                {
                    Gizmos.DrawLine(path[i - 1], path[i]);
                }
            }
        }
    }
}

这个脚本实现了点击移动:点击鼠标左键,单位就会计算路径并朝目标点移动。FollowPath方法只是简单地朝下一个路点直线移动,你可以在这里加入更复杂的移动逻辑,比如旋转朝向、使用CharacterController或NavMeshAgent进行最终移动。

实现一个基础的A寻路系统只是第一步。在我最近参与的一个2D战术项目中,我们最初使用标准的网格A,但在超过100个单位同时寻路时遇到了明显的卡顿。后来我们将网格从精细的32x32替换为更粗糙的128x128分区,并在每个分区内使用A*,同时结合了简单的流场思想来处理集群移动,帧率立刻稳定了。另一个教训是关于路径平滑的,直接使用网格中心点路径会让单位移动显得很“呆”,后来我们加入了一个简单的后处理:在得到原始路径后,从起点开始,尝试用射线检测是否能“看到”后面的路点,如果能就直接跳过中间点。这个小小的改动让单位的移动轨迹立刻变得流畅自然了许多。记住,没有完美的通用方案,最好的优化永远是针对你具体游戏的需求和瓶颈来进行的。

C#实现A*寻路算法:从原理到Unity游戏集成实战 在游戏开发和人工智能领域,路径规划是核心基础技术之一,它决定了角色如何在复杂环境中智能移动。其基本原理是通过图搜索算法,在节点网络中寻找从起点到终点的最优路径,技术价值在于平衡搜索效率与路径质量。A*算法作为经典启发式搜索算法,通过引入预估成本(启发函数)显著提升搜索效率,广泛应用于游戏NPC移动、机器人导航、战略游戏单位调度等场景。本文聚焦于A*算法在游戏开发中的工程实践,详细拆解其核心三要素(G、H、F值)与运行流程,并提供完整的C#实现方案,重点解决游戏开发中常见的NPC智能移动与障碍物规避问题,通过 阅读详情

相关推荐

unity中实现A*算法的一些问题

声明:这不是A*算法教程!!!这不是A*算法教程!!! 前一段时间,我参与了某游戏项目,负责AI部分。项目提出这样的要求: 游戏场景为一个巨大的立方体。立方体的每一个面都是一个迷宫,而上面都有若干怪物。如果玩家到达立方体的某一个侧面上,该侧面上的怪物会进行寻路,追踪玩家。 该项目采用unity3D游戏引擎,但是我发现unity自带NavMesh组件似乎不能实现一个场景建立多个Nav

就是这样一个博客 3543

Unity 实现A* 寻路算法

A* 寻路算法在Unity中的示例

YF云飞的博客 1万+

Unity游戏开发实战:A*寻路算法原理与可视化实现详解

在游戏开发与人工智能领域,寻路算法是解决智能体自主移动问题的核心技术。其核心原理在于,通过评估节点成本,在状态空间中高效搜索从起点到目标点的最优或可行路径。A*算法因其结合了Dijkstra算法的最优性保证和启发式搜索的高效性,成为该领域广泛应用的技术方案。它通过启发函数估算未来成本,引导搜索方向,在游戏开发、机器人导航等场景中实现了效率与结果质量的平衡。本文聚焦于A*算法在Unity引擎中的工程实践,详细拆解了其核心三要素——G成本、H成本与F成本,并构建了包含数据层、算法层、表现层与交互层的完整系统架构

weixin_33796177的博客 407

【Unity】一篇文章搞定AStar(A*)算法

AStar(A*)算法

NOBE 3673

A星寻路的实现,基于Unity C#

算法简介 A*简单高效,可用于许多寻路算法中。 在这里我们共实现Node类,PriorityQueue类,NodeManager类和AStar类。 在Node类中,我们管理每个地图块的信息,包括是否为障碍、估值等。 PriorityQueue类用于储存处理完毕的和待处理的节点。 NodeManager类用来管理我们的Node类,在这里我们使用二维数组来管理。 AStar类则是我们的算法实现类。 步...

qq_38015139的博客 873

Unity游戏开发:从A*算法到动态寻路系统的实现与优化

路径寻找是游戏AI与角色移动的核心基础技术,它决定了虚拟角色如何在游戏世界中智能导航。其原理通常基于图搜索算法,通过评估节点代价来寻找最优或可行路径,在游戏开发中具有关键的技术价值,直接影响角色的行为真实性与游戏性能。常见的应用场景包括NPC移动、群体单位调度、开放世界探索以及策略游戏的单位控制等。本文聚焦于Unity引擎中的寻路实现,深入探讨A*算法原理,并结合网格寻路与导航网格的选型对比,提供动态障碍物处理与性能优化方案,帮助开发者构建高效的寻路系统,应对复杂游戏场景的需求。

weixin_34026276的博客 405

A*算法在Unity中的实现

文章目录一、A*算法是什么一、为什么要在Unity中用A*2.读入数据总结欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 一、A*算法是什么   A星算法是一种搜索策略.

font_weight的博客 5164

【Unity3D进阶4-2】Unity3D A*寻路算法

一、目录 【Unity3D从入门到进阶】文章目录及设置这个专栏的初衷

优快云博客专家,Unity3D高级软件工程师,《Unity 3D从入门到实战》作者。 2765

Unity | A*寻路算法

A*(A-Star)简介在游戏开发的过程中,不免需要涉及到怪物AI的寻路或让玩家自动导航到目标点位置,当在没有障碍的场景中,直接让怪物/玩家向目标点移动即可,但在有障碍物的场景中(如迷宫,房间,围墙等等),寻路算法就变得尤为重要。A*是目前主流的寻路算法之一,它的作用就是寻找开始点与目标点之间,避开障碍的最短路径。

持续学习! 4078

Unity游戏开发中的A星寻路算法实现

欢迎来到游戏开发的世界!在本章中,我们将探索Unity游戏引擎的核心功能和如何搭建开发环境,为掌握高级寻路算法打下坚实基础。在游戏开发中,寻路算法(Pathfinding Algorithm)是一种计算最短路径的方法,其目的是让游戏角色、AI或者敌人等能够在游戏世界中自动找到从出发点到目的地的最佳路径。寻路算法的作用至关重要,它直接影响到游戏的可玩性和真实感,尤其是在策略游戏、角色扮演游戏(RPG)和模拟游戏等多种类型中。

weixin_30923011的博客 1217

Unity3D游戏开发中的A*算法塔防自动寻路解决方案

Unity3D是游戏开发领域的一个领导者,它支持开发者创建2D和3D游戏,同时也支持AR和VR应用的开发。引擎的灵活性允许它广泛应用于各种游戏类型和平台,包括iOS、Android、PC和游戏控制台。Unity3D最引人注目的特性之一是其用户友好的开发环境,这意味着即使是初学者也可以快速上手并创建原型。它提供了一套完整的工具,如场景编辑器、物理引擎、动画系统、音频管理以及一个名为MonoBehaviour的脚本接口。在本章中,我们将深入了解Unity3D的基础架构。

weixin_29301059的博客 1349

游戏开发实战:A*算法在Unity中实现NPC智能寻路(附C#代码)

本文详细介绍了A*算法在Unity游戏开发中实现NPC智能寻路的核心步骤与优化策略。通过构建网格系统、实现算法核心循环,并结合C#代码示例,帮助开发者掌握高效寻路模块的创建。文章还探讨了性能优化、动态障碍处理及分层寻路等高级话题,为RPG、策略等类型游戏提供实战解决方案。

tech5的博客 1118

【脑电分析系列】第7篇:频域分析基石 — 功率谱密度(PSD)的计算与常见方法对比

本文探讨了脑电图(EEG)频域分析的核心方法——功率谱密度(PSD)。通过对比基于快速傅里叶变换的周期图法和更稳健的Welch's方法,指出后者通过分段、加窗和平均处理,能有效降低估计方差,更适合分析非稳态的脑电信号。文章详细解析了关键参数设置对频率分辨率和统计稳定性的影响,并提供了Python计算实现。PSD分析将复杂的脑电信号转换为直观的频域特征,为研究认知功能和神经疾病提供重要依据。

qq_32516809的博客 1801

【Unity学习笔记】A*寻路算法

而不是类似Dijkstra算法,每次遍历相邻结点时尽管下一个结点可能出现权值过大的情况,但是我们不能确保它不是全局最优的,因此我们依然要把它加入到遍历路径中。贪心算法适用于路径带有不同权值的时候,遍历时我们优先遍历相邻结点中权值较小者,这样做的好处是如果找到了路径,那么我们的路径一定是部分最短的(相邻结点永远是最短的,虽然不一定是全局最短,但是相对来说不会是最差的)依次类推,按照表格上的路径,在第二轮发现v4达到不了v3,因此我们放弃这条路径,再次回到第二轮,选择除了这条路径以外最短的,也就是。

milu_ELK的博客 2174

清华电子系保研面试全记录:五轮硬核考核+工程硕博专项解析(附真题)

本文详细解析了清华大学电子工程系保研面试的完整流程,重点剖析了其标志性的五轮专业面试(涵盖电路、电磁场、信号系统、计算机与AI)和综合组群面,并深入解读了工程硕博改革专项的考核要点与备战策略。文章提供了各专业组的真题示例与应对方法,旨在帮助电子信息相关专业学子系统准备,从容应对这场高强度的知识与综合素质挑战。

u0v1w2x3的博客 587

从零开始:Vivado中自定义串口通信IP核的完整实现

本文详细介绍了在Vivado中从零开始实现自定义串口通信IP核的完整流程,包括Verilog核心代码编写、仿真验证、IP核封装与参数化配置技巧。通过AXI-Lite总线实现动态配置,提升FPGA开发效率,适用于各种嵌入式系统通信需求。

weixin_29274969的博客 325

unity中A*算法的实现

算法介绍 A*(念做:A Star)算法是一种很常用的路径查找和图形遍历算法。它有较好的性能和准确度。本文在讲解算法的同时也会提供Python语言的代码实现,并会借助matplotlib库动态的展示算法的运算过程。 A*算法最初发表于1968年,由Stanford研究院的Peter Hart, Nils Nilsson以及Bertram Raphael发表。它可以被认为是Dijkstra算法的扩展。 由于借助启发函数的引导,A*算法通常拥有更好的性能。 A*算法通过下面这个函数来计算每个节点的优先级。 其中

qq_41055920的博客 2680

用户存续期价值评估CLV(二) BG/NBD Model python模拟

BG/NBD Model …继续 想象每一个客户有两个硬币,一个“Buy Coin"控制客户购买的概率;一个"Die Coin"控制客户流失、不再购买的概率。 前面假设了客户的交易次数服从泊松过程: 在一年中的每个月,客户都以抛硬币的方式来决定是否购买。我们所观察到的购买次数取决与泊松分布的参数 ????。 %matplotlib inline import numpy as np impor...

weixin_43171270的博客 3421

Unity游戏开发实战:A*寻路算法如何让你的NPC更智能(附完整代码)

本文详细介绍了在Unity游戏开发中如何实现A*寻路算法,以提升NPC的智能移动能力。通过核心公式F=G+H的解析,结合完整的C#代码示例,手把手教你构建网格系统、实现算法逻辑,并集成到Unity项目中。文章还探讨了性能优化与动态障碍处理等高级技巧,帮助开发者打造更真实、高效的游戏AI。

mango的博客 295

STM32H743实战(三)-- 时钟系统配置

本文详细讲解了STM32H743时钟系统的配置方法,重点解析了为何选择400MHz而非理论极限480MHz作为稳定运行的主频。通过STM32CubeIDE环境,手把手指导如何从外部晶振配置、电源电压设置到PLL锁相环参数调整,最终实现稳定可靠的400MHz系统时钟,并提供了代码验证与常见问题排查指南。

weixin_29164185的博客 368

室内温度控制仿真(Simulink+PLC)

本篇博客将会带领大家解读Simulink自带的仿真模型(Thermal Model of a House),之后再讨论PLC控制系统控制环境温度的一些经验方法

RXXW_Dor的博客 3458

游戏开发者必看:A*算法在Unity中的实战应用(附完整代码)

本文详细介绍了A*算法在Unity游戏开发中的实战应用,包括网格构建、启发函数选择和性能优化等核心内容。通过完整的代码示例和优化技巧,帮助游戏开发者高效实现NPC路径规划,提升游戏AI性能。特别适合需要处理最短路径算法和复杂寻路场景的开发者。

ol789012345的博客 891

ENVI_基于无人机数据的果树提取与相对健康监测

一、数据处理 1.计算植被指数VDVI或GDVI:Band Math / Spectral Indices 2.制作掩膜:ROI——Threshold——Subset 二、单地块果树提取 1.绘制感兴趣区shp 2.确定果树提取参数:Count Crops 3.输出果树矢量文件:Convert Crops To Shapefile 4.果树相对健康程度分析:Calculate Crop Me...

提供技术服务、资源分享 429690771 1398

【算法实战】手把手带你用 C# 实现 A* (A-Star) 寻路算法

本文详细介绍了A寻路算法在游戏开发中的应用。A算法结合了Dijkstra算法的最短路径保证和贪婪算法的效率优势,通过F=G+H公式评估节点价值,其中G为实际移动成本,H为启发式预估成本。文章讲解了曼哈顿距离和欧几里得距离两种常用启发函数,并给出算法流程:维护开启列表和关闭列表,循环查找F值最小的节点,直至找到终点。最后提供了C#实现代码,使用PriorityQueue优化性能,包含节点定义、启发函数计算和路径搜索的核心逻辑。该算法是游戏自动寻路系统的关键技术,兼顾路径最优性和计算效率。

qq_42555291的博客 1551

获取抖音中x-secsdk-csrf-token的方法

获取抖音中x-secsdk-csrf-token的方法

sh_moranliunian的博客 2017

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Unity游戏开发:从Dijkstra到A*寻路算法原理与优化实战

寻路算法是计算机科学中图论与搜索算法的经典应用,其核心目标是在图或网格中找到两点间的最优路径。Dijkstra算法采用广度优先策略保证最短路径,而A*算法通过引入启发式函数(如曼哈顿距离)大幅提升搜索效率,是游戏AI与机器人路径规划的基石。在游戏开发领域,尤其是Unity引擎中,高效的寻路直接影响玩家体验与性能表现。通过优化数据结构(如二叉堆)、调整启发函数权重、结合分层寻路与局部避障,开发者可以应对动态障碍、群体移动等复杂场景。本文以Unity C#实现为例,深入探讨A*算法及其在游戏中的工程化应用与性能

weixin_30924239的博客 752

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

上一篇: LDA主题模型调参指南:如何用困惑度和一致性找到最佳主题数(Python版)
下一篇: 论文实证分析系列(六):工具变量法的实战陷阱与规避策略(附 Python 代码)
tensorflowjs6
博客等级 码龄3年 154粉丝 1495原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值