一键导入
unreal-cpp-developer
Instructions and architectural standards for Unreal Engine 5 C++ development
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Instructions and architectural standards for Unreal Engine 5 C++ development
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | unreal cpp developer |
| description | Instructions and architectural standards for Unreal Engine 5 C++ 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.
Use this skill when:
Do NOT use when:
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.
When asked to generate a NEW C++ class from scratch, you must use the provided templates as your starting point:
templates/TemplateClass.htemplates/TemplateClass.cppNever 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 gameplay logic should be implemented in C++ whenever possible.
Blueprints should mainly be used for:
Avoid implementing complex state machines, heavy math, or core gameplay loops purely in Blueprints.
Always follow the IWYU principle to optimize compile times.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MyComponent.generated.h"
class UHealthComponent; // Forward Declaration
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
};
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().
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.try/catch.auto when the type is strictly obvious from the right side of the assignment, or for complex iterator types. Do not use it blindly.Use const extensively.
const Type& when not modifying them.const.const pointers when the pointed-to object shouldn't be modified.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).
FindComponentByClass) in loops or Tick.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);
}
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());
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)
{ ... }
Understand the difference between Unreal string types:
FName: Fast, lightweight, immutable string used for IDs, tags, and map lookups. Never use FString as a key in a TMap; always use FName.FText: Localizable text. Used for all UI and player-facing text.FString: Mutable string for general manipulation and logging. Heavy.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.
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.
Use modern Epic paradigms for architecture decoupling.
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:
IDamageable, IInteractable), not specific objects.IDoorInterface, IChestInterface).IGameplayTaskOwnerInterface) or GameplayTags can solve the problem before creating a new UINTERFACE.Use UGameInstanceSubsystem, UWorldSubsystem, or ULocalPlayerSubsystem for global managers instead of Singletons or massive Managers on the GameMode.
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.
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);
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.
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.
Expose Blueprint APIs intentionally and safely.
BlueprintCallable: For functions that change state.BlueprintPure: For getter functions that do NOT modify state. Add const.BlueprintReadOnly: For properties that BP can read but only C++ should change.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);
}
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.
Do not execute heavy logic in constructors. Constructors must only be used for:
CreateDefaultSubobject<T>).For object initialization and runtime logic, use the appropriate lifecycle hooks:
PostInitializeComponents(), BeginPlay().InitializeComponent(), OnRegister(), BeginPlay().// Constructor: Setup only
UMyComponent::UMyComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
// Runtime logic
void UMyComponent::BeginPlay()
{
Super::BeginPlay();
// Cache references and start logic here
}
All dependencies on other modules must be minimized.
#includeing headers from other plugin modules.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 |
Never access, modify, or spawn UObjects or AActors outside the Game Thread.
Unreal Engine's Garbage Collector and UObject systems are strictly single-threaded.
check(IsInGameThread());AsyncTask, FRunnable, or ParallelFor strictly for abstract mathematical computations, data processing, or backend logic.AsyncTask(ENamedThreads::GameThread, [this]()
{
// Safe to modify UObjects here
});
UPROPERTY or UDataAsset. For pure math constants, extract them to constexpr or const variables with meaningful names.{} for control flow (if, for, while), even for single lines, to prevent macro expansion issues and maintain clean visual structure.enum class EnumName : uint8. Use TEnumAsByte<EEnumName> only when interfacing with legacy APIs, specific unmanaged arrays for memory, or older serialization formats..clang-format based on the official Unreal Style.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;
Before generating or modifying code, verify:
BeginPlay/InitializeComponent).Tick.TObjectPtr, UPROPERTY()).check, ensure, IsValid) used appropriately.const correctness applied to types and methods.enum class and ENUM_CLASS_FLAGS for bitmasks.FStreamableManager for async loading.UPROPERTY instead of hardcoded paths.Reserve) where possible for TArray/TMap/TSet.BlueprintImplementableEvent or BlueprintNativeEvent.clang-format or strict adherence to the Unreal Style Guide.Generated code should prioritize:
Rules defined in this skill override generic coding suggestions when developing Unreal Engine C++ systems.