My Menu System

Thanks to Sams lectures and guidance I was able to modify my Menu developed in this course to include character selection.

Demo here

Thanks for the great courses gamedev.tv team.

Do you mind if I share your awesome work on twitter and our facebook group?

I have since added a “Solo” button so I can start my game in Single person mode. I made a slightly longer video below.

Please feel free to use them as you like.

Demo here

Thanks for the courses. It really is helping achieve my ‘Bucket List’ item of releasing an Indie game (long way to go yet :slight_smile: ).

Cheers

Hi dear is it possible to tell me how you change character?

Happy to, let me clean up some code so I can share it…

1 Like

Please remember this may not be the “Right” way to go about it… BUT my this is my approach after reading this post:

I needed to find a place to pass data around when loading/travelling to other levels. So I started investigating the problem on google, the most populate response is to use a custom PlayerState class and a custom structure that contains the information you want to pass around.

The PlayerState is already part of (built into UE4) your player controllers and part of the GameMode;

So I first created a common.h that contains an enumerator of my character choices, and the custom structure that will contain information about my characters. At the moment I am only keeping the “Pawn” type and a couple of startup weapon flags, but I will later extend it to remember the customisation of the Character, skin colour, face, hair etc.

<Common.h>

#include "CoreMinimal.h"
#include "Common.generated.h"

UENUM(BlueprintType)
enum class ETypeOfPawn : uint8
{
	None,
	HunterAdventure,
	ArianaS1,
	ArianaS2,
	ArianaS3,
	ArianaWP,
	ArianaWP2,
	ArianaWP3
};

USTRUCT(BlueprintType)
struct FPayerPawnData
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerPawnData")
	ETypeOfPawn Type;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerPawnData")
	bool Sword;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerPawnData")
	bool Handgun;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerPawnData")
	bool Rifle;
};

Now that I had my structure sorted, I created my custom “PlayerState” class as follows (my game is called “Broken Adventure” hence the BA prefix (I wish I had started out with the small prefix but alas most of my custom classes have “BrokenAdeventure” which I got really sick of typing)…

BAPlayerState.h

#pragma once

#include "CoreMinimal.h"
#include "Public/Common.h"
#include "GameFramework/PlayerState.h"
#include "BAPlayerState.generated.h"

/**
 * 
 */
UCLASS()
class BROKENADVENTURE_API ABAPlayerState : public APlayerState
{
	GENERATED_BODY()


public:
	ABAPlayerState(); 

	virtual void CopyProperties(APlayerState* PlayerState) override;

	virtual void OverrideWith(APlayerState* PlayerState) override;

	// UPROPERTY(BlueprintReadOnly, Replicated, Category = "PawnConfiguration")
	UPROPERTY(BlueprintReadOnly, Category = "PawnConfiguration")
FPayerPawnData CurrentPawnData;

	UFUNCTION(BlueprintCallable)
	void SetPawnType(ETypeOfPawn newType);

	UFUNCTION(BlueprintCallable)
	void SetStartWithSword(bool value);

	UFUNCTION(BlueprintCallable)
	void SetStartWithHandGun(bool value);

	UFUNCTION(BlueprintCallable)
	void SetStartWithRifle(bool value);
}

BAPlayerState.cpp

#include "BAPlayerState.h"
#include "Net/UnrealNetwork.h"

// Constructor
ABAPlayerState::ABAPlayerState() : APlayerState()
{
}

// Called by Unreal Engine when "Travelling" to copy the current state - I assume this is "pre-travel"
void ABAPlayerState::CopyProperties(APlayerState * PlayerState)
{
	Super::CopyProperties(PlayerState);

   // Debug notices so I know whats calling what...
	if (Role == ROLE_Authority)
		GEngine->AddOnScreenDebugMessage(5, 2.0f, FColor::Red, "ServerCall");
    else
		GEngine->AddOnScreenDebugMessage(5, 2.0f, FColor::Red, "ClientCall");

    // If we have a PlayerState lets cast it to our custom class
	if (PlayerState)
	{
		ABAPlayerState* BAPlayerState = Cast<ABAPlayerState>(PlayerState);

            // check the cast worked....
		if (BAPlayerState)
		{
                    // Copy over the current values to the new "copy to" pointer object supplied by the engine
			BAPlayerState->CurrentPawnData = CurrentPawnData;
		}
	}
}

// Called by Unreal Engine when "Travelling" to know what to copy - I assume this is "post-travel"
void ABAPlayerState::OverrideWith(APlayerState * PlayerState)
{
	Super::OverrideWith(PlayerState);

   // Debug notices so I know whats calling what...
	if (Role == ROLE_Authority)
		GEngine->AddOnScreenDebugMessage(5, 2.0f, FColor::Red, "ServerCall");
	else
		GEngine->AddOnScreenDebugMessage(5, 2.0f, FColor::Red, "ClientCall");
	

    // If we have a PlayerState lets cast it to our custom class
	if (PlayerState)
	{
		ABAPlayerState* BAPlayerState = Cast<ABAPlayerState>(PlayerState);

            // check the cast worked....
		if (BAPlayerState)
		{
                    // replace our local variable with the one supplied by the server
			CurrentPawnData = BAPlayerState->CurrentPawnData;
		}
	}
}

// Set the pawn type from either C++ or Blueprint
void ABAPlayerState::SetPawnType(ETypeOfPawn newType)
{
	if (Role == ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(5, 2.0f, FColor::Red, "ServerCall");
	}
	if (Role != ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(5, 2.0f, FColor::Red, "ClientCall");
	}

	if (Role < ROLE_Authority)
	{
		SetPawnType(newType);
		return;
	}

	CurrentPawnData.Type = newType;
}

// Set the spawn with Sword flag from either C++ or Blueprint
void ABAPlayerState::SetStartWithSword(bool value)
{
	CurrentPawnData.Sword = value;

}

// Set the spawn with Hanggun flag from either C++ or Blueprint
void ABAPlayerState::SetStartWithHandGun(bool value)
{
	CurrentPawnData.Handgun = value;
}

// Set the spawn with Rifle flag from either C++ or Blueprint
void ABAPlayerState::SetStartWithRifle(bool value)
{
	CurrentPawnData.Rifle = value;
}

With that done, I compiled and ensured I had the ABAPlayerState selected as my PlayerState class within UE4:

image

Next I implemented the UE4 stuff to call the PlayerState function to set/change the “selected” player. I introduced the Male and Female button to the menu, moved the menu to the right of the screen, created a “stage” to show the selected character on and placed the default character on the stage (I tagged the Actor with a “DisplayActor” tag so I can find it in a loop later on)

I created a Character BP what consists of ONLY a Camera and that if you look at the above image is my “DefaultPawn” (CharacterSelect is the BP name, opps forgot the prefix)

In the MainMenu.cpp I added the a “SelectedPawnType” variable to track the displayed pawn and following two functions:

MainMenu.h (additions, no point dumping the whole file in here)

protected:
	UFUNCTION(BlueprintImplementableEvent, Category = "GameState")
	void OnCharacterSelect(ETypeOfPawn PawnType);

private:
	ETypeOfPawn SelectedPawnType;

	UFUNCTION()
	void SelectFemaleCharacter();

	UFUNCTION()
	void SelectMaleCharacter();

MainMenu.cpp (additions, no point dumping the whole file in here)

void UMainMenu::SelectFemaleCharacter()
{
    switch (SelectedPawnType)
    {
        case ETypeOfPawn::HunterAdventure:
        case ETypeOfPawn::None:
                OnCharacterSelect(ETypeOfPawn::ArianaWP);
                SelectedPawnType = ETypeOfPawn::ArianaWP;
                break;
        case ETypeOfPawn::ArianaWP:
                OnCharacterSelect(ETypeOfPawn::ArianaWP2);
                SelectedPawnType = ETypeOfPawn::ArianaWP2;
                break;
        case ETypeOfPawn::ArianaWP2:
                OnCharacterSelect(ETypeOfPawn::ArianaWP3);
                SelectedPawnType = ETypeOfPawn::ArianaWP3;
                break;
        case ETypeOfPawn::ArianaWP3:
                OnCharacterSelect(ETypeOfPawn::ArianaWP);
                SelectedPawnType = ETypeOfPawn::ArianaWP;
                break;
    }
}

void UMainMenu::SelectMaleCharacter()
{
        OnCharacterSelect(ETypeOfPawn::HunterAdventure);
        SelectedPawnType = ETypeOfPawn::HunterAdventure;
}

IF the player clicks the Female button multiple times we switch between three different characters. The OnCharacterSelect event is used to tell the MainMenu Blueprint that a selection change was made and passes a variable to indicate the newly selected “type of Pawn”.

Which brings us to the MainMenu Blueprint:

The blueprint has two Variables:
ActorType of type “Actor Class” (Purple node)
Tags of type “Name” array

Tags has one element in it containing the value “DisplayActor”

So let me explain this a little:
When the MainMenu cpp called OnCharacterSelect

  1. Find All actors on the level with a tag of “DisplayActor”
  2. Loop through all found actors
  3. Delete Actor from level

When that is complete:
Use a Switch Statement on the ETypeOfPawn enumerator defined in our common.h file. For the matching “type of pawn” passed in with the OnCharacterSelect event, assign the class to the “ActorType” blueprint variable.

Next Spawn an actor by passing in the ActorType variable, use a ‘Make Transformation’ to position and rotate the actor onto the stage correctly.

Then we use the Tags variable to assign the “DisplayActor” tag so we know which actor to delete if the player changes their selection…

NEXT is the GameMode (if you’re still with me… Woot for you!!)

So what we need is a way to know which Enumeration value relates to which Character (Blueprint in my case). We also need to make sure we spawn the correct Character when the Player moves to a level that needs his or her toon to be displayed.

First thing first the easy one:
I used a TMAP type to build a dictionary lookup to match ENUM to Class types like such:

TMap<ETypeOfPawn, UClass*> PawnTypes;

I then populated it in the GameMode constructor (again, this is probably NOT most efficient way to do this, but hey its working for me) - See the below cpp file for how I did it.

Next to find a way to Spawn the character: So our friend Google let me in on a “GameMode” function named
GetDefaultPawnClassForController - which “Returns default pawn class for given controller”. The function is called when a player spawns.

What is harder to find is that as of UE 4.8 you can no longer override the function directly because it was making it super easy for cheaters to do their thing. BUT it caused a bit of push back (or so I read) and shortly after the UE team opened up GetDefaultPawnClassForController_Implementation to be overriden and as it can only be called on the server it removed the cheaters easy win (score one for the good guys).

So my BrokenAdventureGameMode.h (oh these long names… :wink: ) file is as such:

#include "CoreMinimal.h"
#include "Public/Common.h"
#include "GameFramework/GameModeBase.h"
#include "BrokenAdventureGameMode.generated.h"

UCLASS(minimalapi)
class ABrokenAdventureGameMode : public AGameModeBase
{
	GENERATED_BODY()

public:
	ABrokenAdventureGameMode();

private:
	TMap<ETypeOfPawn, UClass*> PawnTypes;

protected:
	// virtual UClass* GetDefaultPawnClassForController(AController* InController) override;
	virtual UClass* GetDefaultPawnClassForController_Implementation(AController* InController) override;
	
};

And the BrokenAdventureGameMode.CPP file is

#include "Public/GameMode/BrokenAdventureGameMode.h"
#include "Public/Common.h"
#include "Public/Characters/BaseCharacter/BAPlayerState.h"
#include "Public/Characters/BaseCharacter/BrokenAdventureCharacter.h"
#include "Public/PlayerController/BrokenAdventurePlayerController.h"
#include "UObject/ConstructorHelpers.h"

ABrokenAdventureGameMode::ABrokenAdventureGameMode()
{
	// set default pawn class to our Blueprinted character
	
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/Characters/CharacterSelect/CharacterSelect"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnHunterBPClass(TEXT("/Game/Characters/HunterAdventurer/Blueprints/BP_HunterAdventure"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnArianaBPClassS1(TEXT("/Game/Characters/Ariana/Blueprints/BP_ArianaS1_Character"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnArianaBPClassS2(TEXT("/Game/Characters/Ariana/Blueprints/BP_ArianaS2_Character"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnArianaBPClassS3(TEXT("/Game/Characters/Ariana/Blueprints/BP_ArianaS3_Character"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnArianaBPClassWP(TEXT("/Game/Characters/Ariana/Blueprints/BP_ArianaWP_Character"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnArianaBPClassWP2(TEXT("/Game/Characters/Ariana/Blueprints/BP_ArianaWP2_Character"));
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnArianaBPClassWP3(TEXT("/Game/Characters/Ariana/Blueprints/BP_ArianaWP3Character"));

	// Set the default pawn
	if (PlayerPawnBPClass.Class != NULL)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}

	// Set the Hunter Class
	if (PlayerPawnHunterBPClass.Class != NULL)
	{
		// Always default to some character type, None is just used to determine 
		// if the USER changed selection when returning to the Mainmenu or back to the Lobby....
		PawnTypes.Add(ETypeOfPawn::None, (UClass*)PlayerPawnHunterBPClass.Class); 
		PawnTypes.Add(ETypeOfPawn::HunterAdventure, (UClass*)PlayerPawnHunterBPClass.Class);
	}

	// Set the Default
	if (PlayerPawnArianaBPClassS1.Class != NULL)
	{
		PawnTypes.Add(ETypeOfPawn::ArianaS1, (UClass*)PlayerPawnArianaBPClassS1.Class);
	}

	if (PlayerPawnArianaBPClassS2.Class != NULL)
	{
		PawnTypes.Add(ETypeOfPawn::ArianaS2, (UClass*)PlayerPawnArianaBPClassS2.Class);
	}

	if (PlayerPawnArianaBPClassS3.Class != NULL)
	{
		PawnTypes.Add(ETypeOfPawn::ArianaS3, (UClass*)PlayerPawnArianaBPClassS3.Class);
	}

	// Weapon Ariana
	if (PlayerPawnArianaBPClassWP.Class != NULL)
	{
		PawnTypes.Add(ETypeOfPawn::ArianaWP, (UClass*)PlayerPawnArianaBPClassWP.Class);
	}

	if (PlayerPawnArianaBPClassWP2.Class != NULL)
	{
		PawnTypes.Add(ETypeOfPawn::ArianaWP2, (UClass*)PlayerPawnArianaBPClassWP2.Class);
	}

	if (PlayerPawnArianaBPClassWP3.Class != NULL)
	{
		PawnTypes.Add(ETypeOfPawn::ArianaWP3, (UClass*)PlayerPawnArianaBPClassWP3.Class);
	}

}

UClass * ABrokenAdventureGameMode::GetDefaultPawnClassForController_Implementation(AController * InController)
{
	UWorld* MyWorld = GetWorld();
	if (MyWorld == nullptr) {
		UE_LOG(LogTemp, Error, TEXT("No World! - Spawning the default pawn"));
		return DefaultPawnClass;
	}

	FString CurrentMapName = MyWorld->RemovePIEPrefix(MyWorld->GetMapName());

	if(CurrentMapName.Equals(TEXT("CharSelect"), ESearchCase::IgnoreCase))
	{
		return DefaultPawnClass;
	}


	if (InController == nullptr)
	{
		UE_LOG(LogTemp, Error, TEXT("In Controller doesnt seem to exist! - Spawning the hunter (the default player pawn)"));
		return PawnTypes[ETypeOfPawn::HunterAdventure];
	}

	ABrokenAdventurePlayerController* PlayerController = Cast<ABrokenAdventurePlayerController>(InController);

	if (PlayerController)
	{
		UE_LOG(LogTemp, Error, TEXT("Player Controller Found!"));
		UClass* PawnClass = PawnTypes[Cast<ABAPlayerState>(InController->PlayerState)->CurrentPawnData.Type];

		if (PawnClass)
			return PawnClass;
		else
			return PawnTypes[ETypeOfPawn::HunterAdventure];
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("The Player Controller doesnt seem to exist! - Spawning the hunter (the default player pawn)"));
		return DefaultPawnClass;
	}
}

If you are paying attention I have 6 “Ariana” types in my ENUM but I only swap between three in my MainMenu.cpp, that’s because I haven’t finished the character Animation BPs yet.

NOW the caveat (yes, yes I know its at the end. But lets just put that down to my evil ways) I was so excited to get this running I wanted to share it. I have NOT tested this fully in multiplayer mode. It is my next step but as I was asked to share I figure if its not quite right, it has to be close. I will update this post if I change things to get it working in multiplayer. But for now it works ina single player mode and I “THINK” it will work in multiplayer mode, at lets it gets me into the Lobby in the selected character so it has to be almost right…

I hope this helps…

Cheers

1 Like

Sorry Sam, I didn’t put my response in a “reply” so I’m not sure you would have been notified of it. So just in case, I added my apology :wink:

1 Like

your code is a similar bonus of class thanks a lot dear I’m in the season 4 of the course. your code cause I back to the third season and start again implementing again the third season to reach your model.
thanks

Cross posted on facebook and twitter.

Thanks Sam.

Hello can you help me?
I want to set Player custom Name in PlayerState but it’s not work
I almost same UI “Host” and “Join” button and Editable Text for PlayerName.
I want set PlayerName in Editable Text then press “Host” or “Join” and Name is stored in PlayerState and travel this data into another level
but
When I change the level Data is lost. I am using virtual void CopyProperties() but it’s not work
I can’t find where and how PlayerState connect GameMode and GameInstance?