Gameplay Debugger UI does not appear when pressing apostrophe key (’) in PIE in the [ShooterGame][1] example when Run Dedicated Server is checked in the Play menu.
Some other observations:
- Gameplay Debugger UI appears if Run Dedicated Server is not checked.
- Gameplay Debugger UI appears in an empty project even if Run Dedicated Server is not checked.

Related Bug: UE-74446
Bug is tracked by UE-74446.
The Issue is caused by the Replication Graph code in ShooterGame example!
We solved a similar problem with this PR: https://github.com/EpicGames/UnrealEngine/pull/7720
bAlwaysRelevant
was false in AGameplayDebuggerCategoryReplicator
, so the replication graph didn’t replicate it to the client.
Thanks to @FilipeTessaro and @JonasKjellstrom for pointing me in the right direction. The bug is in UShooterReplicationGraph::OnGameplayDebuggerOwnerChange. The lambda in the function incorrectly captured and used OldOwner when it should have used Controller.
Below in the broken and fixed code.
BROKEN
void UShooterReplicationGraph::OnGameplayDebuggerOwnerChange(AGameplayDebuggerCategoryReplicator* Debugger, APlayerController* OldOwner)
{
auto GetAlwaysRelevantForConnectionNode = [&](APlayerController* Controller) -> UShooterReplicationGraphNode_AlwaysRelevant_ForConnection*
{
if (OldOwner)
{
if (UNetConnection* NetConnection = OldOwner->GetNetConnection())
{
if (UNetReplicationGraphConnection* GraphConnection = FindOrAddConnectionManager(NetConnection))
{
for (UReplicationGraphNode* ConnectionNode : GraphConnection->GetConnectionGraphNodes())
{
if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = Cast<UShooterReplicationGraphNode_AlwaysRelevant_ForConnection>(ConnectionNode))
{
return AlwaysRelevantConnectionNode;
}
}
}
}
}
return nullptr;
};
if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = GetAlwaysRelevantForConnectionNode(OldOwner))
{
AlwaysRelevantConnectionNode->GameplayDebugger = nullptr;
}
if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = GetAlwaysRelevantForConnectionNode(Debugger->GetReplicationOwner()))
{
AlwaysRelevantConnectionNode->GameplayDebugger = Debugger;
}
}
FIXED
void UShooterReplicationGraph::OnGameplayDebuggerOwnerChange(AGameplayDebuggerCategoryReplicator* Debugger, APlayerController* OldOwner)
{
auto GetAlwaysRelevantForConnectionNode = [&](APlayerController* Controller) -> UShooterReplicationGraphNode_AlwaysRelevant_ForConnection*
{
if (Controller)
{
if (UNetConnection* NetConnection = Controller->GetNetConnection())
{
if (UNetReplicationGraphConnection* GraphConnection = FindOrAddConnectionManager(NetConnection))
{
for (UReplicationGraphNode* ConnectionNode : GraphConnection->GetConnectionGraphNodes())
{
if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = Cast<UShooterReplicationGraphNode_AlwaysRelevant_ForConnection>(ConnectionNode))
{
return AlwaysRelevantConnectionNode;
}
}
}
}
}
return nullptr;
};
if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = GetAlwaysRelevantForConnectionNode(OldOwner))
{
AlwaysRelevantConnectionNode->GameplayDebugger = nullptr;
}
if (UShooterReplicationGraphNode_AlwaysRelevant_ForConnection* AlwaysRelevantConnectionNode = GetAlwaysRelevantForConnectionNode(Debugger->GetReplicationOwner()))
{
AlwaysRelevantConnectionNode->GameplayDebugger = Debugger;
}
}
Let this be a lesson to everyone: & lambda capture is evil.