Epicguru / ThreadedPathfinding

A fast multithreaded implementation of A* intended for use with Unity.
MIT License
25 stars 4 forks source link

Entites randomly stop moving #2

Closed vladlugovskoy closed 1 year ago

vladlugovskoy commented 1 year ago

Hello, I am trying to implement your solution into my project and I've run into an issue. I have a generated map and a logic where units start chasing the player directly if they see them, and if the player disappears from their line of sight, then your algorithm is activated. In most cases, everything works correctly, but after restarting the game, on the second launch, sometimes the units just stop moving at all. At this time, errors are being thrown in the console: 1) "That pathfinding request was already submitted." 2) PathfindingResult.ERROR_START_OUT_OF_BOUNDS (although I am passing the correct grid) Do you know what might be causing this issue?

Here's code which moves entities

`public class AstarAgent : MonoBehaviour {

[SerializeField] private Entity _entity;

// The movement speed, in tiles per second.
private float _movementSpeed = 3f;

// Holds the currently active request.
private PathfindingRequest _currentRequest;
// The X and Y coordinates, in tiles.
private Vector2Int _position2D;

// A reference to the map. Used to get width and height of map.
private PathMap _pathMap;

private Transform _requestTargetT = default;

private List<PNode> _currentPath;
private int _previousNodeIndex;
private float _movementTimer;

private bool _isInited = false;
[SerializeField] private bool _isMovementEnabled = false;

#region getters/setters

public bool IsMovementEnabled {
    get => _isMovementEnabled;
    set {
        if (_isMovementEnabled != value) {

            if (value) {

                _position2D = _pathMap.GetTransform2DPosition(_entity.transform);
                _currentRequest = CreateNewRequest();

            } else {

                _currentRequest?.Dispose();
                _currentRequest = null;
                _currentPath.Clear();
            }

            _isMovementEnabled = value;
        }
    }
}

#endregion

public void Start() {
    // Initialize the current path list.
    _currentPath = new List<PNode>();
}

public void Init(PathMap pathMap) {
    _pathMap = pathMap;

    _position2D = _pathMap.GetTransform2DPosition(_entity.transform);

    _isInited = true;
}

private void OnDisable() {
    _isInited = false;
}

private void FixedUpdate() {
    if (_isInited == false || _isMovementEnabled == false) {
        return;
    }

    // async check
    if (_currentRequest != null) {
        return;
    }

    if (_currentPath.Count == 0) {
        Debug.Log("Path empty!");

        // request delay timer
        CreateNewRequest();

        return;
    }

    _movementTimer += Time.deltaTime;

    PNode previousNode = _currentPath[_previousNodeIndex];
    PNode nextNode = _currentPath[_previousNodeIndex + 1];

    var currentPosition = transform.position;
    var nextNodePosition = new Vector3(nextNode.X, currentPosition.y, nextNode.Y);

    float targetDistance = Vector3.Distance(currentPosition, nextNodePosition);

    if (targetDistance < 0.6f) {

        _position2D.x = nextNode.X;
        _position2D.y = nextNode.Y;

        if (_previousNodeIndex == _currentPath.Count - 2) {
            _currentRequest = CreateNewRequest();

        } else {

            _previousNodeIndex++;
            _movementTimer = 0f;                
        }
    }

    var targetDirection = (nextNodePosition - currentPosition).normalized;
    _entity.RB.velocity = targetDirection * (_movementSpeed * Time.fixedDeltaTime);
}

private float GetDistance(PNode a, PNode b) {
    // Assumes  that the nodes (tiles) are touching each other, or diagonal to each other.
    if(a.X != b.X && a.Y != b.Y) {
        // The tiles are diagonal. The distance between their centers is sqrt(2) which is 1.414...
        return 1.41421f;
    } else {
        // Assume that they are horizontal or vertical from each other.
        return 1;
    }
}

private PathfindingRequest CreateNewRequest() {

    if (_requestTargetT == default) {
        Debug.Log("Request Target Transform not setted!");
        return null;
    }

    var target2DPosition = _pathMap.GetTransform2DPosition(_requestTargetT);

    //Debug.Log("Astar request, Start : " + _position2D + " End : " + target2DPosition);

    var request = PathfindingRequest.Create(
        _position2D.x, _position2D.y, 
        target2DPosition.x, target2DPosition.y,
        UponPathCompleted, _currentPath);

    return request;
}

private void UponPathCompleted(PathfindingResult result, List<PNode> path) {

    // This means that the request has completed, so it is important to dispose of it now.

    _currentRequest?.Dispose();

    _currentRequest = null;

    // Check the result...
    if(result != PathfindingResult.SUCCESSFUL) {
        // Debug.LogWarning("Pathfinding failed: " + result);

        // Most likely is that it was impossible to find a route from the start to end.
        // Request a new path then, hopefully this time it won't be a failure.

        _currentRequest = CreateNewRequest();

        //Debug.Log(gameObject.name + " path search failed " + result);

    } else {

        // Apply the path that was just calculated.
        _currentPath = path;

        //Debug.Log("PATH SETTED");
        foreach (var node in _currentPath) {
            Debug.DrawRay(new Vector3(node.X, 0, node.Y), Vector3.up * 3f, Color.cyan,10f);
        }

        _previousNodeIndex = 0;
        _movementTimer = 0f;
    }
}

public void SetRequestTargetTransform(Transform target) {
    _requestTargetT = target;
}

public void SetMovementSpeed(float speed) {
    _movementSpeed = speed;
}

}`

Epicguru commented 1 year ago

I don't see anything in this file that could be an issue. I think your problem lies elsewhere.

PathfindingResult.ERROR_START_OUT_OF_BOUNDS (although I am passing the correct grid)

If you take a look at Pathfinding.cs you will see that this error only happens when start position is out of bounds i.e. provider.TileInBounds(startX, startY) == false which means that:

The issue only occuring when restarting is strange but again without seeing the rest of your code I can't really begin to guess why it's happening.

Also please note that I have not looked at this project in 5 years and I think that there are better pathfinding solutions out there. I'm glad that you've taken an interest in it but if you just want functional pathfinding with good support, you may want to look elsewhere.

vladlugovskoy commented 1 year ago

Thank you very much for the quick response! The problem was indeed with defining the coordinates on the path map, as the starting point of my map may not be at (0, 0), but for example, at (3, 0) due to the map generation. This caused some issues with pathfinding. To solve the problem, I added methods to the path map generation class that allow conversion of world coordinates to local coordinates for the unit and the ability to get the world position of a node back. Everything works fine now, thanks! Here is the corrected code example: ` public class PathMap : MonoBehaviour {

[SerializeField] private BinarySpaceDungeonGenerator _generator;

private int _mapWidth;
private int _mapHeight;

// True means solid, false means walk-available.
private bool[,] _tileFlags;

// Ignore these.
private Dictionary<int, GameObject> walls = new Dictionary<int, GameObject>();

#region getters

public int MapWidth => _mapWidth;
public int MapHeight => _mapHeight;

public bool[,] TileFlags => _tileFlags;

#endregion

public void Init() {

    PathfindingManager manager = PathfindingManager.Instance;

    manager.Provider = new CustomTileProvider(this);
}

public void UpdateTiles(List<Vector2Int> allPositions2D) {

    _mapWidth = 1 + (_generator.RightLimitPosition.x - _generator.LeftLimitPosition.x);
    _mapHeight = 1 + (_generator.UpLimitPosition.y - _generator.DownLimitPosition.y);

    //Debug.Log("Gen map width : " + _mapWidth + " map height : " + _mapHeight);

    _tileFlags = new bool[_mapWidth, _mapHeight];

    for (int col = 0 ; col < _mapWidth; col++) {
        for (int row = 0; row < _mapHeight; row++) {

            var x = _generator.LeftLimitPosition.x + col;
            var y = _generator.DownLimitPosition.y + row;

            var searchPosition = new Vector2Int(x, y);

            bool isWalkable = _generator.IsPositionContainFloor(searchPosition) &&
                              _generator.IsPositionContainWall(searchPosition) == false;

            _tileFlags[col, row] = isWalkable == false;

            /*
            if (isWalkable) {
                Debug.DrawRay(
                    new Vector3(x, 0, y), Vector3.up * 10, Color.green, 40f);
            } else {
                Debug.DrawRay(
                    new Vector3(x, 0, y), Vector3.up * 10, Color.red, 40f);
            }
            */
        }
    }
}

public Vector2Int GetTransform2DPosition(Transform t) {

    var position2D = new Vector2Int(
        Mathf.RoundToInt(t.position.x),
        Mathf.RoundToInt(t.position.z));

    return position2D;
}

public Vector2Int GetTransformPathMap2DPosition(Transform t) {

    var transform2DPosition = GetTransform2DPosition(t);

    int x = transform2DPosition.x - _generator.LeftLimitPosition.x;
    int y = transform2DPosition.y - _generator.DownLimitPosition.y;

    return new Vector2Int(x, y);
}

public Vector3 GetPathNodeWorldPosition(PNode pNode) {
    int x = _generator.LeftLimitPosition.x + pNode.X;
    int z = _generator.DownLimitPosition.y + pNode.Y;

    return new Vector3(x, 0, z);
}

}

`