Fast Paced Multiplayer Possible?

I have tried to bring back one of my old game ideas to work on so I could get familiarized with DOTS, and I still can’t seem to get it to work right.

Can anyone tell if it’s possible to have a fast paced multiplayer game with DOTS? My idea is just have a small arena and two goals and some kind of puck in the middle. The players would stay on their side and just shoot/deflect the puck. But even marking all the ghosts as Predicted and having interpolation, it seems very jitter-ly even with 50ms ping.

Here is the video of how it looks playing it with ping
https://vimeo.com/404385075

Is there anything else I can configure on the Ghost scripts to make this better? I thought since the movement is so simple and linear, with less than 100 ping it shouldn’t be jittery since the prediction wouldn’t be wrong.

The movement code looks like this,
Player:

public class FreezePlayerRotationSystem : JobComponentSystem {
    protected override JobHandle OnUpdate(JobHandle inputDeps) {
        inputDeps.Complete();

        Entities.ForEach((
            ref PhysicsMass pm,
            ref PhysicsVelocity pv,
            ref Rotation rotation,
            ref Translation translation,
            in NetPlayer netPlayer
        ) => {
            pv.Angular = new float3();
            pm.InverseInertia = new float3();

            translation.Value.y = 0.3188247f;
            rotation.Value = quaternion.EulerXYZ(math.radians(-90), math.radians(netPlayer.team == 0 ? 180 : 0), 0);
        }).Run();

        return inputDeps;
    }
}

[UpdateInGroup(typeof(GhostPredictionSystemGroup))]
public class MoveMovementSystem : JobComponentSystem {
    protected override JobHandle OnUpdate(JobHandle inputDeps) {
        inputDeps.Complete();

        var worldForward = new float3(0, 0, 1);
        var worldUp = new float3(0, 1, 0);
        var group = World.GetExistingSystem<GhostPredictionSystemGroup>();
        var tick = group.PredictingTick;
        var deltaTime = Time.DeltaTime;

        Entities.ForEach((
            ref MovementData movementData,
            ref PhysicsMass pm,
            ref PhysicsVelocity pv,
            ref PredictedGhostComponent prediction,
            ref Rotation rotation,
            ref Translation translation,
            in DynamicBuffer<MovementInput> inputBuffer,
            in NetPlayer netPlayer
        ) => {
            if (!GhostPredictionSystemGroup.ShouldPredict(tick, prediction))
                return;

            MovementInput input;
            inputBuffer.GetDataAtTick(tick, out input);

            float speed = movementData.speed;
            float3 velocityChange = new float3(input.horizontal * speed * deltaTime, 0, input.vertical * speed * deltaTime * (netPlayer.team == 0 ? -1 : 1));

            if (velocityChange.x != 0 || velocityChange.z != 0) {
                pv.Linear += velocityChange;
            }

            pv.Angular = new float3();
            pm.InverseInertia = new float3();

            translation.Value.y = 0.3188247f;
            rotation.Value = quaternion.EulerXYZ(math.radians(-90), math.radians(netPlayer.team == 0 ? 180 : 0), 0);
        }).Run();

        return inputDeps;
    }
}
public class BallSystem : JobComponentSystem {
    protected override JobHandle OnUpdate(JobHandle inputDeps) {
        Entities.WithAll<BallData>().ForEach((ref Rotation rotation, ref Translation translation, ref PhysicsMass pm, ref PhysicsVelocity pv) => {
            pm.InverseInertia = new float3();
            pv.Angular = new float3();
            rotation.Value = quaternion.EulerXYZ(math.radians(-90), 0, 0);
            translation.Value.y = -0.03616257f;
        }).Run();

        return inputDeps;
    }
}

[UpdateInGroup(typeof(GhostPredictionSystemGroup))]
public class BallPredictionSystem : JobComponentSystem {
    protected override JobHandle OnUpdate(JobHandle inputDeps) {
        inputDeps.Complete();

        var worldForward = new float3(0, 0, 1);
        var worldUp = new float3(0, 1, 0);
        var group = World.GetExistingSystem<GhostPredictionSystemGroup>();
        var tick = group.PredictingTick;
        var deltaTime = Time.DeltaTime;

        Entities.ForEach((
            ref PhysicsVelocity pv,
            ref PredictedGhostComponent prediction,
            in BallData ballData
        ) => {
            if (!GhostPredictionSystemGroup.ShouldPredict(tick, prediction))
                return;

            if (math.length(pv.Linear) > ballData.maxVelocity) {
                pv.Linear = math.normalize(pv.Linear) * ballData.maxVelocity;
            }
        }).Run();

        return inputDeps;
    }
}

I can’t tell you exactly what is wrong with your code, however I can tell you that DOTS is definitely capable of what you are trying to achieve. It seems that you are relying on the physics here, you may have some sort of race condition with the interpolation and the prediction. I remember having similar issues even outside of DOTS during a gamejam a while ago, it was an issue with collision detection and the order of operations the network was dealing with the data. Sorry cannot be of more help to your specific situation, however the answer to the main question is yes, DOTS is more than capable.

1 Like

@mhernandez88 Thank you for the reply!

I will keep trying then to figure it out since you say it is possible. I have a quick question though, in that kind of physics simulation, is it correct to have all players and the object they interact with be “predicted”? And should all of them have physicsVelocity and translation components have Linear and Value respectively be default ghost fields that are synced and interpolated?

Running physics in prediction is currently not possible out of the box.
Also keep in mind that the prediction systems run multiple times within a single frame on the client, whereas on the server they run just once per frame/tick. So your physics velocity runs out of sync anyway.

The only thing you can do right now is to disable the physics velocity component on the client side ghost to make that system run on the server only. This way it’s no longer predicted but interpolated.

Not sure why you constantly reset the angular velocity and rotation, you can just set the Intertia Tensor in the Physics Body script to “Infinity” in order to prevent the physics system to affect the body’s rotation.

And why the heck are you fixing it’s position on the y axis aswell?
When working with physics you should move the bodies via their velocities only.

Sorry, those were result of me testing how to do things such as disable the rotation on collision. And I fixed the y position because sometimes the ball would just fly up into infinity after some weird collisions.

Thank you for the info. I’ll try that and disable the physics velocity component!

@SebLazyWizard One issue with that approach though now that I think of it, is that since the client doesn’t know the ball’s velocity, it wouldn’t be able to properly collide with the predicted player

The server just synces back the translation and rotation of the players and the ball and since the physics component is on the server only, the physics simulation also runs on the server only.

I removed the physic components from the clients, and even then the ball still lags or seems to lose frames with a little ping. :confused:

https://vimeo.com/404817367

Interpolation with implied lag is still buggy, it’s not related to your code as long as it works fine with zero delay.

Ah ok, thank you!