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.

  • Game Development
  • $unreal-engine
  • 221 lines
  • 2 files
  • ~2,300 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- unreal-engine

Installs to ~/.agents/skills/unreal-engine/ for every project on this machine.

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

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.

~/.agents/skills/unreal-engine/SKILL.md

--- name: unreal-engine description: Writes Unreal Engine 5 gameplay C++ and Blueprint-facing APIs - UCLASS/UPROPERTY/UFUNCTION reflection, the gameplay framework (GameMode, GameState,… license: MIT ---

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

  1. Read AGENTS.md, the .uproject (EngineAssociation gives the version, Modules and Plugins list what is enabled) and Source/<Module>/<Module>.Build.cs.
  2. Note whether the game is multiplayer. That decides where every piece of state lives.
  3. Find existing base classes (AMyCharacterBase, UMyGameInstance) and extend them rather than adding parallel hierarchies.
  4. 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

ClassExists onPut here
AGameModeBase / AGameModeServer onlyRules: spawning, win conditions, which pawn and controller classes
AGameStateBaseServer + all clientsMatch state everyone sees: score, timer, phase
APlayerControllerServer + owning clientInput mapping setup, UI ownership, player-intent RPCs
APlayerStateServer + all clientsPer-player public data: name, team, kills; survives pawn death
APawn / ACharacterServer + relevant clientsBody: movement, health, abilities in play
UGameInstance / subsystemsEach process, whole sessionSave data, platform services, cross-level state
UActorComponentWith its ownerReusable 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

WantSpecifier
Designers set default in Blueprint class onlyEditDefaultsOnly
Per-instance in the levelEditAnywhere / EditInstanceOnly
Blueprint reads, C++ writesBlueprintReadOnly
Blueprint implements, C++ callsUFUNCTION(BlueprintImplementableEvent)
C++ default, Blueprint may overrideUFUNCTION(BlueprintNativeEvent) + Foo_Implementation
Client asks server to actUFUNCTION(Server, Reliable) (validate inputs in the body)
Cosmetic event to everyoneUFUNCTION(NetMulticast, Unreliable)

Pointers and garbage collection

  • Member pointers to UObjects must be UPROPERTY() (use TObjectPtr<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 or StreamableManager.
  • Never new or delete UObjects. Use NewObject<T>(Outer), CreateDefaultSubobject<T> (constructor only) and GetWorld()->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, componentsContent variations, one-off level scripting
Per-frame math over many objectsTuning values, timelines, VFX/SFX hookups
Replication, save data, anything needing code review and diffsUI 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/USTRUCT changes 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, as ApplyDamage does above.
  • GameMode does 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 CoreRedirects entry in Config/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_TEST or Automation Spec)
  • Play In Editor with 2 players, Net Mode “Play As Client”, for anything replicated
  • Output Log has no LogScript or Accessed None errors 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.yamlgenerated from this page

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.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem