Potential Solution for Mob Shout Feedback loop

First post, and first off - want to say how much i appreciate this course. Sam and Rick have done a wonderful job presenting the material - and i greatly appreciate all the source being made available - including for commercial use. And the feedback on the forums has been super helpful.

I’m through the RPG core and Inventory courses - next onto Dialogue and Quests. Was going back through code to comment and tidy - and wanted to work through some of the Mob aggro mechanics - where it seems Mobs could get caught into a feedback loop of retaining aggro. Below is my code with comments and some debugging lines that describe my solution. In a nutshell, introducing three separate timers for timeSinceHitByPlayer, timeSinceSawPlayer, timeSinceHeardShout - and only allowing a Shout (aggravate neighbors) if the AI is hit or sees the player. That is, a shout does not cause another shout. This seems to break the loop.

Comments/Feedback welcome.

Cheers!

using UnityEngine;
using RPG.Combat;
using RPG.Core;
using RPG.Movement;
using RPG.Attributes;
using GameDevTV.Utils;
using System;

namespace RPG.Control {

    public class AIController : MonoBehaviour {
        [SerializeField] float chaseDistance = 5f; // aggro radius
        [SerializeField] float suspicionTime = 3f; // Ager Aggro ends, there is a suspiscion period
        [SerializeField] float aggroCooldownTime = 5f; // if > chaseDistance for this time, -> suspicion mode
        [SerializeField] PatrolPath patrolPath = null;
        [SerializeField] float waypointTolerance = 1f;
        [SerializeField] float wayPointMoveAndDwellTime = 2f; // time stopped per waypoint
        [Range(0,1)] // require next variable to be in this range
        [SerializeField] float patrolSpeedFraction = 0.2f;
        [SerializeField] float shoutRadius = 5f;  // bring your friends...

        // three aggro events: HitByPlayer (Unity Action), SawPlayer, HeardShout
        // each has its own timer: timeSinceHitByPlayer, timeSinceSawPlayer, timeSinceHeardShout
        // will be in an aggro state during a HitByPlayerCD, SawPlayerCD, JoinThePartyCD
        // each aggro event sets each timer to zero
        // a check of any timer within CD window yields an aggrevated state
        // Shout is caused by being hit or seeing a player, not by another shout, this cuts the shout feedback loop in the pack

        Fighter fighter;
        Health health;
        Mover mover;
        GameObject player;
        LazyValue<Vector3> guardPosition_lv;

        float timeSinceArrivedAtLastWaypoint = Mathf.Infinity;

        // replace timeSinceAggrevated counter with separate counters
        float timeSinceHeardShout = Mathf.Infinity;
        float timeSinceLastSawPlayer = Mathf.Infinity;
        float timeSinceHitByPlayer = Mathf.Infinity;

        int currentWaypointIndex = 0;

        private void Awake() {
            fighter = GetComponent<Fighter>();
            health = GetComponent<Health>();
            mover = GetComponent<Mover>();
            player = GameObject.FindWithTag("Player");
            guardPosition_lv = new LazyValue<Vector3>(GetGuardPosition);
        }
        private Vector3 GetGuardPosition() {
            return transform.position;
        }
        private void Start() {
            guardPosition_lv.ForceInit();
        }
        private void Update() {
            if (health.IsDead()) return;
            if ( ( IsAggrevated() || PlayerSeen() ) && fighter.CanAttack(player)) {
                AttackBehaviour();
            }
            else if (timeSinceLastSawPlayer < (aggroCooldownTime +  suspicionTime)) {
                SuspicionBehaviour();
            }
            else {
                PatrolBehaviour();
            }
            UpdateTimers();
        }
        // note: this is also called by a Unity event on the GameObject
        public void PlayerHitMe() {
            print($"{gameObject.name} : you hit me!");
            AggrevateNearbyEnemies(); // shout
            timeSinceHitByPlayer = 0;
        }
        public void HeardShout() {
            print($"{gameObject.name} : heard a shout!");
            // hearing a shout doesn't make you shout, breaks the loop
            timeSinceHeardShout = 0;
        }
        public bool PlayerSeen() {
            float distanceToPlayer = Vector3.Distance(transform.position, player.transform.position);
            if (distanceToPlayer < chaseDistance) {
                print($"{gameObject.name} : i see you!");
                AggrevateNearbyEnemies(); // shout
                timeSinceLastSawPlayer = 0;
                return true;
            }
            return false;
        }
        private void UpdateTimers() {
            timeSinceArrivedAtLastWaypoint += Time.deltaTime;
            timeSinceHeardShout += Time.deltaTime;
            timeSinceLastSawPlayer += Time.deltaTime;
            timeSinceHitByPlayer += Time.deltaTime;
        }
        private void PatrolBehaviour() {
            Vector3 nextPosition = guardPosition_lv.value;

            if ( patrolPath != null) {
                if (AtWaypoint()) {
                    CycleWaypoint();
                }
                nextPosition = getCurrentWaypoint();
            }
            if (timeSinceArrivedAtLastWaypoint > wayPointMoveAndDwellTime) {
                mover.StartMoveAction(nextPosition, patrolSpeedFraction); // stops fighter action
                timeSinceArrivedAtLastWaypoint = 0;
            }
        }
        private Vector3 getCurrentWaypoint() {
            return patrolPath.GetWayPoint(currentWaypointIndex);
        }

        private void CycleWaypoint() {
            currentWaypointIndex = patrolPath.GetNextIndex(currentWaypointIndex);
        }
        private bool AtWaypoint() {
            float distanceToWaypoint = Vector3.Distance(transform.position, getCurrentWaypoint());
            return distanceToWaypoint < waypointTolerance;
        }
        private void SuspicionBehaviour() {
            GetComponent<ActionScheduler>().CancelCurrentAction();
        }
        private void AttackBehaviour() {
            fighter.Attack(player);

        }
        private void AggrevateNearbyEnemies() { // shout
            // just centering a sphere of r shoutRadius on the current enemy
            // it is not traveling - so, any direction with max distance 0
            // find all colliders that hit
            RaycastHit[] hits = Physics.SphereCastAll(transform.position, shoutRadius, Vector3.up, 0);
            foreach (RaycastHit hit in hits) {
                AIController ai = hit.collider.GetComponent<AIController>();
                if (ai == null || ai == this) continue; // ignore self-agro as well
                ai.HeardShout();
            }
        }
        private bool IsAggrevated() {
            bool check = false;
            // has it been a while since i've been hit:
            if (timeSinceHitByPlayer < aggroCooldownTime) check = true;
            // has it been a while since i've heard a shout
            if (timeSinceHeardShout < aggroCooldownTime) check = true;
            // has it been a while since i've seen the player
            if (timeSinceLastSawPlayer < aggroCooldownTime) check = true;
            // else, i'm chill
            print($"{gameObject.name} aggrevated state is {check}");
            return check;
        }
        // add some gizmos which show up in the Scene
        // called by Unity
        private void OnDrawGizmosSelected() {
            Gizmos.color = Color.blue;
            // the chase distance of an enemy
            Gizmos.DrawWireSphere(transform.position, chaseDistance);
        }
    }
}```

Privacy & Terms