I tested the character jumping by reducing the time scale. And found that even with no RTT delay I getting jitter it’s just that it is not visible in the game view.
You can check in the attached video that I am getting multiple debug logs which means CharacterControlUtilities.StandardJump is getting called multiple times. I have tested this on a separate client-server as well. On server I getting the single call but on the client side I am getting the multiple calls.
All increasing the RTT also increases the debug count I am not sure why. I found a documentation snapping mechanism to try to snap the character. Due to multiple calls of StandardJump character might be snapping.
I am trying to update the logic so that StandardJump does not get multiple calls.
Is this bug from the Character Controller package or I am doing something wrong?
Link for the video demonstration
public readonly partial struct ThirdPersonCharacterAspect : IAspect, IKinematicCharacterProcessor<ThirdPersonCharacterUpdateContext>
{
public readonly KinematicCharacterAspect CharacterAspect;
public readonly RefRW<ThirdPersonCharacterComponent> CharacterComponent;
public readonly RefRW<ThirdPersonCharacterControl> CharacterControl;
public readonly RefRW<ActiveWeapon> ActiveWeapon;
public void PhysicsUpdate(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref ThirdPersonCharacterComponent characterComponent = 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 characterComponent.StepAndSlopeHandling);
CharacterAspect.Update_GroundPushing(in this, ref context, ref baseContext, characterComponent.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();
}
private void HandleVelocityControl(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref ThirdPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref ThirdPersonCharacterControl characterControl = ref CharacterControl.ValueRW;
// Rotate move input and velocity to take into account parent rotation
if (characterBody.ParentEntity != Entity.Null)
{
characterControl.MoveVector = math.rotate(characterBody.RotationFromParent, characterControl.MoveVector);
characterBody.RelativeVelocity = math.rotate(characterBody.RotationFromParent, characterBody.RelativeVelocity);
}
CharacterControl.ValueRW.IsGrounded = characterBody.IsGrounded;
if (characterBody.IsGrounded)
{
// Move on ground
float3 targetVelocity = characterControl.MoveVector * characterComponent.GroundMaxSpeed;
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, characterComponent.GroundedMovementSharpness, deltaTime, characterBody.GroundingUp, characterBody.GroundHit.Normal);
// Jump
if (characterControl.Jump)
{
Debug.Log($"characterControl.Jump : {characterControl.Jump}");
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * characterComponent.JumpSpeed, false, characterBody.GroundingUp);
}
}
else
{
CharacterControl.ValueRW.Jump = false;
// Move in air
float3 airAcceleration = characterControl.MoveVector * characterComponent.AirAcceleration;
if (math.lengthsq(airAcceleration) > 0f)
{
float3 tmpVelocity = characterBody.RelativeVelocity;
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, airAcceleration, characterComponent.AirMaxSpeed, characterBody.GroundingUp, deltaTime, false);
// Cancel air acceleration from input if we would hit a non-grounded surface (prevents air-climbing slopes at high air accelerations)
if (characterComponent.PreventAirAccelerationAgainstUngroundedHits && CharacterAspect.MovementWouldHitNonGroundedObstruction(in this, ref context, ref baseContext, characterBody.RelativeVelocity * deltaTime, out ColliderCastHit hit))
{
characterBody.RelativeVelocity = tmpVelocity;
}
}
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, characterComponent.Gravity, deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, characterComponent.AirDrag);
}
}
public void VariableUpdate(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref ThirdPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref ThirdPersonCharacterControl characterControl = ref CharacterControl.ValueRW;
ref quaternion characterRotation = ref CharacterAspect.LocalTransform.ValueRW.Rotation;
ActiveWeapon activeWeapon = ActiveWeapon.ValueRO;
// Add rotation from parent body to the character rotation
// (this is for allowing a rotating moving platform to rotate your character as well, and handle interpolation properly)
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref characterRotation, characterBody.RotationFromParent, baseContext.Time.DeltaTime, characterBody.LastPhysicsUpdateDeltaTime);
// Rotate towards move direction
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, baseContext.Time.DeltaTime, math.normalizesafe(characterControl.MoveVector), MathUtilities.GetUpFromRotation(characterRotation), characterComponent.RotationSharpness);
}
}
#region Character Processor Callbacks
public void UpdateGroundingUp(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
CharacterAspect.Default_UpdateGroundingUp(ref characterBody);
}
public bool CanCollideWithHit(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
return PhysicsUtilities.IsCollidable(hit.Material);
}
public bool IsGroundedOnHit(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit,
int groundingEvaluationType)
{
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
return CharacterAspect.Default_IsGroundedOnHit(
in this,
ref context,
ref baseContext,
in hit,
in characterComponent.StepAndSlopeHandling,
groundingEvaluationType);
}
public void OnMovementHit(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref KinematicCharacterHit hit,
ref float3 remainingMovementDirection,
ref float remainingMovementLength,
float3 originalVelocityDirection,
float hitDistance)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
ref ThirdPersonCharacterControl characterControl = ref CharacterControl.ValueRW;
CharacterAspect.Default_OnMovementHit(
in this,
ref context,
ref baseContext,
ref characterBody,
ref characterPosition,
ref hit,
ref remainingMovementDirection,
ref remainingMovementLength,
originalVelocityDirection,
hitDistance,
characterComponent.StepAndSlopeHandling.StepHandling,
characterComponent.StepAndSlopeHandling.MaxStepHeight,
characterComponent.StepAndSlopeHandling.CharacterWidthForStepGroundingCheck);
}
public void OverrideDynamicHitMasses(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref PhysicsMass characterMass,
ref PhysicsMass otherMass,
BasicHit hit)
{
// Custom mass overrides
}
public void ProjectVelocityOnHits(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref float3 velocity,
ref bool characterIsGrounded,
ref BasicHit characterGroundHit,
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
float3 originalVelocityDirection)
{
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
CharacterAspect.Default_ProjectVelocityOnHits(
ref velocity,
ref characterIsGrounded,
ref characterGroundHit,
in velocityProjectionHits,
originalVelocityDirection,
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane);
}
#endregion
}
/// <summary>
/// Apply inputs that need to be read at a fixed rate.
/// It is necessary to handle this as part of the fixed step group, in case your framerate is lower than the fixed step rate.
/// </summary>
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup), OrderFirst = true)]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation | WorldSystemFilterFlags.ServerSimulation)]
[BurstCompile]
public partial struct ThirdPersonPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ThirdPersonPlayerFixedStepControlJob job = new ThirdPersonPlayerFixedStepControlJob
{
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true),
CharacterControlLookup = SystemAPI.GetComponentLookup<ThirdPersonCharacterControl>(false),
OrbitCameraLookup = SystemAPI.GetComponentLookup<OrbitCamera>(false),
};
state.Dependency = job.Schedule(state.Dependency);
}
[BurstCompile]
//[WithAll(typeof(Simulate))]
public partial struct ThirdPersonPlayerFixedStepControlJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<LocalTransform> LocalTransformLookup;
public ComponentLookup<ThirdPersonCharacterControl> CharacterControlLookup;
public ComponentLookup<OrbitCamera> OrbitCameraLookup;
void Execute(RefRW<ThirdPersonPlayerInputs> playerCommands, in ThirdPersonPlayer player, in CommandDataInterpolationDelay commandInterpolationDelay)
{
if (CharacterControlLookup.HasComponent(player.ControlledCharacter))
{
ThirdPersonCharacterControl characterControl = CharacterControlLookup[player.ControlledCharacter];
float3 characterUp = MathUtilities.GetUpFromRotation(LocalTransformLookup[player.ControlledCharacter].Rotation);
// Get camera rotation, since our movement is relative to it.
quaternion cameraRotation = quaternion.identity;
if (OrbitCameraLookup.HasComponent(player.ControlledCamera))
{
// Camera rotation is calculated rather than gotten from transform, because this allows us to
// reduce the size of the camera ghost state in a netcode prediction context.
// If not using netcode prediction, we could simply get rotation from transform here instead.
OrbitCamera orbitCamera = OrbitCameraLookup[player.ControlledCamera];
cameraRotation = OrbitCameraUtilities.CalculateCameraRotation(characterUp, orbitCamera.PlanarForward, orbitCamera.PitchAngle);
}
float3 cameraForwardOnUpPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(cameraRotation), characterUp));
float3 cameraRight = MathUtilities.GetRightFromRotation(cameraRotation);
// Move
if (!characterControl.IsInFinisherState)
{
characterControl.MoveVector = (playerCommands.ValueRO.MoveInput.y * cameraForwardOnUpPlane) + (playerCommands.ValueRO.MoveInput.x * cameraRight);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);
//move axis
characterControl.MoveAxisValue = playerCommands.ValueRO.MoveInput;
// Set MoveVector to the camera's forward direction
float3 cameraForward = math.mul(cameraRotation, new float3(0, 0, 1));
characterControl.RotationVector = MathUtilities.ClampToMaxLength(cameraForward, 1f);
}
else
{
characterControl.MoveVector = 0;
//move axis
characterControl.MoveAxisValue = 0;
// Set MoveVector to the camera's forward direction
characterControl.RotationVector = 0;
}
// Set Climbing Status
playerCommands.ValueRW.IsClimbing = characterControl.IsClimbing;
// Jump
if (characterControl.IsGrounded)
{
if (playerCommands.ValueRO.JumpPressed)
{
Debug.LogError("m_DefaultActionsMap JumpPressed");
characterControl.Jump = true;
}
else
characterControl.Jump = false;
if (characterControl.IsInFinisherState)
characterControl.Sprint = false;
else
characterControl.Sprint = playerCommands.ValueRO.SprintHeld;
characterControl.Crouch = playerCommands.ValueRO.CrouchHeld;
characterControl.IsDancing = playerCommands.ValueRO.DanceHeld;
characterControl.DanceIndex = playerCommands.ValueRO.DanceIndex;
characterControl.IsAttackPerformed = playerCommands.ValueRO.AttackPerformed.IsSet;
characterControl.SlidePressed = playerCommands.ValueRO.SlidePressed;
}
else
{
characterControl.ClimbPressed = playerCommands.ValueRO.ClimbPressed;
}
characterControl.DashPressed = playerCommands.ValueRO.DashPressed;
characterControl.IsAiming = playerCommands.ValueRO.AimPressed;
characterControl.IsThrowingGrenade = playerCommands.ValueRO.GrenadeThrow;
characterControl.IsShooting = playerCommands.ValueRO.ShootPressed.IsSet;
CharacterControlLookup[player.ControlledCharacter] = characterControl;
}
}
}
}