mcpField
U
ue-gameplay-framework

ue-gameplay-framework

github.com/quodsoler开发4191.2k 次安装4 周前更新
代码仓库

关于 ue-gameplay-framework

面向 UE 5.8 项目的开发技能,按请求定位 Gameplay Framework 相关类与流程,并提供已核对的 API 用法。涵盖服务器规则、复制状态、玩家控制、生成与旅行等主题。

主要功能

  1. 01Gameplay Framework 类职责映射
  2. 02玩家加入与 Pawn 生成流程
  3. 03比赛状态与复制状态管理
  4. 04控制器、玩家状态与 HUD 用法
  5. 05会话与关卡旅行参考

适用场景

  1. 01确定游戏数据应由哪个框架类持有
  2. 02实现玩家加入、生成或重生流程
  3. 03设计客户端可读取的复制比赛状态
  4. 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 覆盖层、命中框、showdebugHUD
不使用 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)时使用它。

加入流程(仅服务器)
顺序要重写的签名说明
1virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)任何玩家加入之前;另有 virtual void InitGameState()
2virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)将 ErrorMessage 设为非空值即可拒绝。若要进行非阻塞凭据检查,请改为重写 PreLoginAsync(..., const FOnPreLoginCompleteDelegate& OnComplete)(GameModeBase.h:303);它必须始终调用 OnComplete(空字符串 = 接受),否则连接会挂起,并且仍须执行 PreLogin 检查
3virtual APlayerController* Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)生成 APlayerController 及其 APlayerState
4virtual void PostLogin(APlayerController* NewPlayer)第一个可以安全使用服务器→客户端 RPC 的时机
5virtual void OnPostLogin(AController* NewPlayer)受保护;在 PostLogin 结束时运行,是 Pawn 生成前的最后一个钩子
6virtual void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)BlueprintNativeEvent;除非玩家必须旁观,否则会调用 RestartPlayer
7virtual 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() overrideOnPossess(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->RemoteViewPitchGetRemoteViewPitch() / RemoteViewPitch16GameFramework/Pawn.h:134 中的 UE_DEPRECATED(5.6)
Actor->NetUpdateFrequency = 10.fSetNetUpdateFrequency(10.f) / GetNetUpdateFrequency()GameFramework/Actor.h:903 中的 UE_DEPRECATED(5.5)
直接访问 Actor->MinNetUpdateFrequency / Actor->NetCullDistanceSquaredSet/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 UInputModifierScalarGameFramework/PlayerController.h:511 中的 UE_DEPRECATED(5.0)
UCheatManager::DebugCameraControllerRefFDebugCameraManagerGameFramework/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、预测、Mover
  • ue-gameplay-cameras — APlayerCameraManager、视图目标和混合、摄像机修饰器、Gameplay
  • ue-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

评论

支持 Markdown