Holy Hedgehogs I figured it out. I’d love to just say Thank EVERYONE for all the support and Ideas you have thrown out, including some of the alternative approaches, which I’d like to take a quick moment to address.
@Fathurahman
The UDataAsset is a great idea unfortunatly the traits are not static, there are ranks that increase as the character levels up and grows. But I learned about a new toy I plan to utilize in the future. Thanks for the idea.
@Manoelneto
The use of ActorComponents would have also been an interesting idea. Similar to the Enum Idea except with more functionality inherently.
The Solution: (v4.11.2)
As a quick recap, I am trying to make the construction of some UObjects within a Component Designer friendly, so that Designers can add Game Data to a Component that then Generates the appropriate UObject. The Designer can Open up the Character Blueprint, Select the Component and then Add/Edit/Remove UObjects from a list within the Component. The hierarchy appears like this:
Character
- ActorComponent
– UObject Array
Quick Solution:
If you are in the editor and plan to construct objects dynamically using PostEdit events then be aware that the outer parameter in the NewObject function must be the object that will directly reference the object.
UObject * Obj = NewObject<UObject>(outer, type);
Technical Overview:
The way it will work is a Designer works directly with a Struct. This struct is a Memento object that is also used to save data to the disk and load from it as well. If you are unfamiliar with a memento it is an internal representation of an object without giving direct access to the internals of that object. Using PostEditChainChange functions you iterate through the struct array and build new objects with the values specified by the designer. Benefit to this is if all your code to manage the construction of these objects is wrapped in the #if WITH_EDITOR then it will only exist when you are using the editor. Your game will not contain the struct array used by the designers and will only have the Created Real UObjects your game runs off of.
Alternative Approaches:
One possible method to implement this would have been to allow Designers to Directly edit the UObject by setting the UObject to be InlineEditNew and the Array to be Instanced. This actually worked pretty well except for a minor bug/problem. Lets say you Derive BP_MySpecialObject. Then add your BP_MySpecialObject to the Array of UObjects. The Editor flags the entire object as changed even if you haven’t changed any value, My rational is that it is treating this as an Instance of a completely new Blueprint object. If you go back and edit BP_MySpecialObject it will not update the instance in your Character. The Second problem is that if the BP_MySpecialObject is not already loaded into memory it will not show up as an option for the Drop down to create a new instance, you will have to open the BP and recompile it to force it into memory and use it (As of version 4.11.2).
The Problem:
The problem I was having was after everything was constructed and ready to go, recompiling caused all the dynamically created objects to vanish.
Solution:
It is simple. There seems to be a requirement/bug that when you dynamically construct a UObject nested within another object in the editor (for example a UObject within a Component while editing the Character Blueprint) the object must have an “outer” set to the object that the Post Edit event is being fired for (it seems). My problem was I was constructing the objects using the Owner of the Component. The moment I set the outer parameter of the NewObject function to be the Component itself the data was saved.
Code:
What follows is my test code for making this work.
MyObject.h
#pragma once
#include "Object.h"
#include "MyObject.generated.h"
USTRUCT(Blueprintable, BlueprintType)
struct MY_PROJECT_API FMyObjectData
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data|MyObject")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Data|MyObject")
int32 Value;
};
UCLASS(Blueprintable, BlueprintType)
class MY_PROJECT_API UMyObject: public UObject
{
GENERATED_BODY()
public:
void GetData(const FMyObjectData& data);
void InitializeFromData(const FMyObjectData& data);
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ACS|Trait")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ACS|Trait")
int32 Value;
};
MyObject.cpp
#include "Apollo.h"
#include "Trait.h"
void UMyObject::GetData(const FMyObjectData & data)
{
data.DisplayName = this->DisplayName;
data.Value = this->Value;
}
void UMyObject::InitializeFromData(const FMyObjectData & data)
{
this->Identifier = data.Identifier;
this->SetData(data);
}
MyObjectComponent.h
#pragma once
#include "Components/ActorComponent.h"
#include "MyObject.h"
#include "MyObjectComponent.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MY_PROJECT_API UMyObjectComponent: public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UMyObjectComponent();
// Called when the game starts
virtual void BeginPlay() override;
// Called every frame
virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;
#if WITH_EDITOR
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Objects")
TArray<FMyObjectData> genObjects;
virtual void PostEditChangeProperty(FPropertyChangedEvent & e) override;
virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent & e) override;
virtual void UpdateObjects();
virtual void AddObjects();
#endif
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Instanced, Category = "Objects")
TArray<UMyObject*> myObjects;
};
#include "Apollo.h"
#include "MyObjectComponent.h"
// Sets default values for this component's properties
UMyObjectComponent::UMyObjectComponent()
{
// 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;
// ...
}
// Called when the game starts
void UMyObjectComponent::BeginPlay()
{
Super::BeginPlay();
// ...
}
// Called every frame
void UMyObjectComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
Super::TickComponent( DeltaTime, TickType, ThisTickFunction );
// ...
}
void UMyObjectComponent::PostEditChangeProperty(FPropertyChangedEvent& e)
{
Super::PostEditChangeProperty(e);
}
void UMyObjectComponent::PostEditChangeChainProperty(FPropertyChangedChainEvent& e)
{
FName PropertyName = (e.Property != nullptr) ? e.Property->GetFName() : NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMyObjectComponent, genObjects))
{
this->AddObjects();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(FMyObjectData, DisplayName) || PropertyName == GET_MEMBER_NAME_CHECKED(FMyObjectData, Value))
{
this->UpdateObjects();
}
Super::PostEditChangeChainProperty(e);
}
void UMyObjectComponent::UpdateObjects()
{
for (int32 i = 0; i < this->xTraits.Num(); i++)
{
FMyObjectData * data = &genObjects*;
UMyObject* myObject = this->myObjects*;
if (myObject != nullptr)
{
myObject ->SetData(*data);
}
}
}
void UMyObjectComponent::AddObjects()
{
this->myObjects.Empty();
for (int32 i = 0; i < this->genObjects.Num(); i++)
{
FMyObjectData* data = &genObjects*;
UMyObject* object= NewObject<UMyObject>(this, UMyObject::StaticClass());
trait->InitializeFromData(*data);
this->myObjects.Add(objuect);
}
}