A星寻路算法(二):实现网格

Number of views 52

我们已在A星寻路算法一:算法中介绍了A星算法的原理,这节主要实现节点网格。

1.新建一个PlaneCube,并标记它们的位置为(0, 0, 0),Plane的缩放值为(3,3,3)。

image1746584833405.png

2.新建两个材质,一个Ground用于更改地面的颜色,一个Obstacle用于更改障碍物的颜色。

image1746585809247.png

3.给Cube添加Layer为Unwalkable,并多复制一些Cube,增加更多障碍物。

image1746586337420.png

4.新增A*节点,并标记坐标为 0,0,0 并把该节点放至在最顶层便于查找。

image1746586574763.png

5.新建两个新的脚本文件,并命名Grid与Node,其中把Grid附加到A*节点中作为GameObject组件。

using UnityEngine;
using System.Collections;

// 网格管理器类,用于生成和管理游戏中的网格系统
public class Grid : MonoBehaviour {
    // 玩家所在节点的引用(在Unity编辑器中拖拽赋值)
    public Transform PlayerNode;
    // 不可行走的图层掩码(用于检测障碍物)
    public LayerMask unwalkableMask;
    // 网格的世界尺寸(x 和 y 方向)
    public Vector2 gridWorldSize;
    // 每个节点的半径(用于计算节点大小)
    public float nodeRadius;
    // 网格节点数组(二维数组存储所有节点)
    Node[,] grid;

    // 节点的直径(由半径计算得出)
    float nodeDiameter;
    // 网格在 x 和 y 方向的节点数量
    int gridSizeX, gridSizeY;

    // Unity 的 Start 方法(初始化网格)
    void Start() {
        // 计算节点直径(半径 * 2)
        nodeDiameter = nodeRadius * 2;
        // 根据网格世界尺寸和节点直径计算网格在 x 和 y 方向的节点数量
        gridSizeX = Mathf.RoundToInt(gridWorldSize.x / nodeDiameter);
        gridSizeY = Mathf.RoundToInt(gridWorldSize.y / nodeDiameter);
        // 创建网格
        CreateGrid();
    }

    // 创建网格的方法
    void CreateGrid() {
        // 初始化网格数组
        grid = new Node[gridSizeX, gridSizeY];
        // 计算网格的左下角世界坐标(相对于网格中心)
        Vector3 worldBottomLeft = transform.position 
            - Vector3.right * gridWorldSize.x / 2 
            - Vector3.forward * gridWorldSize.y / 2;

        // 遍历每个网格节点
        for (int x = 0; x < gridSizeX; x++) {
            for (int y = 0; y < gridSizeY; y++) {
                // 计算当前节点的世界坐标(中心点)
                Vector3 worldPoint = worldBottomLeft 
                    + Vector3.right * (x * nodeDiameter + nodeRadius) 
                    + Vector3.forward * (y * nodeDiameter + nodeRadius);
                // 检测该位置是否有障碍物(不可行走)
                bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, unwalkableMask));
                // 创建节点并存储到网格数组中
                grid[x, y] = new Node(walkable, worldPoint);
            }
        }
    }

    // 将世界坐标转换为对应的网格节点
    public Node NodeFromWorldPoint(Vector3 worldPosition) {
        // 计算世界坐标在网格中的百分比位置(x 方向)
        float percentX = (worldPosition.x + gridWorldSize.x / 2) / gridWorldSize.x;
        // 计算世界坐标在网格中的百分比位置(y 方向)
        float percentY = (worldPosition.z + gridWorldSize.y / 2) / gridWorldSize.y;
        // 限制百分比在 0~1 范围内(防止越界)
        percentX = Mathf.Clamp01(percentX);
        percentY = Mathf.Clamp01(percentY);

        // 根据百分比计算网格索引(x 和 y 方向)
        int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
        int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
        // 返回对应的网格节点
        return grid[x, y];
    }

    // 在 Scene 视图中绘制网格和节点(仅编辑器中可见)
    void OnDrawGizmos() {
        // 绘制网格的外框(线框立方体)
        Gizmos.DrawWireCube(transform.position, new Vector3(gridWorldSize.x, 1, gridWorldSize.y));

        // 如果网格已生成
        if (grid != null) {
            // 获取玩家所在节点
            Node playerNode = NodeFromWorldPoint(PlayerNode.position);
            // 遍历所有节点并绘制
            foreach (Node n in grid) {
                // 根据节点是否可行走设置颜色(可行走为白色,不可行走为红色)
                Gizmos.color = (n.walkable) ? Color.white : Color.red;
                // 如果是玩家所在节点,设置为绿色
                if (playerNode == n) {
                    Gizmos.color = Color.green;
                }
                // 绘制节点的立方体(略小于节点直径以避免重叠)
                Gizmos.DrawCube(n.worldPosition, Vector3.one * (nodeDiameter - 0.1f));
            }
        }
    }
}

6.最后的效果如下:

image1746608571567.png其中红色区域是遮挡区域,绿色是当前角色的位置,灰色区域是网格:

image1746608670410.png

0 Answers