I can use the “Instanced” property specifier to make the property’s properties visible when viewing the owning object as a blueprint.
Does anyone know how to also have these subproperties be grouped together under a category?
I want to do this so a single class can have multiple instanced properties of the same type.
As an example:
I have a character class which contains a health property with the category “Resources|HealthResource”.
The health’s class contains a max health property (which is just a float) with the category “Settings”.
When opening the character class as a blueprint, I would want the max health property to be under the category “Resources|HealthResource|Settings” or “Resources|HealthResource|Health|Settings”.
Instead, it is showing twice, once under “Settings|MaxHealth” (missing the health property’s category), and once under “Resources|HealthResource|Health” (missing the max health property’s category; this I could personally accept if the property was not showing twice).
P.S: if I had categorised the health property as “Resources”, then the max health property would only be showing once, but it would be the one categorised under “Settings|MaxHealth”.
The code for the example
Note that:
- The MaxHealth property is just called Max.
- The health’s class is called RPGResource.
- The character’s class is called RPGCharacter.
RPGResource.h
#pragma once
#include "CoreMinimal.h"
#include "RPGResource.generated.h"
/** Contains some amount of some resource, like health or mana.
* This has a max amount that this can contain.
*/
UCLASS(EditInlineNew, CollapseCategories)
class RPGBASE_API URPGResource : public UObject
{
GENERATED_BODY()
private:
/** How much of the resource this can contain. */
UPROPERTY(Category = "Settings"
, EditAnywhere
, Replicated, ReplicatedUsing = SetMax
, BlueprintGetter = GetMax, BlueprintSetter = SetMax
)
float Max = 1;
/** How much of the resource this contains. */
UPROPERTY(EditInstanceOnly
, Replicated, ReplicatedUsing = SetCurrent
, BlueprintGetter = GetCurrent, BlueprintSetter = SetCurrent
)
float Current;
public:
/** Returns how much of the resource this contains. */
UFUNCTION(BlueprintCallable)
float GetCurrent() const
{
return Current;
}
/** Sets how much of the resource this contains. */
UFUNCTION(BlueprintCallable)
void SetCurrent(float Health)
{
Current = Health;
}
/** Returns how much of the resource this can contain. */
UFUNCTION(BlueprintCallable)
float GetMax() const
{
return Max;
}
/** Sets how much of the resource this can contain. */
UFUNCTION(BlueprintCallable)
void SetMax(float Health)
{
Max = Health;
}
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
RPGResource.cpp
#include "RPGResource.h"
#include "Net/UnrealNetwork.h"
void URPGResource::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(URPGResource, Max);
DOREPLIFETIME(URPGResource, Current);
}
RPGCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "RPGResource.h"
#include "RPGCharacter.generated.h"
UCLASS()
class RPGBASE_API ARPGCharacter : public ACharacter
{
GENERATED_BODY()
private:
/** The health of the character */
UPROPERTY(Category = "Resources|HealthResource"
, Instanced, VisibleAnywhere
, BlueprintGetter = GetHealth
)
TObjectPtr<URPGResource> Health;
public:
/** Returns the health of the character. */
UFUNCTION(BlueprintCallable)
URPGResource* GetHealth() const
{
return Health;
}
public:
ARPGCharacter();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
RPGCharacter.cpp
#include "RPGCharacter.h"
ARPGCharacter::ARPGCharacter()
{
PrimaryActorTick.bCanEverTick = true;
Health = NewObject<URPGResource>(this, TEXT("Health"));
}
void ARPGCharacter::BeginPlay()
{
Super::BeginPlay();
GetHealth()->SetCurrent(GetHealth()->GetMax());
}
void ARPGCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ARPGCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
Image of the example, when looking at a blueprint inheriting from the character’s class: