Thanks to Sams lectures and guidance I was able to modify my Menu developed in this course to include character selection.
Thanks for the great courses gamedev.tv team.
Thanks to Sams lectures and guidance I was able to modify my Menu developed in this course to include character selection.
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.
Thanks for the courses. It really is helping achieve my âBucket Listâ item of releasing an Indie game (long way to go yet ).
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âŚ
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:
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
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âŚ
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⌠) 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
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
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
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?
Sorry Levani, I didnât receive a notification to you request for help. Its been a few months now. Do you still need help or have you worked it out?
Thank you Zanoroy, I have solved it