Help with multiplayer game initialization

Hi, I’m working on my first multiplayer game and am having trouble initializing the game, specifically drawing the HUD. My debugging has really hit a dead end and I’m not knowledgeable enough to know what could be wrong. Here’s what I’m trying to do:

My gamemode class, AMatch_GameModeBase has these two functions in its header file (along with some other stuff obviously):

// AMatch_GameModeBase.h
private:

	// Called when a player successfully logs into the game.
	virtual void PostLogin(APlayerController* NewPlayer) override;

	// Sets up the game state, player pawns, and player controllers.
	void SetUpMatch();

	// An array of all connected player controllers.
	TArray<AMatch_PlayerController*> ConnectedPlayerControllers;

In the implementation file, I want to add the player controller of new players to an array when they log in. Then, if there are two players logged in (it’s a 2-player game), I want to set up the match, which calls setup functions in the game state, player pawn, and player controller classes.

// AMatch_GameModeBase.cpp
void AMatch_GameModeBase::PostLogin(APlayerController* NewPlayer)
{
    Super::PostLogin(NewPlayer);

    // If the connected player is has a match player controller...
    if (Cast<AMatch_PlayerController>(NewPlayer))
    {
        // Add that controller to the controller array
        ConnectedPlayerControllers.Add(Cast<AMatch_PlayerController>(NewPlayer));
    }

    // If two players have connected...
    if (ConnectedPlayerControllers.Num() == 2)
    {
        // Set up the match.
        SetUpMatch();
    }
}

void AMatch_GameModeBase::SetUpMatch()
{
    // Get a pointer to the game state.
    MatchGameState = Cast<AMatch_GameStateBase>(GetWorld()->GetGameState());

    // Begin the match.
    if (MatchGameState)
    {
        // Begin the first player's turn.
        MatchGameState->BeginMatch();
    }

    // Initialize both player pawns.
    Cast<AMatch_PlayerPawn>(ConnectedPlayerControllers[0]->GetPawn())->InitializePlayerPawn(0);
    Cast<AMatch_PlayerPawn>(ConnectedPlayerControllers[1]->GetPawn())->InitializePlayerPawn(1);

    // Initialize both player controllers.
    Cast<AMatch_PlayerController>(ConnectedPlayerControllers[0])->InitializePlayerController(0);
    Cast<AMatch_PlayerController>(ConnectedPlayerControllers[1])->InitializePlayerController(1);
}

The only thing that InitializePlayerController() does is set a local variable to the integer parameter. The problem I’m having is in the player pawn.

// AMatch_PlayerPawn.h
public:

	// Initializes values and sets up the HUD for this player once they connect.
	void InitializePlayerPawn(int PassedPlayerIndex);

	// Index used to differentiate players.
	UPROPERTY()
	int PlayerIndex;

This function should set the player index variable and call two functions from this player’s pawn’s controller’s HUD (that’s a mouthful):

// AMatch_PlayerPawn.cpp
void AMatch_PlayerPawn::InitializePlayerPawn(int PassedPlayerIndex)
{
    // Set the player index for this pawn.
    PlayerIndex = PassedPlayerIndex;

    // Get this pawn's controller.
    AMatch_PlayerController* PlayerController = Cast<AMatch_PlayerController>(UGameplayStatics::GetPlayerController(this, PlayerIndex));

    // If this pawn's player controller was found...
    if (PlayerController)
    {
        // Get this pawn's controller's HUD.
        AMatch_HUD* Match_HUD = Cast<AMatch_HUD>(PlayerController->GetHUD());

        if (Match_HUD)
        {
            // Create the base widget.
            Match_HUD->CreateBaseWidget(PlayerIndex);

            // Update the displayed name of this map.
            Match_HUD->UpdateMapNameHUD("Clockwork");
        }
    }
}

I think that this is where the problem is. When adding debug messages around the if (Match_HUD) line, it says that it returns false.

I’d really appreciate anyone’s help. I’ve tried everything I can think of: using BeginPlay(), using the Tick function to retry every frame, making them client, server, or multicast functions, but nothing has worked. I’m really stumped.

Thanks in advance!

A few things in this thread.

First, the engine already has an array of the player controllers, on the server. Why do you need to duplicate this? There may be a good reason, but when you can use what the engine already has, it generally ends up saving time on trying to keep things in sync.

Second, Pawns aren’t necessarily immediately created for, or possessed by player controllers. If you know that you’re getting into the PlayerPawn, though, then that’s probably not the problem in this particular case.

Third, servers don’t generally run with graphics, and thus won’t have a display at all for the pawns. There’s a special case when you use the magic “is both a server and one of the players” mode, in which case one of the pawns will have a local display, but the others won’t.

Fourth, the HUD isn’t actually auto-created for you, you’ll need to create it yourself.

Fifth, a Pawn doesn’t yet have a Controller until after the Possess process has completed. And Possession is a thing that happens on server, not on client. I remember spending a fair amount of time tracking down where the best place was to determine that I had both a Pawn and the corresponding PlayerController on a client (where there is only one playercontroller, but of course remote-controlled pawns for the other players) – I think the result was something on top of OnRep?

1 Like

First, the engine already has an array of the player controllers, on the server. Why do you need to duplicate this?

Really? I knew that the game state had an array of player states, but where can you access the player controllers?

Second, Pawns aren’t necessarily immediately created for, or possessed by player controllers. If you know that you’re getting into the PlayerPawn, though, then that’s probably not the problem in this particular case.

Yeah, I think HandleStartingNewPlayer() creates and possesses a pawn for each player when they connect by default, and I haven’t overridden it. During runtime, it’s telling me that the pawns are possessed by the correct player controllers.

Third, servers don’t generally run with graphics, and thus won’t have a display at all for the pawns. There’s a special case when you use the magic “is both a server and one of the players” mode, in which case one of the pawns will have a local display, but the others won’t.

I’m not really sure what that means.

Fourth, the HUD isn’t actually auto-created for you, you’ll need to create it yourself.

How? I thought that having the HUD set in your game mode and implementing the DrawHUD() function draws it for the player. Is that wrong?

Fifth, a Pawn doesn’t yet have a Controller until after the Possess process has completed. And Possession is a thing that happens on server, not on client.

I tried overriding the OnPossess() function in the player controller, but nothing changed. Is that what you mean?

I’m suggesting things I’ve run into when trying to make multiplayer startup work right on each client – whether those apply to your case or not, I don’t know! It’s things to check.

I’m still not sure which context you’re in when you say there’s no HUD. On the server instance, there will be an array of player controllers, and OnPossess() will happen, but there will be no graphics, no HUD. On the client instance, there will be one player controller only (the local player controller) and OnPossess() will not get called, but there will be graphics and a HUD. And, in a “listen server,” it’s a little bit of each, which is even more confusing :-/

PlayerIndex is local to the process – on a client, “player index 0” is always the “local player.” On a server, there’s more than one player, but there’s no HUD.

If you support multi-controller local multiplayer, there will be more than one “local” playercontroller and thus playerindex. Maybe I misunderstand when you say “multiplayer” game – is it “local” multiplayer (a la Smash Brothers) or “networked” multiplayer (a la Counter-Strike)? If it’s smash-style local multiplayer, then my suggestions probably aren’t applicable.

Note the “index” parameter:
image

The implementation for this node starts as follows:

class APlayerController* UGameplayStatics::GetPlayerController(const UObject* WorldContextObject, int32 PlayerIndex ) 
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
	{
		uint32 Index = 0;
		for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
		{

Hopefully this helps.

It’s a networked game, like online chess or an RTS.

Oh, right, duh. Haha, I forgot about that. Is the gamemode able to access every player controller that way?

What I mean is that this code:

AMatch_HUD* Match_HUD = Cast<AMatch_HUD>(PlayerController->GetHUD());

if (Match_HUD)
{

… fails. PlayerController is a pointer to this pawn’s player controller, and it’s valid. But for whatever reason, it can’t get the player controller’s HUD.

That code can run twice, once on the server, and once on the client, unless you have some other work done to only run it in a particular context.

Which of the calls is failing? Both of them? Server only?

If it returns NULL on the client (where I would expect it to succeed, assuming everything else is set up correctly): If you break out the call to a temporary variable before casting, is the the GetHUD() or the Cast that returns NULL?

Both fail at GetHUD(). If I use a listen server instead of a dedicated server, then the server-side client works correctly.

I think it has something to do with the timing of everything. I put this code in the Tick() function and it eventually succeeded, but that just created tons of new problems. Right now, this all executes when a second player logs in. Where else should it be?

As far as the HUD goes, I know it wont be in the viewport until its created even if its in the specified in the Game Mode. You can reliably create it in the Game Instance and call that function in the level when it opens. That will put it on the client’s screen without duplicating it for however many players you have.