Unreal Engine 5 C++ Modern Development
This skill defines strict development standards for Unreal Engine 5 C++ code. The goal is to enforce high-quality, performant Unreal Engine code that follows official recommendations.
When to Use
Use this skill when:
- Writing C++ code for Unreal Engine 5.x
- Developing gameplay systems
- Writing Actors, Components, or Subsystems
- Exposing APIs to Blueprint
- Optimizing performance
- Building reusable engine systems
Do NOT use when:
- Working with Blueprint-only logic
- Writing non-Unreal code
- Working in other engines
Custom Project Rules
Before generating or modifying any code, you MUST first check for project-specific rules in custom_rules.md within this skill directory.
If custom_rules.md exists, its rules override and take priority over any rule in this file.
Class Templates
When asked to generate a NEW C++ class from scratch, you must use the provided templates as your starting point:
- Header Template:
templates/TemplateClass.h - Source Template:
templates/TemplateClass.cpp
Never invent a new class structure if templates are provided. Always start from the templates.
Substitute TEMPLATE_CLASS_NAME and YOURMODULE_API with the appropriate names.
Core Philosophy
C++ First Architecture
Core gameplay logic should be implemented in C++ whenever possible.
Blueprints should mainly be used for:
- configuration and asset references
- UI layout and animation (UMG)
- simple event wiring and visual effects
Avoid implementing complex state machines, heavy math, or core gameplay loops purely in Blueprints.
Modern Unreal Code Structure
Include What You Use (IWYU) & Headers
Always follow the IWYU principle to optimize compile times.
- Never include unnecessary headers.
- Use forward declarations for classes and structs in headers whenever possible.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MyComponent.generated.h"
class UHealthComponent; // Forward Declaration
Class Layout
Classes must be organized by access level and functionality.
UCLASS()
class MYMODULE_API UMyComponent : public UActorComponent
{
GENERATED_BODY()
public:
UMyComponent();
virtual void TickComponent(...) override; // 1. Virtuals
void Heal(float Amount); // 2. Public API
protected:
virtual void BeginPlay() override; // 3. Protected Lifecycle
private:
void UpdateState(); // 4. Internal Logic
UPROPERTY(VisibleAnywhere, Category = "Health")
TObjectPtr<UHealthComponent> HealthComponent; // 5. UProperties
int32 InternalCounter = 0; // 6. Native Variables
};
Modern UE5 C++ & Memory
UObjects & Garbage Collection
Always track UObject references dynamically allocated or exposed to the editor.
Preferred pointer types:
TObjectPtr<T>: For class members pointing to UObjects (replaces raw pointers in UE5).TWeakObjectPtr<T>: To observe UObjects without preventing garbage collection.TSoftObjectPtr<T>: For lazy-loading asynchronous assets.
Never store unmanaged raw UObject* as class members without UPROPERTY().
Standard C++ vs Unreal Types
- Standard Containers: Avoid using
std::containers (std::vector,std::string) in gameplay code. Always prefer Unreal equivalents (TArray,TMap,FString).std::is only acceptable in standalone editor tools or isolated third-party wrappers. - No C++ Exceptions: Unreal builds with exceptions disabled. Do not use
try/catch. - Auto keyword: Only use
autowhen the type is strictly obvious from the right side of the assignment, or for complex iterator types. Do not use it blindly.
Const Correctness
Use const extensively.
- Pass parameters by
const Type&when not modifying them. - Mark getter functions as
const. - Use
constpointers when the pointed-to object shouldn't be modified.
Performance Guidelines
Tick
Tick must be disabled by default in constructors:
PrimaryActorTick.bCanEverTick = false;
PrimaryComponentTick.bCanEverTick = false;
Only enable Tick for continuous visual updates, interpolation, or custom physics.
For logic state checks, use Timers or Delegates. If Tick is required, reduce frequency (TickInterval = 0.1f).
Casting & Component Lookup
- Avoid casting inside loops or Tick.
- Avoid string-based or class-based lookups (
FindComponentByClass) in loops or Tick. - When you need to retrieve multiple components without allocating heap memory, use
TInlineComponentArray:
TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents(TargetActor);
for (UPrimitiveComponent* PrimComp : PrimitiveComponents) { ... }
Cache essential component references during initialization:
void AMyActor::PostInitializeComponents()
{
Super::PostInitializeComponents();
HealthComponent = FindComponentByClass<UHealthComponent>();
check(HealthComponent);
}
Array Memory Preallocation
When populating arrays/maps and the final size is known (or roughly known), always use Reserve() to avoid hidden memory allocations.
TArray<AActor*> ValidActors;
ValidActors.Reserve(SourceActors.Num());
Assertions & Validation
Fail fast during development. Use Unreal's assertion macros.
check(Condition): Halts execution if false (stripped in Shipping).checkf(Condition, TEXT("Message")): Halts with formatted log.ensure(Condition): Logs a callstack the first time it fails, but continues execution.ensureAlways(Condition): Like ensure, but logs every time it fails.
Pointer Validation:
if (IsValid(TargetActor)) // preferred for Actors/UObjects (checks PendingKill)
{ ... }
Strings & Text
Understand the difference between Unreal string types:
FName: Fast, lightweight, immutable string used for IDs, tags, and map lookups. Never useFStringas a key in aTMap; always useFName.FText: Localizable text. Used for all UI and player-facing text.FString: Mutable string for general manipulation and logging. Heavy.
Asset References
Avoid unnecessary hard references that cause chain-loading.
Bad (Loads AWeapon into memory as soon as this class loads):
UPROPERTY(EditDefaultsOnly)
TSubclassOf<AWeapon> WeaponClass;
Good (Loads only when explicitly requested):
UPROPERTY(EditDefaultsOnly)
TSoftClassPtr<AWeapon> WeaponClass;
Asynchronous Loading:
For heavy assets, use the FStreamableManager from UAssetManager to load TSoftObjectPtr or TSoftClassPtr asynchronously, preventing hitches/freezing on the main thread.
Hardcoded Paths
Avoid hardcoding asset paths in gameplay systems using ConstructorHelpers::FObjectFinder (e.g., TEXT("/Game/P_Explosion")).
This is brittle and forces synchronous loading. Expose UPROPERTY(EditDefaultsOnly) on the class and assign the asset in Blueprint. Exception: Hardcoded paths are acceptable for core UI or developer tools.
Interfaces & Subsystems
Use modern Epic paradigms for architecture decoupling.
Interfaces & Loose Coupling
Avoid direct Cast<T> to specific classes for gameplay events. Hardcasting creates rigid dependencies and forces headers to be included across modules.
Instead, use UINTERFACE to decouple systems.
if (TargetActor->Implements<UInteractable>())
{
IInteractable::Execute_OnInteract(TargetActor, Instigator);
}
Rule of thumb for Interfaces:
- Create interfaces based on shared behavior (e.g.,
IDamageable,IInteractable), not specific objects. - Do not create hyper-specific micro-interfaces (
IDoorInterface,IChestInterface). - First check if an existing engine interface (like
IGameplayTaskOwnerInterface) orGameplayTagscan solve the problem before creating a newUINTERFACE.
Subsystems
Use UGameInstanceSubsystem, UWorldSubsystem, or ULocalPlayerSubsystem for global managers instead of Singletons or massive Managers on the GameMode.
Data-Driven Architecture (Data Assets)
Avoid hardcoding parameters or configurations purely into UPROPERTY() instance variables on Actors if those values describe a "type" or "definition" of an object (like Weapon stats, Character stats, Item data).
Instead, move those definitions into a UPrimaryDataAsset or UDataAsset.
// GOOD: Data-Driven paradigm
UPROPERTY(EditDefaultsOnly, Category = "Config")
TObjectPtr<UWeaponDataAsset> WeaponConfig;
Gameplay Tags: Prefer GameplayTags over hardcoded enums or booleans when defining gameplay categories, states, or events.
Static Functions & GetWorld()
Do not rely on implicit GetWorld() access within generic utility classes.
When creating Blueprint macro libraries or static C++ helper functions, always ask for a const UObject* WorldContextObject to accurately trace the execution context.
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject"))
static void SpawnCustomEffect(const UObject* WorldContextObject);
Logging & Debugging
Define custom log categories for systems.
DEFINE_LOG_CATEGORY_STATIC(LogInventory, Log, All);
UE_LOG(LogInventory, Warning, TEXT("Inventory is full for %s!"), *GetName());
Use Visual Logger (UE_VLOG) for spatial, timeline-based debugging of AI and gameplay systems.
Avoid excessive logging in Tick or tight loops. Logging should never execute every frame unless protected by a flag.
Delegates & Cleanup
Always remove delegates when objects are destroyed.
void UMyComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
SomeDelegate.RemoveAll(this);
}
This prevents fatal crashes during shutdown or map transitions.
Blueprint API & Event Handling
Expose Blueprint APIs intentionally and safely.
BlueprintCallable: For functions that change state.BlueprintPure: For getter functions that do NOT modify state. Addconst.BlueprintReadOnly: For properties that BP can read but only C++ should change.
Enhanced Input System
UE5 utilizes the Enhanced Input System. Never use deprecated Action/Axis Mappings or UPlayerInputComponent::BindAction.
Always use UEnhancedInputComponent and UInputAction / UInputMappingContext.
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
}
Event Handling
When C++ needs to trigger logic that might be implemented in Blueprint, use:
BlueprintImplementableEvent: C++ calls it, BP implements it. (No native C++ body).BlueprintNativeEvent: C++ provides a default implementation (via*_Implementation()), BP can override it.
Avoid exposing too much internal implementation detail.
Constructor & Lifecycle Rules
Do not execute heavy logic in constructors. Constructors must only be used for:
- Initializing default variables.
- Creating default subobjects (
CreateDefaultSubobject<T>).
For object initialization and runtime logic, use the appropriate lifecycle hooks:
- Actors:
PostInitializeComponents(),BeginPlay(). - Components:
InitializeComponent(),OnRegister(),BeginPlay().
// Constructor: Setup only
UMyComponent::UMyComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
// Runtime logic
void UMyComponent::BeginPlay()
{
Super::BeginPlay();
// Cache references and start logic here
}
Strict Module Isolation (Plugin-First)
All dependencies on other modules must be minimized.
- Never include headers from project game modules inside the plugin. (The plugin must not know about the game).
- Use Interfaces and Delegates to communicate outwards.
- Use Forward Declarations wherever possible instead of
#includeing headers from other plugin modules.
Unreal Naming Conventions
Follow Epic Games naming standards rigidly:
| Type | Prefix | Example |
|---|---|---|
| Template | T |
TArray |
| UObject | U |
UHealthComponent |
| Actor | A |
AWeapon |
| Struct | F |
FVector |
| Enum | E |
EEndPlayReason |
| Interface | I |
IInteractable |
| Slate Widget | S |
SButton |
| Boolean | b |
bIsInitialized |
Multi-threading & Async Safety
Never access, modify, or spawn UObjects or AActors outside the Game Thread.
Unreal Engine's Garbage Collector and UObject systems are strictly single-threaded.
- Always verify the current thread before complex UObject operations:
check(IsInGameThread()); - Use
AsyncTask,FRunnable, orParallelForstrictly for abstract mathematical computations, data processing, or backend logic. - When an async thread finishes, dispatch the result back to the Game Thread to update the game state safely:
AsyncTask(ENamedThreads::GameThread, [this]()
{
// Safe to modify UObjects here
});
General Coding Style & Formatting
- No Magic Numbers: Never embed gameplay tuning values directly in code. Expose them via
UPROPERTYorUDataAsset. For pure math constants, extract them toconstexprorconstvariables with meaningful names. - Braces: Always use braces
{}for control flow (if,for,while), even for single lines, to prevent macro expansion issues and maintain clean visual structure. - Enums: Always prefer
enum class EnumName : uint8. UseTEnumAsByte<EEnumName>only when interfacing with legacy APIs, specific unmanaged arrays for memory, or older serialization formats.
Code Formatting
- Unreal Engine C++ Style Guide: All code must strictly follow the official Epic Games coding standards (indentation, naming, bracket placement).
- clang-format: Automate style enforcement using
.clang-formatbased on the official Unreal Style. - Format code locally in the IDE (Visual Studio, Rider, VSCode) before every commit.
- Limit line length: Keep horizontal columns readable (typically max 120 characters) and break logic vertically to aid Code Review parsing.
Comments & Documentation
In the body of functions and methods, DO NOT write comments. The code must be entirely self-documenting.
Mandatory comments are allowed only for UPROPERTY and UFUNCTION declarations using Javadoc format (/** ... */) for Editor tooltips. No inline logic comments. No variable comments.
// BAD: Inline comment explaining logic
float Health = 100.f; // The starting health
// GOOD: Editor-facing Tooltip
/** The maximum health value this character can have. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float MaxHealth;
Code Generation Checklist
Before generating or modifying code, verify:
- Uses IWYU and Forward Declarations.
- Logic stays out of constructors (deferred to
BeginPlay/InitializeComponent). - Strict Module Isolation (Plugin doesn't depend on Game logic).
- Tick disabled unless absolutely required.
- Code avoids heavy logic or operations in
Tick. - Safe pointer usage (
TObjectPtr,UPROPERTY()). - Validation macros (
check,ensure,IsValid) used appropriately. -
constcorrectness applied to types and methods. - Enums use
enum classandENUM_CLASS_FLAGSfor bitmasks. - Soft references used for assets with
FStreamableManagerfor async loading. - Asset references exposed through
UPROPERTYinstead of hardcoded paths. - Memory preallocated (
Reserve) where possible forTArray/TMap/TSet. - Subsystems and Interfaces used to decouple logic (Gameplay logic separated from presentation logic).
- Blueprint APIs safely and minimally exposed.
- BP Events explicitly use
BlueprintImplementableEventorBlueprintNativeEvent. - No magic numbers; constants and constexpr used instead.
- UObject modifications strictly confined to the Game Thread.
- No inline comments; Javadoc Tooltips used exclusively for UPROPERTY/UFUNCTION.
- Code formatted automatically via
clang-formator strict adherence to the Unreal Style Guide. - Code remains modular and maintainable.
Code Quality Goals
Generated code should prioritize:
- strict adherence to Unreal architecture
- fast compile times (IWYU)
- memory safety and garbage collection compliance
- performance (No tick, Reserved Memory, Enum/FName over strings)
- readability
Priority
Rules defined in this skill override generic coding suggestions when developing Unreal Engine C++ systems.