Simple Random Enemy Roam

I am trying to add a simple Roam Action where the unit will move a few spaces in a random direction. It sounds fairly simple but im very new to coding. Awesome course by the way!

That’s a tricky one, since our AI setup returns the “best” location for each available action, rather than a collection of actions. The key is going to be somewhere in the MovementAction, perhaps selecting a random spot from the top three locations…
Unfortunately, I’m out of time for the evening, but I’ll see what I can come up with tomorrow or likely Wednesday.

1 Like

I wasn’t sure on how to get the top three locations on the MovementAction but after a lot of experimenting i found an unorthadox solution.

(EnemyAI Script)

if (bestEnemyAIAction != null && enemyUnit.TrySpendActionPointsToTakeAction(bestBaseAction))
        {
            if (enemyUnit.alerted)
            {
                //Take Best Action
                bestBaseAction.TakeAction(bestEnemyAIAction.gridPosition, onEnemyAIActionComplete);
            }
            
            else
            {
                //Random Roam
                bestBaseAction.TakeAction(enemyUnit.GetRandomGridPos(), onEnemyAIActionComplete);
            }
            
            return true;
        }
        else
        {
            return false;
        }

(Unit Script)

public GridPosition GetRandomGridPos()
    {
        //Randomizes a position of an object that checks for any colliders (obstacles, units)
        _RandomRoamCheck.CheckForObjectsInRoamPosition(); 
        
        //Adds the value of the collider objects x and z values to the units current grid position
        rndGridPosition.x = gridPosition.x += _RandomRoamCheck.rndX;
        rndGridPosition.z = gridPosition.z += _RandomRoamCheck.rndZ;

        return rndGridPosition;
    }

(RandomRoamCheck Script)

public class RandomRoamCheck : MonoBehaviour
{
    [SerializeField] private Unit unit;
    [SerializeField] private LayerMask collidableLayers;
    [SerializeField] private float raycastDist = 3; // 3 = 1 grid square?
    public int rndX;
    public int rndZ;
    public void CheckForObjectsInRoamPosition()
    {
        //Randomize position values
        rndX = Random.Range(-1, 2);
        rndZ = Random.Range(-1, 2);

        //Reset check object position then apply new values
        gameObject.transform.localPosition = new Vector3(0, 0, 0);
        gameObject.transform.parent = null;
        gameObject.transform.rotation = Quaternion.identity;
        gameObject.transform.position += new Vector3(rndX * 2, 0, rndZ * 2); 
        gameObject.transform.parent = unit.gameObject.transform;

        //Shoot ray between unit and check oject for any objects collided
        Vector3 dir = (transform.position - unit.transform.position).normalized;
        float offSetHeight = 1.7f;
        var ray = new Ray(unit.transform.position + Vector3.up * offSetHeight, dir);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, raycastDist, collidableLayers))
        {
            Debug.Log(hit.transform.gameObject + " HIT BY RAY Invalid roam position, reroll");
            CheckForObjectsInRoamPosition();
        }

        else
        {
            Debug.Log("Ray hit nothing, set as roam position");
        }

    }
}

This works OK for the stealth element i am trying to add but im still open for cleaner ways to solve this. Could you show how i could pick a random spot from the top 3 locations in the MoveAction and then make the unit move there?

I actually found the simplest fix for this is in BaseAction.cs within the GetBestEnemyAIAction() method.

In most cases, like if it’s ShootAction, BaseAction.GetBestEnemyAIAction() is going to get nothing if there isn’t a target within shooting range. Same with SwordAction, because these methods only yield return tiles if there are valid ones.

MoveAction, on the other hand, actually returns every possible tile that can be moved to, along with a score if that tile can lead to an attack. If it CAN’T lead to an attack, it is returned with a score of zero.

So what we can do is determine if the highest score is zero or less, then we select a random tile from the tiles returned by Mover.

      if (enemyAIActionList.Count > 0)
        {
            enemyAIActionList.Sort((EnemyAIAction a, EnemyAIAction b) => b.actionValue - a.actionValue);
            if (enemyAIActionList[0].actionValue <= 0)
            {
                return enemyAIActionList[Random.Range(0, enemyAIActionList.Count)];
            }
            return enemyAIActionList[0];
        } else
        {
            // No possible Enemy AI Actions
            return null;
        }
1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms