Loading...
Loading...
Use 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 spawning'. See references/framework-class-map.md for the full authority/presence matrix. For networking/replication, see ue-networking-replication. For input setup, see ue-input-system.
npx skill4agent add quodsoler/unreal-engine-skills ue-gameplay-framework.agents/ue-project-context.mdEnteringMapWaitingToStartInProgressWaitingPostMatchLeavingMapAbortedReadyToStartMatchReadyToEndMatchAGameModeBaseAGameMode// Class assignments — set in constructor
TSubclassOf<APawn> DefaultPawnClass;
TSubclassOf<AGameStateBase> GameStateClass;
TSubclassOf<APlayerController> PlayerControllerClass;
TSubclassOf<APlayerState> PlayerStateClass;
TSubclassOf<AHUD> HUDClass;
uint32 bUseSeamlessTravel : 1;
// Server startup and player join lifecycle (server only)
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage);
virtual void PreLogin(const FString& Options, const FString& Address,
const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage);
virtual APlayerController* Login(UPlayer* NewPlayer, ENetRole InRemoteRole,
const FString& Portal, const FString& Options,
const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage);
virtual void PostLogin(APlayerController* NewPlayer); // first safe point for RPCs (DispatchPostLogin deprecated 5.6 — override PostLogin directly)
virtual void Logout(AController* Exiting);
virtual void HandleStartingNewPlayer(APlayerController* NewPlayer);
// Spawn pipeline
virtual AActor* FindPlayerStart(AController* Player, const FString& IncomingName = TEXT(""));
virtual void RestartPlayer(AController* NewPlayer);
virtual APawn* SpawnDefaultPawnFor(AController* NewPlayer, AActor* StartSpot);
// Travel
virtual void ProcessServerTravel(const FString& URL, bool bAbsolute = false);
virtual void GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList);PlayerArrayAPlayerState// All PlayerStates, always replicated
UPROPERTY(Transient, BlueprintReadOnly)
TArray<TObjectPtr<APlayerState>> PlayerArray;
// The GameMode class (not instance) replicated to clients
UPROPERTY(Transient, BlueprintReadOnly, ReplicatedUsing=OnRep_GameModeClass)
TSubclassOf<AGameModeBase> GameModeClass;
// Server-authoritative clock, automatically synced
virtual double GetServerWorldTimeSeconds() const;
virtual bool HasBegunPlay() const;
virtual bool HasMatchStarted() const;
virtual bool HasMatchEnded() const;UCLASS()
class AMyGameState : public AGameStateBase
{
GENERATED_BODY()
public:
UPROPERTY(Replicated, BlueprintReadOnly) int32 TeamAScore;
UPROPERTY(Replicated, BlueprintReadOnly) int32 TeamBScore;
UPROPERTY(ReplicatedUsing=OnRep_MatchTimer) float MatchTimeRemaining;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyGameState, TeamAScore);
DOREPLIFETIME(AMyGameState, TeamBScore);
DOREPLIFETIME(AMyGameState, MatchTimeRemaining);
}TObjectPtr<APlayerCameraManager> PlayerCameraManager; // camera, local only
TObjectPtr<APawn> AcknowledgedPawn; // server-confirmed possession
TObjectPtr<AHUD> MyHUD; // local only
uint32 bShowMouseCursor : 1;
uint32 bEnableStreamingSource : 1; // drives World Partition loading for this viewport
void SetInputMode(const FInputModeDataBase& InData); // FInputModeGameOnly, UIOnly, GameAndUI
virtual void PlayerTick(float DeltaTime); // only ticked locally
virtual void SetupInputComponent() override;SetupInputComponentAPawn::SetupPlayerInputComponent()ue-input-systemvoid AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (IsLocalController())
{
if (auto* Sub = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
Sub->AddMappingContext(DefaultMappingContext, 0);
SetInputMode(FInputModeGameOnly());
}
}UFUNCTION(Server, Reliable, WithValidation) void ServerRequestRespawn(); // client → server
UFUNCTION(Client, Reliable) void ClientNotifyMatchStart(); // server → client// Take control of a new pawn — must run on server
PlayerController->Possess(NewPawn);
// Release the currently possessed pawn
PlayerController->UnPossess();ROLE_AuthorityIsLocalController()PlayerController->ClientTravel(TEXT("127.0.0.1:7777"), TRAVEL_Absolute);GetWorld()->ServerTravel(TEXT("/Game/Maps/NewMap?listen"));AControllerAPlayerControllerAAIControllerPossessUnPossessGetPawnControlRotationAPlayerControllerAAIControllerUCLASS()
class AMyPlayerState : public APlayerState
{
GENERATED_BODY()
public:
UPROPERTY(Replicated, BlueprintReadOnly) int32 Kills;
UPROPERTY(Replicated, BlueprintReadOnly) int32 Deaths;
UPROPERTY(ReplicatedUsing=OnRep_Team) uint8 TeamIndex;
};
// Access patterns
APlayerState* PS = MyPawn->GetPlayerState();
APlayerState* PS = MyPC->PlayerState;
for (APlayerState* PS : GetGameState<AGameStateBase>()->PlayerArray) { /* all players */ }// Minimal subclass pattern
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;ADefaultPawnDefaultPawnClassUCharacterMovementComponent// Private, access via getters
TObjectPtr<UCapsuleComponent> CapsuleComponent; // GetCapsuleComponent() — root
TObjectPtr<USkeletalMeshComponent> Mesh; // GetMesh()
TObjectPtr<UCharacterMovementComponent> CharacterMovement; // GetCharacterMovement()
TObjectPtr<UArrowComponent> ArrowComponent; // GetArrowComponent() — editor-only direction indicatorAMyCharacter::AMyCharacter()
{
GetCapsuleComponent()->SetCapsuleHalfHeight(96.f);
GetCapsuleComponent()->SetCapsuleRadius(42.f);
GetMesh()->SetRelativeLocation(FVector(0.f, 0.f, -97.f));
GetMesh()->SetRelativeRotation(FRotator(0.f, -90.f, 0.f));
GetCharacterMovement()->MaxWalkSpeed = 600.f;
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->GravityScale = 1.75f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->NavAgentProps.bCanCrouch = true;
}// Jump — from Character.h
virtual void Jump(); // set bPressedJump, triggers on next tick
virtual void StopJumping(); // clear bPressedJump
bool CanJump() const;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated) float JumpMaxHoldTime; // variable height
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated) int32 JumpMaxCount; // double jump
virtual void LaunchCharacter(FVector LaunchVelocity, bool bXYOverride, bool bZOverride);
// Crouch
void Crouch(bool bClientSimulation = false); // requests crouch via CharacterMovementComponent
void UnCrouch(bool bClientSimulation = false);
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_IsCrouched) uint8 bIsCrouched : 1;
// Movement mode
// MOVE_Walking, MOVE_Falling, MOVE_Swimming, MOVE_Flying, MOVE_Custom
GetCharacterMovement()->SetMovementMode(MOVE_Flying);MOVE_CustomPhysCustom(float deltaTime, int32 Iterations)UCharacterMovementComponentCustomMovementModePhysCustom// Custom movement mode: override in CMC subclass
void UMyCharacterMovement::PhysCustom(float DeltaTime, int32 Iterations)
{
if (CustomMovementMode == (uint8)ECustomMovement::Flying)
{
// Custom flying physics here
Velocity.Z += GetGravityZ() * DeltaTime;
}
Super::PhysCustom(DeltaTime, Iterations);
}
// Activate: CharMoveComp->SetMovementMode(MOVE_Custom, (uint8)ECustomMovement::Flying);ServerMovePackedClientMoveResponsePacked// Lifecycle overrides
virtual void Init() override; // called once at startup; subsystem init, save-game loading
virtual void OnStart() override; // called when the instance is ready, after Init
virtual void Shutdown() override; // called on application exit
// Access from actor or component
UMyGameInstance* GI = GetWorld()->GetGameInstance<UMyGameInstance>();
// Subsystems (also survive level travel)
UMySubsystem* Sub = GetGameInstance()->GetSubsystem<UMySubsystem>();// Access the Online Subsystem session interface from GameInstance
IOnlineSubsystem* OSS = IOnlineSubsystem::Get();
IOnlineSessionPtr Sessions = OSS ? OSS->GetSessionInterface() : nullptr;
if (!Sessions.IsValid()) return;
// Create session (host)
FOnlineSessionSettings Settings;
Settings.bIsLANMatch = false;
Settings.NumPublicConnections = 4;
Settings.bShouldAdvertise = true;
Sessions->OnCreateSessionCompleteDelegates.AddUObject(
this, &UMyGameInstance::OnCreateSessionComplete);
Sessions->CreateSession(0, NAME_GameSession, Settings);
// Find sessions (client)
TSharedRef<FOnlineSessionSearch> Search = MakeShared<FOnlineSessionSearch>();
Sessions->OnFindSessionsCompleteDelegates.AddUObject(
this, &UMyGameInstance::OnFindSessionsComplete);
Sessions->FindSessions(0, Search);
// Join a found session
Sessions->OnJoinSessionCompleteDelegates.AddUObject(
this, &UMyGameInstance::OnJoinSessionComplete);
Sessions->JoinSession(0, NAME_GameSession, Search->SearchResults[0]);"OnlineSubsystem""OnlineSubsystemUtils"GetResolvedConnectStringClientTravelAMyGameMode::AMyGameMode()
{
DefaultPawnClass = AMyCharacter::StaticClass();
PlayerControllerClass = AMyPlayerController::StaticClass();
GameStateClass = AMyGameState::StaticClass();
PlayerStateClass = AMyPlayerState::StaticClass();
HUDClass = AMyHUD::StaticClass();
bUseSeamlessTravel = true;
}InitGame() → called before any player joins; use for map-specific rules init
PreLogin() → reject here (server full, banned)
Login() → create PlayerController, assign UniqueId
PostLogin() → first safe point for server→client RPCs; assign teams here
HandleStartingNewPlayer() → triggers RestartPlayer()
RestartPlayer() → FindPlayerStart() → SpawnDefaultPawnFor() → Possess()bool AMyGameMode::ReadyToStartMatch_Implementation()
{
return GetNumPlayers() >= MinPlayersToStart;
}
bool AMyGameMode::ReadyToEndMatch_Implementation()
{
return GetGameState<AMyGameState>()->TeamAScore >= ScoreLimit;
}| Pattern | Clients disconnect? | GameMode/GameState survive? | Use when |
|---|---|---|---|
Non-seamless ( | Yes, reconnect | No, recreated | Map change with clean slate |
Seamless ( | No | No, recreated | Lobby→game, round change |
UGameInstanceAPlayerControllerAPlayerStateAGameModeAGameStateGetSeamlessTravelActorList()// WRONG
GetWorld()->GetAuthGameMode<AMyGameMode>()->EndMatch(); // nullptr on client
// RIGHT
if (HasAuthority()) { if (auto* GM = GetWorld()->GetAuthGameMode<AMyGameMode>()) GM->EndMatch(); }Score visible to all clients → APlayerState, NOT APlayerController
Match timer on clients → AGameState replicated property, NOT AGameMode
Data surviving level travel → UGameInstance, NOT AGameState
Input binding → PlayerController or Pawn::SetupPlayerInputComponent, NOT ACharacter bodyGetPawn()AcknowledgedPawnif (GetNetMode() != NM_DedicatedServer)
{
// HUD, camera, audio — never run these on dedicated server
}ue-actor-component-architectureue-networking-replicationue-input-system