Unreal Engine 5 Gameplay skill for Codex
Right class, right side of the network. Codex places each piece of logic in the correct gameplay framework class, writes reflected C++ that Blueprints can use without reaching into internals, keeps UObject pointers visible to the garbage collector, and treats the server as the authority for replicated state. It compiles the editor target from the command line and lists the Blueprint steps it cannot perform on binary assets.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- unreal-engine Installs to ~/.agents/skills/unreal-engine/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install unreal-engine The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project unreal-engine Installs to .agents/skills/unreal-engine/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/unreal-engine.zipunzip -o unreal-engine.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) unreal-engine PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $unreal-engine, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Writes Unreal Engine 5 gameplay C++ and Blueprint-facing APIs - UCLASS/UPROPERTY/UFUNCTION reflection, the gameplay framework (GameMode, GameState, PlayerController, PlayerState, Pawn, Character), Enhanced Input, components, replication basics, garbage-collection-safe pointers, module Build.cs dependencies and command-line builds. Use when adding a character, weapon, pickup or game rule in a UE5 project, exposing C++ to Blueprints, deciding what belongs in C++ vs Blueprint, fixing crashes from dangling UObject pointers, or getting a project to compile with UnrealBuildTool.
Use it when
- Adding gameplay C++ to an Unreal Engine 5 project
- Exposing C++ functions and events to Blueprints
- Deciding between GameMode, GameState, PlayerState, Controller and Pawn
- Fixing build, link or garbage-collection crashes in UE5 code
Use something else when
- You need deep prediction or rollback design across engines: use multiplayer-netcode
- The task is general modern C++ outside Unreal: use cpp-modern
How to check the result
The editor target builds from the command line, automation tests pass headless, and a two-client Play In Editor session shows replicated state matching the server.
The mistake it prevents
Holding a raw UObject pointer that is not a UPROPERTY, so the garbage collector frees the object and the next access crashes.
Done well means: logic sits in the right gameplay framework class, C++ owns systems and performance-sensitive code while Blueprints own content and tuning, every UObject pointer is visible to the garbage collector, the project compiles from the command line, and networked state is replicated from the server, not set on clients.
Before writing code
- Read
AGENTS.md, the.uproject(EngineAssociationgives the version,ModulesandPluginslist what is enabled) andSource/<Module>/<Module>.Build.cs. - Note whether the game is multiplayer. That decides where every piece of state lives.
- Find existing base classes (
AMyCharacterBase,UMyGameInstance) and extend them rather than adding parallel hierarchies. - Blueprint assets (
.uasset) are binary. Codex cannot edit them; changes that require reparenting a Blueprint or setting defaults must be listed for the user to do in the editor.
Which class owns it
| Class | Exists on | Put here |
|---|---|---|
AGameModeBase / AGameMode | Server only | Rules: spawning, win conditions, which pawn and controller classes |
AGameStateBase | Server + all clients | Match state everyone sees: score, timer, phase |
APlayerController | Server + owning client | Input mapping setup, UI ownership, player-intent RPCs |
APlayerState | Server + all clients | Per-player public data: name, team, kills; survives pawn death |
APawn / ACharacter | Server + relevant clients | Body: movement, health, abilities in play |
UGameInstance / subsystems | Each process, whole session | Save data, platform services, cross-level state |
UActorComponent | With its owner | Reusable behaviour: health, inventory, interaction |
Prefer components and subsystems (UGameInstanceSubsystem, UWorldSubsystem) over growing
Character classes and over global singletons.
Reflection macros
// HealthComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "HealthComponent.generated.h" // must be the last include
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, NewHealth, float, Delta);
UCLASS(ClassGroup=(Gameplay), meta=(BlueprintSpawnableComponent))
class MYGAME_API UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
UHealthComponent();
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Health", meta=(ClampMin="1"))
float MaxHealth = 100.f;
UPROPERTY(BlueprintAssignable, Category="Health")
FOnHealthChanged OnHealthChanged;
UFUNCTION(BlueprintCallable, Category="Health")
void ApplyDamage(float Amount);
UFUNCTION(BlueprintPure, Category="Health")
float GetHealth() const { return Health; }
protected:
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutProps) const override;
UPROPERTY(ReplicatedUsing=OnRep_Health, VisibleInstanceOnly, Category="Health")
float Health = 0.f;
UFUNCTION()
void OnRep_Health(float OldHealth);
};
// HealthComponent.cpp
#include "HealthComponent.h"
#include "Net/UnrealNetwork.h"
UHealthComponent::UHealthComponent()
{
PrimaryComponentTick.bCanEverTick = false; // no Tick unless needed
SetIsReplicatedByDefault(true);
}
void UHealthComponent::BeginPlay()
{
Super::BeginPlay();
if (GetOwner()->HasAuthority())
Health = MaxHealth;
}
void UHealthComponent::ApplyDamage(float Amount)
{
if (!GetOwner()->HasAuthority() || Amount <= 0.f) return; // server decides
const float Old = Health;
Health = FMath::Clamp(Health - Amount, 0.f, MaxHealth);
OnHealthChanged.Broadcast(Health, Health - Old); // OnRep does not run on server
}
void UHealthComponent::OnRep_Health(float OldHealth)
{
OnHealthChanged.Broadcast(Health, Health - OldHealth);
}
void UHealthComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutProps) const
{
Super::GetLifetimeReplicatedProps(OutProps);
DOREPLIFETIME(UHealthComponent, Health);
}
Specifier quick reference
| Want | Specifier |
|---|---|
| Designers set default in Blueprint class only | EditDefaultsOnly |
| Per-instance in the level | EditAnywhere / EditInstanceOnly |
| Blueprint reads, C++ writes | BlueprintReadOnly |
| Blueprint implements, C++ calls | UFUNCTION(BlueprintImplementableEvent) |
| C++ default, Blueprint may override | UFUNCTION(BlueprintNativeEvent) + Foo_Implementation |
| Client asks server to act | UFUNCTION(Server, Reliable) (validate inputs in the body) |
| Cosmetic event to everyone | UFUNCTION(NetMulticast, Unreliable) |
Pointers and garbage collection
- Member pointers to UObjects must be
UPROPERTY()(useTObjectPtr<T>in UE5 headers) or the GC may destroy the object and leave a dangling pointer. - Non-owning references that should not keep an object alive:
TWeakObjectPtr<T>. - Asset references that should not load eagerly:
TSoftObjectPtr<T>/TSoftClassPtr<T>, loaded through the Asset Manager orStreamableManager. - Never
newordeleteUObjects. UseNewObject<T>(Outer),CreateDefaultSubobject<T>(constructor only) andGetWorld()->SpawnActor<T>(). - Check with
IsValid(Ptr); it also rejects objects pending kill.
Enhanced Input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
auto* Input = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
Input->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
}
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (auto* Sub = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
Sub->AddMappingContext(DefaultMappingContext, /*Priority*/ 0);
}
MoveAction, JumpAction and DefaultMappingContext are EditDefaultsOnly UPROPERTYs
assigned in the Blueprint subclass. Add "EnhancedInput" to PublicDependencyModuleNames.
C++ or Blueprint
| Put in C++ | Put in Blueprint |
|---|---|
| Systems used by many actors, base classes, components | Content variations, one-off level scripting |
| Per-frame math over many objects | Tuning values, timelines, VFX/SFX hookups |
| Replication, save data, anything needing code review and diffs | UI layout (UMG), animation graphs |
Expose small, well-named BlueprintCallable functions rather than letting Blueprints reach
into internals. Avoid Tick in Blueprints for anything that runs on many actors.
Build from the command line
# Windows: compile the editor target without opening the editor
"<UE>/Engine/Build/BatchFiles/Build.bat" MyGameEditor Win64 Development -Project="C:/Proj/MyGame.uproject" -WaitMutex
# macOS
"<UE>/Engine/Build/BatchFiles/Mac/Build.sh" MyGameEditor Mac Development -Project="/Proj/MyGame.uproject"
# Automation tests, headless
"<UE>/Engine/Binaries/Win64/UnrealEditor-Cmd.exe" "C:/Proj/MyGame.uproject" -ExecCmds="Automation RunTests MyGame; Quit" -unattended -nullrhi -nosplash -log
Packaging uses RunUAT BuildCookRun; it is slow and writes large outputs, so confirm with the
user before running it.
Gotchas
- Changing a header with
UPROPERTY/UFUNCTION/USTRUCTchanges requires a full rebuild with the editor closed. Live Coding handles function bodies only and can corrupt Blueprints that depend on changed layouts. - Constructor code runs for the Class Default Object at editor load. Do not access the world,
other actors or game state there; use
BeginPlay. OnRep_functions do not fire on the server (or listen-server host). Call the shared logic explicitly there, asApplyDamagedoes above.GameModedoes not exist on clients.GetWorld()->GetAuthGameMode()returns null there.- Missing module dependency gives linker errors (
unresolved external symbol). Add the module (UMG,EnhancedInput,AIModule,NavigationSystem,GameplayTasks) to Build.cs. - Renaming a C++ class referenced by Blueprints or maps needs a
CoreRedirectsentry inConfig/DefaultEngine.ini, or the assets lose their parent class.
Verification
- Editor target builds from the command line with no new warnings
- Automation tests for new logic pass (
IMPLEMENT_SIMPLE_AUTOMATION_TESTor Automation Spec) - Play In Editor with 2 players, Net Mode “Play As Client”, for anything replicated
- Output Log has no
LogScriptorAccessed Noneerrors during the test play - Editor-only steps (Blueprint reparenting, asset defaults) are listed for the user
Hand-off summary
**Engine:** UE 5.x (EngineAssociation), module MyGame
**Added:** UHealthComponent (replicated, BlueprintAssignable OnHealthChanged)
**Build.cs:** + EnhancedInput, UMG
**Editor steps for you:** add HealthComponent to BP_PlayerCharacter; assign IA_Move, IA_Jump, IMC_Default
**Checks:** Build.sh MyGameEditor Mac Development passed; PIE 2 clients, damage replicates What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Unreal Engine 5 Gameplay"
short_description: "Right class, right side of the network"
default_prompt: "$unreal-engine Add a replicated health component to our UE5 character" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.