hello everyone, I am encountering an issue related to client-side predicted characters. There is a significant jitter problem that intermittently manifests and disappears every few seconds. I have followed all the steps outlined in the Character Controller Networking section.
Note: In my case, it’s a top-down game, so I don’t need variable updates, and the rotation is directly determined by the the input joystick .
Here is my code implementation :
[UpdateInGroup(typeof(GhostInputSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
public partial class ThridPersonPlayerInputsSystem : SystemBase
{
private InputManager _inputManager;
protected override void OnCreate()
{
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ControlledCharacters, ThirdPersonPlayerCommands>().Build());
RequireForUpdate<GameResources>();
RequireForUpdate<NetworkTime>();
RequireForUpdate<NetworkId>();
// Get the Local InputManager instance
_inputManager = InputManager.instance;
}
protected override void OnUpdate()
{
NetworkTick tick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
foreach (var (playerCommands, player, ghostOwner, entity) in SystemAPI
.Query<RefRW<ThirdPersonPlayerCommands>, RefRW<ControlledCharacters>, GhostOwner>()
.WithAll<GhostOwnerIsLocal>().WithEntityAccess())
{
playerCommands.ValueRW = default;
// Move
playerCommands.ValueRW.MoveInput = new JoystickData(new float2(_inputManager.MovementJoystick.Horizontal,
_inputManager.MovementJoystick.Vertical));
//Aim Input
playerCommands.ValueRW.AimInputDelta = new JoystickData(new float2(_inputManager.Aimoystick.Horizontal,
_inputManager.Aimoystick.Vertical));
player.ValueRW.LastKnownCommandsTick = tick;
player.ValueRW.LastKnownCommands = playerCommands.ValueRW;
}
}
}
___________________________________
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup), OrderFirst = true)]
[BurstCompile]
public partial struct ThirdPersonPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ControlledCharacters, ThirdPersonPlayerCommands>()
.Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerCommands, player, entity) in SystemAPI
.Query<ThirdPersonPlayerCommands, ControlledCharacters>().WithAll<Simulate>().WithEntityAccess())
{
// Character
if (SystemAPI.HasComponent<CharacterControl>(player.ControlledCharacter))
{
var characterControl = SystemAPI.GetComponent<CharacterControl>(player.ControlledCharacter);
//Get the MovementInput back as Float2
var moveInput = playerCommands.MoveInput.ToFloat2();
characterControl.MoveVector = new float3(moveInput.x, 0, moveInput.y);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);
// Aim
characterControl.AimVector = playerCommands.AimInputDelta.ToFloat2();
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}
___________________________________
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct ThirdPersonCharacterPhysicsUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private FirstPersonCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
KinematicCharacterConfig,
CharacterControl>()
.Build(ref state);
_context = new FirstPersonCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<NetworkTime>())
return;
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
ThirdPersonCharacterPhysicsUpdateJob job = new ThirdPersonCharacterPhysicsUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct ThirdPersonCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public FirstPersonCharacterUpdateContext Context;
[LIST=1]
[*] public KinematicCharacterUpdateContext BaseContext;
[/LIST]
void Execute(FirstPersonCharacterAspect characterAspect)
{
characterAspect.PhysicsUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{
}
}
}
___________________________________
public void PhysicsUpdate(ref FirstPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterConfig characterConfig = ref CharacterComponent.ValueRW;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
// First phase of default character update
CharacterAspect.Update_Initialize(in this, ref context, ref baseContext, ref characterBody, baseContext.Time.DeltaTime);
CharacterAspect.Update_ParentMovement(in this, ref context, ref baseContext, ref characterBody, ref characterPosition, characterBody.WasGroundedBeforeCharacterUpdate);
CharacterAspect.Update_Grounding(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
// Update desired character velocity after grounding was detected, but before doing additional processing that depends on velocity
HandleVelocityControl(ref context, ref baseContext);
// Second phase of default character update
CharacterAspect.Update_PreventGroundingFromFutureSlopeChange(in this, ref context, ref baseContext, ref characterBody, in characterConfig.StepAndSlopeHandling);
CharacterAspect.Update_GroundPushing(in this, ref context, ref baseContext, characterConfig.Gravity);
CharacterAspect.Update_MovementAndDecollisions(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
CharacterAspect.Update_MovingPlatformDetection(ref baseContext, ref characterBody);
CharacterAspect.Update_ParentMomentum(ref baseContext, ref characterBody);
CharacterAspect.Update_ProcessStatefulCharacterHits();
}
Jitter Video: