[Resolved] Movement not predicted - NetCode

I’m working from the multiplayerSample and I tried changing the movement, but now it isn’t predicted anymore for the client.

I replaced the commented part with the part underneath.

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

#if true

[UpdateInGroup(typeof(GhostPredictionSystemGroup))]
public class MoveCubeSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        var group = World.GetExistingSystem<GhostPredictionSystemGroup>();
        var tick = group.PredictingTick;
        var deltaTime = Time.DeltaTime;
        Entities.ForEach((DynamicBuffer<CubeInput> inputBuffer, ref Translation trans, ref MoveVelocity moveVelocity, ref PredictedGhostComponent prediction) =>
        {  
            if (!GhostPredictionSystemGroup.ShouldPredict(tick, prediction))
                return;
            CubeInput input;
            inputBuffer.GetDataAtTick(tick, out input);
            //if (input.horizontal > 0)
            //    trans.Value.x += deltaTime;
            //if (input.horizontal < 0)
            //    trans.Value.x -= deltaTime;
            //if (input.vertical > 0)
            //    trans.Value.z += deltaTime;
            //if (input.vertical < 0)
            //    trans.Value.z -= deltaTime;

            float runAcceleration = 1;
            float maxRunSpeed = 10;

            Vector3 inputs = new Vector3(input.horizontal, 0, input.vertical);
            if (inputs.magnitude > 1)
                inputs = inputs.normalized;
            moveVelocity.Value += inputs * runAcceleration;

            float runningDragCoeff = runAcceleration / (maxRunSpeed + runAcceleration);
            Vector3 runningDrag = new Vector3(moveVelocity.Value.x, 0, moveVelocity.Value.z) * -runningDragCoeff;
            moveVelocity.Value += runningDrag;

            trans.Value.x += moveVelocity.Value.x * deltaTime;
            trans.Value.z += moveVelocity.Value.z * deltaTime;
        });
    }
}

#endif

I also created a component containing the velocity:

using Unity.Entities;
using Unity.NetCode;
using UnityEngine;

public struct MoveVelocity : IComponentData
{
    public Vector3 Value;
}

And added that component to the cube in the game.cs file:

var ghostCollection = GetSingleton<GhostPrefabCollectionComponent>();
            var ghostId = MehGhostSerializerCollection.FindGhostType<PlayerSnapshotData>();
            var prefab = EntityManager.GetBuffer<GhostPrefabBuffer>(ghostCollection.serverPrefabs)[ghostId].Value;
            var player = EntityManager.Instantiate(prefab);
            EntityManager.SetComponentData(player, new MovableCubeComponent { PlayerId = EntityManager.GetComponentData<NetworkIdComponent>(reqSrc.SourceConnection).Value });
            EntityManager.AddComponentData<MoveVelocity>(player, new MoveVelocity());

            PostUpdateCommands.AddBuffer<CubeInput>(player);
            PostUpdateCommands.SetComponent(reqSrc.SourceConnection, new CommandTargetComponent { targetEntity = player });

If I uncomment the first original part and comment my added code, the movement is predicted for the client.
Do I have to send the velocity as ghost as well? Any help is appreciated! :slight_smile:

Since the system in which you added that component to the player ghost runs on the server, only the server-side ghost has that component, so your move system also only runs on the server ofc.

So that’s the “EntityManager.AddComponentData(player, new MoveVelocity);” line in the game.cs file?
Where would I add it to solve the issue? Thanks for the answer!

The easiest way is to just add that component to the ghost prefab itself, but dont make it sync (so no GhostDefaultField attribute).
If you dont want to go for that, you also have to add the component to the system (SampleCubeInput) where the local client ghost get’s set up;

                if (cube.PlayerId == localPlayerId)
                {
                    PostUpdateCommands.AddBuffer<CubeInput>(ent);
                    PostUpdateCommands.SetComponent(GetSingletonEntity<CommandTargetComponent>(), new CommandTargetComponent {targetEntity = ent});
//add the component here
                }

Also make your compent data a float3, not a Vector.

Cool! I think I understand it a bit more now. I was wondering about the different ghosts before but didn’t quite understand. I changed the component data to float3. I wanted to before but didn’t quite understand how to get the magnitude of float3’s, but I’ve changed that now.

It does add a new problem tho. The movement rubberbands a lot now. It predicts the movement client-side then kind of goes back to where it was and then gets the server-side information and moves back to where it should be.
How come it isn’t syncing correctly?
Thanks a lot for your help SebLazyWizard. I haven’t really used forums before and you’re a great help!
I will, of course, try to solve it myself :wink:

Debug the final translation data in the system and check for differences between server and client. Are you applying some latency to the simulation? If yes then that’s likely caused by that, cause there’s still a bug related to it.

It looks like you are updating the MoveVelocity component in the prediction group, when doing that you have to make sure the velocity is synced or you will get rubberbanding.
The way prediction works it to restore the server state to all replicated fields, then run the simulation from the restored tick to the current server tick. If you update a non-synced field in the prediction group you will update it multiple times every frame which will result in the client moving way to fast and then snapping back to the servers position.

Thank you Tim Johansson and SebLazyWizard! The movement is correctly predicted now. All I had to do was add a field for the velocity value, with quantization and interpolation, on the ghost authoring script. I have the occasional jitter when stressing it with latency above 100ms, but for the rest it works perfectly.

So to recap for myself:
-I need to send the inputs from client to server.
-Add the moveVelocity component, with [GenerateAuthoringComponent] and [GhostDefaultValue], to the prefab. This way it get’s added to the server- and client-ghost.
-Make sure it actually has a field with quantization and interpolation so that the value syncs between server and client.
-Use the moveVelocity to move the player in the MoveCubeSystem.

Is that all correct? Many thanks!

If you don’t need the velocity component to be synced on interpolated clients (which is the case from just the code you posted), then only send it to predicted ghosts, that saves a bit bandwidth.

Yeah, ok. I can’t just sync everything on the player. Thank you!

I thought I wouldn’t have to sync the velocity since it would only change through the input, but it has the desired effect when synced. I think it would also make more sense to be synced if the velocity were to change from something else than the input, like another player bumping you or something.