关于 ue-gameplay-framework
面向 UE 5.8 项目的开发技能,按请求定位 Gameplay Framework 相关类与流程,并提供已核对的 API 用法。涵盖服务器规则、复制状态、玩家控制、生成与旅行等主题。
主要功能
- 01Gameplay Framework 类职责映射
- 02玩家加入与 Pawn 生成流程
- 03比赛状态与复制状态管理
- 04控制器、玩家状态与 HUD 用法
- 05会话与关卡旅行参考
适用场景
- 01确定游戏数据应由哪个框架类持有
- 02实现玩家加入、生成或重生流程
- 03设计客户端可读取的复制比赛状态
- 04处理控制器输入及关卡切换
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-gameplay-framework安装后在 Claude Code 中输入/ue-gameplay-framework确认已加载。
附带文件references/framework-class-map.md
详细介绍
UE Gameplay Framework
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;旧版形式列在“已弃用 — 请勿使用”下。
Gameplay 框架是一组由引擎为你生成的类——AGameModeBase、AGameStateBase、APlayerController、APlayerState、APawn、ACharacter、AHUD、UGameInstance——以及将它们连接起来的加入、生成、比赛状态、关卡切换和伤害流程。所有这些都包含在 Engine 模块(Classes/GameFramework、Classes/Engine、Classes/Kismet)中,因此 Build.cs 需要 "Core"、"CoreUObject"、"Engine"。绑定输入时添加 "EnhancedInput";只有在处理会话时才添加 "OnlineSubsystem" + "OnlineSubsystemUtils"(或 "OnlineServicesInterface")。
上下文
如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、GAS/网络设置)。如果该文件不存在,请继续操作。
根据请求和代码库确定相关领域。只有当两种合理的理解会导致不同代码时,才提出问题。
| 请求涉及…… | 请参阅 |
|---|---|
| 哪个类保存哪些数据;服务器与客户端上的存在情况 | 类职责映射 |
| 加入流程、玩家起始点、Pawn 生成、比赛状态 | GameMode |
| 复制的分数、计时器、已连接玩家列表 | GameState |
| 每个玩家的输入、RPC 桥接、控制权、输入模式 | PlayerController |
| 每个玩家复制的属性、队伍、名称 | PlayerState |
可被控制的身体、PossessedBy、角色设置 | Pawn 和 Character |
Canvas 覆盖层、命中框、showdebug | HUD |
| 不使用 Gameplay Abilities 的生命值和伤害 | 不使用 GAS 的伤害 |
| 关卡加载后仍保留的数据、子系统、访问器 | GameInstance |
| 托管、查找和加入在线会话 | Sessions |
ServerTravel、ClientTravel、无缝关卡切换 | 关卡切换 |
存在情况/权限矩阵、这些代码片段所依据的完整类声明、生命周期顺序和继承属性表: references/framework-class-map.md。
类职责映射
| 类 | 存在于 | 负责内容 | 绝不放在这里 |
|---|---|---|---|
AGameModeBase / AGameMode | 仅服务器和独立运行模式;客户端上为 nullptr | 规则、加入审批、生成位置选择、比赛状态 | 客户端必须读取的任何内容 |
AGameStateBase / AGameState | 各处,已复制 | 全局复制状态、PlayerArray、服务器时钟 | 仅限服务器的决策 |
APlayerController | 服务器持有所有实例;每个客户端仅持有自己的实例 | 输入、RPC 桥接、控制权、输入模式、视角目标 | 其他客户端需要的数据 |
APlayerState | 各处(bAlwaysRelevant = true) | 每个玩家复制的属性、队伍、名称、Ping | 输入处理、UI 状态 |
APawn / ACharacter | 服务器和所有客户端 | 身体:碰撞、网格体、移动 | 规则、计分 |
AHUD | 仅本地玩家;专用服务器上不会存在 | Canvas 覆盖层、命中框、调试显示 | 复制数据 |
UGameInstance | 每个进程一个,跨越所有关卡加载而存在 | 会话句柄、跨关卡状态、子系统 | 每场比赛的状态 |
APlayerState 在其构造函数中设置 bReplicates = true 和 bAlwaysRelevant = true,因此记分板在任何距离都能正常工作。
GameMode(仅服务器)
AGameModeBase 是精简的基类。AGameMode 添加了比赛状态机——当你需要 MatchState::EnteringMap → WaitingToStart → InProgress → WaitingPostMatch → LeavingMap(失败时还包括 Aborted)时使用它。
加入流程(仅服务器)
| 顺序 | 要重写的签名 | 说明 |
|---|---|---|
| 1 | virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) | 任何玩家加入之前;另有 virtual void InitGameState() |
| 2 | virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) | 将 ErrorMessage 设为非空值即可拒绝。若要进行非阻塞凭据检查,请改为重写 PreLoginAsync(..., const FOnPreLoginCompleteDelegate& OnComplete)(GameModeBase.h:303);它必须始终调用 OnComplete(空字符串 = 接受),否则连接会挂起,并且仍须执行 PreLogin 检查 |
| 3 | virtual APlayerController* Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) | 生成 APlayerController 及其 APlayerState |
| 4 | virtual void PostLogin(APlayerController* NewPlayer) | 第一个可以安全使用服务器→客户端 RPC 的时机 |
| 5 | virtual void OnPostLogin(AController* NewPlayer) | 受保护;在 PostLogin 结束时运行,是 Pawn 生成前的最后一个钩子 |
| 6 | virtual void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) | BlueprintNativeEvent;除非玩家必须旁观,否则会调用 RestartPlayer |
| 7 | virtual void RestartPlayer(AController* NewPlayer) | FindPlayerStart → SpawnDefaultPawnFor → Possess |
| — | virtual void Logout(AController* Exiting) | 带有 PlayerState 的控制器离开时触发 |
// MyGameMode.cpp - include the header of every class named below
#include "MyGameMode.h"
AMyGameMode::AMyGameMode()
{
DefaultPawnClass = AMyCharacter::StaticClass();
PlayerControllerClass = AMyPlayerController::StaticClass();
GameStateClass = AMyGameState::StaticClass();
PlayerStateClass = AMyPlayerState::StaticClass();
HUDClass = AMyHUD::StaticClass();
bUseSeamlessTravel = true;
}
bool AMyGameMode::ReadyToStartMatch_Implementation()
{
return GetNumPlayers() >= MinPlayersToStart;
}
ChoosePlayerStart、FindPlayerStart、SpawnDefaultPawnFor、PlayerCanRestart、MustSpectate、ReadyToStartMatch 和 ReadyToEndMatch 都是 BlueprintNativeEvent —— 重写生成的 _Implementation,绝不要重写原始名称。完整签名:virtual AActor* ChoosePlayerStart_Implementation(AController* Player)、virtual AActor* FindPlayerStart_Implementation(AController* Player, const FString& IncomingName)、virtual APawn* SpawnDefaultPawnFor_Implementation(AController* NewPlayer, AActor* StartSpot)。同一流程中的普通虚函数:virtual void RestartPlayerAtPlayerStart(AController* NewPlayer, AActor* StartSpot)、virtual void RestartPlayerAtTransform(AController* NewPlayer, const FTransform& SpawnTransform)、virtual bool ShouldSpawnAtStartSpot(AController* Player)。生成点是 APlayerStart Actor,根据其 FName PlayerStartTag 选择。
比赛状态(仅限 AGameMode)
公共接口:FName GetMatchState() const、virtual bool IsMatchInProgress() const、virtual void StartMatch()、virtual void EndMatch()、virtual void AbortMatch()、virtual void RestartGame()。
受保护的钩子:virtual void SetMatchState(FName NewState)、virtual void OnMatchStateSet()、virtual void HandleMatchIsWaitingToStart()、virtual void HandleMatchHasStarted()、virtual void HandleMatchHasEnded()、virtual void HandleLeavingMap()、virtual void HandleMatchAborted()。
SetMatchState 是唯一受认可的状态切换方式;它会将新状态写入 AGameState::MatchState,并将其复制到客户端。不要直接写入 MatchState。
GameState(全局)
客户端无法看到 GameMode,因此它们需要的一切都放在这里。
// MyGameState.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "MyGameState.generated.h"
UCLASS()
class MYGAME_API AMyGameState : public AGameStateBase
{
GENERATED_BODY()
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Match")
int32 TeamAScore = 0;
UPROPERTY(ReplicatedUsing = OnRep_MatchTimeRemaining, BlueprintReadOnly, Category = "Match")
float MatchTimeRemaining = 0.f;
protected:
UFUNCTION()
void OnRep_MatchTimeRemaining();
};
// MyGameState.cpp - also include Net/UnrealNetwork.h
void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyGameState, TeamAScore);
DOREPLIFETIME(AMyGameState, MatchTimeRemaining);
}
void AMyGameState::OnRep_MatchTimeRemaining()
{
// Client-side reaction: refresh the timer widget.
}
每个 ReplicatedUsing = X 都需要有对应的 UFUNCTION() void X(); 声明;每个 Replicated/ReplicatedUsing 属性都需要在 GetLifetimeReplicatedProps 中有一行 DOREPLIFETIME。条件、COND_*、推送模型和 RPC 规则请参阅 ue-networking-replication。
继承而来:TArray<TObjectPtr<APlayerState>> PlayerArray(此属性本身不复制;每个 APlayerState 都会通过 AddPlayerState 在每台机器上添加自身,因为 PlayerState 始终处于相关状态)。继承而来且会复制:TSubclassOf<AGameModeBase> GameModeClass(ReplicatedUsing = OnRep_GameModeClass)、TSubclassOf<ASpectatorPawn> SpectatorClass、bReplicatedHasBegunPlay、ReplicatedWorldTimeSecondsDouble。访问器:virtual double GetServerWorldTimeSeconds() const、virtual bool HasBegunPlay() const、virtual bool HasMatchStarted() const、virtual bool HasMatchEnded() const、virtual void AddPlayerState(APlayerState* PlayerState)、virtual void RemovePlayerState(APlayerState* PlayerState)。AGameState 添加了 FName MatchState(ReplicatedUsing = OnRep_MatchState)和 int32 ElapsedTime(replicatedUsing = OnRep_ElapsedTime)。
PlayerController
// MyPlayerController.cpp
#include "MyPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "InputMappingContext.h"
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (IsLocalController())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
SetInputMode(FInputModeGameOnly());
}
}
void AMyPlayerController::ServerRequestRespawn_Implementation()
{
// Runs on the server.
}
bool AMyPlayerController::ServerRequestRespawn_Validate()
{
return true;
}
上述声明(参考资料中包含完整的类体):virtual void BeginPlay() override;、virtual void SetupInputComponent() override;、virtual void OnPossess(APawn* aPawn) override;、virtual void OnUnPossess() override;,以及 UFUNCTION(Server, Reliable, WithValidation) void ServerRequestRespawn();。
AController::Possess 和 AController::UnPossess 是 virtual final;请改为重写 OnPossess / OnUnPossess。APlayerController::SetupInputComponent() 是在 APlayerController 本身上声明的受保护虚函数——用于处理生命周期长于控制权的输入(菜单、观察者按键、全局快捷键)。Pawn 专属的绑定应放在 APawn::SetupPlayerInputComponent 中;映射上下文、操作、修饰器和触发器请参阅 ue-input-system。
重要成员:TObjectPtr<APawn> AcknowledgedPawn(服务器确认的控制权)、TObjectPtr<AHUD> MyHUD、TObjectPtr<APlayerCameraManager> PlayerCameraManager、TObjectPtr<UPlayerInput> PlayerInput(仅本地)、TSubclassOf<UCheatManager> CheatClass、uint32 bShowMouseCursor:1、uint32 bEnableClickEvents:1、uint32 bEnableStreamingSource:1(驱动此视口的 World Partition 加载)、uint16 SeamlessTravelCount。输入模式 FInputModeGameOnly、FInputModeUIOnly 和 FInputModeGameAndUI 通过 virtual void SetInputMode(const FInputModeDataBase& InData) 应用。
| 摄像机工作 | 所属 |
|---|---|
PlayerCameraManager、SetViewTarget、SetViewTargetWithBlend、摄像机修饰器、摄像机震动、Gameplay Cameras(5.8 中为实验性功能) | ue-gameplay-cameras |
**监听服务器的双重角色:**主机的 PlayerController 同时拥有 ROLE_Authority 和本地玩家身份。除了 HasAuthority(),还要用 IsLocalController() 进行判断;为专用服务器编写的代码在监听服务器主机上往往会运行两次。
PlayerState
// MyPlayerState.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "MyPlayerState.generated.h"
UCLASS()
class MYGAME_API AMyPlayerState : public APlayerState
{
GENERATED_BODY()
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void CopyProperties(APlayerState* PlayerState) override;
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Score")
int32 Kills = 0;
UPROPERTY(ReplicatedUsing = OnRep_TeamIndex, BlueprintReadOnly, Category = "Team")
uint8 TeamIndex = 0;
protected:
UFUNCTION()
void OnRep_TeamIndex();
};
// MyPlayerState.cpp - also include Net/UnrealNetwork.h
void AMyPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyPlayerState, Kills);
DOREPLIFETIME(AMyPlayerState, TeamIndex);
}
void AMyPlayerState::OnRep_TeamIndex()
{
// Client-side reaction: recolour the team badge.
}
void AMyPlayerState::CopyProperties(APlayerState* PlayerState)
{
Super::CopyProperties(PlayerState);
if (AMyPlayerState* Target = Cast<AMyPlayerState>(PlayerState))
{
Target->Kills = Kills;
Target->TeamIndex = TeamIndex;
}
}
如果没有 UFUNCTION() OnRep 声明和 DOREPLIFETIME 条目,这些属性虽然可以编译和运行,却会悄无声息地永不复制。有关复制条件和推送模型:ue-networking-replication。
无缝旅行钩子:virtual void CopyProperties(APlayerState* PlayerState)(受保护;在旧 PlayerState 上调用,并将保留的 PlayerState 作为参数;断开连接时保存非活动 PlayerState 也会调用)、virtual void OverrideWith(APlayerState* PlayerState)、virtual void SeamlessTravelTo(class APlayerState* NewPlayerState)。内置接口:FString GetPlayerName() const、float GetScore() const / void SetScore(const float NewScore)、int32 GetPlayerId() const、APawn* GetPawn() const、class APlayerController* GetPlayerController() const、virtual void ClientInitialize(class AController* C)。
通过 MyPawn->GetPlayerState() 或 MyController->PlayerState 获取一个 PlayerState;通过 GameState 上的 PlayerArray 获取所有 PlayerState:
if (const AGameStateBase* GS = GetWorld()->GetGameState<AGameStateBase>())
{
for (APlayerState* PS : GS->PlayerArray) { /* every connected player */ }
}
Pawn 和 Character
APawn 是最精简的可被控制的 Actor:没有网格、碰撞组件或移动组件。ACharacter 添加了胶囊体根组件、骨骼网格以及支持网络预测的 UCharacterMovementComponent。ADefaultPawn 是引擎提供的飞行占位类(GetCollisionComponent()、GetMeshComponent()、bAddDefaultMovementBindings);ASpectatorPawn 继承自它。
// Pawn-side overrides
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;
virtual void OnRep_Controller() override;
virtual void NotifyControllerChanged() override;
PossessedBy 只在服务器上运行。OnRep_Controller 是复制后的 Controller 指针对应的客户端回调;NotifyControllerChanged 会在服务器和所属客户端上触发(模拟代理不会触发,Pawn.h:396),因此适合放置所有者端逻辑,例如刷新输入映射上下文。
在 Character 构造函数中,使用 GetCapsuleComponent()->SetCapsuleSize(42.f, 96.f) 设置胶囊体尺寸,并使用 GetMesh()->SetRelativeLocation(...) / SetRelativeRotation(...) 放置网格;这需要 #include "Components/CapsuleComponent.h" 和 #include "Components/SkeletalMeshComponent.h"。
| 区域 | 所属文档 |
|---|---|
UCharacterMovementComponent 调优、EMovementMode、PhysCustom(先调用 Super::PhysCustom(DeltaTime, Iterations))、根运动、预测、FMovementBaseInterfaceData、Mover(5.8 中为实验性功能) | ue-character-movement |
可直接使用的 Character API:virtual void Jump()、virtual void StopJumping()、bool CanJump() const、virtual void LaunchCharacter(FVector LaunchVelocity, bool bXYOverride, bool bZOverride)、virtual void Crouch(bool bClientSimulation = false)、virtual void UnCrouch(bool bClientSimulation = false)、virtual void OnRep_IsCrouched()。复制状态:float JumpMaxHoldTime、int32 JumpMaxCount、uint8 bIsCrouched:1。组件获取函数:GetCapsuleComponent()、GetMesh()、GetCharacterMovement() 和 GetArrowComponent()(仅在 #if WITH_EDITORONLY_DATA 下声明;调用前需添加条件判断)。
HUD
AHUD 是面向单个本地玩家的即时模式画布叠加层,由 AGameModeBase::HUDClass 生成。专用服务器上不会存在该对象。对于保留式 UI(菜单、生命值条、布局),请使用 UMG——参见 ue-ui-umg-slate。
// MyHUD.cpp — declares: virtual void DrawHUD() override; virtual void NotifyHitBoxClick(FName BoxName) override;
#include "Engine/Canvas.h" // plus MyHUD.h and GameFramework/PlayerController.h
static const FName MenuBoxName(TEXT("MenuButton"));
void AMyHUD::DrawHUD()
{
Super::DrawHUD();
if (Canvas == nullptr)
{
return;
}
const float CentreX = Canvas->SizeX * 0.5f;
const float CentreY = Canvas->SizeY * 0.5f;
DrawRect(FLinearColor::White, CentreX - 2.f, CentreY - 2.f, 4.f, 4.f);
DrawText(TEXT("Menu"), FLinearColor::White, 40.f, 40.f);
AddHitBox(FVector2D(40.f, 40.f), FVector2D(120.f, 24.f), MenuBoxName, true, 0);
}
void AMyHUD::NotifyHitBoxClick(FName BoxName)
{
Super::NotifyHitBoxClick(BoxName);
if (BoxName == MenuBoxName)
{
if (APlayerController* PC = GetOwningPlayerController())
{
PC->SetInputMode(FInputModeUIOnly());
}
}
}
TObjectPtr<UCanvas> Canvas 仅在 DrawHUD 内有效——切勿缓存。绘图辅助函数:DrawText、DrawLine、DrawRect、DrawTexture、DrawTextureSimple。通过 void AddHitBox(FVector2D Position, FVector2D Size, FName InName, bool bConsumesInput, int32 Priority = 0) 添加的命中框会触发原生函数 virtual void NotifyHitBoxClick(FName BoxName)、NotifyHitBoxRelease、NotifyHitBoxBeginCursorOver 和 NotifyHitBoxEndCursorOver,以及 ReceiveHitBoxClick / ReceiveHitBoxRelease / ReceiveHitBoxBeginCursorOver Blueprint 事件。所有者访问方式:TObjectPtr<APlayerController> PlayerOwner、APlayerController* GetOwningPlayerController() const、APawn* GetOwningPawn() const。
调试显示:uint8 bShowHUD:1 控制所有绘图,并由 ShowHUD exec 切换;virtual void ShowDebug(FName DebugType = NAME_None) 为 showdebug 控制台命令提供支持(引擎支持的值包括 AI、physics、net、camera、collision);重写 virtual void ShowDebugInfo(float& YL, float& YPos) 可添加自定义行。
不使用 GAS 的伤害
引擎伤害机制由一个虚函数和四个 UGameplayStatics 入口函数组成。未使用 GAS 时可采用此机制;属性、执行计算和提示效果应使用 ue-gameplay-abilities。
// Dealing damage - server authority only. Needs Kismet/GameplayStatics.h.
// Target, Hit, ShotDirection, InstigatorController and IgnoreActors are locals of the caller.
UGameplayStatics::ApplyDamage(Target, 25.f, InstigatorController, this, UMyDamageType::StaticClass());
UGameplayStatics::ApplyPointDamage(Target, 40.f, ShotDirection, Hit,
InstigatorController, this, UMyDamageType::StaticClass());
UGameplayStatics::ApplyRadialDamage(this, 100.f, Hit.ImpactPoint, 500.f,
UMyDamageType::StaticClass(), IgnoreActors, this, InstigatorController, false, ECC_Visibility);
UGameplayStatics::ApplyRadialDamageWithFalloff(this, 100.f, 10.f, Hit.ImpactPoint, 200.f, 600.f, 1.f,
UMyDamageType::StaticClass(), IgnoreActors, this, InstigatorController, ECC_Visibility);
// Receiving damage. AMyCharacter declares UPROPERTY(Replicated) float Health = 100.f; and FName LastHitBone;
// Declaration:
// virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
#include "Engine/DamageEvents.h"
float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
const float Applied = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
if (!HasAuthority() || Applied <= 0.f)
{
return 0.f;
}
if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
{
const FPointDamageEvent& Point = static_cast<const FPointDamageEvent&>(DamageEvent);
LastHitBone = Point.HitInfo.BoneName;
}
Health = FMath::Max(0.f, Health - Applied);
return Applied;
}
FDamageEvent 包含 TSubclassOf<UDamageType> DamageTypeClass,并通过 virtual bool IsOfType(int32 InID) const 分派。FPointDamageEvent 添加了 float Damage、FVector_NetQuantizeNormal ShotDirection 和 FHitResult HitInfo;FRadialDamageEvent 添加了 FRadialDamageParams Params、FVector Origin 和 TArray<FHitResult> ComponentHits。UDamageType 是作为类使用的 UObject,每次命中不会单独实例化——可将其子类化以携带 float DamageImpulse、float DestructibleImpulse、float DamageFalloff、uint32 bCausedByWorld:1、uint32 bScaleMomentumByMass:1。
以下是动态多播替代方案,可用于替代重写 TakeDamage;它们都属于 AActor:
| 委托 | 处理函数签名 |
|---|---|
OnTakeAnyDamage | (AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser) |
OnTakePointDamage | (AActor* DamagedActor, float Damage, AController* InstigatedBy, FVector HitLocation, UPrimitiveComponent* FHitComponent, FName BoneName, FVector ShotFromDirection, const UDamageType* DamageType, AActor* DamageCauser) |
OnTakeRadialDamage | (AActor* DamagedActor, float Damage, const UDamageType* DamageType, FVector Origin, const FHitResult& HitInfo, AController* InstigatedBy, AActor* DamageCauser) |
使用 OnTakeAnyDamage.AddDynamic(this, &AMyCharacter::HandleAnyDamage); 进行绑定,并将处理函数标记为 UFUNCTION()。通过 void SetCanBeDamaged(bool bInCanBeDamaged) / bool CanBeDamaged() const 控制是否可受伤。当角色从 AWorldSettings::KillZ 下方掉落时,会调用 virtual void FellOutOfWorld(const class UDamageType& dmgType),并传入 AWorldSettings::KillZDamageType。
GameInstance
每个进程只有一个;在每次关卡加载后都会继续存在,包括无缝切换关卡。重写 virtual void Init()(启动时调用一次;Super::Init() 会最后初始化游戏实例子系统,因此只有在调用它之后才能使用这些子系统)、受保护的 virtual void OnStart()(实例就绪时调用一次)以及 virtual void Shutdown()(退出时调用)。UGameInstance::GetWorld() 是 final,不要重写它。游戏实例子系统(UGameInstanceSubsystem)适合用于跨关卡服务;完整的子系统表见 ue-cpp-foundations。
| 需求 | 调用 |
|---|---|
| 从 Actor 获取游戏实例 | AActor::GetGameInstance<UMyGameInstance>() 或 UWorld::GetGameInstance<UMyGameInstance>() |
| 从任何拥有 World 的 UObject 获取游戏实例 | UGameplayStatics::GetGameInstance(const UObject* WorldContextObject) |
| 游戏实例子系统 | UGameInstance::GetSubsystem<UMyGameInstanceSubsystem>() |
| 第一个本地玩家控制器 | UWorld::GetFirstPlayerController() 或 UGameInstance::GetFirstLocalPlayerController(const UWorld* World = nullptr) |
| 特定玩家控制器 | UGameplayStatics::GetPlayerController(const UObject* WorldContextObject, int32 PlayerIndex) |
| 游戏模式(仅限服务器) | UWorld::GetAuthGameMode<AMyGameMode>() 或 UGameplayStatics::GetGameMode(const UObject* WorldContextObject) |
| 游戏状态 | UWorld::GetGameState<AMyGameState>() 或 UGameplayStatics::GetGameState(const UObject* WorldContextObject) |
Sessions
5.8 中提供了两套体系。旧版 OnlineSubsystem(模块 "OnlineSubsystem"、"OnlineSubsystemUtils";后端包括 Null、Steam、EOS)是大多数现有项目使用的方案。Online Services v2(Plugins/Online/OnlineServices,模块 "OnlineServicesInterface")是新代码的现代方案,OnlineServicesOSSAdapter 可在两者之间进行桥接。Session 代码应放在 UGameInstance 或游戏实例子系统中,因为它必须比关卡存续更久。
// Legacy OnlineSubsystem - host. Needs OnlineSubsystem.h, OnlineSessionSettings.h and
// Interfaces/OnlineSessionInterface.h; CreateSessionHandle is an FDelegateHandle member.
IOnlineSubsystem* OSS = IOnlineSubsystem::Get();
IOnlineSessionPtr Sessions = OSS ? OSS->GetSessionInterface() : nullptr;
if (Sessions.IsValid())
{
FOnlineSessionSettings Settings;
Settings.NumPublicConnections = 4;
Settings.bShouldAdvertise = true;
Settings.bIsLANMatch = false;
Settings.bUsesPresence = true;
CreateSessionHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(
FOnCreateSessionCompleteDelegate::CreateUObject(this, &UMyGameInstance::HandleCreateSessionComplete));
Sessions->CreateSession(0, NAME_GameSession, Settings);
}
使用 FOnlineSessionSearch 和 Sessions->FindSessions(0, SessionSearch.ToSharedRef()) 搜索;使用 Sessions->JoinSession(0, NAME_GameSession, SessionSearch->SearchResults[Index]) 加入;然后使用 Sessions->GetResolvedConnectString(NAME_GameSession, ConnectInfo) 解析地址,并调用 PlayerController->ClientTravel(ConnectInfo, TRAVEL_Absolute)。处理函数为 void HandleCreateSessionComplete(FName SessionName, bool bWasSuccessful)、void HandleFindSessionsComplete(bool bWasSuccessful) 和 void HandleJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)。使用匹配的 Clear...Delegate_Handle 释放所有已存储的 FDelegateHandle。完整的搜索和加入代码:references/framework-class-map.md。
// Online Services (v2). Needs Online/OnlineServices.h and Online/Sessions.h.
using namespace UE::Online;
TSharedPtr<IOnlineServices> Services = GetServices(EOnlineServices::Default);
ISessionsPtr SessionsV2 = Services.IsValid() ? Services->GetSessionsInterface() : nullptr;
v2 调用返回 TOnlineAsyncOpHandle / TOnlineResult,而非委托句柄;与 ISessions 并列的接口有 IAuth、ILobbies、IAchievements、IPresence 和 ILeaderboards。
旅行
| 模式 | 调用 | 客户端 | GameMode / GameState |
|---|---|---|---|
| 服务端旅行,非无缝 | UWorld::ServerTravel(const FString& InURL, bool bAbsolute = false, bool bShouldSkipGameNotify = false) | 断开连接并重新连接 | 重新创建 |
| 服务端旅行,无缝 | 使用 AGameModeBase::bUseSeamlessTravel = true 调用相同方法 | 保持连接 | 重新创建 |
| 将一个客户端移至另一台服务器 | APlayerController::ClientTravel(const FString& URL, ETravelType TravelType, bool bSeamless = false, FGuid MapPackageGuid = FGuid()) | 仅该客户端 | 不适用 |
| 在本地加载地图 | UGameplayStatics::OpenLevel(const UObject* WorldContextObject, FName LevelName, bool bAbsolute = true, FString Options = FString(TEXT(""))) | 不适用 | 重新创建 |
在 GameMode 中,监听服务器切换地图的调用为 GetWorld()->ServerTravel(TEXT("/Game/Maps/NewMap?listen"))。
无缝旅行分为两个阶段(当前地图 → 过渡地图 → 目标地图),并且会在两个阶段都调用 virtual void GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)。UGameInstance、APlayerController 和 APlayerState 会保留;AGameModeBase、AGameStateBase、Pawn 和关卡 Actor 不会保留。AGameModeBase::ProcessServerTravel(const FString& URL, bool bAbsolute = false) 是自定义 URL 处理的重载点。关卡流送、World Partition 和 Data Layers 归属 ue-world-level-streaming。
已弃用 — 请勿使用
| 请勿生成 | 在 5.8 中使用 | 来源 |
|---|---|---|
DispatchPostLogin(NewPlayer) | 重载 OnPostLogin(AController* NewPlayer);它会在 PostLogin 结束时运行 | GameFramework/GameModeBase.h:329 中的 UE_DEPRECATED(5.6) |
virtual void Possess(APawn*) override / virtual void UnPossess() override | OnPossess(APawn* aPawn) / OnUnPossess() | GameFramework/Controller.h:284,288 中的 virtual final |
Pawn->IsControlled() | IsPawnControlled() 或 IsPlayerControlled() | GameFramework/Pawn.h:254 中的 UE_DEPRECATED(4.24) |
Pawn->GetMovementBase() | GetMovementBaseObject() 或 GetMovementBaseInterfaceData() | GameFramework/Pawn.h:59、GameFramework/Character.h:797 中的 UE_DEPRECATED(5.8) |
Character->SetBase(UPrimitiveComponent*, BoneName, bNotifyActor) | SetBase(FMovementBaseInterfaceData*, const FName, bool) | GameFramework/Character.h:515 中的 UE_DEPRECATED(5.8) |
Pawn->RemoteViewPitch | GetRemoteViewPitch() / RemoteViewPitch16 | GameFramework/Pawn.h:134 中的 UE_DEPRECATED(5.6) |
Actor->NetUpdateFrequency = 10.f | SetNetUpdateFrequency(10.f) / GetNetUpdateFrequency() | GameFramework/Actor.h:903 中的 UE_DEPRECATED(5.5) |
直接访问 Actor->MinNetUpdateFrequency / Actor->NetCullDistanceSquared | Set/GetMinNetUpdateFrequency()、Set/GetNetCullDistanceSquared() | GameFramework/Actor.h:898,908 中的 UE_DEPRECATED(5.5) |
PlayerController::InputKey(...) 旧重载 | 接受 FInputKeyEventArgs 的重载 | GameFramework/PlayerController.h:1785 中的 UE_DEPRECATED(5.6) |
PlayerController::InputTouch(...) 旧重载 | 接受 FTouchId 的重载 | GameFramework/PlayerController.h:1794 中的 UE_DEPRECATED(5.8) |
PlayerController::ProcessTouchHitResult(FInputDeviceId, uint32, ...) | 接受 FTouchId 的重载 | GameFramework/PlayerController.h:2005 中的 UE_DEPRECATED(5.8) |
SetDeprecatedInputYawScale() 和 Pitch/Roll 对应方法 | Enhanced Input UInputModifierScalar | GameFramework/PlayerController.h:511 中的 UE_DEPRECATED(5.0) |
UCheatManager::DebugCameraControllerRef | FDebugCameraManager | GameFramework/CheatManager.h:107 中的 UE_DEPRECATED(5.8) |
常见错误
在客户端解引用 GameMode:GetAuthGameMode() 除了服务器,在其他地方都会返回 nullptr,因此 GetWorld()->GetAuthGameMode<AMyGameMode>()->EndMatch() 会在客户端崩溃。应改为 if (AMyGameMode* GM = GetWorld()->GetAuthGameMode<AMyGameMode>()) { GM->EndMatch(); }。
**使用 ReplicatedUsing 却未声明 OnRep:**UHT 会导致构建失败。每个 ReplicatedUsing = OnRep_X 都需要在同一个类定义中声明 UFUNCTION() void OnRep_X();。
**复制属性未添加 DOREPLIFETIME:**代码能编译、能运行,却会静默地不进行复制。添加 GetLifetimeReplicatedProps,先调用 Super::,然后为每个属性添加一个 DOREPLIFETIME。
**重写 Possess / UnPossess:**这两个函数在 AController 中都是 virtual final。应重写 OnPossess(APawn* aPawn) 和 OnUnPossess()。
按原名重写 BlueprintNativeEvent:ChoosePlayerStart、FindPlayerStart、SpawnDefaultPawnFor、HandleStartingNewPlayer、ReadyToStartMatch、ReadyToEndMatch、PlayerCanRestart 和 MustSpectate 都需要加上 _Implementation 后缀;串联调用时,相应的 Super:: 调用也要带上该后缀。
**在专用服务器上处理 HUD、摄像机或音频:**这些对象在专用服务器上不存在。用 if (GetNetMode() != NM_DedicatedServer) 对表现层代码进行保护。
**数据归属的类不正确:**所有客户端都能看到的分数应放在 APlayerState 中,而不是 APlayerController 中;客户端读取的比赛计时器应放在复制的 AGameStateBase 属性中,而不是 AGameModeBase 中;生命周期长于关卡的数据应放在 UGameInstance 中,而不是 AGameStateBase 中;Pawn 的输入绑定应放在 APawn::SetupPlayerInputComponent 中,而不是 Character 构造函数中。
**服务器确认占有前调用 GetPawn():**需要获取服务器已确认的 Pawn 时,请使用 AcknowledgedPawn。
**使用多个玩家进行 PIE:**每个玩家都有自己的 PlayerController,但他们共享同一个 GameMode 实例。在信任任何多人游戏路径之前,请先将 PIE 的“Number of Players”设为 2 或更多。
相关技能
ue-gameplay-abilities— GAS 属性、游戏效果和由能力驱动的伤害;可作为替代方案ue-networking-replication—DOREPLIFETIME条件、COND_*、RPC 规则、推送模型、Iris(5.8 中为 Beta)ue-character-movement—UCharacterMovementComponent、移动模式、PhysCustom、预测、Moverue-gameplay-cameras—APlayerCameraManager、视图目标和混合、摄像机修饰器、Gameplayue-input-system— Enhanced Input 映射上下文、输入操作、修饰器和触发器ue-actor-component-architecture— Actor 生命周期、组件、附加、Tick 组ue-cpp-foundations—UCLASS/UPROPERTY/UFUNCTION说明符、TObjectPtr、子系统表ue-ui-umg-slate— UMG 控件和保留式 UI;通常可替代画布 HUD 绘制ue-serialization-savegames—USaveGame、存档槽、如何将数据持久化到UGameInstance之外ue-world-level-streaming— 关卡流送、World Partition、Data Layers、流送源ue-ai-navigation— AI 控制器、行为树、感知、EQS 和导航网格查询ue-audio-system— UAudioComponent、MetaSounds、子混音、衰减和并发- 另请参阅:
ue-blueprint-cpp-interop、ue-game-features、ue-mover、ue-physics-collision、ue-state-trees、ue-testing-debugging