EnemyChaseState bugs

Hello all

When exit the state and return to it
the enemy is going to the player but stopping in the last known location of the player
every time the player come near him he go back
like the last distance the player and enemy had before he lost him

Here is a video of the bug

The code is a bit different to the course but not much i dont use public much in my project and i dont want to redo my code to adjust it to nathan hope you understand

I tried Reseting the path in enter in exit
i tried moving the navmesh agent to EnemyStatemachine and calling all from there
nothing seems to fix this bug

watch the video and see

My Movement Script dont mind the knockbacks i do stuff different a bit with the coding more organized
i didnt like everything on the StateMachine

[RequireComponent(typeof(CharacterController),typeof(ForceReciver))]
    public class Movement : MonoBehaviour
    {
        ForceReciver forceReciver;
        CharacterController controller;

        Vector3 moveDirection;

        bool isInKnockBack = false;
        float timeSinceLastKnockback = 0;

        [Header("KnockBack vars")]
        [SerializeField] float knockBackTime;
        [SerializeField] float forcePower;

        private void Awake()
        {
            controller = GetComponent<CharacterController>();
            forceReciver = GetComponent<ForceReciver>();
        }

        public Vector3 GetVelocity()
        {
            return controller.velocity;
        }

        public void MoveTo(Vector3 newDirection, float deltaTime)
        {
            moveDirection = newDirection;
            Vector3 forceDirection = forceReciver.GetMotion();
            controller.Move((forceDirection + moveDirection) * deltaTime);
        }

EnemyChaseState

public class EnemyChaseState : EnemyBaseState
    {
        Fighter fighter;
        AnimatorHandler animatorHandler;
        Movement mover;
        NavMeshAgent agent;

        private readonly int LocomotionHash = Animator.StringToHash("Locomotion");
        private readonly int forwardHash = Animator.StringToHash("forward");
        private const float crossFadeDuration = 0.1f;

        public EnemyChaseState(EnemyStateMachine stateMachine) : base(stateMachine)
        {
        }

        public override void Enter()
        {
            animatorHandler = stateMachine.GetComponent<AnimatorHandler>();
            mover = stateMachine.GetComponent<Movement>();
            agent = stateMachine.GetComponent<NavMeshAgent>();
            agent.updatePosition = false;
            agent.updateRotation = false;
            animatorHandler.animator.CrossFadeInFixedTime(LocomotionHash, crossFadeDuration);
        }

        public override void Executue(float deltaTime)
        {
            if (!IsInChaseRange())
            {
                stateMachine.SwitchState(new EnemyIdleState(stateMachine));
            }
            MoveToPlayer(deltaTime);
            stateMachine.FacePlayer(deltaTime);
            animatorHandler.ChangeAnimationMovement(forwardHash, 1f, deltaTime);
        }

        public override void Exit()
        {
            agent.ResetPath();
            agent.velocity = Vector3.zero;
        }

        private void MoveToPlayer(float deltaTime)
        {
            if (agent.isOnNavMesh)
            {
                agent.destination = stateMachine.GetPlayer().transform.position;
                mover.MoveTo(agent.desiredVelocity.normalized * stateMachine.GetMovementSpeed(), deltaTime);
            }
            agent.velocity = mover.GetVelocity();
        }

EnemyStateMachine.cs

public class EnemyStateMachine : StateMachine
    {
        [SerializeField] float chasingRange = 10f;
        [SerializeField] float movementSpeed = 6f;
        [SerializeField] float rotationSpeed = 10f;

        GameObject player;

        private void Awake()
        {
            player = GameObject.FindGameObjectWithTag("Player");
        }

        private void Start()
        {
            SwitchState(new EnemyIdleState(this));
        }

        private void OnDrawGizmosSelected()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(transform.position, chasingRange);
        }

        public float GetChasingRange()
        {
            return chasingRange;
        }

        public GameObject GetPlayer()
        {
            return player;
        }

        public float GetMovementSpeed()
        {
            return movementSpeed;
        }

        public void FacePlayer(float deltaTime)
        {
            Vector3 lookPosition = player.transform.position - transform.position;
            lookPosition.y = 0;
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(lookPosition),
            deltaTime * rotationSpeed);
        }
    }

EnemyIdleState.cs

public class EnemyIdleState : EnemyBaseState
    {
        AnimatorHandler animatorHandler;
        Movement mover;

        private readonly int LocomotionHash = Animator.StringToHash("Locomotion");

        private const float crossFadeDuration = 0.1f;

        public EnemyIdleState(EnemyStateMachine stateMachine) : base(stateMachine) { }

        public override void Enter()
        {
            animatorHandler = stateMachine.GetComponent<AnimatorHandler>();
            mover = stateMachine.GetComponent<Movement>();
            animatorHandler.animator.CrossFadeInFixedTime(LocomotionHash, crossFadeDuration);
        }

        public override void Executue(float deltaTime)
        {
            mover.MoveTo(Vector2.zero, deltaTime);
            if (IsInChaseRange())
            {
                stateMachine.SwitchState(new EnemyChaseState(stateMachine));
                return;
            }
            stateMachine.FacePlayer(deltaTime);
            animatorHandler.StopAnimationMovement(deltaTime);
        }

        public override void Exit() { }
    }

@Brian_Trotter I dont know if you have any ideas on this one as i know we had a similar issue with knockback being the cause but the enemy is not attacking in this case.
I cant think of anything that would cause this other than in a GOAP project where the navmesh and the actual gameobject had a difference of opinion to where the position was.

@babi6k Have you checked the components in a paused state to see the position of them to see if the above is occuring?

When i enter the state again the agent.destination stay the same as before thats why have the problem
i did a Debug.Log for it

After many Debugs of different things i found that the agent.desiredVelocity when come back to the state and is in the last distance from player to enemy
then it equals to 0 and thats why it isnt going anywhere

I still don’t know how to fix it
Does the normal project have this bug too ?

Hey, @babi6k, you’ve made many modifications to the course code, and I don’t think there’s a straightforward way to help you. A couple of things stand out as “bad” practices and, in a larger/more complex project, will create a lot of headaches if you implement them in that way.

  1. In your EnemyChaseState.cs - Enter Method . You are using GetComponent for all the shared elements from all the states. Why is this a bad practice? Because every time you switch states, you create memory-intensive calls to objects that do not change (and should be cached). Any reference to a component is handled in the course by a single script, EnemyStateMachine.cs. This creates a clear way for all the calls we make in any state. Every time you “switch” states, you are destroying any information that was in them ( you create more garbage collection)

  2. You created a new script that is called “Movement.” Why? In the course, all the shared methods are part of the “EnemyBaseState.cs”. This gives ALL the states direct access to any functions that are in there (ex: “Move,” “IsInChaseRange,” etc.) without having to assign a special script just for them.

I’ve made a quick diagram of how I understood the system and its benefits (please correct me if I’m wrong)

I would recommend you revisit the initial lectures about what and how the State Machine architecture is implemented in the course and revert all those changes you made in each individual states ( Constructors & Inheritance | GameDev.tv )

I will take a look at this when I get home.

I think the issue may be the line I’ve highlighted in the quoted method above.
Ideally, what we want to do is tell the NavMeshAgent what it’s new position is rather than trying to set it’s velocity directly.
Try replacing that line with:

agent.nextPosition = stateMachine.transform.position;
1 Like

Ok thats good to know
i thought i was organizing it to be by class dependent
i made a mover/movement script for controling the CharacterController/ rigidbody before
i will move everything to be cached only in the statemachine in player and in enemy
so i have access from the statemachine
i thought i am writing the code in a way like we did in the rpg courses
each class in charge of its own stuff

thanks brian that work perfect but anyway i will refactor my code like mihaivladan suggested

and thats a lot of stuff lol
all the player states and playerstatemachine :]

im looking now at all my code and its a lot of refactoring probably better to start again from scratch
and write the code differently

So you’re aware, many students will be working to transition what is learned in the Third Person Character course into the RPG course. Some of the changes Yanev was making are in line with my upcoming 3rd Person to RPG Integration guide.

So your saying dont refactor what i did ?
i still havent fully refactored my AnimatorHandler script
that controls all the animations stuff

My actual suggestion in the migration guide will be to have a clean copy of the RPG project, a clean copy of the Third Person Project, and then clone the RPG project and make the changes to integrate the 3rd person controller.

Refactoring back is up to you, and I never want to discourage customization or alterations to the course project. The reason that I recommend students who do so keep a clean copy that closely tracks the course project is simply that it makes it infinitely easier to troubleshoot when something goes wrong. If you have an example of something going right, you have something to compare and often times you’ll find the issue yourself just in the comparison. (For example, I went to my copy of the code and compared the Move method in your Mover to my own Move code in the EnemyBaseAction, and was able to spot the likely culprit).

The line you found is in Nathan code too

I change the code to be this way
so PlayerStateMachien and EnemyStateMachine
wont be a big script with many different methods

so each script will be controlled of what it needs to do
i didnt know it created more garbage lol

i think i accidentally deleted your comment lol

Anyway yeah this project is starting to become bigger and i want to take also some stuff from the RPG courses here

So probably better to have also a Clean copy of Nathan code following along with small altercations that don’t damage the basic code

the thing is i started this project before the course came out lol
i did many stuff similar to nathan and then i started looking at the course :]

2 Likes

Hey @Brian_Trotter, thanks for the heads-up. I can see the similarities between systems ( ActionScheduler has the same function as SwitchStates). Each has its benefits, disadvantages, and use cases. Based on my current level of understanding, my suggestion was solely on the current context: we create a new state every time we switch states. Please correct me if I misunderstood this.

In the Rpg combat course, this is not an issue as those “states” (ex: fighter/mover / etc.) are initialized on start and will “remember” all the references, whereas here, we have to remake the calls every time we go back a state because of our usage of the word “new.” If we used a dictionary or list to create a cached version of all the states a player/Ai has and used those references when we switch states, that would have solved the issue.

Hey @babi6k, Yes, you are correct. As I mentioned in my comment to Brian, I think the current context and how the StateMachine is implemented creates an extra problem that needs to be addressed before you try to implement the RPG structure. However, as with everything game dev, there’s no “right” or “wrong” way to do something, and a problem will have multiple solutions or ways to go about it.

Yeah i understand now the problem and im fixing it
im leaving the Mover And animatorHandler and fighter i made
just caching them in the EnemyStateMachine and in the PlayerStateMachine

that way all will run smoothly and without extra Get Components

Also i will make them public but hide in inspector that way you just cached them from the script and dont need to drag them in

1 Like

I actually have a trick for that, which allows you to both drag in references, or have them automatically set (I’ve been moving more towards pulling some things like the Animator off of the outer Gameobject to reduce clutter).

void OnValidate()
{
    if(ForceReceiver==null) ForceReceiver =GetComponent<ForceReciever>();
    if(Health==null) Health = GetComponent<Health>();
    if(Animator==null) 
    {
           Animator = GetCompnent<Animator>();
           if(Animator==null) Animator = GetComponentInChildren<Animator>();
     }
     //continue with each component
}

what i did is this

//Components

        [HideInInspector] public InputHandler inputHandler { get; private set; }
        [HideInInspector] public AnimatorHandler animatorHandler { get; private set; }
        [HideInInspector] public Movement mover { get; private set; }
        [HideInInspector] public Fighter fighter { get; private set; }
        [HideInInspector] public Targeter targeter { get; private set; }

private void Awake()
        {
            inputHandler = GetComponent<InputHandler>();
            animatorHandler = GetComponent<AnimatorHandler>();
            mover = GetComponent<Movement>();
            fighter = GetComponent<Fighter>();
            targeter = GetComponentInChildren<Targeter>();

        }

Then you can reach your components in the PlayerStateMachine
and have only 1 ref to it

also do you know how i can make this vars public but also keep the Header

        [Header("Movement Speeds")]
        [SerializeField] float walkingSpeed = 1.5f;
        [SerializeField] float runningSpeed = 5;
        [SerializeField] float targetingSpeed = 4f;
        [SerializeField] float rotationSpeed = 10f;

i tried doing this but i get an error for the header

        [Header("Movement Speeds")]
        [field: SerializeField] public float walkingSpeed  {get; private set;}

Nope, Unity doesn’t like this construction one bit. [field: SerializeField] is doing something internally that is a bit different than [SerializeField]. Behind the scenes there’s a lot more going on as a backing field for the property is created, along with a getter and a setter. You’d need to return to a more classic construction:

        [Header("Movement Speeds")]
        [SerializeField] float walkingSpeed = 1.5f;
        [SerializeField] float runningSpeed = 5;
        [SerializeField] float targetingSpeed = 4f;
        [SerializeField] float rotationSpeed = 10f;

        public float WalkingSpeed=>walkingSpeed;
        public float RunningSpeed=>runningSpeed;
        public float TargetingSpeed=>targetingSpeed;
        public float RotationSpeed=>rotationSpeed;

Thats what i have :smile:

this is now my new and improve PlayerStateMachine

public class PlayerStateMachine : StateMachine
    {
        [SerializeField] Transform cameraTransform;
        [SerializeField] CinemachineFreeLook freeLookCamera;
        [SerializeField] CinemachineVirtualCamera targetCamera;

        [Header("Movement Speeds")]
        [SerializeField] float walkingSpeed = 1.5f;
        [SerializeField] float runningSpeed = 5;
        [SerializeField] float targetingSpeed = 4f;
        [SerializeField] float rotationSpeed = 10f;

        //Getters
        public float WalkingSpeed { get => walkingSpeed; }
        public float RunningSpeed { get => runningSpeed; }
        public float TargetingSpeed { get => targetingSpeed; }

        //Components

        [HideInInspector] public InputHandler inputHandler { get; private set; }
        [HideInInspector] public AnimatorHandler animatorHandler { get; private set; }
        [HideInInspector] public Movement mover { get; private set; }
        [HideInInspector] public Fighter fighter { get; private set; }
        [HideInInspector] public Targeter targeter { get; private set; }

        private void Awake()
        {
            inputHandler = GetComponent<InputHandler>();
            animatorHandler = GetComponent<AnimatorHandler>();
            mover = GetComponent<Movement>();
            fighter = GetComponent<Fighter>();
            targeter = GetComponentInChildren<Targeter>();

        }
        private void Start()
        {
            SwitchState(new PlayerFreeLookState(this));
        }

        public void RecenterFreeLookCamera(bool enable)
        {
            //freeLookCamera.m_RecenterToTargetHeading.m_enabled = enable;
            if (enable)
            {
                targetCamera.GetCinemachineComponent<CinemachineTransposer>().m_YawDamping = 6;
            }
            else
            {
                targetCamera.GetCinemachineComponent<CinemachineTransposer>().m_YawDamping = 0.68f;
            }

        }

        public Vector3 CalculateFreeLookMovementDirection()
        {
            Vector3 inputDirection = cameraTransform.forward * inputHandler.GetVerticalMovementInput();
            inputDirection += cameraTransform.right * inputHandler.GetHorizontalMovementInput();
            inputDirection.Normalize();
            inputDirection.y = 0;
            return inputDirection;

        }

        public Vector3 CalculateTargetMovementDirection()
        {
            Vector3 movement = transform.right * inputHandler.GetHorizontalMovementInput();
            movement += transform.forward * inputHandler.GetVerticalMovementInput();
            return movement;
        }

        public void FaceMovementDirection(Vector3 moveDirection, float deltaTime)
        {
            if (animatorHandler.isInteracting) return;
            if (moveDirection == Vector3.zero) moveDirection = transform.forward;

            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveDirection),
            deltaTime * rotationSpeed);
        }

        public void FaceTarget(float deltaTime)
        {
            if (!targeter.HaveTarget()) return;
            if (animatorHandler.isInteracting) return;
            Vector3 lookPosition = targeter.GetCurrentTarget().transform.position - transform.position;
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(lookPosition),
            deltaTime * rotationSpeed);
        }
    }

Privacy & Terms