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.