Best practice for player movement

Hi. I’m using RecastGraph and I’m trying to achieve player movement based on input, but I’m not sure what I’m missing. Below I’ll specify what things I’ve tried so far and what doesn’t work.

Player object has RVOController, Seeker, RichAI and movement happens using [RVOMovement(RVOController - A* Pathfinding Project) + input
- it seems that other objects avoid player, but player can pass through them.
- when reaching walls or obstacles it starts to slow down because of RichAI. I tried to decrease the slowdown variable but it seems it didn’t work.
- SetTarget visual result doesn’t seem a nice feeling for a player Input but documentation exaplains why this happens
- colliders on obstacles/walls are no longer needed.

I also tried other combinations of documentation movement examples, or methods examples but nothing worked. Keep in mind that everything went smooth on implementing A* on enemies using destination or calculating path with RichAI

I’m thinking if I should handle player movement as a new system or is there any tricks/stuff I can do with A* or I’m missing something? Should I keep A* just for enemies?

My solution for now is movement with RVOController, canMove = false on RichAI with manual handling and CharacterController. I’m just using CharacterController for walls/obstacle avoidance, but I’m not sure this is correct.

Hi

Before I start, would you mind explaining what kind of player movement you want to achieve? There are many wildly different types of player movement and it’s hard to give advice about best practices if I don’t know what suits your game.

Hi. Thanks for your response. I have a mobile game, player input is driven by joystick. I have a dungeon environment where player should walk on navmesh. Both enemies and player should have local avoidance. Also player should not enter through obstacles, wall, prop (objects in general you don’t want player/enemies to collide with).

This is my setup for my player for now: Image

This is how I handle the player movement for now.

    protected void MoveAgent(float dt)
    {
        ai.canMove = false;
        Vector3 nextPosition;
        Quaternion nextRotation;
        ai.MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
        CharacterStateController.Animator.SetFloat(PlanarSpeed, InputMovementReference.magnitude * playerSpeed);
        rvoController.Move(InputMovementReference * playerSpeed);
        ai.FinalizeMovement(nextPosition, nextRotation);
    }

This is an example of how it looks right now: Video

After futher investigations I think I found a possible solution for my current player movement setup with avoiding walls.

    protected void MoveAgent(float dt)
    {
        ai.canMove = false;
        Vector3 nextPosition;
        Quaternion nextRotation;
        
        ai.MovementUpdate(dt, out nextPosition, out nextRotation);
        
        if (InputMovementReference != Vector3.zero)
        {
            CharacterStateController.Animator.SetFloat(PlanarSpeed, InputMovementReference.magnitude * playerSpeed);

            ai.maxSpeed = InputMovementReference.magnitude * playerSpeed;
            ai.destination = controller.position + InputMovementReference;
            ai.SearchPath();
        }

        ai.FinalizeMovement(nextPosition, nextRotation);

        if (controller.lockWhenNotMoving)
            controller.locked = InputMovementReference.magnitude < 0.001f;
    }

I saw in documentation how ai.destination is used but is there any chance I can use it in Update?
I like that by setting destination every frame, it takes in account local avoidance and obstacle avoidance. The only thing that I would like to adjust is the slowness of how player stops near obstacles.

  1. Is there any way I can handle the slowness?

  2. Is there any other movement solution beside setting ai.destination on Update() ?

Hi

Cool! That looks very similar to the setup I would recommend.

I would recommend some changes, though.

  1. Check if the previous path is calculated before trying to calculate a new one. I.e. change the SearchPath call to: if (!ai.pathPending) ai.SearchPath(). Otherwise, if paths take long to calculate you may end up aborting the calculations all the time.
  2. Since you don’t seem to change nextPosition/nextRotation, you don’t need that code.
  3. You can adjust how quickly it slows down next to walls using the RVOController → Obstacle Time Horizon field (assuming you are using the beta, which it seems like you do).

Hi!

Thank you for your response. I’ve made some changes taking in consideration your recommendations and everything is almost looking like what I need.

  1. I enabled RichAI canMove by default and for synching movement blend tree with velocity I’m using ai.desiredVelocity, because it takes in account local avoidance and when player is getting near a wall it starts to slow down the desired velocity so I can transition from Run → Walk → Idle animations. I’ve tried velocity but seemed like a jitter movement. Is desiredVelocity a good practice for my case?

This is how my MoveAgent method looks like now.

    protected void MoveAgent(float dt)
    {
        var desiredVel = new Vector3(ai.desiredVelocity.x, 0f, ai.desiredVelocity.z);
        if (InputMovementReference != Vector3.zero)
        {
            CharacterStateController.Animator.SetFloat(PlanarSpeed, desiredVel.magnitude);
            
            ai.maxSpeed = InputMovementReference.magnitude * playerSpeed;
            ai.destination = controller.position + InputMovementReference;
            if(!ai.pathPending)
                ai.SearchPath();
        }
    }
  1. Another thing my player has are states. For example I have Idle and Move states. When Input is Vector3.zero I set ai.isStopped = true and I set false when Input is != Vector3.zero. As far as documentation says, isStopped is better use than canMove. Is this correct?

  2. I’ve set “Obstacle Time Horizon” in RVOController to 0, but the thing is I would like the slow down of character when is near obstacle to start a little later, because it seems the distance between obstacle and player looks bigger visually. A “hackfix” that worked for me was to decrease obstacle collider size so the player will start to slow down later in time, but I don’t think this is a good practic. Is there any chance I can do that?

EDIT: Sorry forgot to ask one last thing.
4. This is how I get the distance between player and enemy, but it seems ai.remainingDistance returns a better value. I saw that remaningDistance is calculate just when destination is set, so in my case when isStooped = true, I saw that remainingDistance is not calculated. What is the best practice to calculate distance between player and enemy?

    var enemyPos = currentEnemy.Position;
    float remainingDistance = controller.To2D(enemyPos - ai.position).sqrMagnitude;

For the places where I ask if things I made are good practices, they are for trying to better understand how to correct use your package.

Thank your for your time!

Hmm, that’s true. That value will become stale if isStopped = true.
What you can do is to run:

var tmp = ai.isStopped;
ai.isStopped = false;
ai.MovementUpdate(0, out var nextPosition, out var nextRotation);
// Remaining distance is up to date now
Debug.Log(ai.remainingDistance);
ai.isStopped = tmp;

to work around that issue.