As a quick functional summary - I’m experimenting with a card game where clicking a board means one of two things:
- Drawing a card if no card is selected
- Play a card and create a token if a card is selected (and delete the card)
I’m using a single IInputComponent
for all my Input data:
[GhostComponent(PrefabType = GhostPrefabType.AllPredicted)]
public struct BoardInput : IInputComponentData
{
[GhostField] public InputEvent DrawCard;
[GhostField] public int PlayCardGhostId; // -1 means no played card
}
I set my input in a GhostInputSystemGroup
ISystem
:
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);
var networkTime = SystemAPI.GetSingleton<NetworkTime>();
var newBoardInput = new BoardInput();
newBoardInput.PlayCardGhostId = -1;
var spawnedToken = false;
foreach (var (board, clickedComponent, spriteRenderer, boardEntity) in SystemAPI.Query<RefRO<BoardTag>,
RefRW<ClickedComponent>,
RefRW<SpriteRendererComponent>>().WithEntityAccess())
{
// Check for spawning cards
foreach( var (card, ghost, cardEntity) in SystemAPI.Query<CardComponent, GhostInstance>().WithAll<SpawningCardTag, GhostOwnerIsLocal>().WithEntityAccess())
{
spawnedToken = true;
newBoardInput.PlayCardGhostId = ghost.ghostId;
Debug.Log($"NewBoardInput Play Card assigned with id {newBoardInput.PlayCardGhostId}");
}
// For now, if not spawning a token, then we are drawing a card
if (!spawnedToken)
{
newBoardInput.DrawCard.Set();
}
// Remove Process Clicked
ecb.RemoveComponent<ClickedComponent>(boardEntity);
}
// Documentation claims Set() resets every frame, but thats actually wrong. InputEvent needs to be reset every frame:
// https://discussions.unity.com/t/inputevent-does-not-fire-exactly-once/929531/4
foreach (var boardInput in SystemAPI.Query<RefRW<BoardInput>>().WithAll<GhostOwnerIsLocal>())
{
if (spawnedToken)
{
Debug.Log($"Assigning board input play card ghost id - {newBoardInput.PlayCardGhostId} on tick {networkTime.ServerTick.TickValue}");
}
boardInput.ValueRW = newBoardInput;
}
ecb.Playback(state.EntityManager);
}
I process the input in a PredictedSimulationSystemGroup
ISystem
:
public void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);
var networkTime = SystemAPI.GetSingleton<NetworkTime>();
var DebugWorld = state.World.IsServer() ? "Server" : "Cient";
if (!networkTime.IsFirstTimeFullyPredictingTick) return;
foreach (var (boardInput, ghostOwner) in SystemAPI.Query<BoardInput, GhostOwner>().WithAll<Simulate>())
{
if (boardInput.PlayCardGhostId == -1)
{
Debug.Log("Play card ghost id is -1 or 0 so no card played");
return;
}
Debug.Log($"Play Card {boardInput.PlayCardGhostId} set on tick {networkTime.ServerTick.TickValue}");
// Get token for card
foreach(var (playedCard, ghost, cardEntity) in SystemAPI.Query<CardComponent, GhostInstance>().WithAll<Simulate>().WithEntityAccess())
{
if (ghost.ghostId != boardInput.PlayCardGhostId) {
Debug.Log($"Ghost id {ghost.ghostId} does not equal played ghost id {boardInput.PlayCardGhostId} on tick {networkTime.ServerTick.TickValue} on {DebugWorld}");
continue;
}
Debug.Log($"Play Card ghost id {ghost.ghostId} found ghost set on tick {networkTime.ServerTick.TickValue} on {DebugWorld}");
var tokenEntity = ecb.Instantiate(playedCard.TokenPrefab);
ecb.SetName(tokenEntity, "TokenEntity");
ecb.SetComponent(tokenEntity, new GhostOwner { NetworkId = ghostOwner.NetworkId });
// Place appopriately
var spawnPosition = new float3(0, 0, -1); // TODO: Get mouse position
ecb.SetComponent<LocalTransform>(tokenEntity, LocalTransform.FromPosition(spawnPosition));
if (state.World.IsServer())
{
ecb.AddComponent<DestroyEntityTag>(cardEntity);
}
}
}
ecb.Playback(state.EntityManager);
}
Problem
I’m experiencing an issue when playing a card where occasionally (about 15% of the time) the server appears to completely miss a played card which is triggered via PlayCardGhostId
being set to a positive number.
“Its Working” logs:
(Both client and server see input)
(Just server sees input first - I expected client to be ahead of server on prediction so it surprises me to see no client processing, but it still works)

Problematic log:
(Above I see a
Play card ghost id is -1 or 0 so no card played
log for this tick)
Other Info:
- All entities (card, token, board) are Owner Predicted ghosts with the same settings:
- My
BoardInput
InputEvent DrawCard
never misses and is also a “one off” event. The difference beingPlayCardGhostId
is just an int. There is a comment in myGhostInputSystemGroup
system about needing to setnew BoardInput();
on every frame. - Im unsure if using the
ghostId
to reference a played card is proper, but it was my first attempt. Regardless, the issue isn’t finding the associated ghost on the server, its that thePlayCardGhostId
simply seems like its never set on the expected server tick.