虚幻引擎 GameState 介绍

📌 一、核心概念:GameState 是干什么的?

GameState 是 UE 网络框架中存储和同步游戏全局状态的核心类。它存在于服务器和所有客户端,服务器上的 GameState 拥有权威数据,会自动将标记为复制的属性同步到所有客户端。可以把它理解为一块“公共公告板”或“数据中心”——所有玩家都必须知道的全局信息(当前比分、剩余时间、游戏阶段等)都放在这里

官方文档明确指出:

Game State 负责启用客户端监控游戏状态。从概念上而言,Game State 应该管理所有已连接客户端已知的信息(特定于 Game Mode 但不特定于任何个体玩家)

这意味着:它不问“谁”做的,只问“发生了什么”。玩家的私人信息(血量、弹药、击杀数)请交给 PlayerState;客户端只读副本,安全又高效。


🔁 二、生命周期与复制:它活多久,怎么传?

⏳ 生命周期

生命周期阶段 说明
创建 加载关卡时,GameMode 自动创建 GameState。创建后,GameState 便存在并开始工作
存在 游戏会话期间一直存在,不因玩家加入/退出而销毁
销毁 关卡卸载或游戏结束时销毁
特点 说明
服务器端 拥有数据的 “写”权限,所有修改必须由服务器执行
客户端 仅拥有 “只读副本”,不能直接修改,但可随时获取最新数据
复制方向 服务器 → 所有客户端,保障所有玩家看到一致的游戏大局

🌐 网络同步(Replication)

操作 说明
标记复制 用 UPROPERTY(Replicated) 标记属性,UE 自动同步
同步原理 在服务器上修改 → 引擎检测变化 → 自动推送给所有客户端
GetLifetimeReplicatedProps C++ 中需实现此函数注册复制属性

⚠️ 核心红线:客户端绝对不能直接修改 GameState!
任何全局状态变更都必须通过 RPC 通知服务器,由服务器修改 GameState 上的权威数据。客户端修改会被覆盖,还会引发严重不一致


🔍 三、与 GameMode、PlayerState 的关键区别

权威者 存在位置 主要职责
GameState 服务器 服务器 + 所有客户端 存储和同步全局游戏状态(剩余时间、比分、游戏阶段、玩家列表)
PlayerState 服务器 服务器 + 所有客户端 存储单个玩家的公开状态(姓名、得分、K/D、队伍)
GameMode 服务器 仅服务器 定义游戏规则、处理流程、生成玩家/Pawn、判定胜负
PlayerController 各自客户端 服务器 + 所属客户端 处理玩家输入、管理 HUD/Camera、代表玩家与服务器通信
Pawn / Character 各自客户端 服务器 + 所有客户端 玩家在游戏世界中的物理实体(位置、移动、动画同步)

📝 一个精妙的比喻(来自开发者社区):

  • GameMode → 裁判长(只在后台制定规则,观众看不见)

  • GameState → 记分牌 + 大屏幕(向所有观众显示比分、剩余时间)

  • PlayerState → 记分牌上某个球员的个人数据

  • PlayerController → 球员的大脑(决定何时跑动、射门)

  • Pawn → 球员的身体(在场上执行动作)


🧩 四、GameState 详解与最佳实践

📂 应该放在 GameState 里的数据

数据类型 示例
游戏计时 剩余时间、已进行时间
团队分数 TeamA_Score、TeamB_Score
游戏阶段 WaitingToStart、InProgress、PostGame
获胜结果 获胜的团队/玩家
全局世界状态 大逃杀安全区位置与缩小时间、占领进度
玩家列表 TArray<APlayerState*> PlayerArray(GameState 已内置)

❌ 不应该放在 GameState 里的数据

  • 单个玩家的生命值/弹药/装备 → 放在 PlayerState

  • 与玩家实体相关的动态数据(位置、动画) → 放在 Pawn/Character

  • 输入处理和 UI 相关 → 放在 PlayerController

  • 跨关卡持久数据 → 放在 GameInstance

  • 非游戏会话范围的全局配置 → 使用 GameInstanceSubsystem

🧪 C++ 代码示例:夺旗游戏 GameState

以下示例展示如何创建一个处理团队分数的 GameState,并正确配置网络复制:

cpp
// 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 = "Score")
    int32 TeamAScore;

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

    // 当前游戏阶段(如 WaitingToStart、InProgress、GameOver)
    UPROPERTY(Replicated, BlueprintReadOnly, Category = "GameState")
    FName CurrentGamePhase;

    // 服务器端修改分数的函数(蓝图可调用)
    UFUNCTION(BlueprintCallable, Category = "Score")
    void AddScoreToTeamA(int32 Points);

    UFUNCTION(BlueprintCallable, Category = "Score")
    void AddScoreToTeamB(int32 Points);

protected:
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
cpp
// MyGameState.cpp
#include "MyGameState.h"
#include "Net/UnrealNetwork.h"

AMyGameState::AMyGameState()
{
    TeamAScore = 0;
    TeamBScore = 0;
    CurrentGamePhase = TEXT("WaitingToStart");
}

void AMyGameState::AddScoreToTeamA(int32 Points)
{
    // 关键:仅在服务器上修改复制属性!
    if (!HasAuthority())
    {
        return;
    }
    
    TeamAScore += Points;
    
    // 分数变化后,检查胜利条件(示例)
    if (TeamAScore >= 5)
    {
        CurrentGamePhase = TEXT("GameOver");
        // 可选择在此处广播游戏结束事件
    }
}

void AMyGameState::AddScoreToTeamB(int32 Points)
{
    if (!HasAuthority())
    {
        return;
    }
    
    TeamBScore += Points;
    
    if (TeamBScore >= 5)
    {
        CurrentGamePhase = TEXT("GameOver");
    }
}

void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    // 注册需要复制的属性
    DOREPLIFETIME(AMyGameState, TeamAScore);
    DOREPLIFETIME(AMyGameState, TeamBScore);
    DOREPLIFETIME(AMyGameState, CurrentGamePhase);
}

⚠️ 关键要点

  1. HasAuthority() 检查确保只有服务器执行修改

  2. DOREPLIFETIME 注册复制属性,UE 自动同步

  3. BlueprintReadOnly(而非 BlueprintReadWrite)防止客户端误修改

🎨 蓝图中使用 GameState

蓝图中可通过“Get Game State”节点访问当前 GameState 及其公开变量,但同样不能修改标记为 Replicated 的属性(修改无效且会被覆盖)。


🚀 五、进阶主题与常见问题

🔌 UE5 复制扩展

  • GameStateSubsystem:为 GameState 添加可复制的子系统,适合状态管理需求复杂的大型项目

  • Replication Graph:高并发优化插件(如《堡垒之夜》单场 100 名玩家 + 50,000 个复制 Actor),需对网络底层有深入了解

🐛 常见误区与 FAQ

误区 正确做法
“在客户端直接修改 GameState 来更新 UI” 客户端修改无效,应通过 RPC 通知服务器更新
“把玩家血量放在 GameState” 血量是玩家个人数据,应放在 PlayerState
“把跨关卡数据放在 GameState” 切换关卡后 GameState 被销毁,数据丢失,应放在 GameInstance
“在 GameState 里放很重的逻辑” GameState 是数据容器,复杂逻辑应放在 GameMode 或独立 Subsystem
“分不清 GameMode、GameState、PlayerState” 记住:GameMode = 规则(仅服务器);GameState = 全局状态PlayerState = 玩家数据

💎 总结:GameState 使用原则

  1. 只存全局信息:GameState 只存放所有玩家都需要知道的游戏层面状态

  2. 服务器是唯一写入者:只有服务器能修改游戏状态,客户端只读

  3. 数据驱动,逻辑分离:GameState 负责“数据”,GameMode 负责“规则”

  4. 善用内置结构:GameState 已提供 PlayerArray(玩家列表),可直接复用

  5. 理解生命周期:GameState 随关卡存在,切换关卡即销毁

感谢您的来访,获取更多精彩文章请收藏本站。

THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容