NavMeshAgent for player character movement (Mobile, Joystick)

What the system is. Definition of problem.
I am developing a mobile game without using physics (except for trigger volumes). The player moves purely on a navmesh, using Unity’s built-in navmesh and NavmeshAgents. The player has an on-screen joystick, and so movement is done every Update() rather than in a click-to-move style. The fact that I must update the player’s heading every Update() tick seems to be part of my problem in creating a Player character which is able to smoothly collide with obstacles and other navmesh agents.

Previous Research
I read a great thread:
http://forum.unity3d.com/threads/130012-Need-Advice-or-Recommendations-with-NavMesh-and-Agents
In which AngryAnt so helpfully explains a good way to implement movement in such a system. As we build our game, I have used (kind of) his approach, and also another approach of just using SetDestination(pos). Each approach seems to have its own flaws, and I cannot seem to find a perfect, smooth solution.

Approach #1 flaws
When using the (kind of) approach suggested by AngryAnt (IE: Calling .Move(pos)), The overall movement of the agent is very jittery (but no rubber-banding). I have the script setup with: navAgent.updatePosition = true;

Approach #2 flaws
When using the more simplistic .SetDestination approach, I get rubber-banding under certain conditions (the conditions are not clear to me, but mostly the rubberbanding only happens when the system is under heavy load, like when I am running full-screen image capture at the same time as simulating the game in Unity).

Approach #3 flaws
I expanded the 2nd approach to use the NavMesh’s raycast function to try to resolve the rubberbanding. I thought that perhaps the rubberbanding was due to trying to set a destination outside the navmesh bounds. However, making sure that my .SetDestination() calls always pointed to a position on the navmesh did not resolve the problem.

Summary
I have tried the above 3 approaches with various different sets of NavMeshAgent script settings, like enabling or disabling AutoBraking or AutoRepath and so forth. AutoBraking seems to have the biggest impact, but since Unity’s NavMeshAgent code is not visible, I really cannot tell what Unity is doing behind the scenes with all those attributes, and it does not appear that the NavMesh or NavMeshAgent scripts expose any way to detect other agents or obstacles, so I must rely on the NavMeshAgent script itself to update my character’s position, which makes all those attributes still quite relevant (even though in reality they are irrelevant, since I am not trying to do pathfinding)

If anyone has any insight into how to properly set this up (again, not using physics, using a joystick to move, no jitter, no rubber-banding, colliding with other Agents + Obstacles, and also preferably sliding around obstacles), I greatly appreciate it.

If the answer to this inquiry is that I must track all NavMeshAgents and NavMeshObstacles myself, and raycast against the navmesh, etc. (effectively duplicating the NavMeshAgent script, minus pathfinding), then I will, but I want to be sure I haven’t missed something first.

Thanks!

Since nobody replied this post, I will share what worked for me for a Joystick controlled agent. From the joystick motion I calculate a motionVector (velocity vector) proportional to Time.deltaTime and the desired character speed (the one set in NavMeshAgent.speed). Then I move the agent using:

NavAgent.Move(motionVector);
NavAgent.SetDestination(transform.position+motionVector);

4 Likes

Hi, i know this is a bit late but i found a good youtube vid with your problem in mind.

not sure this will help with your player movement though ;( Have you tried moving the player with physics?

here’s video that helped me :

In combination with what MiguelKing said I’ve settled on something like this:

    Vector3 movement;
    NavMeshAgent agent;

    // Start is called before the first frame update
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
    }

    // Update is called once per frame
    void Update()
    {
        float horizontalInput = Input.GetAxisRaw("Horizontal");
        float verticalInput = Input.GetAxisRaw("Vertical");

        movement.Set(horizontalInput, 0f, verticalInput);

        agent.Move(movement * Time.deltaTime * agent.speed);
        agent.SetDestination(transform.position + movement);
    }

Hope it helps

1 Like

Stumbled onto this by chance. One last thing I noticed that seemed to only happen in editor was stuttering when using the Move function. My game isn’t super sensitive to a little sponginess in controls so I ended up turning off NavMeshAgent.updatePosition and handling that myself. I’m pretty happy with the result and appreciate this entire thread!

private float _moveSmoothing = 0.3f;
private NavMeshAgent _agent;

void Start()
{
    _agent = GetComponent<NavMeshAgent>();
    _agent.updatePosition = false;
}

void Update()
{
    float horizontal = Input.GetAxis("Horizontal");
    float vertical = Input.GetAxis("Vertical");

    Vector3 movement = new Vector3(horizontal, 0, vertical);

    _agent.Move(movement * Time.deltaTime * _agent.speed)

    this.transform.position = Vector3.Lerp(this.transform.position, _agent.nextPosition, _moveSmoothing);
}
1 Like

Here’s something I did recently, basicly it rotates to the right direction then moves. Seems to work for both point and click and direct control or a mix.

Maybe your game doesn’t need that, but it comes out pretty much 100% smooth and animated (assuming you have correct belnds set on your character animator):

        protected override void LateUpdate()
        {
            _agent.nextPosition = transform.position;
        }

        // Assumes agent target destination has been set, works best if agent has high acceleration
        // For direct control set the target direction at pos + input dir * k (where k == 2 for me, but adjust for your game)

        private void Move()
        {
            // Rotate towards direction we need to head
            var desiredVelocity = _agent.desiredVelocity;
            var dir = desiredVelocity.normalized;
            var targetSpeed = desiredVelocity.magnitude;
            if (desiredVelocity != Vector3.zero)
            { 
                // Look towards target dir
                var lookRotation = Quaternion.LookRotation(dir);
                _transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, _agent.angularSpeed * Time.deltaTime);
                var rotationDifference = Quaternion.Angle(_transform.rotation, lookRotation);

                // Adjust speed based on angle
                if (Mathf.Abs(rotationDifference) > 45)
                {
                    //  Bigger than 45 and we can't move forward and instead play a rotating animation
                    targetSpeed = 0;
                    _animator.SetBool(QuickRotateParam, true);
                } 
                else
                {
                    targetSpeed *= Mathf.Cos((Mathf.Deg2Rad * 2 )* rotationDifference);
                }
            }
            else
            {
                targetSpeed = 0;
            }

            // Accelerate to target speed
            if (_currentSpeed > targetSpeed)
            {
                _currentSpeed += Time.deltaTime * deceleration;
            }
            else
            {
                _currentSpeed += Time.deltaTime * acceleration;
            }

            // Move
            _transform.position += _transform.forward * (Time.deltaTime * _currentSpeed);
            _animator.SetFloat(ForwardSpeedParam, _currentSpeed);
        }
    }
}
1 Like