We are working on a multiplayer third-person shooter game using DOTS-ECS and NetCode for Entities.
I am facing one issue, which is a jitter when a player jumps. I have faced this issue before as well. At that time it was due to the GhostVariant. I have not used the GhostVariant as mentioned in this character controller sample documentation (https://github.com/Unity-Technologies/CharacterControllerSamples/tree/master/_Documentation). After adding this class it was working as expected.
But now I am facing this issue again. I am not able to figure out the issue here.
A few developers on Discord suggested that this issue might be due to the high ping value.
Currently, in-game I am getting around 150 Ping even if I connect to the nearest server.
I tried replicating this issue using PlayMode Tools by setting the custom RTT delay and jitter. I was able to reproduce this issue, but it is not consistent as much as the client connected to the dedicated server.
I have a few questions here:
- Is the jitter while jumping due to the high ping value?
- Why I am getting the higher ping value even if I connect to the closet region? What could be the reason for this high ping value?
The fact you are seeing your character motion become jittery may be due to multiple reasons.
First of all I would strongly suggest to remove any CharacterController interpolation or in general physics interpolation to start excluding some root causes and bisect the problem,
What camera setup you have? Are the camera following the player or static ? In the former, are you using the LocalTransform or the LocalToWorld matrix to follow the character?
In case of character interpolation, you must use the LocalToWorld or the character will start looking jittering back and forth because of that.
In general if higher ping start getting the character jitter it may be related to some misprediction.
I would start logging some state (like position / rotation) and verify actually the value you are getting for a specific tick on both client and server. It is hard to give more suggestion without actually seeing other code parts or some short video, to make understanding how big the jitter is and what the frequency.
You can also use the NetDbg tool to check what values have been mispredicted, and what is the actual delta.
Question: did you try when using the playmode tool to set jitter to 0 bu keep latency? Does this make the overall behaviour better? Because, if this is the case, this even more suggest that the local client is definitively doing something different in the prediction loop in respect to the server.
1 Like
I use this sample project as the base structure. This is a first-person shooter game but I changed the character controller to a third-person using sample from the CharacterController Package from the package manager.
My orbit camera is also a ghost prefab and I use rotation and position values from this ghost object for the main camera.
I tried debugging the problem using the Netcode browser debug to check where I got the ghost prediction errors. I found that when a player jumps I get higher prediction errors in two fields Character LocalTransform and relative velocity from the Character Kinematice Body.
I am also getting other production errors, but to narrow down the problem, I commented on all that code. At last, these two fields were there while getting the jitter while jumping.
One interesting thing is I am not getting the jitter while jumping players in the localhost if the player’s ping value is around 30-50. If I set the higher ping value jittering is happening on the local host as well.
On the dedicated server even on the lower ping value like 30, I am getting the jittering problem.
I also tried Smoothing the local transform in case of prediction errors. It reduces the jitter amount. @CMarastoni Am I in the right direction or I am missing something?
using System;
using AOT;
using Unity.Assertions;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
/// <summary>
/// The MegacityMetro-tuned prediction error <see cref="SmoothingAction"/> function for the <see cref="LocalTransform"/> component.
/// </summary>
[BurstCompile]
public unsafe struct ClientSmoothingAction
{
/// <summary>
/// The default value for the <see cref="DefaultSmoothingActionUserParams"/> if the no user data is passed to the function.
/// Position is corrected if the prediction error is at least 1 unit (usually mt) and less than 10 unit (usually mt)
/// </summary>
public sealed class DefaultStaticUserParams
{
internal static readonly SharedStatic<float> maxDist = SharedStatic<float>.GetOrCreate<DefaultStaticUserParams, MaxDistKey>();
internal static readonly SharedStatic<float> delta = SharedStatic<float>.GetOrCreate<DefaultStaticUserParams, DeltaKey>();
static DefaultStaticUserParams()
{
maxDist.Data = 20;
delta.Data = 0.5f;
}
class MaxDistKey { }
class DeltaKey { }
}
/// <summary>
/// Return a the burst compatible function pointer that can be used to register the smoothing action to the
/// <see cref="GhostPredictionSmoothing"/> singleton.
/// </summary>
public static readonly PortableFunctionPointer<GhostPredictionSmoothing.SmoothingActionDelegate>
Action = new(SmoothingAction);
[BurstCompile(DisableDirectCall = true)]
[MonoPInvokeCallback(typeof(GhostPredictionSmoothing.SmoothingActionDelegate))]
private static void SmoothingAction(IntPtr currentData, IntPtr previousData, IntPtr usrData)
{
ref var trans = ref UnsafeUtility.AsRef<LocalTransform>((void*)currentData);
ref var backup = ref UnsafeUtility.AsRef<LocalTransform>((void*)previousData);
float maxDist = DefaultStaticUserParams.maxDist.Data;
float delta = DefaultStaticUserParams.delta.Data;
Assert.IsTrue(usrData.ToPointer() == null);
var dist = math.distance(trans.Position, backup.Position);
if (dist < maxDist && dist > delta && dist > 0)
{
trans.Position = backup.Position + (trans.Position - backup.Position) * delta / dist;
}
}
}