Calling CharacterController.Move() more than one time per frame.

Hi folks!
I’m currently working on multiplayer game and do some client-side prediction. The problem is when client receive acknowledge state from server its rewind to that state and play back. But CharacterController.Move() move faster than it should be, The docs says “It is recommended that you make only one call to Move or SimpleMove per frame.”. But I want to call it multiple time!!. What should I do?

1 Like

We had much more success writing a custom character controller built around Raycast, Spherecast and Physics.ComputePenetration

2 Likes

Can you share the code? :slight_smile:
I have the same problem with CharacterController and GC

Agreed!

Instead of calling the CharacterControllers Move() method directly, you can write your own MoveCharacter() method, where you can collect all the movements you want to do in this update-frame and add the movement combined. Everytime you want to Call the CharacterControllers Move() you instead call the MoveCharacter() method.

    Vector3 _cumulatedMovement;

    public void MoveCharacter(Vector3 movement) {
        _cumulatedMovement += movement;
    }

    private void Update() {
        _characterController.Move(_cumulatedMovement);
        _cumulatedMovement = Vector3.zero;
    }
2 Likes

Thanks @Strieglitz but unfortunately that doesn’t take changes in direction into account (to walk around an obstacle, for example).

If you call Physics.SyncTransforms() between moves it will move properly.

Did you ever find an answer to this, or is the only solution to create a custom kinematic character controller?

This does not seem to work unfortunately.

I never got it to work and will likely do my own. It may be worth looking into the source code for Unity’s Netcode for GameObjects to see how they handle it (if they do).

Hey, so I actually got it working now!

Instead of Physics.SyncTransforms() like @Punfish suggested I step the physics scene using physiscScene.Simulate(deltaTime) , but I think syncing the transforms would also work. My problem was I forgot to call sync/simulate after resetting to the state of the message from the server. So the client started re-simulating from his current position and not from the position of the state Message.
Hope this helps. :slight_smile:

My rewinding code looks something like this (simplified):

// Get buffer index for comparison with state buffer
var rewind_tick_number = stateMsg.tick_number;
var bufferIndex = rewind_tick_number % CLIENT_BUFFER_SIZE;

// Compare and decide if we need to reconcile
// {...}

// Reset to state from state msg
transform.position = stateMsg.position;
transform.rotation = stateMsg.rotation;

// This is the line I forgot and which caused my issues
_physicsScene.Simulate(dt);

// Re-simulate player from necessary tick to current client tick
while (rewind_tick_number < c_tick_number)
{
                // Step player with inputs from buffer
                bufferIndex = rewind_tick_number % CLIENT_BUFFER_SIZE;
                var rewindInput = c_input_buffer[bufferIndex];

                // Movement is handled in different class, but you would call CharacterController.Move() here;
                 _playerMovement.EarlyProcessAbility(rewindInput);
                 _playerMovement.ProcessAbility();
                 
                 // Step physics scene
                  _physicsScene.Simulate(dt);
             
                // Save resulting state
                c_state_buffer[bufferIndex] = new ClientState {
                    position = transform.position,
                    rotation = transform.rotation,
                };

                ++rewind_tick_number;
}
1 Like