UE_联机_GameState

GameState 是 UE 网络框架中一个至关重要且强大的类,理解它是构建稳健多人游戏的关键。

1. 什么是 GameState?

GameState(游戏状态)是一个在服务器和所有客户端上都存在的 Actor。它的核心职责是存储和同步整个游戏层面的状态信息

可以把它想象成游戏的“公告板”或“数据中心”,所有玩家都需要知道的全局信息都存放在这里。

  • 服务器 拥有权威的 GameState,负责更新其上的数据。
  • 客户端 拥有 GameState 的副本,服务器会通过网络自动将相关属性同步给它们,让所有玩家对游戏大局有一致的认知。

2. GameState 的核心特性

  1. 网络同步: GameState 及其内部的 PlayerState 会自动从服务器复制到所有客户端。
  2. 全局存在: 在游戏会话的整个生命周期中都存在。
  3. 权威性: 只有服务器可以修改 GameState 的核心数据。客户端上的 GameState 是只读的副本。
  4. 存储全局信息: 它应该包含那些不属于某个特定玩家,而是属于整局游戏的信息。

3. GameState 与其它关键类的区别

理解 GameState 的最好方式就是把它和其他核心类进行对比。

类名权威者存在位置主要职责
GameState服务器服务器和所有客户端存储和同步全局游戏状态(如剩余时间、当前比分、游戏阶段)。
PlayerState服务器服务器和所有客户端存储和同步玩家的公开状态(如玩家姓名、得分、杀敌/死亡数)。一个 PlayerState 对应一个玩家。
GameMode服务器仅服务器定义游戏规则、处理游戏流程(如如何获胜、生成玩家)。包含敏感逻辑和数据。
PlayerController各自客户端服务器和所属客户端代表玩家的人机接口。处理输入、管理UI。服务器有所有 PlayerController,但只能控制自己的。
Pawn / Character各自客户端服务器和所有客户端玩家或AI在游戏世界中的物理实体。其位置、动画等会被同步。

简单比喻:

  • GameMode 是比赛的裁判长(只在后台工作,制定规则)。
  • GameState 是体育场里的记分牌和大屏幕(向所有观众显示比分、剩余时间)。
  • PlayerState 是记分牌上显示的某个球员的个人数据(如进球数、犯规次数)。
  • PlayerController 是球员的大脑(决定何时跑动、何时射门)。
  • Pawn 是球员的身体(在场上执行动作)。

4. GameState 中应该放什么?

以下是一些非常适合放在 GameState 中的数据的例子:

  • 游戏计时器: 整局游戏的剩余时间、已经进行的时间。
  • 团队分数: 在多团队游戏中,各个团队的当前得分。
  • 游戏阶段: 例如 WaitingToStartInProgress, PostGame 等。
  • 获胜者和结果: 游戏结束时,哪个团队或玩家获胜。
  • 全局世界状态: 比如一个“大逃杀”游戏中的安全区位置和缩小时间。
  • 玩家列表的引用: GameState 持有一个所有 PlayerState 的数组,方便客户端查询其他玩家的信息。

5. 如何在 C++ 中实现一个 GameState

让我们通过一个“夺旗”游戏的例子来演示。

MyGameState.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "Net/UnrealNetwork.h" // 重要:用于网络复制
#include "MyGameState.generated.h"

UCLASS()
class MYGAME_API AMyGameState : public AGameStateBase
{
    GENERATED_BODY()

public:
    AMyGameState();

    // 团队分数的复制属性
    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Game State")
    int32 TeamAScore;

    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Game State")
    int32 TeamBScore;

    // 游戏剩余时间的复制属性
    UPROPERTY(ReplicatedUsing = OnRep_MatchTime, BlueprintReadOnly, Category = "Game State")
    float MatchRemainingTime;

    // 游戏阶段的枚举
    UENUM(BlueprintType)
    enum class EGamePhase : uint8
    {
        WaitingForPlayers,
        InProgress,
        RoundOver,
        GameOver
    };

    // 游戏阶段的复制属性
    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Game State")
    EGamePhase CurrentGamePhase;

    // 服务器端增加分数的方法
    void AddScoreToTeamA(int32 Amount);
    void AddScoreToTeamB(int32 Amount);

    // 服务器端设置时间的方法
    void SetMatchTime(float NewTime);

protected:
    // 网络复制所需的函数
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

    // 当 MatchRemainingTime 在客户端上更新时调用的函数
    UFUNCTION()
    void OnRep_MatchTime();
};

MyGameState.cpp

#include "MyGameState.h"

AMyGameState::AMyGameState()
{
    // 设置初始值
    TeamAScore = 0;
    TeamBScore = 0;
    MatchRemainingTime = 600.0f; // 10分钟
    CurrentGamePhase = EGamePhase::WaitingForPlayers;

    // 设置此 Actor 每帧复制一次,对于频繁变化的数据(如时间)很重要。
    NetUpdateFrequency = 100.0f;
}

void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // 告诉引擎哪些属性需要复制
    DOREPLIFETIME(AMyGameState, TeamAScore);
    DOREPLIFETIME(AMyGameState, TeamBScore);
    DOREPLIFETIME(AMyGameState, MatchRemainingTime);
    DOREPLIFETIME(AMyGameState, CurrentGamePhase);
}

void AMyGameState::AddScoreToTeamA(int32 Amount)
{
    // 确保这个逻辑只在服务器上运行
    if (HasAuthority())
    {
        TeamAScore += Amount;
        // 由于 TeamAScore 是 Replicated,变化会自动同步到所有客户端
        // 你可以在这里添加一些额外的逻辑,比如检查是否获胜
        // CheckWinCondition();
    }
}

void AMyGameState::AddScoreToTeamB(int32 Amount)
{
    if (HasAuthority())
    {
        TeamBScore += Amount;
    }
}

void AMyGameState::SetMatchTime(float NewTime)
{
    if (HasAuthority())
    {
        MatchRemainingTime = NewTime;
    }
}

void AMyGameState::OnRep_MatchTime()
{
    // 这个函数在客户端上执行,当 MatchRemainingTime 从服务器复制更新后
    // 你可以在这里更新客户端的UI,播放时间警告音效等
    // 例如: UpdateHUDTimer();
}

6. 如何在蓝图中使用 GameState

在蓝图中获取 GameState 非常方便:

  1. 使用 Get Game State 节点。
  2. 将其转换为你的自定义 GameState 类(例如 MyGameState)。

之后,你就可以安全地访问所有标记为 BlueprintReadOnly 的变量,如 TeamAScore, MatchRemainingTime 等,并在UI(如UMG)中绑定它们。

示例蓝图流程:

  • 服务器(GameMode 中):BeginPlay 时,获取 GameState 并调用 SetMatchTime
  • 服务器(当玩家夺旗时): 获取 GameState 并调用 AddScoreToTeamA
  • 所有客户端(在UMG Widget中):Construct 事件中,获取 GameState,并将其 TeamAScore 变量绑定到一个文本控件上。当分数从服务器复制过来时,UI会自动更新。

总结

GameState 是 UE 多人游戏的粘合剂,它确保了所有客户端对游戏全局状态的一致性。设计时应遵循以下原则:

  • 由服务器驱动: 所有对 GameState 的修改都必须源自服务器。
  • 只读客户端: 客户端只能读取其 GameState 副本的数据,用于显示和本地逻辑。
  • 职责分离: 不要把属于 PlayerState(个人数据)或 GameMode(游戏规则)的东西放在 GameState 里。

熟练掌握 GameState 将极大地帮助你构建清晰、稳定、可维护的多人游戏体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值