Movement bug in RPG Project

I’ve finished the RPG Core Combat course, found it good and learned a lot. However, I’ve noticed a bug and I’m not sure exactly what stage it started on. For whatever reason the character has a tendency to slide around sometimes. Especially when walking into objects. I’ve managed to capture a video of it happening:

And in case it’s a bug in the mover code here’s what that looks like for me:

using RPG.Core;
using UnityEngine;
using UnityEngine.AI;
using RPG.Saving;
using RPG.Attributes;

namespace RPG.Movement
{
    public class Mover : MonoBehaviour, IAction, ISaveable
    {
        [SerializeField] Transform target;
        [SerializeField] float maxSpeed = 6f;
        [SerializeField] float maxPathLength = 40f;

        NavMeshAgent navMeshAgent;
        Health health;
        private void Start()
        {
            navMeshAgent = GetComponent<NavMeshAgent>();
            health = GetComponent<Health>();
        }

        void Update()
        {
            navMeshAgent.enabled = !health.IsDead();
            UpdateAnimator();
        }

        public void StartMoveAction(Vector3 destination, float speedFraction)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            MoveTo(destination, speedFraction);
        }

        public bool CanMoveTo(Vector3 destination)
        {
            NavMeshPath path = new NavMeshPath();
            bool hasPath = NavMesh.CalculatePath(transform.position, destination, NavMesh.AllAreas, path);
            if (!hasPath) return false;
            if (path.status != NavMeshPathStatus.PathComplete) return false;
            if (GetPathLength(path) > maxPathLength) return false;
            return true;
        }

        public void MoveTo(Vector3 destination, float speedFraction)
        {
            navMeshAgent.destination = destination;
            navMeshAgent.speed = maxSpeed * Mathf.Clamp01(speedFraction);
            navMeshAgent.isStopped = false;
        }

        private float GetPathLength(NavMeshPath path)
        {
            float total = 0f;
            if (path.corners.Length < 2) return total;
            for (int i = 0; i < path.corners.Length - 1; i++)
            {
                total += Vector3.Distance(path.corners[i], path.corners[i + 1]);
            }

            return total;
        }

        public void Cancel()
        {
            navMeshAgent.isStopped = true;
        }

        private void UpdateAnimator()
        {
            Vector3 velocity = GetComponent<NavMeshAgent>().velocity;
            Vector3 localVelocity = transform.InverseTransformDirection(velocity);
            float speed = localVelocity.z;
            GetComponent<Animator>().SetFloat("ForwardSpeed", speed);
        }

        public object CaptureState()
        {
            return new SerializableVector3(transform.position);
        }

        public void RestoreState(object state)
        {
            SerializableVector3 position = (SerializableVector3)state;
            GetComponent<NavMeshAgent>().enabled = false;
            transform.position = position.ToVector();
            GetComponent<NavMeshAgent>().enabled = true;
            GetComponent<ActionScheduler>().CancelCurrentAction();
        }
    }
}

It looks like two different things are going on here…
In the first few seconds of the video, we see the character in the process of firing a fireball, but he is still moving. Once the firing animation begins, the walk animation won’t fire regardles of the current value sent by Mover to the speed variable. This means if the agent is still in motion, it will get a sliding effect.
Here’s a few tricks to fix this:

  • Making sure that you call Mover.Stop when the character is in range to fire. Within Mover.Stop, it should be setting the Agent.isStopped to true.
  • Making sure that the braking distance is very low.

The later end of the video shows the character moving at an angle relative to his orientation. This means that the character may be improperly aligned in the Prefab. Here’s a few tricks to fix this:

  • Open your player’s prefab and check the Y rotation on the actual character (the prefab you put into your character’s prefab. Generally, this should be 0. You can try adjusting the angle to offset the odd angle the animation is taking you.
  • Open the import for the animation and adjust the Y rotaition of the animation to match the direction the character is walking/running.

Hey Brian, thanks for the quick and detailed response. I double checked the mover.cancel() method and it looks as if it should be canceling the movement like so:

`public void Cancel()
        {
            navMeshAgent.isStopped = true;
        }`

It is also being called in the fighter script, where the range check would be made like this:

private void Update()
        {
            timeSinceLastAttack += Time.deltaTime;

            if (target == null) return;
            if (target.IsDead()) return;

            if (!GetIsInRange(target.transform))
            {
                GetComponent<Mover>().MoveTo(target.transform.position, 1f);
            }
            else
            {
                GetComponent<Mover>().Cancel();
                AttackBehaviour();
            }
        }

I can’t see anything wrong with the code myself. I also double checked in the prefab to make sure the rotation was set to 0. Perhaps the video I recorded did a poor job illustrating the issue. It mostly seems to happen when the nav mesh agent runs into something that it can’t navigate through, like a dead body. (Usually a dead body in fact.)

Here are some better videos illustrating the issue: https://streamable.com/6789l2 , Move Bug 3
The movement works normally at first but begins to spin or run in place, sometimes breaking the game, as I move around. This happens to varying degress.

(I’d be happy to include my repo, a zip, or anything if someone wants to get more info from my project)

That’s part of the joy of diagnosing off of a video.

We’ll start with the bodies: The NavMesh system doesn’t handle things like dead bodies very well. They weren’t baked into the NavMesh, and the agent and the rigidbody can fight with each other. The two NavMeshAgents can wind up fighting with each other as well. When an enemy dies, I like to destroy the colliders and disable the NavMeshAgent to isolate it from those system.

The last issue is a little trickier. It has to do with the NavMeshAgent trying to reach the destination. If the agent’s speed is too high relative to it’s angular speed, then it can’t quite turn enough to technically reach the destination. It just goes a little wide of what’s needed. Usually this is demonstrated by “hovering”… making visible circles around the target (for example, if your Speed was set to 10 and your angular speed was set to 45.
You could increase the turning speed to compensate (in our course project the angular speed is 360, which means that it generally turns fast enough). Sometimes even this isn’t enough, and the downside is that the character appears to turn unnaturally.
Another solution is to add this little check to Mover’s Update() method:

if (!agent.isStopped && agent.remainingDistance < 1.0f) agent.isStopped = true; 

You can adjust the distance to compare to taste, just make sure it’s never higher than any WeaponConfig’s attack distance or you’ll never attack.

1 Like

Wow, thanks a ton Brian.

I added a line to the Health Script that on death disables the collider:

GetComponent<CapsuleCollider>().enabled = false;

And added the line of code you provided. And the mover component (at least so far) seems to work much better now.

1 Like

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

Privacy & Terms