How to spawn a custom blueprint pawn from c++

Hello every one,

I have started some weeks ago a game project with unreal engine, learning how to use this engine and how achieve my objectives with it.

today I start to hit some trouble that I don’t arrive to solve by my self.

before to speak about my trouble some background arround the project.

this project is based on twin stick template, I have handle to get it playable in multiplayer, after 1 week with the small base pawn of this template I have by a back of 7 low poly ship for this game, and created 7 blueprint ship for these one with a parent class in c++

i’m now handling the player spawn when he enter the arena I have success to made it with a temporary blueprint replacing my game mode, and a blueprint for the playerController, but i’m actually in progress to get these one in c++, and try to only use blueprint for my baseActor customisation with only some minimal logic in it.

now the trouble, on the Game mode blueprint I use handle starting player event to spawn a pawn for my player, based on the OptionString (color customisation, modele selection, weapon selection …).

here the blueprint: GameModeBP posted by anonymous | blueprintUE | PasteBin For Unreal Engine 4

the collapsed node are just a spawn actor with some base property change (like hide the new player to other one until his first move) before to return the BasePawn

and there the cpp file of the gameMode to replace this one:

#include "FreeForAllArenaGameMode.h"
#include "ArenaPlayerController.h"
#include "PawnShipBase.h"

#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"

void AFreeForAllArenaGameMode::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
{
    UE_LOG(LogTemp, Warning, TEXT("processing New player"));
    // store player controler?
    AArenaPlayerController *ArenaPC = (AArenaPlayerController *) NewPlayer;
    ArenaPC->ShipType = StringToEnum(EShipType, UGameplayStatics::ParseOption(OptionsString, "Shiptype"));
    UWorld *World = GetWorld();
    APawnShipBase *ShipToSpawn = nullptr;
    UClass *ShipClass = nullptr;
    //TODO: switch is fine cause we have not a large list of ship do factory here? else create a private function to store it
    switch (ArenaPC->ShipType)
    {
    case EShipType::Harbinger:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Harbinger.BP_Harbinger"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    case EShipType::Interceptor:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Interceptor.BP_Interceptor"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    case EShipType::Icarus:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Icarus.BP_Icarus"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    case EShipType::Avadora:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Avadora.BP_Avadora"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    case EShipType::ThunderStorm:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_ThunderStorm.BP_ThunderStorm"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    case EShipType::Praetor:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Praetor.BP_Praetor"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    case EShipType::Vanquisher:
    default:
        ShipToSpawn = FindObject<APawnShipBase>(ANY_PACKAGE, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Vanquisher.BP_Vanquisher"));
        ShipClass = ShipToSpawn->StaticClass();
        break;
    }
    AActor *SpawnPoint = FindPlayerStart(ArenaPC);
    //APawnShipBase* pShip = GetWorld()->SpawnActorDeferred<APawnShipBase>(ShipClass, FTransform(FRotator(0, 0, 0), SpawnPoint->GetActorLocation()));
    APawnShipBase* pShip = Cast<APawnShipBase>(UGameplayStatics::BeginDeferredActorSpawnFromClass(ArenaPC, ShipClass, FTransform(FRotator(0, 0, 0), SpawnPoint->GetActorLocation())));
    if (pShip != nullptr) {
        //set color from option here
        pShip->BaseColor.InitFromString(UGameplayStatics::ParseOption(OptionsString, "PrimaryColor"));
        pShip->AdditionalColor.InitFromString(UGameplayStatics::ParseOption(OptionsString, "AdditionalColor"));
        pShip->WindshieldColor.InitFromString(UGameplayStatics::ParseOption(OptionsString, "WindshieldColor"));
        pShip->EnginesColor.InitFromString(UGameplayStatics::ParseOption(OptionsString, "EnginesColor"));
        pShip->WeaponsColor.InitFromString(UGameplayStatics::ParseOption(OptionsString, "WeaponsColor"));
        pShip->JetGlowColor.InitFromString(UGameplayStatics::ParseOption(OptionsString, "JetGlowColor"));
        UGameplayStatics::FinishSpawningActor(pShip, FTransform(FRotator(0, 0, 0), SpawnPoint->GetActorLocation()));
        ArenaPC->Possess(pShip);
    }
}

// TODO: event begin play must be call here and init AI (cf GM_FreeForAllArena BP)

my trouble with this way is my BP_pawn constructor are not call, and I have to init some materials on the ship (custom colors), the material binding is not exactly the same on each ship so I can’t setup it in my BaseShip, I’m in search of a way to do it (the ship selection is made from an other level dedicated to the ship customisation.

if some one got a way to achieve it I will be happy to read it.

1 Like

hello again, after some time to search, I’m always blocking on this point, every try I made always result on spawn my PawnShipBase instead of the blueprints version… I really don’t know how to deal with it.

Hi Guss42,

Here is how I spawn a blueprint pawn class from C++.

Inside the constructor, I load the blueprint pawn class :

static ConstructorHelpers::FObjectFinder<UClass> PawnObject(TEXT("Class'/Game/Characters/Blueprints/BP_Pawn.BP_Pawn_C'"));
PawnClass = PawnObject.Object; // TSubclassOf<class APAwn> PawnClass; is declared in my class' header.

Note that the reference of the resource has to end with a “_C”.

Now inside the function executed server-side that spawns the pawn, I do as follows :

FActorSpawnParameters SpawnInfo;
APawn * pawn = GetWorld()->SpawnActor<APawn>(PawnClass, playerStart->GetActorLocation(), playerStart->GetActorRotation(), SpawnInfo);

Hope it helps!

Cheers.

as I will add many more ship in future I don’t think load them all is a good idea but thanks, I now Use LoadObject instead of FindObject, fetching derectly the class in ship class and it’s start to be good I have now to rewrite some part of my BP to instantiate my materials on eventBeginPlay instead of BP constructor (as the constructor is call just before colour customisation).

but thanks to you for your reply :wink:

Problem solved with:

ShipClass = LoadObject<UBlueprint>(nullptr, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Vanquisher.BP_Vanquisher"))->GeneratedClass;

in the switch to load asset dynamically :wink:

Problem solved with:

ShipClass = LoadObject<UBlueprint>(nullptr, TEXT("/Game/Blueprints/ProjectName/Ship/BP_Vanquisher.BP_Vanquisher"))->GeneratedClass;

in the switch to load asset dynamically :wink:

Well, I believe that loading an asset via its path (with LoadObject or FObjectFinder) is an expensive operation. That’s why I would advice against using it in your spawning function (that I guess will be called many times when your game is played). In my case I load the asset reference only once in the constructor of the class handling the spawn (e.g. AGameMode). Cheers

I don’t really know when to lazy load or preload asset, but this finction is called once on player connection to the arena, so I think it’s fine and if my available ship pool grow I must be able to load only one. I think it’s a good case to use it

thanks for your answers, I really apreciate your try to help :wink:

I would add a new class member :
TMap<EShipType, UClass*> ShipClasses
and I would populate it inside the constructor class.

I will take a look to this advice, and look to bench the difference between the 2 way :slight_smile: