[TUTORIAL] C++ - Runtime Async Load Modular Character (Intermediate)

DISCLAIMER: This is a C++ tutorial if someone get interested on implement the Blueprint part, feel free to. :wink:

Hi!

Now, let’s suppose you are doing a game where you’ll need a “LOT” of character customization, imagine that your characters will need to exchange
gloves, boots, chest armor, legs armor, and helmet, but it’s not just this, you’ll have also gender selection and classes… Well, I don’t wanna
be on such situation! :smiley:

But I am. :frowning:

And I thought that some of you also would, well, how to manage such variety? And make it flexible enough to your artists do the items’
model database while you are focusing into coding the “remaining”? Well, my first problem on port my UDK game to U4 was exactly rewrite my “modular
pawn”, allowing some dynamic loads. U4 has a nice way on doing this asyncronously but to implement the system… Wow, you’ll have to deal at least
with three other features.

This “tour” to anyone starting to deal with U4C++ is a nice way to get introduced to the current state of the engine, of course it’s a “just working”
thing, I don’t know if it’s perfect, so if you get ideas about some optimizations feel free to share, also to the Epic Guys, and more experienced
programmers if I did something wrong please point! :smiley: I don’t wanna be distributing and using “leaking code” lol.

STAGE 1 - THE ITEMS DATABASE CLASS

One nice thing about Async Loading is that it’s based on classes that are elegant on the editor: FStringAssetReference, and TAssetPtr<class> being
this second a “filtered allowed” way to look for and assign just objects from some class as reference to a resource. On editor it shows more or
less on this way:

By filtering on your class, your buddies artists will not assign a Skeleton or any other kind of asset inside a list that should just to contain
SkeletalMeshes or as you know, our code would break, so “filter” is a nice way to prevent further problems.

To implement the class you’ll need a struct that boxes a TAssetPtr and also an ID (I’m using int 32 to it) to don’t need doing searchs by
strings, you’ll declare both this struct and class on the same file.

The Database class is derived from UDataAsset what allows to be “creatable, fillable and usable” trough the U4 editor.

Here is the code:

ItemInfoDatabase.h



//Holds the information about a single character "part"
USTRUCT()
struct FItemInfo
{
	GENERATED_USTRUCT_BODY()
	UPROPERTY(EditAnywhere, Category = "DATA", meta = (ToolTip = "The Mesh ID"))
	int32 MeshID;

	UPROPERTY(EditAnywhere, Category = "DATA", meta = (ToolTip = "Related Asset"))
	TAssetPtr<USkeletalMesh> MeshResource;

	FVCharPartInfo()
	{
		MeshID = 0;
		MeshResource = FStringAssetReference("");
	}
};

//Holds a dynamic collection of character parts
UCLASS()
class UItemInfoDatabase : public UDataAsset
{
	GENERATED_UCLASS_BODY()
	UPROPERTY(EditAnywhere, Category = "Model List", meta = (ToolTip = "Asset Info")) //Exposes the array as editable on editor
	TArray<FItemInfo> MeshList;	
public:
	UItemInfoDatabase(UItemInfoDatabase&);
	UItemInfoDatabase();
	
};

The cpp file just holds the constructor.

ItemInfoDatabase.cpp



#include "ItemInfoDatabase.h"

UItemInfoDatabase::UItemInfoDatabase(const class FPostConstructInitializeProperties& PCIP):Super(PCIP)
{

}

If you compile this, on next time your artists try to create a UDataAsset, they can point ItemInfoDatabase class as base and so, go adding new
items to the list and just needs to assign the same LIST INDEX to IDs to allow you to easy call everything after, if they keep an Excel table
about what’s going where will also make your life a lot easier.

IMG_02.jpg

IMG_03.jpg

While our artists works we still have lots of work to do…

STAGE 2 - FStreamableManager and the Singleton of Doom

If we do a check on the Async Loading page we’ll see that the author points that a nice place to put the FStreamableManager is on a “Singleton”
class, well, now I know that this is a class that can holds the Game StateMachine and our global variables, but to discover this and get a sample
to implement… OMG… Well, I’ll save your time and put the entire thing below, don’t think that I figured by myself, it’s based on UGameShooterKing
from Epic and after 05/14/2014 we got a review from Ben Zeigler to improve it.

MyGameSingleton.h



#include "object.h"
//A Singleton class based on Epic Sample

UCLASS(Config=Game, notplaceable) // < needed flags
class UMyGameSingleton : public UObject , public FTickerObjectBase // Notice the multiple inheritance, C++ is a monster :D
{
	GENERATED_UCLASS_BODY()
private:
	UMyGameSingleton(); 			  // On privatize the constructor it will be just built by the engine once

public:
	static UMyGameSingleton& Get(); 	// Get method to access this object
	FStreamableManager AssetLoader;		// Your asset loader

	// Override from FTicker::Tick()
	// if you forget to Override this and implement on cpp file you'll get Error 2259
	virtual bool Tick(float DeltaSeconds) OVERRIDE;
};


MyGameSingleton.cpp



#include "OurGameModule.h" // this file is that one with the call to "IMPLEMENT_PRIMARY_GAME_MODULE" 
#include "MyGameSingleton.h"

// Default constructor
UMyGameSingleton::UMyGameSingleton(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
{

}

//Get method to access the instance
UMyGameSingleton& UMyGameSingleton::Get()
{
	UMyGameSingleton *Singleton = Cast<UMyGameSingleton>(GEngine->GameSingleton);

	if (Singleton)
	{
		return *Singleton;
	}
	else
	{
		return *ConstructObject<UMyGameSingleton>(UMyGameSingleton::StaticClass()); // never calls this
	}
}

// OVERRIDE from FTICKER TICK (Preventing Error 2259)
bool UMyGameSingleton::Tick(float DeltaSeconds)
{
	return true;
}

Well, this almost finishes our work with the Singleton, but you’ll notice that if the engine doesn’t knows which class to use
as Singleton, our cast will fail. Ben Zeigler said the most effective way to assign a Singleton class is on the editor.

So you can access this option on MENU EDIT > Project Settings:

After do the assignment, press the Set As Default button and this will override the DefaultEngine.ini inserting the line about
the Singleton class.

Ok! Finaly we got all pre-requisites to implement the customization system on our character class…

STAGE 3 - Some last moment preparations

Before we begin here, let’s remember that to make an Async loading we’ll not use JUST ONE, BUT TWO METHODS.
This is because FStreamableManager if we watch on docs does Async Loading on a 2 part process:

    • On the first, we’ll “request” the asset loading and on the same point a second method (delegate) to the machine execute when loading is ready.
    • On the second, we’ll really do the SkeletalMesh assignment extracting it from where the first method loaded it.

Also, remember that we are doing this not just to deal with a single collection of character parts, but so many as we need.
I do preffer “hardcode” the paths to collections, thinking that these characters will be spawned by code and will need “to pick” a collection
path based on the data from the “savefile”. If I need to have one character Blueprint to each class and gender my game will need to
consider around 10 different class spawns if we consider just a scenario of 5 races (let’s suppose: elf, dwarf, human, martian and goblins) and 2 genders
OR I can simply have N “hardcoded path databases” on a single Character Class and just make a “switch” to load the one I’ll need. :wink:

So, the first thing I’ll do before begin our CharacterClass will be go back to the Singleton file (do you remember that we can use it to store things
that we’ll need watch from other places?) and define a new “enum” to help me sort the different character’s setups we could have, to this example,
I’ll just use one option, but keep in mind that we can have any number of them.

Back to…
MyGameSingleton.h



#include "object.h"
//A Singleton class based on Epic Sample

// *** I like to declare enums and structs before the classes

UENUM()
enum FCharacterRace
{
	RACESEX_ElfMale,
	RACESEX_ElfFemale // You could proceed just typing "," and inserting new races-***
};

// *** done!
UCLASS(Config=Game, notplaceable) // < needed flags

Ok, now we need to define how many “slots” you need to assemble a character, let’s suppose 5 (gloves, boots, chest armor, legs armor, and helmet),
so you tell your artists to assume at least the “first armor tier” to ALL characters on this way:

  • 0 - Helmet
  • 1 - Chest
  • 2 - Hands
  • 3 - Legs
  • 4 - Feet

And assign the right asset to each, of course you can write your save file to store 5 different “armor parts codes”, read it and just do a direct
assignment, but “by now” we’ll use these numbers as the basic ones to start a “NEW” character, now we are ready to code a bit more.

STAGE 4 - The MultiPartCharacter class

We need to begin calling the Singleton Header and also the ItemInfoDatabase one to our character class understand the FCharacterRace enum, and also
be capable to understand our UDataAsset, with this, we could just manage on getting the pieces we need.

Character_MultiPart.h



#pragma once
#include "MyGameSingleton.h"
#include "ItemInfoDatabase.h"
#include "GameFramework/Character.h"
#include "Character_MultiPart.generated.h"

UCLASS() //Based on Shootergame Character from Epic
class ACharacter_MultiPart : public ACharacter
{
	GENERATED_UCLASS_BODY()

	//MESHES AND NAMES	
	
	UPROPERTY(Category = "Body", VisibleAnywhere, BlueprintReadOnly)
	TSubobjectPtr<class USkeletalMeshComponent> HeadMSHComponent;
	static FName HeadComponentName;	
	
	UPROPERTY(Category = "Body", VisibleAnywhere, BlueprintReadOnly)
	TSubobjectPtr<class USkeletalMeshComponent> ChestMSHComponent;
	static FName ChestComponentName;		
	
	UPROPERTY(Category = "Body", VisibleAnywhere, BlueprintReadOnly)
	TSubobjectPtr<class USkeletalMeshComponent> LegsMSHComponent;
	static FName LegsComponentName;			

	UPROPERTY(Category = "Body", VisibleAnywhere, BlueprintReadOnly)
	TSubobjectPtr<class USkeletalMeshComponent> FeetMSHComponent;
	static FName FeetComponentName;			

	UPROPERTY(Category = "Body", VisibleAnywhere, BlueprintReadOnly)
	TSubobjectPtr<class USkeletalMeshComponent> HandsMSHComponent;
	static FName HandsComponentName;
	
	FCharacterRace ThisCharRace; 	// enum to switch control
	FString DatabasePath;		// possible hardcoded paths
	
	//-------MESHES AND NAMES

public:
	/** spawn inventory, setup initial variables */
	virtual void PostInitializeComponents() OVERRIDE;

	// Direct ID mesh changing "request" methods, usable with inventory loading, swaping systems... etc
	bool ChangeHeadMeshByID(int32 IDCode);
	bool ChangeChestMeshByID(int32 IDCode);
	bool ChangeLegsMeshByID(int32 IDCode);
	bool ChangeFeetMeshByID(int32 IDCode);
	bool ChangeHandsMeshByID(int32 IDCode);

private:
	// We need different places to load each part to don't risk override a loading process
	FStringAssetReference HeadAssetToLoad;
	FStringAssetReference ChestAssetToLoad;
	FStringAssetReference LegsAssetToLoad;
	FStringAssetReference FeetAssetToLoad;
	FStringAssetReference HandsAssetToLoad;

	// Method to setup/initialize skeletal mesh components
	void InitSkeletalMeshComponent(TSubobjectPtr<class USkeletalMeshComponent> SMeshPointer, bool AttachToParent);
	
	// Method to setup the new character
	void InitDefaultMeshes(int32 HeadID, int32 ChestID, int32 HandsID, int32 LegsID, int32 FeetID);
	
	// Delegates to be "shoot" at end of loading processes
	void DoAsyncHeadMeshChange();
	void DoAsyncChestMeshChange();
	void DoAsyncLegsMeshChange();
	void DoAsyncFeetMeshChange();
	void DoAsyncHandsMeshChange();
};

OK, now let’s put all this together…

Character_MultiPart.cpp



#include "VIDA.h"
#include "Character_MultiPart.h"

FName ACharacter_MultiPart::HeadComponentName(TEXT("HeadMeshComponent"));
FName ACharacter_MultiPart::ChestComponentName(TEXT("ChestMeshComponent"));
FName ACharacter_MultiPart::LegsComponentName(TEXT("LegsMeshComponent"));
FName ACharacter_MultiPart::FeetComponentName(TEXT("FeetMeshComponent"));
FName ACharacter_MultiPart::HandsComponentName(TEXT("HandsMeshComponent"));

ACharacter_MultiPart::ACharacter_MultiPart(const class FPostConstructInitializeProperties& PCIP) : 
Super(PCIP.DoNotCreateDefaultSubobject(TEXT("CharacterMesh0"))) // <- First let's get rid from the "default" Mesh
{
	// Components' initialization and creation
	HeadMSHComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, ACharacter_MultiPart::HeadComponentName);
	if (HeadMSHComponent)
		InitSkeletalMeshComponent(HeadMSHComponent, false); // On each, we call the setup method below

	ChestMSHComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, ACharacter_MultiPart::ChestComponentName);
	if (ChestMSHComponent)
		InitSkeletalMeshComponent(ChestMSHComponent, true);

	LegsMSHComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, ACharacter_MultiPart::LegsComponentName);
	if (LegsMSHComponent)
		InitSkeletalMeshComponent(LegsMSHComponent, true);

	HandsMSHComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, ACharacter_MultiPart::HandsComponentName);
	if (HandsMSHComponent)
		InitSkeletalMeshComponent(HandsMSHComponent, true);

	FeetMSHComponent = PCIP.CreateOptionalDefaultSubobject<USkeletalMeshComponent>(this, ACharacter_MultiPart::FeetComponentName);
	if (FeetMSHComponent)
		InitSkeletalMeshComponent(FeetMSHComponent, true);	

	// Just initializing...
	ThisCharRace = FCharacterRace::RACESEX_ElfFemale;
	DatabasePath = " "; 	
}

// Initializes a SkeletalMeshComponent, if AttachToParent is true, assigns the head as Animation master

void ACharacter_MultiPart::InitSkeletalMeshComponent(TSubobjectPtr<class USkeletalMeshComponent> SMeshPointer, bool AttachToParent)
{
	SMeshPointer->AlwaysLoadOnClient = true;
	SMeshPointer->AlwaysLoadOnServer = true;
	SMeshPointer->bOwnerNoSee = false;
	SMeshPointer->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::AlwaysTickPose;
	SMeshPointer->bCastDynamicShadow = true;
	SMeshPointer->bAffectDynamicIndirectLighting = true;
	SMeshPointer->PrimaryComponentTick.TickGroup = TG_PrePhysics;
	// force tick after movement component updates
	if (CharacterMovement)
	{
		SMeshPointer->PrimaryComponentTick.AddPrerequisite(this, CharacterMovement->PrimaryComponentTick);
	}
	SMeshPointer->bChartDistanceFactor = false;
	SMeshPointer->AttachParent = CapsuleComponent; 
	static FName CollisionProfileName(TEXT("CharacterMesh"));
	SMeshPointer->SetCollisionObjectType(ECC_Pawn);
	SMeshPointer->SetCollisionProfileName(CollisionProfileName);
	SMeshPointer->bGenerateOverlapEvents = false;

	// Mesh acts as the head, as well as the parent for both animation and attachment.
	if (AttachToParent)
	{
		SMeshPointer->AttachParent = HeadMSHComponent;
		SMeshPointer->SetMasterPoseComponent(HeadMSHComponent);
		SMeshPointer->bUseBoundsFromMasterPoseComponent = true;
	}
}

// Mesh assignment should be done after not just character, but other game objects created, if a save file system does exist, we could
// be getting the MeshIDs also here

void ACharacter_MultiPart::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ThisCharRace = FCharacterRace::RACESEX_ElfMale; //<- This could be coming from a save file overriding
	
	// Picks the right Database Path and name;
	switch (ThisCharRace)
	{
		case FCharacterRace::RACESEX_ElfMale:
			DatabasePath = "/Game/SubDirectory/ElfMaleData.ElfMaleData"; // <- You need databases to load from!
		break;

		default:
			DatabasePath = "/Game/SubDirectory/ElfFemaleData.ElfFemaleData"; // <- You need databases to load from!
		break
	}	

	// Call to load library and setup meshes
	InitDefaultMeshes(0,1,2,3,4);
}

void ACharacter_MultiPart::InitDefaultMeshes(int32 HeadID, int32 ChestID, int32 HandsID, int32 LegsID, int32 FeetID)
{
	// Registers the loading requests
	ChangeHeadMeshByID(HeadID);
	ChangeHeadMeshByID(ChestID);
	ChangeHeadMeshByID(HandsID);
	ChangeHeadMeshByID(LegsID);
	ChangeHeadMeshByID(FeetID);	
}

// Head Change Request
bool ACharacter_MultiPart::ChangeHeadMeshByID(int32 IDCode)
{
	// Loads the desired database trough StaticLoad, deferencing a FString results on TCHAR that is the Path text format
	UItemInfoDatabase* LoadedMeshesDatabase = Cast<UItemInfoDatabase>(StaticLoadObject(UVMultiPartCharDATA::StaticClass(), NULL, *DatabasePath, NULL, LOAD_None, NULL));
	
	if (LoadedMeshesDatabase != NULL && LoadedMeshesDatabase->MeshList.Num() >= IDCode)
	{
		TArray<FStringAssetReference> ObjToLoad;
		FStreamableManager& BaseLoader = MyGameSingleton::Get().AssetLoader;			
		HeadAssetToLoad = LoadedMeshesDatabase->MeshList[IDCode].MeshResource.ToStringReference();
		ObjToLoad.AddUnique(HeadAssetToLoad);
		BaseLoader.RequestAsyncLoad(ObjToLoad, FStreamableDelegate::CreateUObject(this, &ACharacter_MultiPart::DoAsyncHeadMeshChange));
		return true;
	}

	return false;
}

// Chest Change Request
bool ACharacter_MultiPart::ChangeChestMeshByID(int32 IDCode)
{
	// Loads the desired database trough StaticLoad, deferencing a FString results on TCHAR that is the Path text format
	UItemInfoDatabase* LoadedMeshesDatabase = Cast<UItemInfoDatabase>(StaticLoadObject(UVMultiPartCharDATA::StaticClass(), NULL, *DatabasePath, NULL, LOAD_None, NULL));

	if (LoadedMeshesDatabase != NULL && LoadedMeshesDatabase->MeshList.Num() >= IDCode) // Prevents out of index access
	{
		TArray<FStringAssetReference> ObjToLoad;				// Unfortunatelly, Asyncs just accepts arrays
		FStreamableManager& BaseLoader = MyGameSingleton::Get().AssetLoader; 	// Gets our Asset Loader			
		ChestAssetToLoad = LoadedMeshesDatabase->MeshList[IDCode].MeshResource.ToStringReference();	//Extracts the path/obj	
		ObjToLoad.AddUnique(ChestAssetToLoad);					// Puts our loading task into array
		BaseLoader.RequestAsyncLoad(ObjToLoad, FStreamableDelegate::CreateUObject(this, &ACharacter_MultiPart::DoAsyncChestMeshChange)); //Assigns the delegate to the task end
		return true;
	}

	return false;
}

// Legs Change Request
bool ACharacter_MultiPart::ChangeLegsMeshByID(int32 IDCode)
{
	// Loads the desired database trough StaticLoad, deferencing a FString results on TCHAR that is the Path text format
	UItemInfoDatabase* LoadedMeshesDatabase = Cast<UItemInfoDatabase>(StaticLoadObject(UVMultiPartCharDATA::StaticClass(), NULL, *DatabasePath, NULL, LOAD_None, NULL));

	if (LoadedMeshesDatabase != NULL && LoadedMeshesDatabase->MeshList.Num() >= IDCode)
	{
		TArray<FStringAssetReference> ObjToLoad;
		FStreamableManager& BaseLoader = MyGameSingleton::Get().AssetLoader;			
		LegsAssetToLoad = LoadedMeshesDatabase->MeshList[IDCode].MeshResource.ToStringReference();	
		ObjToLoad.AddUnique(LegsAssetToLoad);
		BaseLoader.RequestAsyncLoad(ObjToLoad, FStreamableDelegate::CreateUObject(this, &ACharacter_MultiPart::DoAsyncLegsMeshChange));
		return true;
	}

	return false;
}

// Feet Change Request
bool ACharacter_MultiPart::ChangeFeetMeshByID(int32 IDCode)
{
	// Loads the desired database trough StaticLoad, deferencing a FString results on TCHAR that is the Path text format
	UItemInfoDatabase* LoadedMeshesDatabase = Cast<UItemInfoDatabase>(StaticLoadObject(UVMultiPartCharDATA::StaticClass(), NULL, *DatabasePath, NULL, LOAD_None, NULL));

	if (LoadedMeshesDatabase != NULL && LoadedMeshesDatabase->MeshList.Num() >= IDCode)
	{
		TArray<FStringAssetReference> ObjToLoad;
		FStreamableManager& BaseLoader = MyGameSingleton::Get().AssetLoader;			
		FeetAssetToLoad = LoadedMeshesDatabase->MeshList[IDCode].MeshResource.ToStringReference();	
		ObjToLoad.AddUnique(FeetAssetToLoad);
		BaseLoader.RequestAsyncLoad(ObjToLoad, FStreamableDelegate::CreateUObject(this, &ACharacter_MultiPart::DoAsyncFeetMeshChange));
		return true;
	}

	return false;
}

// Hands Change Request
bool ACharacter_MultiPart::ChangeHandsMeshByID(int32 IDCode)
{
	// Loads the desired database trough StaticLoad, deferencing a FString results on TCHAR that is the Path text format
	UItemInfoDatabase* LoadedMeshesDatabase = Cast<UItemInfoDatabase>(StaticLoadObject(UVMultiPartCharDATA::StaticClass(), NULL, *DatabasePath, NULL, LOAD_None, NULL));

	if (LoadedMeshesDatabase != NULL && LoadedMeshesDatabase->MeshList.Num() >= IDCode)
	{
		TArray<FStringAssetReference> ObjToLoad;
		FStreamableManager& BaseLoader = MyGameSingleton::Get().AssetLoader;			// Colhe referência para o Asset Loader
		HandsAssetToLoad = LoadedMeshesDatabase->MeshList[IDCode].MeshResource.ToStringReference();	// Desencapsula o path
		ObjToLoad.AddUnique(HandsAssetToLoad);
		BaseLoader.RequestAsyncLoad(ObjToLoad, FStreamableDelegate::CreateUObject(this, &ACharacter_MultiPart::DoAsyncHandsMeshChange));
		return true;
	}

	return false;
}

// DELEGATE - Do the Async Head Change
void ACharacter_MultiPart::DoAsyncHeadMeshChange()
{
	check(HeadAssetToLoad.ResolveObject() != nullptr)
	{
		UObject* NewHeadMesh = HeadAssetToLoad.ResolveObject(); 		// Creates a pointer to store the loaded object
		HeadMSHComponent->SetSkeletalMesh(Cast<USkeletalMesh>(NewHeadMesh));	// Casts and assigns
// ******** UPDATE 4.11.2  Check Notes *****
                ChestMSHComponent->SetMasterPoseComponent(HeadMSHComponent);
                LegsMSHComponent->SetMasterPoseComponent(HeadMSHComponent);
                FeetMSHComponent->SetMasterPoseComponent(HeadMSHComponent);
                FeetMSHComponent->SetMasterPoseComponent(HeadMSHComponent);
	}
		
}

// DELEGATE - Do the Async Chest Change
void ACharacter_MultiPart::DoAsyncChestMeshChange()
{
	check(ChestAssetToLoad.ResolveObject() != nullptr)
	{
		UObject* NewChestMesh = ChestAssetToLoad.ResolveObject();
		ChestMSHComponent->SetSkeletalMesh(Cast<USkeletalMesh>(NewChestMesh));
	}
}

// DELEGATE - Do the Async Legs Change
void ACharacter_MultiPart::DoAsyncLegsMeshChange()
{
	check(LegsAssetToLoad.ResolveObject() != nullptr)
	{
		UObject* NewLegsMesh = LegsAssetToLoad.ResolveObject();
		LegsMSHComponent->SetSkeletalMesh(Cast<USkeletalMesh>(NewLegsMesh));
	}
}

// DELEGATE - Do the Async Feet Change
void ACharacter_MultiPart::DoAsyncFeetMeshChange()
{
	check(FeetAssetToLoad.ResolveObject() != nullptr)
	{
		UObject* NewFeetMesh = FeetAssetToLoad.ResolveObject();
		FeetMSHComponent->SetSkeletalMesh(Cast<USkeletalMesh>(NewFeetMesh));
	}
}

// DELEGATE - Do the Async Hands Change
void ACharacter_MultiPart::DoAsyncHandsMeshChange()
{
	check(HandsAssetToLoad.ResolveObject() != nullptr)
	{
		UObject* NewHandsMesh = HandsAssetToLoad.ResolveObject();
		HandsMSHComponent->SetSkeletalMesh(Cast<USkeletalMesh>(NewHandsMesh));
	}
}

Phew! Now if you have the database with at least one asset, made a blueprint from this class and place on your level
you’ll see just the Capsule Component, but if you press Play or Simulate on editor the magic will happens! Notice that you
can use this method to load anything anywhere you wants. :wink:

Enjoy!

it’ll be a while before I use this but I definately will sometime in the future
thanks for sharing this!

btw have you tested it for multiplayer purposes? how well does it behave?

Good tutorial Creasso! This is pretty similar to how we’re doing it in fortnite. I have a few suggestions for a cleaner setup:

Instead of creating the object in your module, I would suggest using GameSingletonClassName. If you set that path in your ini or project settings to the class you want to be your singleton, it’ll spawn it for you at game creation time. Then you can access it directly off of the engine. Your case with static variable will behave strangely in certain situations. Here’s the relevant code from Fortnite to get our singleton.


UFortGlobals& UFortGlobals::Get()
{
	UFortGlobals *Singleton = Cast<UFortGlobals>(GEngine->GameSingleton);

	if (Singleton)
	{
		return *Singleton;
	}
	else
	{
		UE_LOG(LogFort, Fatal, TEXT("Invalid singleton"));
		return *ConstructObject<UFortGlobals>(UFortGlobals::StaticClass()); // never calls this
	}
}

Also, just as a heads up to people following this tutorial, FStringClassReference and TAssetSubclassOf now exist in the latest version of the engine, and you would use those to refer to blueprints, using basically the same method as the skeletal mesh component here

@Chosker: Man, I didn’t tested on Multiplayer, but I wrote it assuming that to assemble a character on each client we’ll need send the enum + 7 int32s (considering 2 more to left and right hand weapon), all this logic will stay on client. Maybe you can improve the needed info, but by now my goal was just to put async dlos to work.

@Ben:

Thank you by your input, I did a review on the tutorial and also made changes on my project following your tip, works like a charm. :smiley:

Also, reviewing the code seen some misstypes from variable’s names, I use “VTypeandHelpers” here instead of “UMyGameSingleton”, so I apologize to all if I did some mistakes on replace it on the tutorial. :frowning:

Hope that with this review it’s OK now. :smiley:

Another thing to consider is to use skeletal mesh merging. While this method allows maximum flexibility each character would use a lot of draw calls. Skeletal mesh merging allows you to merge the skeletal meshes together to form a new skeletal mesh and it will be a single draw call (dependent on the number of materials the final skeletal mesh uses of course). FSkeletalMeshMerge is the class you want to check out.

I’ve done basic character customisation in the past with a single mesh.
As clothing is added, areas that would be covered and/or clip through are masked out.

Not sure what affect masking has on performance, but the idea works fine.

Hi,

@Ben,
What’s the difference between : FStringAssetReference and FStringClassReference? because BP is considered as an asset isn’t it? You can set them in a FStringAssetReference in the Editor, so what the purpose of those 2 class?

TAssetSubclassOf is to use for BP, and I suppose for asset also, is there any other class for BP only like String Reference?

I’m a little bit lost on this point. Might a beginner question, but I didn’t get it :smiley:

Thanks

@Solid Snake: Wow! First I must welcome and thank you learnt a lot from your posts in the past regarding UScript (respect :D).

The only “trouble” with mesh merging was that (by what I’ve seen) the assets should be prepared on a very specific way to this work (following JamesG tip).

https://forums.unrealengine.com/showthread.php?1052-Does-UE4-have-mesh-compositing

So this “one draw call could” come with a lot of other things to consider, I don’t know if worths on games where the player will be switching character parts frequently sort of: Ow! New drop! Let me equip this!

But I’ll check anyway and maybe try to implement it.

Thank you by your tip (as usual) :smiley:

@Ben,

Regarding FStringClassReference, there is no RequestAsyncLoad that take an array of FStringClassReference as parameter. Is it in the next engine version? I check GitHub and there is no update on StreamableManager since 2 month.

Thanks,

No problems creasso.

Yes, there are a lot of rules with mesh merging but it is really handy when used right :slight_smile:

Hi,

Did someone succeed in getting this tutorial working with FStringClassReference?

Async seems not to be possible as StreamLoader can’t manage FStringClassReference.

I tried a non async implementation and the editor crash when editing the FStringClassReference on my DataAsset. I raised a bug in the answer hub, but it could be my mistake so I’m opened to suggestion or feedback on wether it’s workingon your side.

Thanks,

i dont uderstud how it work, mb some one do video tutor step by step ?

One question: In ItemInfoDatabase.h, shouldn’t FVCharPartInfo() have a data type declared? Visiual Studio had a compiler error when compiling this function.

I’ve set the data type to **void FVCharPartInfo() **for testing and to get it compiled, but I just start with this tutorial, so I’m not sure if this function will need a return value later on.

That looks like a constructor, but it doesn’t match the name of the struct. Try charging it to FItemInfo() (without the void), so that it is a constructor for the struct it is part of.

Hi creasso. I desire modular everything in my game (aka lots-of-customization). I would expect a modular pawn to be dynamic or on-demand swappable parts during run-time. Perhaps I misunderstand your context of dynamic. Can you elaborate what this Modular Character C++ implementation is accomplishing, that can not be accomplished with Blueprints Components. I’m a C++ Coder, however, Blueprints Components appears ideal for modular entity construction. I appreciate your time and insight.

The source needs to be changed according to the tutorial.
FStreamableManager& BaseLoader = UMyGameSingleton::Get().AssetLoader;

The lines with UVMultiPartCharDATA need to be changed to UDataAsset.

MeshID from FItemInfo should probably be removed since changing meshes is dependent on the array index. -(#EDIT: Nevermind I understand it’s value with referencing a specific item externally after modifying the database.)

Other than that everything works. Thanks for the tutorial it helped a lot to understand how this works.

@ Yo Techlord The parts seem to swap out during runtime in blueprint.


	// Direct ID mesh changing "request" methods, usable with inventory loading, swaping systems... etc
	UFUNCTION(BlueprintCallable, Category = "Mesh")
	bool ChangeHeadMeshByID(int32 IDCode);

	UFUNCTION(BlueprintCallable, Category = "Mesh")
	bool ChangeChestMeshByID(int32 IDCode);

	UFUNCTION(BlueprintCallable, Category = "Mesh")
	bool ChangeLegsMeshByID(int32 IDCode);

	UFUNCTION(BlueprintCallable, Category = "Mesh")
	bool ChangeFeetMeshByID(int32 IDCode);

	UFUNCTION(BlueprintCallable, Category = "Mesh")
	bool ChangeHandsMeshByID(int32 IDCode);


Send a reference of the ingame object to a variable in the player controller for debugging.
48808b28d73a10caabdf0e981bddf549cf68db56.jpeg

Then use player input to call one of the functions for changing the mesh by id.

I couldn’t get FSkeletalMeshMerge to work do you know how? It crashes on .Merge() from Assertion failed: bExtraBoneInfluences = bExtraBoneInfluencesT from SkeletalMeshTypes.h Anyways thanks a lot this is cool.

Is there an example somewhere of doing this for a Texture2D, from a buffer of pixel data?

@TechLord

Sorry by the late response (after get this working I got other things to do). Well, I sort of thought that if the class resides just “on code” and not as a Placed Actor I would get greater control about what will be loaded, how (I don’t know if BP asset loading is Async or not) and when.

I apologize to everyone following this that got some naming problems, I use some “custom” naming convetions because an internal and previous Lib I wrote to UDK, I’ll try to review the code when possible.

Regards!

good to hear this was revised, as I was planning to take a look at it :slight_smile:

two questions:

  • does it work on 4.4? I’ve had some minor troubles with some c++ tutorials that were written for older versions (4.2, all the way back to pre-4.0-beta)
  • what’s the actual benefit of using async loading? it kinda sounds like it uses streaming and/or works in a separate CPU thread, but what’s the benefit over not just using the regular creation of vars/meshes?

Hi Chosker…

1 - Well, my character still working and my project was upgraded trough 4.2, 4.3, 4.4 and now 4.5 Prev without trouble (on this class at least lol).
2 - By what I’ve read on the Async method docs, the game doesn’t “stall” while you are loading assets trough this method, also you can save some memory and “by logic” get faster loading times, here is an enxerpt that describes these last two advantages:

[FONT=Arial Narrow][FONT=Book Antiqua][SIZE=4]“The easiest way to have an artist or designer reference an asset is to create a UProperty of a hard pointer and give it a category. In UE4, if you have a hard UObject pointer property referencing an asset, that asset will be loaded when the object containing the property is loaded (either by being placed in a map, or referenced from something like a gameinfo). If you are not careful, you can end up loading 100% of your assets at game startup time. If you want artists/designers to be able to make references to specific assets using the same UI as a hard pointer, without always loading the referenced asset, use a FStringAssetReference or TAssetPtr.”[/SIZE]

I recommend the entire article: Docs about Async.