Blueprint Subclasses and NewObject creation

Hi all,

I am trying to create a system to equip different weapons. I used Lyra Game as a starting point and I try to create a “reduced” version of it. I am relatively new to Unreal and C++ (I am a SE working mainly with C#, Java, Python), and I don’t fully get the connection between Blueprint subclasses and C++ parent classes.

What I want to do:

  • I have a C++ class WeaponDefinition, it contains things like abilities to grant, actors to spawn, animation montages etc.
  • My custom Character class has a member WeaponDefinition* CurrentWeaponDefinition, which I set when I equip a Weapon. From this class I create different Blueprints like Positol, Sword, etc
  • Additionally the class has a member TSubclassOf StartingWeaponDefinition which contains a reference to the WeaponDefinition that will be equipped on game start.
  • In the equip function I want to assign CurrentWeaponDefinition a reference to a new object of type StartingWeaponDefinition (Blueprint Class) and fire an event “OnEquipped”

Problem is, it seems that I always only create objects of the base class WeaponDefinition and not of the blueprint subclasses. In the quip function, the debug message shows the Name of the parent class and not the one I set in the blueprint class. Also, the blueprint event OnEquipped doesnt fire, only the C++ _Implementation gets called.

UCLASS()
class UNTITLEDCOMBATGAME_API AUCGBaseCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AUCGBaseCharacter();

	UPROPERTY(BlueprintReadWrite, Category = Weapon, EditAnywhere)
	TSubclassOf<UUCGWeaponDefinition> StartingWeaponDefinition;

	UPROPERTY(BlueprintReadOnly, Category = Weapon )
	UUCGWeaponDefinition* CurrentWeaponDefinition;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UFUNCTION(BlueprintCallable, Category = "UCG|Character")
	void EquipWeapon(TSubclassOf<UUCGWeaponDefinition> WeaponDefinition);
};
void AUCGBaseCharacter::BeginPlay()
{
	Super::BeginPlay();

	if (StartingWeaponDefinition) {
		if (GEngine)
			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("StartingWeaponDefinition was found and is equipped"));	
		EquipWeapon(StartingWeaponDefinition);
	}
	else {
		if(GEngine)
			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("No StartingWeaponDefinition was found"));
	}
}

// Equip new weapon
void AUCGBaseCharacter::EquipWeapon(TSubclassOf<UUCGWeaponDefinition> WeaponDefinition)
{
	check(WeaponDefinition != nullptr);
	check(HasAuthority());
	UUCGWeaponDefinition* DefaultWD = WeaponDefinition.GetDefaultObject();
	if (GEngine) {
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("EquipWeapon"));
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, DefaultWD->Name.ToString());
	}
	CurrentWeaponDefinition = NewObject<UUCGWeaponDefinition>(this, WeaponDefinition);
	CurrentWeaponDefinition->OnEquipped();
}
UCLASS(BlueprintType, Blueprintable)
class UNTITLEDCOMBATGAME_API UUCGWeaponDefinition : public UObject
{
	GENERATED_BODY()
public:
	UUCGWeaponDefinition(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	//~UObject interface
	virtual bool IsSupportedForNetworking() const override { return true; }
	virtual UWorld* GetWorld() const override final;
	//~End of UObject interface

	UFUNCTION(BlueprintPure, Category = Weapon)
	APawn* GetPawn() const;

	UFUNCTION(BlueprintPure, Category = Weapon, meta = (DeterminesOutputType = PawnType))
	APawn* GetTypedPawn(TSubclassOf<APawn> PawnType) const;

	// Gameplay ability sets to grant when this is equipped
	UPROPERTY(EditDefaultsOnly, Category = Weapon)
	FName Name = "UCGWeaponDefinition";

	virtual void OnEquipped();
	virtual void OnUnequipped();
protected:
	UFUNCTION(BlueprintNativeEvent, Category = Equipment, meta = (DisplayName = "OnEquipped"))
	void K2_OnEquipped();

	UFUNCTION(BlueprintNativeEvent, Category = Equipment, meta = (DisplayName = "OnUnequipped"))
	void K2_OnUnequipped();
};
void UUCGWeaponDefinition::OnEquipped()
{
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("OnEquipped called"));
	this->K2_OnEquipped();
}

void UUCGWeaponDefinition::OnUnequipped()
{
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("OnUnequipped called"));
	this->K2_OnUnequipped();
}

void UUCGWeaponDefinition::K2_OnEquipped_Implementation()
{
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("K2 OnEquipped called"));
}

void UUCGWeaponDefinition::K2_OnUnequipped_Implementation()
{
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("K2 OnUnequipped called"));
}

GetDefaultObject() gets the “Parent” of the subclass…

1 Like

And how would I instantiate the subclass? I tried various ways but all seem to instantiate a copy of the parent.

You are instantiating the object correctly. Can you go step by step with the debugger and see what’s the class of CurrentWeaponDefinition right after you call NewObject?

1 Like

Thanks for the tip. For some reason it works now. I don’t know if maybe it was a problem with love coding or blueprint compilation but this implementation fires blueprint events and shows the correct subclass name

// Equip new weapon
void AUCGBaseCharacter::EquipWeapon(TSubclassOf<UUCGWeaponDefinition> WeaponDefinition)
{
	check(WeaponDefinition != nullptr);
	check(HasAuthority());

	const UUCGWeaponDefinition* WeaponCDO = GetDefault<UUCGWeaponDefinition>(WeaponDefinition);

	UUCGWeaponDefinition* DefaultWD = WeaponDefinition.GetDefaultObject();
	if (GEngine) {
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("EquipWeapon"));
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, WeaponCDO->Name.ToString());
	}
	CurrentWeaponDefinition = NewObject<UUCGWeaponDefinition>(this, WeaponDefinition);
	GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, CurrentWeaponDefinition->Name.ToString());
	CurrentWeaponDefinition->OnEquipped();
}