Velocity based character controller issue with client prediction

I have made a basic velocity based character controller.

Here is simple version of the code:

using Unity.Entities;
using Unity.Transforms;
using Unity.NetCode;
using UnityEngine;
using UnityEngine.Windows;
using Unity.Mathematics;

[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
partial struct PlayerMovementSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (localTransform, playerInputData, playerCharacterPhysics) 
            in SystemAPI.Query<RefRW<LocalTransform>, RefRO<PlayerInputData>, RefRW<PlayerCharacterPhysics>>())
        {
            // apply damping
            playerCharacterPhysics.ValueRW.velocity *= math.pow(playerCharacterPhysics.ValueRO.damping, SystemAPI.Time.DeltaTime);

            float3 localMoveDir = new float3(0, 0, 0);

			   // Collect input from IInputComponentData and convert from the bit format into a vector representing the desired move direction
            // strafe left / right
            if ((playerInputData.ValueRO.movement & (1 << 0)) != 0)
            {
                localMoveDir += new float3(-1, 0, 0);
            }
            else if ((playerInputData.ValueRO.movement & (1 << 1)) != 0)
            {
                localMoveDir += new float3(1, 0, 0);
            }

            // forward / backward
            if ((playerInputData.ValueRO.movement & (1 << 2)) != 0)
            {
                localMoveDir += new float3(0, 0, 1);
            }
            else if ((playerInputData.ValueRO.movement & (1 << 3)) != 0)
            {
                localMoveDir += new float3(0, 0, -1);
            }


			// convert the desired move direction from local space to world space, remove the Y component so we can't fly and then normalize it
            var rotation = quaternion.AxisAngle(new float3(0, 1, 0), playerInputData.ValueRO.yRotation);
            rotation = math.mul(rotation, quaternion.AxisAngle(new float3(1, 0, 0), -playerInputData.ValueRO.xRotation));
            var worldMoveDir = math.mul(rotation, localMoveDir);
            worldMoveDir.y = 0;
            var worldMoveDirNormalized = math.normalizesafe(worldMoveDir);

			// apply the world move direction (acceleration) to the peristant velocity state of the character
            playerCharacterPhysics.ValueRW.velocity += worldMoveDirNormalized * 10f * SystemAPI.Time.DeltaTime;
			
			// apply the velocity to characters world position
            localTransform.ValueRW.Position += playerCharacterPhysics.ValueRW.velocity * SystemAPI.Time.DeltaTime;
			
			// IF I SWAP OUT THE LINE ABOVE WITH THIS NEW ONE DOWN BELOW, THE CLIENT PREDICTION WORKS FINE, IT IS SMOOTH
			//localTransform.ValueRW.Position += worldMoveDirNormalized * SystemAPI.Time.DeltaTime; // DEBUG
        }
    }
}


public struct PlayerCharacterPhysics : IComponentData
{
    public float damping;
    public float3 velocity;
}


public struct PlayerInputData : IInputComponentData
{
    public byte movement; // bit #1 is left, bit #2 is right, bit #3 is forward, bit #4 is backward p.s. bit #1 is right most
	
	   // the character/cameras rotation
    public float xRotation;
    public float yRotation;
}

In play mode there is heavy rubberbanding/jitter. This happens when accelerating, decellerating or changing direction, when moving in a straight line with a steady velocity it is pretty much stable as far as i can tell.

If i instead don’t apply the velocity and instead just apply input straight to the position localTransform.ValueRW.Position += worldMoveDirNormalized * SystemAPI.Time.DeltaTime; // DEBUG There is no jittering.

If i run that velocity based code NOT in PredictedSimulationSystemGroup and change the ghost mode from Owner Predicted to Interpolate then it works smoothly with no jitter (but with input lag, because no client prediction).

How can I implement velocity based movement with client prediction?

server network and simulation tick are both at 64 Hz
Client frame rate is uncapped and runs over 100fps usually.

If I set the simlated RTT ping to 2ms then the rubberbanding/jitter is barley noticable, but at 132ms it is very noticable.

If you need anymore information please ask.

Are you aware of the Character Controller package for Entities?

1 Like

Nope, I’ll have a look at it now :+1:

Your velocity isn’t synchronized, it doesn’t have a [GhostField] attribute on it. It won’t be rolledback during prediction and won’t be synced on the network. If your damping can change too, I’d make sure to sync this as well.
Also don’t forget to always check for the Simulate tag in your query. Not all ghosts should be simulated in every iteration of the prediction group.

1 Like

Legend!
Adding the ghost field to velocity fixed the issue. Checking for the simulate tag made no difference.
I just noticed there is a big section on prediction in the manual, I will give that a thorough read.