GameDev
June 26, 2024

Game Development with UE

UE data types

My data table of the most frequently used classes
Screenshot made from Reddit thread

Как вычислить размеры для VAT (Vertex Animation Texture)?

Frame_count * point_count =  texture size

Сколько точек у персонажа * на количество фреймов  =  размер текстуры (2048 * 2048) (количество точек у текстуры)

Screenshot made by one developer - enthusiast

1 вертекс на фрейм берёт 8 байт на Вертекс оффсет и 4 байта на Нормаль

Что означает 1 вертекс/фрейм это 12 байт памяти



Ticking order

The ticking order of components within an actor is generally designed to ensure that parent components tick before their child components. This is because the parent's state often influences the child's state, and ensuring the parent ticks first helps maintain logical consistency in updates.

Screenshot from source code UE5.2

Unreal Insight for Blueprints

If you are going to profile blueprints and functions of your blueprints, you need to enable AssetLoadTime channel in Trace settings. Then all blueprint functions will be written with their name instead of the default Function.

Screenshot from UE5.2

ECollisionChannel Enum

Names of custom collision channels are saved locally in the project and are not exported during packaging. It's literaly the meta tags of the channels. You cannot rely on them in the game (you can only use them in the editor)

Screenshot from game exe file made with UE5.2

Developing the AssetActionUtility in C++

Add these lines to <module_name>.Build.cs

Screenshot from Rider for UE5.2

Function Pointers

From https://mikelis.net/functions-as-data-in-unreal-engine-5/

TFunction<> is very basic, and it has a lower overhead and complexity than delegates. This makes it a common choice for passing functions as arguments in the engine's C++ code.

Screenshot made from ChatGPT4.o
Screenshot made from ChatGPT4.o

Function-Scoped Static Variables

Screenshot made from ChatGPT4.o

MACRO

Screenshot made from ChatGPT4.o
Screenshot made from ChatGPT4.o
Screenshot made from UE5.2
Screenshot made from ChatGPT4.o
Screenshot made from ChatGPT4.o

FORCEINLINE and virtual

Screenshot made from ChatGPT4.o

Why You Should Declare a Virtual Destructor in an Interface

In the context of interfaces (or abstract classes), you often declare a virtual destructor even if it's empty. This is crucial for ensuring proper cleanup when deleting objects through a pointer to the interface.

When a class is intended to be used as an interface (i.e., other classes inherit from it and are accessed via pointers to the base interface class), you need a virtual destructor to ensure that the destructors of derived classes are called correctly. If the destructor is not virtual, only the base class's destructor will be called when an object is deleted through a pointer to the base class, leading to potential memory leaks or incomplete destruction of the object.

Screenshot made from ChatGPT4.o

Garbage Collection

When a UObject-derived object is instantiated it gets registered with Unreal Engine's garbage collection (GC) system. The garbage collection system automatically runs every 30-60 seconds (or less depending on how much free memory remains on the system) and looks for any objects which are no longer being used and removes them.

From https://unrealcommunity.wiki/garbage-collection-36d1da

AddReferencedObjects() function for communication with Garbage Collector.

//Non-UObject-derived class---------------------------------
/** Collect references held by this collection */
void AddReferencedObjects(UObject* Referencer, FReferenceCollector& Collector);

//.cpp file
void FBSModuleCollectionBase::AddReferencedObjects(UObject* Referencer, FReferenceCollector& Collector)
{
    Collector.AddStableReferenceMap(ModuleMap);
}

//UObject-derived class---------------------------------
//Communication with Garbage Collector
static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector){};

//.cpp file
void UBSCoreSubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
    UBSCoreSubsystem* This = CastChecked<UBSCoreSubsystem>(InThis);
    This->TemporalModuleCollection.AddReferencedObjects(This, Collector);
    This->PersistentModuleCollection.AddReferencedObjects(This, Collector);
    //BSCoreSubsystem doesn't need to be referenced this way because
    //the Outer (UGameInstance) has already did it by its SubsystemCollection object, so the next line is redundant.
    //UObject::AddReferencedObjects(This, Collector);
}

AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) needs to be static. This is because it’s part of Unreal Engine's garbage collection system, and it operates at the class level, not on individual instances of objects. As a result, it cannot be virtual, and you must implement it manually in each class that needs custom garbage collection behavior.

Non-UObject-derived class are not called by Garbage Collector, that is why we need to provide such connection manualy. The example above shows it: UBSCoreSubsystem (UObject-derived) send references from FBSModuleCollectionBase (native cpp class) what stores a TMap of UObjects. Without this functions GC will clear the UObjects in TMap but IsValid() will not catch it. This situation will lead to crash (pointer will be valid but memory what this pointer redirect to will be cleaned).

I spent a lot of time to understand the nature of such strange error. Pay attention!


constexpr vs const

The constexpr specifier is used to declare that the value of a variable or function can be evaluated at compile time. It ensures that the variable or function is a constant expression, which means it can be computed at compile time.

The const qualifier indicates that the value of a variable cannot be changed after it has been initialized. It is a runtime constant, meaning its value can be set at runtime but cannot be modified afterward.


Memory and MoveTemp()

Screenshot made from ChatGPT4.o

Inheritance

Screenshot made from ChatGPT4.o

Public Inheritance:

Syntax: class Derived : public Base

Protected Inheritance:

Syntax: class Derived : protected Base

Private Inheritance:

Syntax: class Derived : private Base


Friend classes

class USubsystem : public UObject { 
    private: friend class FSubsystemCollectionBase; 
    FSubsystemCollectionBase* InternalOwningSubsystem; 
};

This line declares FSubsystemCollectionBase as a friend of the USubsystem class. Because of this friendship, any function within FSubsystemCollectionBase can access private and protected members of USubsystem, including the InternalOwningSubsystem pointer

Friendship is Not Inherited: When a class is declared as a friend, only that specific class gets access to the private and protected members of the declaring class. Friendship is not inherited by any derived classes. This means that derived classes of the friend class do not automatically get access to private members of the class that declared the friendship.

Private vs. Public friend: The visibility (private or public) of a friend declaration has no impact on the friendship itself. Whether the friend class declaration is in the private, protected, or public section, it still grants the same access rights to the specified friend class. This does not restrict or expand the scope of the friendship.


TGuardValue

TGuardValue is a utility in Unreal Engine's codebase that is used to temporarily set a variable to a specific value for the duration of a scope, and then automatically restore it to its original value when the scope ends. This is particularly useful for managing state in a way that ensures the original state is restored, even if the function exits early due to an error or return statement.

TGuardValue<bool> PopulatingGuard(bPopulating, true);

1. Temporary Assignment: Sets the bPopulating variable to true immediately upon creation.

2. Automatic Restoration: When the PopulatingGuard object goes out of scope (typically at the end of the function or the end of a block of code), it automatically sets bPopulating back to its original value (whatever it was before TGuardValue was created).


Unreal Engine's coding convention

Screenshot made from ChatGPT4.o

F: This prefix stands for Framework and is used for plain C++ classes and structs that are not part of Unreal's reflection system (i.e., they are not UCLASS, USTRUCT, etc.). Classes prefixed with F are often utility classes, data containers, or core engine framework types that don't require runtime reflection or Blueprint exposure.


C++ preprocessor

Screenshot made from ChatGPT4.o

They processes your code before the actual compilation. You can use these directives to conditionally compile sections of your code based on whether a macro is defined or not.

#if + #endif //Used for checking the value (true/false)

TSharedPtr vs TSharedRef

Screenshot made from ChatGPT4.o

Function cannot be both virtual and static at the same time in C++

  • virtual functions: These are part of the object-oriented nature of C++. A virtual function depends on an instance of a class (i.e., an object) to determine which version of the function to call based on the actual type of the object at runtime. Virtual functions are used for runtime polymorphism and require an object instance because the virtual function table (vtable) is tied to the object's type.
  • static functions: These belong to the class itself, not to an instance of the class. A static function does not require an object instance to be called. It can be called using just the class name and does not have access to the instance-specific data (such as member variables or other non-static members). static functions exist at the class level and are resolved at compile time, not runtime.

ComposeRotators()

RotatorAB = UKismetMathLibrary::ComposeRotators( RotatorA, RotatorB));

RotatorBA = UKismetMathLibrary::ComposeRotators( RotatorB, RotatorA));

RotatorAB != RotatorBA !!!

ComposeRotators() has matrix multiplication inside. Matrix multiplication is not commutative.


Separate page about pointers in UE C++ framework





Different types of casts


Add an object to the root set. This prevents the object and all its descendants from being deleted during garbage collection.

Garbage collector finds every uobject thankfully to Root objects. How does it work? Like Roots in Builder System?



Unable to bind delegate to '%s' (function might not be marked as a UFUNCTION or object may be pending kill)



Pointers:

TObjectPtr:


its participation in garbage collection is identical to a
* raw pointer to a UObject.


LogStreaming: Warning: LoadPackage: SkipPackage: /VVRBuilderSystem/BuilderToolkit/BP_MenuDisplay (0xB9A4E0B630520533) - The package to load does not exist on disk or in the loader
[2024.11.18-10.50.38:213][193]LogUObjectGlobals: Warning: Failed to find object 'Object /VVRBuilderSystem/BuilderToolkit/BP_MenuDisplay.BP_MenuDisplay_C'

SoftObjectPTR can failed if it references to redirector to asset

UE_BUILD_SHIPPING or WITH_EDITOR macros