[Unity] AStar 寻路卡墙问题

567 阅读2分钟

解决方法简单来说:

  1. 不允许对角线路径

  2. 调小寻路 agent 的碰撞体


虽然包含很多优化的 AStar 教程珠玉在前:www.youtube.com/watch?v=-L-…

但是由于他是 3d 的,我一时半会不想改,于是用了一个别人的用于 Tilemap 的 AStar 代码:github.com/wjdeclan/un…

简单 AStar 下我写的一个一直在追逐敌人的 AI

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

public class ChaserAI : EnemyAI
{
    /// <summary>
    /// 目标对象
    /// </summary>
    [ReadOnly] public GameObject target;

    protected override void Start()
    {
        base.Start();

        type = 0;

        target = GameObject.Find("Player");
    }

    private void FixedUpdate()
    {
        Vector3 dir = GetTargetDir();

        movableIns?.MoveBy(dir);
    }

    private Vector3 GetTargetDir()
    {
        if (target != null && movableIns != null & pathFinding != null)
        {
            Vector3 dir = Vector3.zero;
            Vector3 nextPos = Vector3.zero;

            // 寻找直线路径
            List<AStarNode> sPath = pathFinding.FindStraightPath(transform.position, target.transform.position);

            if (sPath != null)
            {
                // 如果格子数大于 1,那么朝着第一个格子走
                if (sPath.Count > 1)
                    nextPos = pathFinding.WorldPointFromAStarNode(sPath[1]);
                // 如果格子数小于等于 1,那么直接朝着目标走
                else
                    nextPos = target.transform.position;
            }
            // 如果没有直线路径,那么沿着 AStar 路径走
            else
            {
                List<AStarNode> path = pathFinding.FindPath(transform.position, target.transform.position);

                if (path != null)
                {
                    // 如果格子数大于 1,那么朝着第二个格子走
                    if (path.Count > 1)
                        nextPos = pathFinding.WorldPointFromAStarNode(path[1]);
                    // 如果格子数小于等于 1,那么直接朝着目标走
                    else
                        nextPos = target.transform.position;
                }
                // 如果找不到 AStar 路径,那么直接朝着目标走
                else
                    nextPos = target.transform.position;
            }

            dir = (nextPos - transform.position).normalized;

            return dir;
        }

        return Vector3.zero;
    }
}

如果要加额外的效果,应用 Steering AI 的想法:

避开主角

    private Vector3 AddAwayFromPlayerMove(Vector3 dir)
    {
        if (target != null)
        {
            Vector3 dis = target.transform.position - transform.position;

            dis = player.transform.position - transform.position;
            Vector3 playerDir = dis.normalized;

            float force = Mathf.Clamp(5f - dis.magnitude, 0f, 5f) / 5;

            return (dir - playerDir * force).normalized;
        }
        else
            return dir;
    }

围绕主角

private Vector3 AddIntimidatorMove(Vector3 dir)
    {
        if (target != null)
        {
            Vector3 dis = target.transform.position - transform.position;

            Vector3 normalDir = Vector3.Cross(dir, new Vector3(0, 0, 1)).normalized;

            int reversed = dis.magnitude - (intimidateDis + probeDis * Mathf.Sin(Time.time)) > 0 ? 1 : -1;

            return (dir * reversed + normalDir).normalized;
        }
        else
            return dir;
    }

然后应用

    private void FixedUpdate()
    {
        Vector3 dir = GetTargetDir();

        dir = AddAwayFromPlayerMove(dir);

        movableIns.MoveBy(dir);
    }

但是简单 AI 始终会有 AI 沿着对角线路径走卡在墙角的问题

类似这样:gamedev.stackexchange.com/questions/7…

所以我再找到一个现成的,改好的 AStar:github.com/Ceeeeed/Pat…

然后再把它改成用于 Tilemap 的

但是改完之后发现让路径平滑的这部分,有点鸡肋,这就跟对角线路径一样蠢

所以由删掉平滑这部分

结果这一删,也就剩地图罚函数和多线程了hhh

那么其实 A* 里面跟我一开始 copy 的没啥区别……

而且 A* 还要改成不允许对角线

把 Grid.cs 里面的 GetNeighbours() 改一下就好了

	public List<AStarNode> GetNeighbours(AStarNode node) {
		neighbours.Clear();

		for (int x = -1; x <= 1; x++) {
			for (int y = -1; y <= 1; y++) {
				if (x == 0 && y == 0)
					continue;
				// forbid diagonal to avoid stuck in corner
				if (x * y != 0)
					continue;
				int checkX = node.gridX + x;
				int checkY = node.gridY + y;

				if (checkX >= 0 && checkX < gridSizeX && checkY >= 0 && checkY < gridSizeY) {
					neighbours.Add(grid[checkX,checkY]);
				}
			}
		}

		return neighbours;
	}

结果我发现,不允许对角线之后还是会有卡在墙上的状况

我一看场景,怪物的半径是 0.5f,然后竖直向上运动,怪物的碰撞球的边缘与墙的碰撞体稍微接触,这都卡住了

于是我瞬间想到调小碰撞体……然后效果就很好……

有点搞笑了,搞那么多代码最后没有调小碰撞体有用hhh