Playercontroller HUD issues

I have a playercontroller that cycles through some states on a dedicated server, as below:

.h

UENUM()
enum class PlayState
{
	None,
	Frontend,
	Playing,
	GameOver
};

UCLASS()
class ASTEROIDS_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()

public:

	AMyPlayerController();

	virtual void BeginPlay() override;

	void	SetPlayState(PlayState newState);
	PlayState GetPlayState() { return CurrentState; }

	UFUNCTION()
	void OnRep_PlayState();

protected:
	UPROPERTY(Replicated, ReplicatedUsing = OnRep_PlayState)
	PlayState	CurrentState;
}

.cpp

void AMyPlayerController::BeginPlay()
{
	Super::BeginPlay();

	//[Server]
	if (Role == ROLE_Authority)
	{
		// Server sets state, which is then replicated to clients
		SetPlayState(PlayState::Frontend);
	}
}


void AMyPlayerController::SetPlayState(PlayState newState)
{
	if (Role < ROLE_Authority)
	{
		// [All Clients]
		// HUD only on clients
		AMyHUD *pHUD = Cast<AMyHUD>(GetHUD());	// <- NULLPTR on client on first call
		if (pHUD)
		{
			// Change hud
		}
	}

	CurrentState = newState;
}

void AMyPlayerController::OnRep_PlayState()
{
	SetPlayState(CurrentState);
}

void AMyPlayerController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(AMyPlayerController, CurrentState);
}

Essentially, it sets the state on the server side (in BeginPlay), and then replicates to the clients via void AMyPlayerController::OnRep_PlayState();

This works fine, however, when the first call to change the HUD in SetPlayState(PlayState newState) always returns a nullptr for the HUD.

As a test I added a Tick(float dt) func:-

void AMyPlayerController::Tick(float deltaTime)
{
	Super::Tick(deltaTime);

	if (Role == ROLE_Authority)
	{
		if(GetPlayState() != PlayState::Playing)
			SetPlayState(PlayState::Playing);
	}
}

To test subsequent state changes, and then a valid HUD pointer is returned and the HUD appears.

Is there some delay in HUD creation? I would have expected it to be valid at BeginPlay(), but doesn’t appear to be.

Is there a better hook point to set the initial state when the HUD ptr within the playercontroller (client side) is valid?

(Hope that makes sense) :S

OK, looks like it the HUD isn’t created until the World Ticks (callstack below):

>	UE4Editor-Engine.dll!APlayerController::ClientSetHUD_Implementation(TSubclassOf<AHUD> NewHUDClass) Line 1253	C++
 	UE4Editor-Engine.dll!APlayerController::execClientSetHUD(UObject * Context, FFrame & Stack, void * const Z_Param__Result) Line 239	C++
 	UE4Editor-CoreUObject.dll!UFunction::Invoke(UObject * Obj, FFrame & Stack, void * const Z_Param__Result) Line 4643	C++
 	UE4Editor-CoreUObject.dll!UObject::ProcessEvent(UFunction * Function, void * Parms) Line 1464	C++
 	UE4Editor-Engine.dll!AActor::ProcessEvent(UFunction * Function, void * Parameters) Line 769	C++
 	UE4Editor-Engine.dll!FObjectReplicator::ReceivedRPC(FNetBitReader & Reader, const FReplicationFlags & RepFlags, const FFieldNetCache * FieldCache, const bool bCanDelayRPC, bool & bOutDelayRPC, TSet<FNetworkGUID,DefaultKeyFuncs<FNetworkGUID,0>,FDefaultSetAllocator> & UnmappedGuids) Line 1000	C++
 	UE4Editor-Engine.dll!FObjectReplicator::ReceivedBunch(FNetBitReader & Bunch, const FReplicationFlags & RepFlags, const bool bHasRepLayout, bool & bOutHasUnmapped) Line 833	C++
 	UE4Editor-Engine.dll!UActorChannel::ProcessBunch(FInBunch & Bunch) Line 2551	C++
 	UE4Editor-Engine.dll!UActorChannel::ReceivedBunch(FInBunch & Bunch) Line 2408	C++
 	UE4Editor-Engine.dll!UChannel::ReceivedSequencedBunch(FInBunch & Bunch) Line 358	C++
 	UE4Editor-Engine.dll!UChannel::ReceivedNextBunch(FInBunch & Bunch, bool & bOutSkipAck) Line 775	C++
 	UE4Editor-Engine.dll!UChannel::ReceivedRawBunch(FInBunch & Bunch, bool & bOutSkipAck) Line 456	C++
 	UE4Editor-Engine.dll!UNetConnection::ReceivedPacket(FBitReader & Reader) Line 2011	C++
 	UE4Editor-Engine.dll!UNetConnection::ReceivedRawPacket(void * InData, int Count) Line 1109	C++
 	UE4Editor-OnlineSubsystemUtils.dll!UIpNetDriver::TickDispatch(float DeltaTime) Line 289	C++
 	[Inline Frame] UE4Editor-Engine.dll!TMemberFunctionCaller<UNetDriver,void (__cdecl UNetDriver::*)(float)>::operator()(float &) Line 156	C++
 	[Inline Frame] UE4Editor-Engine.dll!UE4Tuple_Private::TTupleImpl<TIntegerSequence<unsigned int> >::ApplyAfter(TMemberFunctionCaller<UNetDriver,void (__cdecl UNetDriver::*)(float)> &&) Line 498	C++
 	[Inline Frame] UE4Editor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,TTypeWrapper<void> __cdecl(float)>::Execute(float) Line 617	C++
 	UE4Editor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float)>::ExecuteIfSafe(float <Params_0>) Line 679	C++
 	UE4Editor-Engine.dll!TBaseMulticastDelegate<void,float>::Broadcast(float <Params_0>) Line 977	C++
 	UE4Editor-Engine.dll!UWorld::Tick(ELevelTick TickType, float DeltaSeconds) Line 1448	C++
 	UE4Editor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 1618	C++
 	UE4Editor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 403	C++
 	UE4Editor-Win64-DebugGame.exe!FEngineLoop::Tick() Line 3967	C++
 	[Inline Frame] UE4Editor-Win64-DebugGame.exe!EngineTick() Line 62	C++
 	UE4Editor-Win64-DebugGame.exe!GuardedMain(const wchar_t * CmdLine, HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, int nCmdShow) Line 168	C++
 	UE4Editor-Win64-DebugGame.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow) Line 261	C++
 	[External Code]	

Adding a delay to the state switch solves the problem.

Out of interest, how did you change the hud to a new hud class? I’ve had some trouble with that and using ClientSetHUD didn’t seem to work.