Getting A.I. to target closest Player

Hello, I’m making a multiplayer game and I wan’t my enemys to target the closest player, I’ve tried a few ways of doing this but I’m getting mixed results as the enemys often don’t go for the closest player and I believe it’s because there is an issue with how the paths are being calculated or my code is wrong :slight_smile:

ATTEMPT 1:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
using RootMotion.FinalIK;
using Unity.Netcode;

public class ClosestObjectByPath : NetworkBehaviour
{
    public string targetTag = "Player";
    public float updateRate = 2f; // Time between target updates
    public LookAtIK lookAtIK;

    private GameObject[] targets;

    void Start()
    {
         if(isServer)
          {
             targets = GameObject.FindGameObjectsWithTag(targetTag);
             StartCoroutine(FindClosestTargetByPath());
          }
    }

    IEnumerator FindClosestTargetByPath()
    {
        while (true)
        {
            float shortestDistance = Mathf.Infinity;
            GameObject closest = null;
            Seeker seeker = GetComponent<Seeker>();

            foreach (GameObject target in targets)
            {
                ABPath p = ABPath.Construct(transform.position, target.transform.position, null);
                AstarPath.StartPath(p);
                yield return StartCoroutine(p.WaitForPath()); // Wait for the path to finish calculating

                if (!p.error)
                {
                    float pathLength = p.GetTotalLength();
                    if (pathLength < shortestDistance)
                    {
                        shortestDistance = pathLength;
                        closest = target;
                    }
                }
                yield return null; // Spread out path calculations over multiple frames
            }

            AIDestinationSetter destinationSetter = GetComponent<AIDestinationSetter>();
            if (destinationSetter != null && closest != null)
            {
                //get the first child of closest
                Transform lookAt = closest.transform.GetChild(0);
                lookAtIK.solver.target = lookAt;
                destinationSetter.target = closest.transform;
            }

            yield return new WaitForSeconds(updateRate);
        }
    }
}

I then came on to this form to see if I could find any other ways I could get enemys to target the closest player and I tried this code which is semi complicated and does not work (I prefer attempt 1)

ATTEMPT #2

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
using Unity.Netcode;

public class ClosestObjectByPath : NetworkBehaviour
{
    public float updateRate = 2f; // Time between path updates
    private Seeker seeker;
    private Dictionary<Vector3, Transform> positionToTransform;
    GameObject[] playerObjects;


    void Start()
    {
        if(IsServer)
        {
            seeker = GetComponent<Seeker>();
            InitializePositionToTransformMap();
            StartCoroutine(UpdatePath());
        }
    }

    private void InitializePositionToTransformMap()
    {
        positionToTransform = new Dictionary<Vector3, Transform>();
        playerObjects = GameObject.FindGameObjectsWithTag("Player");
        foreach (var player in playerObjects)
        {
            // Using the player's position as the key for the dictionary
            positionToTransform[player.transform.position] = player.transform;
        }
    }

    IEnumerator UpdatePath()
    {
        while (true)
        {
            foreach (var player in playerObjects)
            {
                UpdatePlayerPosition(player.transform);
            }
            Vector3[] positions = new Vector3[positionToTransform.Count];
            positionToTransform.Keys.CopyTo(positions, 0);
            seeker.StartMultiTargetPath(transform.position, positions, false, OnPathComplete);
            yield return new WaitForSeconds(updateRate);
        }
    }

    void OnPathComplete(Path p)
    {
        if (p.error)
        {
            Debug.LogError("Path error: " + p.errorLog);
        }
        else
        {
            // The closest point is the last point on the path vector
            Vector3 closestPoint = p.vectorPath[p.vectorPath.Count - 1]; // This is the corrected line

            // Now find the transform associated with this position
            if (positionToTransform.TryGetValue(closestPoint, out Transform closestTransform))
            {
                AIDestinationSetter destinationSetter = GetComponent<AIDestinationSetter>();
                if (destinationSetter != null)
                {
                    destinationSetter.target = closestTransform;
                }
            }
        }
    }


    // This method should be called whenever a player moves to update the mapping.
    public void UpdatePlayerPosition(Transform playerTransform)
    {
        // Remove the old position entry
        if (positionToTransform.ContainsValue(playerTransform))
        {
            Vector3 oldPosition = Vector3.zero;
            foreach (var pair in positionToTransform)
            {
                if (pair.Value == playerTransform)
                {
                    oldPosition = pair.Key;
                    break;
                }
            }
            positionToTransform.Remove(oldPosition);
        }

        // Add the new position with the associated transform
        positionToTransform[playerTransform.position] = playerTransform;
    }
}

If anyone has any idea how to get this working that would be great thanks :))))))

Hi

It sounds like the code is working, it’s just that accurate?

In that case, try updating to the beta version. It has more accurate pathfinding on recast graphs.

It seems to work but sometimes the code-paths return 0 or will return the same value between player 1 and player 2 even though they are different distances and I’m not sure why?. Our game is 3D but our levels are pretty static so not much changes, would you recommend sticking with recast or would another option be better?.

Thanks for taking the time to reply <3

Recast works fine for that.
But the distance measurements in the beta will be more accurate. And distance measurements on grid graphs are also more accurate.