ue-gameplay-framework
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE Gameplay Framework
UE Gameplay框架
You are an expert in Unreal Engine's gameplay framework architecture.
您是Unreal Engine Gameplay框架架构方面的专家。
Context Check
上下文检查
Read 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?
.agents/ue-project-context.md继续操作前,请阅读文件。游戏类型(单人、合作、竞技多人游戏、专用服务器 vs 监听服务器)决定了需要继承哪些类以及适用哪些复制模式。请明确:是单人还是多人游戏?是专用服务器还是监听服务器?您要实现的内容是什么?
.agents/ue-project-context.mdClass Responsibility Map
类职责映射
Each class exists on specific machines for specific reasons. Getting this wrong is the primary source of multiplayer bugs.
每个类都因特定原因存在于特定的设备上。这部分出错是多人游戏bug的主要来源。
AGameModeBase / AGameMode — Server Only
AGameModeBase / AGameMode — 仅服务器端存在
Exists on: Server and standalone only. Never instantiated on clients.
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.
AGameMode adds the full match-state machine ( → → → → ; on failure) with and hooks. Use for lobby/simple games, for match flow.
EnteringMapWaitingToStartInProgressWaitingPostMatchLeavingMapAbortedReadyToStartMatchReadyToEndMatchAGameModeBaseAGameModeKey API from source (GameModeBase.h):
cpp
// 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);**存在于:**仅服务器端和单机模式下。客户端永远不会实例化该类。
**为何仅服务器端:**GameMode是具有权威性的裁判类。它决定谁可以加入、比赛何时开始、玩家在哪里生成以及获胜条件是什么。如果在客户端执行,玩家可通过操纵本地状态进行作弊。
AGameMode新增功能:完整的比赛状态机( → → → → ;失败时进入状态),并带有和钩子函数。大厅/简单游戏使用,有匹配流程的游戏使用。
EnteringMapWaitingToStartInProgressWaitingPostMatchLeavingMapAbortedReadyToStartMatchReadyToEndMatchAGameModeBaseAGameMode源码中的核心API(GameModeBase.h):
cpp
// 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);AGameStateBase / AGameState — Server + All Clients
AGameStateBase / AGameState — 服务器端 + 所有客户端
Exists on: Everywhere. Fully replicated.
Why everywhere: Clients cannot read GameMode (it does not exist on them). Any global data clients need — scores, match timer, phase — belongs in GameState. exposes all connected instances to every machine.
PlayerArrayAPlayerStateKey API from source (GameStateBase.h):
cpp
// 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;Custom replicated match data:
cpp
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);
}**存在于:**所有设备上。完全复制。
**为何存在于所有设备:**客户端无法读取GameMode(客户端上不存在该类)。客户端需要的任何全局数据(分数、比赛计时器、阶段)都应放在GameState中。向所有设备公开所有已连接的实例。
PlayerArrayAPlayerState源码中的核心API(GameStateBase.h):
cpp
// 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;自定义复制比赛数据:
cpp
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);
}APlayerController — Server (all) + Owning Client (own only)
APlayerController — 服务器端(所有实例) + 所属客户端(仅自身实例)
Exists on: Server holds one per connected player. Each client holds only its own. Remote clients do not see other players' PlayerControllers.
Why this split: The PlayerController bridges one human to the server. Both ends run it for client-side prediction and server validation. A client has no reason to know another player's input state.
Key API from source (PlayerController.h):
cpp
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-systemEnhanced Input setup:
cpp
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (IsLocalController())
{
if (auto* Sub = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
Sub->AddMappingContext(DefaultMappingContext, 0);
SetInputMode(FInputModeGameOnly());
}
}RPC patterns:
cpp
UFUNCTION(Server, Reliable, WithValidation) void ServerRequestRespawn(); // client → server
UFUNCTION(Client, Reliable) void ClientNotifyMatchStart(); // server → clientPossess/UnPossess (server-authority required):
cpp
// Take control of a new pawn — must run on server
PlayerController->Possess(NewPawn);
// Release the currently possessed pawn
PlayerController->UnPossess();Listen-server dual-role: On a listen server, the host's PlayerController is both and locally controlled. Guard dual-role logic with checks. This is a common source of bugs where code assumes authority implies non-local (i.e., code written for dedicated servers runs incorrectly on a listen server host).
ROLE_AuthorityIsLocalController()ClientTravel — connect this client to a different server:
cpp
PlayerController->ClientTravel(TEXT("127.0.0.1:7777"), TRAVEL_Absolute);ServerTravel — move all players to a new map (called from GameMode, server only):
cpp
GetWorld()->ServerTravel(TEXT("/Game/Maps/NewMap?listen"));**存在于:**服务器端为每个已连接玩家保留一个实例。每个客户端仅保留自身的实例。远程客户端无法看到其他玩家的PlayerController。
**为何采用这种拆分方式:**PlayerController是连接人类玩家与服务器的桥梁。两端均运行该类,用于客户端预测和服务器验证。客户端无需了解其他玩家的输入状态。
源码中的核心API(PlayerController.h):
cpp
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;**PlayerController上的**用于非Pawn输入: spectator操作、UI快捷键或跨 possession 变更仍保留的全局按键绑定。Pawn专属输入请覆盖 — 详见。
SetupInputComponentAPawn::SetupPlayerInputComponent()ue-input-system增强型输入设置:
cpp
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (IsLocalController())
{
if (auto* Sub = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
Sub->AddMappingContext(DefaultMappingContext, 0);
SetInputMode(FInputModeGameOnly());
}
}RPC模式:
cpp
UFUNCTION(Server, Reliable, WithValidation) void ServerRequestRespawn(); // client → server
UFUNCTION(Client, Reliable) void ClientNotifyMatchStart(); // server → clientPossess/UnPossess(需服务器权限):
cpp
// Take control of a new pawn — must run on server
PlayerController->Possess(NewPawn);
// Release the currently possessed pawn
PlayerController->UnPossess();**监听服务器双重角色:**在监听服务器上,主机的PlayerController同时具有权限并受本地控制。使用检查来保护双重角色逻辑。这是常见的bug来源,比如为专用服务器编写的代码在监听服务器主机上运行异常。
ROLE_AuthorityIsLocalController()ClientTravel — 将此客户端连接到其他服务器:
cpp
PlayerController->ClientTravel(TEXT("127.0.0.1:7777"), TRAVEL_Absolute);ServerTravel — 将所有玩家转移到新地图(仅服务器端从GameMode调用):
cpp
GetWorld()->ServerTravel(TEXT("/Game/Maps/NewMap?listen"));AController — Shared Base
AController — 共享基类
AControllerAPlayerControllerAAIControllerPossessUnPossessGetPawnControlRotationAPlayerControllerAAIControllerAControllerAPlayerControllerAAIControllerPossessUnPossessGetPawnControlRotationAPlayerControllerAAIControllerAPlayerState — Server + All Clients (Always Relevant)
APlayerState — 服务器端 + 所有客户端(始终相关)
Exists on: Server and all clients. Marked always-relevant so it replicates to everyone regardless of distance.
Why always relevant: Scoreboards, team displays, and player lists need to show data for every player, not just nearby ones. PlayerState survives pawn death — when a pawn is destroyed and respawned, the PlayerController keeps its PlayerState, preserving accumulated stats.
cpp
UCLASS()
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 */ }**存在于:**服务器端和所有客户端。标记为始终相关,因此无论距离如何,都会复制到所有设备。
**为何始终相关:**计分板、队伍显示和玩家列表需要显示所有玩家的数据,而不仅仅是附近玩家的。PlayerState在pawn死亡后仍然存在 — 当pawn被销毁并重生时,PlayerController会保留其PlayerState,从而保存累计的统计数据。
cpp
UCLASS()
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 */ }APawn — Server + All Clients (Replicated)
APawn — 服务器端 + 所有客户端(已复制)
Exists on: Server (authority) and all clients (simulated or autonomous proxy). Minimal base — no mesh, no collision component, no movement component.
Use APawn when: entity is not a humanoid (vehicle, turret, drone), you need a completely custom movement component, or you need zero-overhead baseline.
cpp
// Minimal subclass pattern
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;ADefaultPawnDefaultPawnClass**存在于:**服务器端(权限方)和所有客户端(模拟或自主代理)。基础功能极少 — 没有网格体、没有碰撞组件、没有移动组件。
**何时使用APawn:**实体不是人形(载具、炮塔、无人机)、需要完全自定义移动组件,或者需要零开销基线时。
cpp
// Minimal subclass pattern
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;ADefaultPawnDefaultPawnClassACharacter — Server + All Clients (Replicated with Prediction)
ACharacter — 服务器端 + 所有客户端(带预测的复制)
Exists on: Server (authority) and all clients. The locally controlled instance runs client-side prediction; simulated proxies interpolate from server updates.
Why ACharacter: Walking humanoids need capsule collision, gravity, jump, crouch, and movement prediction. ACharacter bundles all of this with built-in networked prediction via .
UCharacterMovementComponentComponent layout from source (Character.h):
cpp
// Private, access via getters
TObjectPtr<UCapsuleComponent> CapsuleComponent; // GetCapsuleComponent() — root
TObjectPtr<USkeletalMeshComponent> Mesh; // GetMesh()
TObjectPtr<UCharacterMovementComponent> CharacterMovement; // GetCharacterMovement()
TObjectPtr<UArrowComponent> ArrowComponent; // GetArrowComponent() — editor-only direction indicatorConstructor configuration:
cpp
AMyCharacter::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;
}Key ACharacter API from source:
cpp
// 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);Custom movement modes: Set then override in your subclass. The byte lets you distinguish multiple custom modes within the same dispatch.
MOVE_CustomPhysCustom(float deltaTime, int32 Iterations)UCharacterMovementComponentCustomMovementModePhysCustomcpp
// 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);Movement replication: Client sends , server validates and replies via . This is automatic — do not call these RPCs manually.
ServerMovePackedClientMoveResponsePacked**存在于:**服务器端(权限方)和所有客户端。本地控制的实例运行客户端预测;模拟代理从服务器更新中插值。
**为何使用ACharacter:**行走的人形需要胶囊碰撞、重力、跳跃、蹲伏和移动预测。ACharacter通过将所有这些功能与内置的网络预测捆绑在一起。
UCharacterMovementComponent源码中的组件布局(Character.h):
cpp
// Private, access via getters
TObjectPtr<UCapsuleComponent> CapsuleComponent; // GetCapsuleComponent() — root
TObjectPtr<USkeletalMeshComponent> Mesh; // GetMesh()
TObjectPtr<UCharacterMovementComponent> CharacterMovement; // GetCharacterMovement()
TObjectPtr<UArrowComponent> ArrowComponent; // GetArrowComponent() — editor-only direction indicator构造函数配置:
cpp
AMyCharacter::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;
}ACharacter源码中的核心API:
cpp
// 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_CustomUCharacterMovementComponentPhysCustom(float deltaTime, int32 Iterations)CustomMovementModePhysCustomcpp
// 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);**移动复制:**客户端发送,服务器验证后通过回复。这是自动完成的 — 请勿手动调用这些RPC。
ServerMovePackedClientMoveResponsePackedUGameInstance — Process Lifetime Singleton
UGameInstance — 进程生命周期单例
Exists on: One per process. Survives ALL level loads.
Why: On level travel, every actor (including GameMode, GameState, PlayerController, PlayerState) is destroyed. GameInstance is never destroyed. It holds session handles, save game references, analytics state, and any data that must span the entire application lifetime.
cpp
// 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>();**存在于:**每个进程一个实例。在所有关卡加载过程中都不会被销毁。
**为何使用:**在关卡切换时,所有actor(包括GameMode、GameState、PlayerController、PlayerState)都会被销毁。GameInstance永远不会被销毁。它保存会话句柄、存档游戏引用、分析状态以及任何必须跨越整个应用生命周期的数据。
cpp
// 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>();Session Management (Online Subsystem)
会话管理(Online Subsystem)
cpp
// 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]);The Online Subsystem abstracts platform-specific backends (Steam, EOS, Null for testing). Add and to your Build.cs dependencies. After joining, retrieve the connect string with and call .
"OnlineSubsystem""OnlineSubsystemUtils"GetResolvedConnectStringClientTravelcpp
// 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]);Online Subsystem抽象了平台特定的后端(Steam、EOS、用于测试的Null)。在Build.cs依赖项中添加和。加入会话后,使用获取连接字符串并调用。
"OnlineSubsystem""OnlineSubsystemUtils"GetResolvedConnectStringClientTravelGameMode: Registration and Spawn Pipeline
GameMode:注册与生成流程
cpp
AMyGameMode::AMyGameMode()
{
DefaultPawnClass = AMyCharacter::StaticClass();
PlayerControllerClass = AMyPlayerController::StaticClass();
GameStateClass = AMyGameState::StaticClass();
PlayerStateClass = AMyPlayerState::StaticClass();
HUDClass = AMyHUD::StaticClass();
bUseSeamlessTravel = true;
}Join sequence (server only):
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()Match state (AGameMode only):
cpp
bool AMyGameMode::ReadyToStartMatch_Implementation()
{
return GetNumPlayers() >= MinPlayersToStart;
}
bool AMyGameMode::ReadyToEndMatch_Implementation()
{
return GetGameState<AMyGameState>()->TeamAScore >= ScoreLimit;
}cpp
AMyGameMode::AMyGameMode()
{
DefaultPawnClass = AMyCharacter::StaticClass();
PlayerControllerClass = AMyPlayerController::StaticClass();
GameStateClass = AMyGameState::StaticClass();
PlayerStateClass = AMyPlayerState::StaticClass();
HUDClass = AMyHUD::StaticClass();
bUseSeamlessTravel = true;
}加入流程(仅服务器端):
InitGame() → 任何玩家加入前调用;用于初始化地图特定规则
PreLogin() → 在此处拒绝玩家(服务器已满、被封禁)
Login() → 创建PlayerController,分配UniqueId
PostLogin() → 服务器→客户端RPC的第一个安全点;在此处分配队伍
HandleStartingNewPlayer() → 触发RestartPlayer()
RestartPlayer() → FindPlayerStart() → SpawnDefaultPawnFor() → Possess()比赛状态(仅AGameMode):
cpp
bool AMyGameMode::ReadyToStartMatch_Implementation()
{
return GetNumPlayers() >= MinPlayersToStart;
}
bool AMyGameMode::ReadyToEndMatch_Implementation()
{
return GetGameState<AMyGameState>()->TeamAScore >= ScoreLimit;
}Travel Patterns
切换模式
| 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 |
Seamless travel survival:
- Always: ,
UGameInstance,APlayerControllerAPlayerState - Never: ,
AGameMode, level actorsAGameState - Optional: actors you add in
GetSeamlessTravelActorList()
| 模式 | 客户端是否断开连接? | GameMode/GameState是否保留? | 使用场景 |
|---|---|---|---|
非无缝( | 是,重新连接 | 否,重新创建 | 需要全新状态的地图切换 |
无缝( | 否 | 否,重新创建 | 大厅→游戏、回合变更 |
无缝切换保留的内容:
- 始终保留:、
UGameInstance、APlayerControllerAPlayerState - 永远不保留:、
AGameMode、关卡actorAGameState - 可选保留:您在中添加的actor
GetSeamlessTravelActorList()
Common Mistakes
常见错误
GameMode on client (null crash):
cpp
// WRONG
GetWorld()->GetAuthGameMode<AMyGameMode>()->EndMatch(); // nullptr on client
// RIGHT
if (HasAuthority()) { if (auto* GM = GetWorld()->GetAuthGameMode<AMyGameMode>()) GM->EndMatch(); }Wrong class for data:
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 bodyAcknowledgedPawn vs GetPawn:
on a PlayerController may return a pawn before the server confirms possession. Use when you need the server-confirmed pawn.
GetPawn()AcknowledgedPawnDedicated server guard:
cpp
if (GetNetMode() != NM_DedicatedServer)
{
// HUD, camera, audio — never run these on dedicated server
}PIE multi-player: In PIE with multiple players, each has its own PlayerController but all share the same GameMode instance. Test multiplayer logic with PIE > Number of Players set to 2 or more.
客户端调用GameMode(空指针崩溃):
cpp
// WRONG
GetWorld()->GetAuthGameMode<AMyGameMode>()->EndMatch(); // nullptr on client
// RIGHT
if (HasAuthority()) { if (auto* GM = GetWorld()->GetAuthGameMode<AMyGameMode>()) GM->EndMatch(); }数据存放类错误:
所有客户端可见的分数 → APlayerState,而非APlayerController
客户端显示的比赛计时器 → AGameState复制属性,而非AGameMode
跨关卡保留的数据 → UGameInstance,而非AGameState
输入绑定 → PlayerController或Pawn::SetupPlayerInputComponent,而非ACharacter主体AcknowledgedPawn与GetPawn的区别:
PlayerController上的可能在服务器确认possession前返回pawn。当您需要服务器确认的pawn时,请使用。
GetPawn()AcknowledgedPawn专用服务器防护:
cpp
if (GetNetMode() != NM_DedicatedServer)
{
// HUD、相机、音频 — 永远不要在专用服务器上运行这些代码
}**PIE多人模式:**在多玩家PIE模式下,每个玩家都有自己的PlayerController,但所有玩家共享同一个GameMode实例。设置PIE > 玩家数量为2或更多来测试多人游戏逻辑。
Related Skills
相关技能
- — actor lifecycle, component tick, attachment
ue-actor-component-architecture - — DOREPLIFETIME conditions, RPC patterns, push model
ue-networking-replication - — Enhanced Input mapping contexts and input actions
ue-input-system
- — actor生命周期、组件tick、附着
ue-actor-component-architecture - — DOREPLIFETIME条件、RPC模式、推送模型
ue-networking-replication - — 增强型输入映射上下文和输入动作
ue-input-system