$npx -y skills add quodsoler/unreal-engine-skills --skill ue-gameplay-frameworkUse this skill when working with Unreal Engine's gameplay framework classes: GameMode, GameState, PlayerController, PlayerState, Pawn, Character, or GameInstance. Also use when the user mentions 'gameplay framework', 'game rules', 'player management', 'match flow', or 'player spa
| 1 | # UE Gameplay Framework |
| 2 | |
| 3 | You are an expert in Unreal Engine's gameplay framework architecture. |
| 4 | |
| 5 | ## Context Check |
| 6 | |
| 7 | Read `.agents/ue-project-context.md` before proceeding. The game type (single player, co-op, competitive multiplayer, dedicated vs listen server) determines which classes to subclass and which replication patterns apply. Resolve: single-player or multiplayer? Dedicated or listen server? What are you implementing? |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Class Responsibility Map |
| 12 | |
| 13 | Each class exists on specific machines for specific reasons. Getting this wrong is the primary source of multiplayer bugs. |
| 14 | |
| 15 | ### AGameModeBase / AGameMode — Server Only |
| 16 | |
| 17 | **Exists on:** Server and standalone only. Never instantiated on clients. |
| 18 | |
| 19 | **Why server-only:** GameMode is the authoritative referee. It decides who joins, when the match starts, where players spawn, and what the win conditions are. Client execution would allow cheating via local state manipulation. |
| 20 | |
| 21 | **AGameMode adds** the full match-state machine (`EnteringMap` → `WaitingToStart` → `InProgress` → `WaitingPostMatch` → `LeavingMap`; `Aborted` on failure) with `ReadyToStartMatch` and `ReadyToEndMatch` hooks. Use `AGameModeBase` for lobby/simple games, `AGameMode` for match flow. |
| 22 | |
| 23 | **Key API from source (GameModeBase.h):** |
| 24 | ```cpp |
| 25 | // Class assignments — set in constructor |
| 26 | TSubclassOf<APawn> DefaultPawnClass; |
| 27 | TSubclassOf<AGameStateBase> GameStateClass; |
| 28 | TSubclassOf<APlayerController> PlayerControllerClass; |
| 29 | TSubclassOf<APlayerState> PlayerStateClass; |
| 30 | TSubclassOf<AHUD> HUDClass; |
| 31 | uint32 bUseSeamlessTravel : 1; |
| 32 | |
| 33 | // Server startup and player join lifecycle (server only) |
| 34 | virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage); |
| 35 | virtual void PreLogin(const FString& Options, const FString& Address, |
| 36 | const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage); |
| 37 | virtual APlayerController* Login(UPlayer* NewPlayer, ENetRole InRemoteRole, |
| 38 | const FString& Portal, const FString& Options, |
| 39 | const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage); |
| 40 | virtual void PostLogin(APlayerController* NewPlayer); // first safe point for RPCs (DispatchPostLogin deprecated 5.6 — override PostLogin directly) |
| 41 | virtual void Logout(AController* Exiting); |
| 42 | virtual void HandleStartingNewPlayer(APlayerController* NewPlayer); |
| 43 | |
| 44 | // Spawn pipeline |
| 45 | virtual AActor* FindPlayerStart(AController* Player, const FString& IncomingName = TEXT("")); |
| 46 | virtual void RestartPlayer(AController* NewPlayer); |
| 47 | virtual APawn* SpawnDefaultPawnFor(AController* NewPlayer, AActor* StartSpot); |
| 48 | |
| 49 | // Travel |
| 50 | virtual void ProcessServerTravel(const FString& URL, bool bAbsolute = false); |
| 51 | virtual void GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList); |
| 52 | ``` |
| 53 | |
| 54 | --- |
| 55 | |
| 56 | ### AGameStateBase / AGameState — Server + All Clients |
| 57 | |
| 58 | **Exists on:** Everywhere. Fully replicated. |
| 59 | |
| 60 | **Why everywhere:** Clients cannot read GameMode (it does not exist on them). Any global data clients need — scores, match timer, phase — belongs in GameState. `PlayerArray` exposes all connected `APlayerState` instances to every machine. |
| 61 | |
| 62 | **Key API from source (GameStateBase.h):** |
| 63 | ```cpp |
| 64 | // All PlayerStates, always replicated |
| 65 | UPROPERTY(Transient, BlueprintReadOnly) |
| 66 | TArray<TObjectPtr<APlayerState>> PlayerArray; |
| 67 | |
| 68 | // The GameMode class (not instance) replicated to clients |
| 69 | UPROPERTY(Transient, BlueprintReadOnly, ReplicatedUsing=OnRep_GameModeClass) |
| 70 | TSubclassOf<AGameModeBase> GameModeClass; |
| 71 | |
| 72 | // Server-authoritative clock, automatically synced |
| 73 | virtual double GetServerWorldTimeSeconds() const; |
| 74 | virtual bool HasBegunPlay() const; |
| 75 | virtual bool HasMatchStarted() const; |
| 76 | virtual bool HasMatchEnded() const; |
| 77 | ``` |
| 78 | |
| 79 | **Custom replicated match data:** |
| 80 | ```cpp |
| 81 | UCLASS() |
| 82 | class AMyGameState : public AGameStateBase |
| 83 | { |
| 84 | GENERATED_BODY() |
| 85 | public: |
| 86 | UPROPERTY(Replicated, BlueprintReadOnly) int32 TeamAScore; |
| 87 | UPROPERTY(Replicated, BlueprintReadOnly) int32 TeamBScore; |
| 88 | UPROPERTY(ReplicatedUsing=OnRep_MatchTimer) float MatchTimeRemaining; |
| 89 | |
| 90 | virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; |
| 91 | }; |
| 92 | |
| 93 | void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const |
| 94 | { |
| 95 | Super::GetLifetimeReplicatedProps(OutLifetimeProps); |
| 96 | DOREPLIFETIME(AMyGameState, TeamAScore); |
| 97 | DOREPLIFETIME(AMyGameState, TeamBScore); |
| 98 | DOREPLIFETIME(AMyGameState, MatchTimeRemaining); |
| 99 | } |
| 100 | ``` |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ### APlayerControl |