UObjects, Generated Classes, and Class Default Objects (CDOs)

Introduction

This post documents how a UObject functions in Unreal Engine and how the classes generated by the Unreal Header Tool (UHT) work.

If we create a new game project and add a c++ class which derives from ActorComponent we get this:

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UOBJECTS_API UMyActorComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UMyActorComponent();

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

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

The UCLASS and GENERATED_BODY() macros are used by the Unreal header tool to generate additional code which supports reflection, serialization etc. and most importantly supports using the class in the Unreal Editor. It also support using the class from python, which we will do below in the interest of making the examples shorter.

We add a member variable and use the UPROPERTY macro like so:

UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 Age = 20;

Now we can access the Age property in two ways, directly, or using the SetEditorProperty method:

// c++
UMyActorComponent* Component = NewObject<UMyActorComponent>();
Component->Age = 13;

or

# python 
component = unreal.MyActorComponent();
component.age = 13;
component.set_editor_property("Age",13);

Using set_editor_property is the preferred way because it interacts with the property system, sending on-property-changed events and flagging the object has dirty and needing saving.

Class Default Objects (CDOs)

A class default object is an instance of a class which is created and owned by Unreal and which holds the default values for the classes properties. The class default object is generated when the engine starts and is sometimes recreated, for example if the class is a blueprint, it is recreated whenever the blueprint is recompiled.

The fact that the class default object is generated as part of engine initialization is important. It means that, in your object's constructor, you should not access the Engine, which might not be completely initialized, or the World, which might not exist, or other object which also might not have been initialized yet. These kind of accesses are better left to other post-initialization stages such as BeginPlay().

For a non-blueprint class the values stored in the properties of the class default object are set by the constructor of that class. For a blueprint class the values stored in the properties of the class default object are read from the blueprint .uasset file.

The class default object can be used when creating a new object - after calling the object constructor the values from the class default object can be copied directly into the new object.

The class default object is accessed in various ways.

In c++ you can do this:

UMyActorComponent* CDO = GetMutableDefault<UMyActorComponent>();

or

UMyActorComponent* CDO = 
     Cast<UMyActorComponent>( UMyActorComponent::StaticClass()->GetDefaultObject() );

or

UMyActorComponent* CDO = 
    UMyActorComponent::StaticClass()->GetDefaultObject<UMyActorComponent>();

and in python you can do this:

component = unreal.MyActorComponent();
cdo = component.get_default_object()

or

cdo = unreal.MyActorComponent.get_default_object()

Static Classes

When the Unreal header tool processes UMyActorComponent.h it generates code for a UClass structure specific to the UMyActorComponent class.

This instance of UClass contains data used in the reflection system including data about each function and property of the class.

The static class is accessed in c++ like this:

UClass* MyClass = UMyActorComponent::StaticClass();

and in python:

my_class = unreal.MyActorComponent.static_class()

The static class can be used to retrieve reflection information, for example the class name:

print(unreal.MyActorComponent().static_class().get_full_name())

Class /Script/UObjects.MyActorComponent

Generated Classes

This section applies only to blueprints.

If we create a new blueprint derived from the Actor class, and then add a MyActorComponent component to it, the "age" property of that component will have the default value of 20 which we set in the MyActorComponent constructor in c++ and which is now in the MyActorComponent class default object.

If we open the new blueprint in the Unreal editor and change the value of "age" to 25 and save the blueprint, 25 is now that default value of age which will be used when any instance of the blueprint is created (for example by dropping it on the world map).

In python we can load the blueprint asset like this:

bp =  unreal.EditorAssetSubsystem().load_asset("/Game/Blueprints/NewBlueprint")

The variable bp now holds an object of type unreal.Blueprint - note that the blueprint has a different type than the object is will create; the blueprint is an unreal.Blueprint, not an unreal.Actor which is the type of thing which the blueprint can create.

Like the MyActorComponent class, a bluepint has a static class:

static = unreal.EditorAssetSubsystem().load_asset("/Game/Blueprints/NewBlueprint").static_class()

and a class default object

cdo = unreal.EditorAssetSubsystem()
   .load_asset("/Game/Blueprints/NewBlueprint").get_default_object()

Note that unreal.EditorAssetSubsystem().load_asset returns a reference to the loaded asset. If the asset is already in memory it will immediately return its address, not load it again, so there is only one instance of that loaded asset returned, so this works:

asset1 = unreal.EditorAssetSubsystem().load_asset("/Game/Blueprints/NewBlueprint")
asset2 = unreal.EditorAssetSubsystem().load_asset("/Game/Blueprints/NewBlueprint")
assert( asset1 is asset2 )

Blueprints also have an associated generated class. This can be retrieved like so:

gen = unreal.EditorAssetSubsystem()
   .load_asset("/Game/Blueprints/NewBlueprint").generated_class()

The generated class is of type BlueprintGeneratedClass. Alternatively the BlueprintGeneratedClass can be loaded directly like this:

gen = unreal.EditorAssetSubsystem()
   .load_blueprint_class("/Game/Blueprints/NewBlueprint.NewBlueprint")

You can do things with the BlueprintGeneratedClass such as spawn an actor into the level like this:

gen = unreal.EditorAssetSubsystem()
   .load_blueprint_class("/Game/Blueprints/NewBlueprint.NewBlueprint")
assert isinstance(gen, unreal.BlueprintGeneratedClass )
location = unreal.Vector(0, 0, 0)
rotation = unreal.Rotator()
unreal.EditorLevelLibrary
   .spawn_actor_from_class(actor_class=gen, location=location, rotation=rotation)

Default values of Blueprint Components

From a blueprint you can find its components and then find their class default objects, to get default values and reflection information about each component which makes up the blueprint.

For example we created a new blueprint "/Game/Blueprint/NewBlueprint" which contains our MyActorComponent component. Now we can load the blueprint and find the MyActorComponent component like so:

subsystem = unreal.get_engine_subsystem(unreal.SubobjectDataSubsystem)
blueprint = unreal.EditorAssetSubsystem().load_asset("/Game/Blueprints/NewBlueprint")
library = unreal.SubobjectDataBlueprintFunctionLibrary()

# go from subobject handles -> subobjects -> component objects

components = [ comp for comp in 
				[ library.get_object(subobject) for subobject in 
					[ subsystem.k2_find_subobject_data_from_handle(handle) for handle in 
						subsystem.k2_gather_subobject_data_for_blueprint(context=blueprint) ] 
					]
				if comp.get_class().get_name() == "MyActorComponent"
			]

# we only have one component of type "MyActorComponent"
assert( len(components) == 1 )

component = components[0]

From this component we can access two different class default objects: (1) for the original MyActorComponent class and (2) for the blueprint component itself. We can retrieve both of these like so:

# get the default value on the MyActorComponent class
print(component.get_default_object().get_editor_property("age"))
> 20
# get the default value on the NewBlueprint blueprint
print(component.get_editor_property("age"))
> 25

References

Epic Objects in Unreal Engine
Base classes
Python nested comprehensions