I have a Ghost entity that will use prediction on all clients. Some logic needs to run for the Ghost Owner and the Server (since they deal with input data that is not available on the other clients), and that logic produces some GhostField values that are propagated to the other clients and used for prediction.
The system currently looks like this:
public partial struct ApplyPlayerInputSystem : ISystem {
public void OnCreate(ref SystemState state) { }
public void OnUpdate(ref SystemState state) {
foreach (var (playerInputData, playerMovementState, entity) in
SystemAPI
.Query<
RefRO<PlayerInputData>,
RefRW<PlayerMovementState>
>()
.WithAll<Simulate>()
.WithEntityAccess()) {
if (state.World.IsServer() || SystemAPI.IsComponentEnabled<GhostOwnerIsLocal>(entity)) {
playerMovementState.ValueRW.MoveDirection = playerInputData.ValueRO.MoveDirection;
if (math.lengthsq(playerInputData.ValueRO.AimDirection) > 0) {
playerMovementState.ValueRW.AimDirection = playerInputData.ValueRO.AimDirection;
} else if (math.lengthsq(playerInputData.ValueRO.MoveDirection) > 0) {
playerMovementState.ValueRW.AimDirection = playerInputData.ValueRO.MoveDirection;
} else if (math.lengthsq(playerMovementState.ValueRW.AimDirection) == 0) {
playerMovementState.ValueRW.AimDirection = new(0, -1);
}
}
}
}
}
PlayerMovementState
is a GhostComponent
that is synchronized to all clients and used later when calculating entity movement. I have stripped some extraneous things out of this example to keep it brief.
The if (state.World.IsServer() || SystemAPI.IsComponentEnabled<GhostOwnerIsLocal>(entity))
check stuck out to me as something that I maybe shouldn’t have to do, so I’m wondering if I’m just missing the mark on how the systems are architected overall.
My current architecture involves all clients processing their own inputs and predicting the state, and the server receives inputs and determines the authoritative state which is then synchronized to all clients to use for prediction. I was originally synchronizing all players’ inputs to other players for prediction, but I wanted the server to “gate” the inputs so that the other clients don’t receive them until they’ve been determined to be valid. So the inputs (playerInput
) are really driving some derived state (playerMovementState
) that is calculated based on the actual player inputs.
Is this a reasonable pattern, or am I missing a simpler solution?