Download

Reference blueprint created class in c++

Hello! It’s me again with another newbie question.

I have a component with c++ base extended with blueprints. I wish to reference that component in a property of my custom Actor object so that it’s accessible to it’s native c++ functions.

My idea was to add that class to my Actor’s blueprint through editor’s “add component” option, then set it’s reference to the appropriate property and use it further on. For reasons I don’t understand, that approach does not work.When I try to check that property at runtime, it’s properties are greyed out with “multiple values” hint, and upon instantiation of any other object VIA that object’s properties, I get a crash. Aparently something gets corrupted but I’m not sure what or why.

Here is a screenshot of what I’m doing:
http://i.imgur.com/QlOSHLC.png

From my experience the construction script is only called within the editor when the object is moved or it’s properties are changed, but it does not run in play mode.
C++ doesn’t run in the editor, only when the game is in play mode. For that reason you’ll want to use the OnBeginPlay event in the events graph to set up all your blueprint/c++ connections.

If you try to do anything with c++ in the blueprint construction script it just won’t do anything at all, and you’ll likely get crashes later because you’ll try to use values that are still null.

Goodluck! :slight_smile:

Will try that. Thank you!

I’m not entirely sure about this explanation. The Blueprint construction script is very much like a regular C++ constructor. It’s true that it’s run in editor when you move the object around, but it’s also called once when the game starts.
C++ constructors are also called in editor, but only when they’re instantiated. Try it out by printing in an actor based C++ class’s constructor and then dragging it into the world.

Regarding your problem, make sure you’ve included the correct .h-files in order for your C++ class to be able to reference your other class.

C++ equivalent of construction script is the function called

void OnConstruction (FTransform& ActorTransform)

this function is present in actor class and will be called each time you move the object in the editor or change value of its variable in the editor.

It’s a virtual function so it can easily be overridden and to call c++ construction script (OnConstruction function) from a derived blueprint you just call parent construction script in the blueprint

hope this helps

Thanx for your answers guys. I have made a workaround in the meantime, but have encountered the same issue again in another way.

I have a class called UStat that inherits from UObject. I have another class called UStatModifier that is also a UObject. In UStat I have among other things one base UStatModifier object that should be created when UStat is created. My code looks like this:



Stat.h
UCLASS(Blueprintable, editinlinenew, BlueprintType, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class TWINSTICK_API UStat : public UObject
{
	GENERATED_BODY()
Public:
UStat();
...

Protected:
UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = "StatBase")
class UStatModifier* BaseModifier;
...



Stat.cpp
UStat::UStat()
{
	BaseModifier = NewObject<UStatModifier>(this->StaticClass(), FName("BaseValue"));
	BaseModifier->SetDescription(NSLOCTEXT("Default", "Base", "Base value"));
	BaseModifier->SetCalculationType(ECalculationType::CalculationTypeBase);
	BaseModifier->SetValue(10);
}




StatModifier.h
UCLASS(Blueprintable, editinlinenew, BlueprintType, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class TWINSTICK_API UStatModifier : public UObject
{
	GENERATED_BODY()

public:
	UStatModifier();

StatModifier.cpp
UStatModifier::UStatModifier()
{
	Description = NSLOCTEXT("Default", "NoDesc" , "No Description");
	Value = 0;
	//CalculationType = CalculationTypeUnknown;
}



When I open my Stat class in Editor, I see my BaseModifier with all the values set up acording to what’s set up in UStat constructor. Problem is, when I change any BaseModifier’s propperties, every property for BaseModifier goes blank/multiple values, and poking the objec much further causes editor to crash. I’m getting quite desperate here, so please help :frowning:

By the way, in the documentation for UObject, the simplest constructor is APylon::APylon(const class FObjectInitializer& ObjectInitializer). On the other hand, when edotor creates a classs, itgives it an empty APylon::APylon() constructor. Which one is the one I should use and why?

Edit. a bit offtopic, I’m a c++ programmer with some experience, so I have some grasp of general programming concepts, but I’m really struggling to do any constructive work with unreal engine since it has quite a few not very well documented specific requirements. Is there any resource (tutorial, book, whatever) that would help me grasp unreal engine’s particularities? All entry level materials I have found focus mostly on describing c++ basics I don’t really need.

That’s not how you usually derive unreal classes.

Most likely it crashes because you forgot to call parent’s constructor via Super(objInit).

Your constructor should have “const FObjectInitializer&” param and you can’t pass extra parameters through it.
From within constructor you usually create objects with CreateDefaultObject, which takes that object initialzier as parameter.



UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter{
	GENERATED_BODY()
....
};
....
AMyCharacter::AMyCharacter(const FObjectInitializer& objInitializer)
:Super(objInitializer){
///
	cameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));//example
}


The best source of information is engine source code and"C++ first person shooter tutorial"](A new, community-hosted Unreal Engine Wiki - Announcements and Releases - Unreal Engine Forums).

Thank you!
I have reworked the constructor. Now I see the object instanced in c++, but I can’t expand it to view it’s propperties or instance a new class (list is empty).

All properties must be marked as EditAnywhere (or VisibleAnywhere), and BlueprintReadOnly. Anything that is not UPROPERTY, won’t be visible in editor. UPROPERTY without flags that would make it visible also won’t be visible in editor.

If your class contains structures, those structures must be based on USTRUCT, and must be declared as USTRUCT(BlueprintType) (i.e. “USTRUCT(BlueprintType) struct FMyStructure{…”). Otherwise data members within the struct won’t be visible. Once again, all data fields within structures must be declared as “UPROPERTY(EditAnywhere, BlueprintReadOnly)” (that’s just example, you could use different settings, but those will show up in editor).

Also, sometimes you need to restart the editor after recompiling your project - if data members don’t show up, for example.

Can’t say more without seeing portion of code for the “stats”.

Ok, I’ll post my code, I hope it isn’t too much

StatsComponent.h



#pragma once

#include "Object.h"
#include "CalculationType.h"
#include "StatsComponent.generated.h"

/**
 * Class for handling statistics
 * Every stat consists of multiple modifiers each with it's own description and type of operation it performs upon the result
 * For example, stat Max_health can have modifiers from different sources like as power armor, skills, buffs
 * For keeping scores and similar stats that are updated often, don't add gazillion modifiers, but change the base modifier
 * Since UPropperties should have garbage collection, there shouldn't be any need to release any memory manually. 
 */

UCLASS(Blueprintable, editinlinenew, BlueprintType, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class TWINSTICK_API UStatsComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UStatsComponent(const FObjectInitializer& ObjectInitializer);

	// Called when the game starts
	virtual void BeginPlay() override;

	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	/**
	* Adds a new modifyer to a stat. If stat does not exist, a new one is created
	* WARNING, every call to this function will create an object. Use AddToStatBase if ou wish to track fast changing data
	*
	* @param aStatName - name of the stat being added to
	* @param aDescription - description of the stat modifyer that could be presented to the user
	* @param aType - type of operation modifier applies to the result
	* @param aValue - value to be added
	* @return returns a pointer to the newly created item
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	class UStatModifier* AddNewModifierToStat(FName aStatName, FText aDescription, ECalculationType aType, float aValue);

	/**
	* Adds a new modifyer to a stat, based on an existing stat modifier. If stat does not exist, a new one is created
	*
	* @param aStatName - name of the stat being added to
	* @param aModifier - Pointer to the modifier being created
	* @return returns the handle of the modifier
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	void AddExistingModifierToStat(FName aStatName, class UStatModifier* aModifier);

	/**
	* Sets the value of the stat base. If stat does not exist, a new one is created
	*
	* @param aStatName - name of the stat being added to
	* @param aValue - value to be added
	* @return returns true if stat was found and edited
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	bool SetStatBase(FName aStatName, float aValue);

	/**
	* Removes modifier from a stat by stat modifier handle 
	*
	* @param aStatName - name of the stat being added to
	* @param aHandle - handle of the modifier being removed
	* @return - true if modifier was correctly erased
	*/
	//bool RemoveStatModifier(FName aStatName, uint32 aHandle); // removed handles thingy so this is not used

	/**
	* Removes modifier from a stat by stat modifier pointer
	*
	* @param aStatName - name of the stat being added to
	* @param aModifier - pointer to the modifier that should be inserted
	* @return - true if modifier was correctly erased
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	bool RemoveStatModifier(FName aStatName, class UStatModifier* aModifier);

	/**
	* Removes all the modifiers
	*
	* @param aStatName - name of the stat for reset
	* @param aResetBase - If true, resets base to zero
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	bool ResetStat(FName aStatName, bool aResetBase = true);

	/**
	* Gets the total value of a stat
	*
	* @param aStatName - name of the stat for reset
	* @param aResetBase - If true, resets base to zero
	* @return - the total valueof a stat
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	float GetStatValue(FName aStatName);

	/**
	* Gets the total value of a stat
	*
	* @param aStatName - name of the stat for reset
	* @param aResetBase - If true, resets base to zero
	* @return - the total valueof a stat
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	float GetStatBaseValue(FName aStatName);

	/**
	* Adds a variable to a bound results list. It will be updated on every recalculation
	* WARNING!!!, never forget to call UnbindResultVariable before destroying anobject referencing this
	*
	* @param aStatName - name of the stat to which variable is bound
	* @param aVariable - reference to the variable being added
	* @return - true if stat was found and variable added
	*/
	UFUNCTION(BlueprintCallable, Category = "ValueBinding")
	bool BindResultVariable(FName aStatName, float& aVariable);

	/**
	* Removes a variable from a bound results list. It should always be done before destroying an object ownint the variable
	*
	* @param aStatName - name of the stat from which variable should be unbound
	* @param aVariable - reference to a variable being removed
	* @return - true if the variable was correctly removed
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsModifiers")
	bool UnbindResultVariable(FName aStatName, float& aVariable);

	/**
	* Adds an existing stat. If the stat with the same name exists, bases are added and modifiers are appended to the resulting list
	* If the stat does not exist, one is created.
	*
	* @param aStat - pointer to a stat being added
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddRemove")
	void AddStat(class UStat* aStat);

	/**
	* Removes a stat from the list.  If the stat with the same name exists, bases are subtracted and modifiers removed.
	* If the stat does not exist, command is ignored
	*
	* @param aStat - pointer to a stat being removed
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddRemove")
	void RemoveStat(class UStat* aStat);

	/**
	* Adds another statistics object to this one. Bases will be added numericaly, modifiers will be shared to the list of item being added to
	*
	* @param StatisticsToAdd - name of the stat from which variable should be unbound
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddRemove")
	void AddStatistics(UStatsComponent* StatisticsToAdd);

	/**
	* Removes added statistics object. Base of the subtracting object will be numerically subtracted, modifier objects will be removed from the list.
	*
	* @param StatisticsToAdd - name of the stat from which variable should be unbound
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddRemove")
	void RemoveStatistics(UStatsComponent* StatisticsToAdd);

	/**
	* Gets a const list of all stats
	*
	* @return - pointer list to all stats
	*/
	UFUNCTION(BlueprintPure, Category = "StatsGetter")
	TArray <class UStat*> GetStats();

protected:

	/**
	* Finds the index of a stat by name
	*
	* @param aStatName - name of the stat to be searched
	* @param AIsCreateIfNotFound - if true and if name is not found, it will be added
	* @return - Index of the searched object. -1 if not found
	*/
	int32 GetStatIndex(FName aStatName, bool AIsCreateIfNotFound = false);

	/**
	* List of stats added to this object
	*/
	UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = "Container")
	TArray<class UStat*> Stats;	

	//TESTING
	UPROPERTY(EditAnywhere, instanced, BlueprintReadWrite, Category = "Container")
	class AMyActor* Test = nullptr;
};


StatsComponent.h



// Fill out your copyright notice in the Description page of Project Settings.

#include "TwinStick.h"
#include "Stat.h"
#include "StatsComponent.h"

UStatsComponent::UStatsComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	bWantsBeginPlay = true;
	PrimaryComponentTick.bCanEverTick = true;

	// ...
}

void UStatsComponent::BeginPlay()
{
	Super::BeginPlay();
}

void UStatsComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}

UStatModifier* UStatsComponent::AddNewModifierToStat(FName aStatName, FText aDescription, ECalculationType aType, float aValue)
{
	int32 index = GetStatIndex(aStatName, true);

	if (index >= 0)
		return Stats[index]->AddNewStatModifier(aDescription, aType, aValue);
	else
		return nullptr;
	//{
	//	UStat* newStat = NewObject<UStat>(this->StaticClass(), aStatName);
	//	newStat->SetName(aStatName);
	//	class UStatModifier* mod = newStat->AddNewStatModifier(aDescription, aType, aValue);
	//	Stats.Add(newStat);
	//	return mod;
	//}
}

void UStatsComponent::AddExistingModifierToStat(FName aStatName, class UStatModifier * aModifier)
{
	int32 index = GetStatIndex(aStatName, true);

	if (index >= 0)
		Stats[index]->AddExistingStatModifier(aModifier);
	//else
	//{
	//	UStat* newStat = NewObject<UStat>(this->StaticClass(), aStatName);
	//	newStat->SetName(aStatName);
	//	newStat->AddExistingStatModifier(aModifier);
	//	Stats.Add(newStat);
	//}
}

bool UStatsComponent::SetStatBase(FName aStatName, float aValue)
{
	int32 index = GetStatIndex(aStatName, false);

	if (index >= 0)
	{
		Stats[index]->SetBaseValue(aValue);
		return true;
	}

	else
	{
		UStat* newStat = NewObject<UStat>(this->StaticClass(), aStatName);
		newStat->SetName(aStatName);
		newStat->SetBaseValue(aValue);
		Stats.Add(newStat);
		return true;
	}

	return false;
}

//bool UStatsComponent::RemoveStatModifier(FName aStatName, uint32 aHandle) //removed because handles aren't used
//{
//}

bool UStatsComponent::RemoveStatModifier(FName aStatName, class UStatModifier* aModifier)
{
	int32 index = GetStatIndex(aStatName, true);

	if (index >= 0)
	{
		Stats[index]->RemoveStatModifier(aModifier);

		return true;
	}
	return false;
}

bool UStatsComponent::ResetStat(FName aStatName, bool aResetBase)
{
	int32 index = GetStatIndex(aStatName);

	if (index >= 0)
	{
		Stats[index]->Reset(aResetBase);

		return true;
	}
	return false;
}

float UStatsComponent::GetStatValue(FName aStatName)
{
	int32 index = GetStatIndex(aStatName);

	if (index >= 0)
	{
		return Stats[index]->GetValue();
	}
	return 0;
}

float UStatsComponent::GetStatBaseValue(FName aStatName)
{
	int32 index = GetStatIndex(aStatName);

	if (index >= 0)
	{
		return Stats[index]->GetBaseValue();
	}
	return 0;
}

bool UStatsComponent::BindResultVariable(FName aStatName, float& aVariable)
{
	int32 index = GetStatIndex(aStatName);

	if (index >= 0)
	{
		Stats[index]->BindResultVariable(aVariable);
		return true;
	}
	return false;
}

bool UStatsComponent::UnbindResultVariable(FName aStatName, float & aVariable)
{
	int32 index = GetStatIndex(aStatName);

	if (index >= 0)
	{
		Stats[index]->UnbindResultVariable(aVariable);
		return true;
	}
	return false;
}

void UStatsComponent::AddStat(UStat* aStat)
{
	if (IsValid(aStat))
	{
		int32 index = GetStatIndex(aStat->GetName());
		if (index < 0)
		{
			Stats.Add(aStat);
		}
		else
		{
			Stats[index]->AddStat(aStat);
		}
	}
}

void UStatsComponent::RemoveStat(UStat* aStat)
{
	if (IsValid(aStat))
	{
		uint32 index = GetStatIndex(aStat->GetName());
		if (index >= 0)
		{
			Stats[index]->RemoveStat(aStat);
		}
	}
}

void UStatsComponent::AddStatistics(UStatsComponent * StatisticsToAdd)
{
	if (IsValid(StatisticsToAdd))
	{
		for (UStat* statToAdd : StatisticsToAdd->GetStats())
		{
			int32 index = GetStatIndex(statToAdd->GetName(), true);

			if (index >= 0)
			{
				Stats[index]->AddStat(statToAdd);
			}
		}
	}
}

void UStatsComponent::RemoveStatistics(UStatsComponent * StatisticsToAdd)
{
	if (IsValid(StatisticsToAdd))
	{
		for (UStat* statToAdd : StatisticsToAdd->GetStats())
		{
			int32 index = GetStatIndex(statToAdd->GetName());

			if (index >= 0)
			{
				Stats[index]->RemoveStat(statToAdd);
			}
		}
	}
}

TArray<UStat*> UStatsComponent::GetStats()
{
	return Stats;
}

int32 UStatsComponent::GetStatIndex(FName aStatName, bool AIsCreateIfNotFound /*= false*/)
{
	for (int i = 0; i < Stats.Num(); ++i)
	{
		if (IsValid(Stats*) && Stats*->GetName() == aStatName)
			return i;
	}

	if (AIsCreateIfNotFound)
	{
		UStat* newStat = NewObject<UStat>(this->GetClass(), aStatName);
		newStat->SetName(aStatName);
		Stats.Add(newStat);

		return Stats.Num() - 1;
	}

	else
		return -1;
}


Stat.h



#pragma once
#include "CalculationType.h"
#include "Stat.generated.h"

/**
 * Class that holds one stat, it's name, value, modifiers, result bindings...
 * Stat consists of base value and modifiers. 
 * When adding one stat to another, modifiers from the first list will be appended, and bases will be summed
 * If you wish to hold just moifier values for adding to other stats (eg. in buffs) just leave the base at 0
 */

UCLASS(Blueprintable, editinlinenew, BlueprintType, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class TWINSTICK_API UStat : public UObject
{
	GENERATED_BODY()

public:

	/**
	* Default constructor
	*/
	UStat(const FObjectInitializer& ObjectInitializer);

	/**
	* Default destructor
	*/
	~UStat();

	/**
	* Gets the total value of the stat
	*
	* @return - value
	*/
	UFUNCTION(BlueprintPure, Category = "GettersAndSetters")
	float GetValue();

	/**
	* Gets the base value of the stat
	*
	* @return - base value
	*/
	UFUNCTION(BlueprintPure, Category = "GettersAndSetters")
	float GetBaseValue();

	/**
	* Sets the base value of the stat
	*
	* @param aValue - base value to set
	*/
	UFUNCTION(BlueprintCallable, Category = "GettersAndSetters")
	void SetBaseValue(float aValue);

	/**
	* Gets the name of the stat
	*
	* @return - base value
	*/
	UFUNCTION(BlueprintPure, Category = "GettersAndSetters")
	FName GetName();

	/**
	* Sets the name of the stat
	*
	* @param aName - name to be set
	*/
	UFUNCTION(BlueprintCallable, Category = "GettersAndSetters")
	void SetName(FName aName);

	/**
	* Adds a new modifier to a stat. Modifier object is owned by 
	*
	* @param aDescription - description of the stat modifyer that could be presented to the user
	* @param aType - type of operation modifier applies to the result
	* @param aValue - value to be added
	* @return returns a const pointer to the created item which can be used to remove the item later
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	class UStatModifier* AddNewStatModifier(FText aDescription, ECalculationType aType, float aValue);

	/**
	* Adds a modifyer to a stat through a pointer to an existing one 
	*
	* @param aModifier - Pointer to the modifier being created
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	void AddExistingStatModifier(class UStatModifier* aModifier);

	///**
	//* Removes modifier from a stat by stat modifier handle
	//*
	//* @param aHandle - handle of the modifier being removed
	//* @return - true if modifier was correctly erased
	//*/
	//UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	//bool RemoveStatModifier(uint32 aHandle);

	/**
	* Removes modifier from a stat by stat modifier pointer
	*
	* @param aModifier - pointer to the modifier that should be inserted
	* @return - true if modifier was correctly erased
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	bool RemoveStatModifier(class UStatModifier* aModifier);

	/**
	* Adds a variable to a bound results list. It will be updated on every recalculation
	* Warning, never forget to call UnbindResultVariable before destroying anobject referencing this
	*
	* @param aVariable - reference to the variable being added
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	void BindResultVariable(float& aVariable);

	/**
	* Removes a variable from a bound results list. It should always be done before destroying an object ownint the variable
	*
	* @param aVariable - reference to a variable being removed
	* @return - true if the variable was correctly removed
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	bool UnbindResultVariable(float& aVariable);

	/**
	* Removes all modifiers and resets basevalue if aResetBase
	*
	* @param aResetBase - default: true - if true, base willalso be reset to 0
	*/
	UFUNCTION(BlueprintCallable, Category = "Reset")
	void Reset(bool aResetBase = true);

	/**
	* Adds another stat values to this one. Bases will be numerically added, modifiers apended to list
	*
	* @param AStatToAdd - stat to be added to this one
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	void AddStat(class UStat* AStatToAdd);

	/**
	* Removes another stat values from this one. Bases will be numerically subtracted, modifiers will be removed from the list
	*
	* @param AStatToAdd - stat to eb added to this one
	*/
	UFUNCTION(BlueprintCallable, Category = "StatsAddAndRemove")
	void RemoveStat(class UStat* AStatToRemove);


	/**
	* Gets a list of all stats
	*
	* @return - pointer list to all stats
	*/
	UFUNCTION(BlueprintPure, Category = "StatsAddAndRemove")
	TArray <class UStatModifier*> GetModifiers();

protected:

	/**
	* Calculates total score and updates all bound variables
	*
	* @return - score
	*/
	UFUNCTION(BlueprintPure, Category = "InnerCalculation")
	float CalculateScores();

	/**
	* Name that identifies the stat.
	*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StatPropperties")
	FName Name;

	/**
	* Total value of the stat. Should be automatically updated on modifier add or edit
	*/
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "StatPropperties")
	float Value;

	/**
	* Counter that determines the next free handle
	*/
	UPROPERTY()
	int32 NextHandle;

	/**
	* Lost of locally created modifiers and their handles
	*/
	UPROPERTY()
	TMap <int32, class UStatModifier*> ModifierHandles;

	/**
	* List of external variabls that should be updated to corespond with this stat's value
	*/
	//UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VariableBinding")
	TArray <float*> BoundResultVriables;

	/**
	* Base modifier
	*/
	UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = "StatBase")
	class UStatModifier* BaseModifier;

	/**
	* List of modifier items that make up the final stat value
	*/
	UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category = "Modifier container")
	TArray <class UStatModifier*> Modifiers;



	//UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EnvironmentObjectOptions")
};


Stat.cpp



#include "TwinStick.h"
#include "StatModifier.h"
#include "Stat.h"

UStat::UStat(const class FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	NextHandle = 0;

	BaseModifier = NewObject<UStatModifier>(this->StaticClass(), FName("BaseValue"));
	BaseModifier->SetDescription(NSLOCTEXT("Default", "Base", "Base value"));
	BaseModifier->SetCalculationType(ECalculationType::CalculationTypeBase);
	BaseModifier->SetValue(10);
}

UStat::~UStat()
{
}

float UStat::GetValue()
{
	return Value;
}

float UStat::GetBaseValue()
{
	if (IsValid (BaseModifier))
		return BaseModifier->GetValue();
	else 
		return 0;
}

void UStat::SetBaseValue(float aValue)
{
	if (IsValid (BaseModifier))
		BaseModifier->SetValue(aValue);
}

FName UStat::GetName()
{
	return Name;
}

void UStat::SetName(FName aName)
{
	Name = aName;
}

UStatModifier* UStat::AddNewStatModifier(FText aDescription, ECalculationType aType, float aValue)
{
	UStatModifier* createdMod = NewObject <UStatModifier>();
	createdMod->SetDescription(aDescription);
	createdMod->SetCalculationType(aType);
	createdMod->SetValue(aValue);

	Modifiers.Add(createdMod);

	ModifierHandles[NextHandle] = createdMod;

	CalculateScores();
	//return NextHandle++; //handles system disabled, might be needed in the future
	return createdMod;
}

void UStat::AddExistingStatModifier(UStatModifier* aModifier)
{
	if (IsValid(aModifier))
	{
		Modifiers.Add(aModifier);

		ModifierHandles[NextHandle] = aModifier;
		CalculateScores();
		//return NextHandle++;
	}
}

//bool UStat::RemoveStatModifier(uint32 aHandle)
//{
//	UStatModifier** modToRemove = ModifierHandles.Find(aHandle);
//
//	if (modToRemove)
//	{
//		Modifiers.Remove(*modToRemove);
//		return true;
//	}
//
//	return false;
//}

bool UStat::RemoveStatModifier(UStatModifier* aModifier)
{
	if (Modifiers.Remove(aModifier))
	{
		CalculateScores();
		return true;
	}
	else
		return false;
}

void UStat::BindResultVariable(float& aVariable)
{
	BoundResultVriables.Add(&aVariable);
}

bool UStat::UnbindResultVariable(float& aVariable)
{
	if (BoundResultVriables.Remove(&aVariable))
		return true;
	else
		return false;
}

void UStat::Reset(bool aResetBase)
{
	Modifiers.Empty();

	if (aResetBase)
	{
		SetBaseValue(0);
	}
}

void UStat::AddStat(UStat* AStatToAdd)
{
	if (IsValid(AStatToAdd))
	{
		SetBaseValue(GetBaseValue() + AStatToAdd->GetBaseValue());

		for (class UStatModifier* mod : AStatToAdd->GetModifiers())
		{
			AddExistingStatModifier(mod);
		}
	}
}

void UStat::RemoveStat(UStat * AStatToRemove)
{
	if (IsValid(AStatToRemove))
	{
		SetBaseValue(GetBaseValue() - AStatToRemove->GetBaseValue());
		for (class UStatModifier* mod : AStatToRemove->GetModifiers())
		{
			RemoveStatModifier(mod);
		}
	}
}

TArray<UStatModifier*> UStat::GetModifiers()
{
	return Modifiers;
}

float UStat::CalculateScores()
{
	int sum = BaseModifier->GetValue();
	int percentage = 0;
	int basePercentage = 0;

	for (UStatModifier* mod : Modifiers)
	{
		switch (mod->GetCalculationType())
		{
			case ECalculationType::CalculationTypeAdd:
				sum += mod->GetValue();
				break;
			case ECalculationType::CalculationTypeAddPercent:
				percentage += mod->GetValue();
				break;
			case ECalculationType::CalculationTypeAddBasePercent:
				basePercentage += mod->GetValue();
				break;
		}	
	}

	sum += BaseModifier->GetValue() * basePercentage;
	sum += sum * percentage;

	Value = sum;

	for (float* boundVal : BoundResultVriables)
	{
		*boundVal = sum;
	}

	return sum;
}


StatModifier.h



#pragma once
#include "CalculationType.h"
#include "StatModifier.generated.h"


/**
 * Stat modifier class. Adds or reduces values to stats. 
 */

UCLASS(Blueprintable, editinlinenew, BlueprintType, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class TWINSTICK_API UStatModifier : public UObject
{
	GENERATED_BODY()

public:

	UStatModifier(const FObjectInitializer& ObjectInitializer);

	~UStatModifier();

	/**
	* Gets the modifier value
	*
	* @return - modifier value
	*/
	UFUNCTION(BlueprintPure, Category = "GettersAndSetters")
	float GetValue();

	/**
	* Sets the modifier value
	*
	* @param aValue - value to set
	*/
	UFUNCTION(BlueprintCallable, Category = "GettersAndSetters")
	void SetValue (float aValue);

	/**
	* Gets the modifier description
	*
	* @return - modifier value
	*/
	UFUNCTION(BlueprintPure, Category = "GettersAndSetters")
	FText GetDescription();

	/**
	* Sets the modifier description
	*
	* @param aDescription - description to set
	*/
	UFUNCTION(BlueprintCallable, Category = "GettersAndSetters")
	void SetDescription(FText aDescription);

	/**
	* Gets the modifier calculation type
	*
	* @return - modifier calculation type
	*/
	UFUNCTION(BlueprintPure, Category = "GettersAndSetters")
	ECalculationType GetCalculationType();

	/**
	* Sets the modifier calculation type
	*
	* @param aType - calculation type to set
	*/
	UFUNCTION(BlueprintCallable, Category = "GettersAndSetters")
	void SetCalculationType(ECalculationType aType);

protected:

	/**
	* modifier description
	*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModifierPropperties")
	FText Description;

	/**
	* modifier value
	*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModifierPropperties")
	float Value;

	/**
	* modifier calculation type
	*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ModifierPropperties")
	ECalculationType CalculationType;
};


StatModifier.cpp



#include "TwinStick.h"
#include "StatModifier.h"


UStatModifier::UStatModifier(const class FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	Description = NSLOCTEXT("Default", "NoDesc" , "No Description");
	Value = 0;
	//CalculationType = CalculationTypeUnknown;
}

UStatModifier::~UStatModifier()
{
}

float UStatModifier::GetValue()
{
	return Value;
}

void UStatModifier::SetValue(float aValue)
{
	Value = aValue;
}

FText UStatModifier::GetDescription()
{
	return Description;
}

void UStatModifier::SetDescription(FText aDescription)
{
	Description = aDescription;
}

ECalculationType UStatModifier::GetCalculationType()
{
	return CalculationType;
}

void UStatModifier::SetCalculationType(ECalculationType aType)
{
	CalculationType = aType;
}


Why are you dynamically creating UStat objects?

It isn’t “optional” object, and there’s absolutely no reason to create it dynamically or to use UObject as a base class. Just turn it into USTRUCT and things will be significantly easier. Also, there’s no reason to use getters and setters when they aren’t doing anything special. Just make variable public in that case.

The design overall looks needlessly overcomplicated.


Anyway. I never had a need to dynamically create anything that is not a component and have it editable editor.

If you want data that needs to be filled in editor, try using USTRUCTS. TArray of USTRUCTS works well and it is editable.

For private/protected variables you may need to specify meta = (AllowPrivateAccess = “true”)



	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;


It makes sense to use pointers only when they point at asset or something in the scene, or if they can be nullptr or if several of them can point at the same object. Your code does not look like it needs pointers OR dynamically created objects, although I might’ve overlooked something.

Well, what I wanted to accomplish was to make UStats addable and editable from the editor as some of the people in the office start crying at the mention of visual studio. I could have used USTRUCT, but I’m green enough in unreal not to know anything about them until now.

As for getters and setters, I have a habit of using them always for consistency and future proofing. It turned out during my last project that not knowing what can be accessed directly and what should be accessed with a getter was quite inconvenient.

I’ll try converting to USTRUCT and see if it works, but I’d still appreciate it a lot if someone could point me to an error in the code quoted above since I’ll surely encounter similar situation again.

I’d want a bit simpler interface in that case. I think non-programmer wouldn’t like using getters/setters via blueprints. You can keep variables protected and allow direct read/write from blueprints too, if that makes sense in the situation.

Also, if you use UStructs, you’ll get the same thing. Addable/editable elements, using built-in editor. No point in dynamically creating them with NewObject.

Also see: