Game Development with UE
UE data types
Как вычислить размеры для VAT (Vertex Animation Texture)?
Frame_count * point_count = texture size
Сколько точек у персонажа * на количество фреймов = размер текстуры (2048 * 2048) (количество точек у текстуры)
1 вертекс на фрейм берёт 8 байт на Вертекс оффсет и 4 байта на Нормаль
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.
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.
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)
Developing the AssetActionUtility in C++
Function Pointers
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.
Function-Scoped Static Variables
MACRO
FORCEINLINE and virtual
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.
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.
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()
Inheritance
Syntax: class Derived : public 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
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
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
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++. Avirtual
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));
ComposeRotators() has matrix multiplication inside. Matrix multiplication is not commutative.
Separate page about pointers in UE C++ framework
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:
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