笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 引擎骨架

    • UE 引擎(客户端—服务器交互)
    • 引擎架构与一帧的时序
    • UObject / GC / 反射
    • Gameplay Framework 与端存在性矩阵
  • 复制栈底层

    • NetDriver / Channel / Bunch
    • 属性复制
    • RPC
  • 带宽与规模

    • Relevancy 与带宽预算
    • UE5 Iris 复制系统
  • 局内同步

    • 移动预测与回滚
    • 延迟补偿
    • 网络物理同步
  • 系统与运维

    • GAS 网络模型
    • 专用服务器 · Session · Travel
    • 服务器权威与反作弊

Gameplay Framework 与端存在性矩阵

多人游戏里最容易踩的坑:在客户端访问 GameMode 拿到 nullptr、把分数存 PlayerController 结果别的玩家看不到。根源是——UE 的每个框架类只存在于特定的端。搞清「谁在服务器、谁在所有客户端、谁只在本地」,一半的网络 bug 都能提前避开。


一句话结论

GameMode 只在服务器,PlayerController 只复制给 owner,PlayerState/GameState 复制给所有人——存数据前先问「这个类在哪端存在」。

场景问题

打个比方:一场线上考试。监考老师(GameMode)只在考场服务器那头,规则由他定,考生(客户端)根本见不到他本人;每个考生的答题器(PlayerController)只发到自己手上,你看不到别人的答题器;成绩公示栏(PlayerState)贴在墙上所有人都能看;考场大屏(GameState)显示全场进度也是人人可见;而你戴的耳机(HUD/UI)只有你自己有。存东西前先想清楚「这玩意儿贴哪」——把成绩写进自己的答题器(PlayerController),别人当然看不见;得贴到公示栏(PlayerState)上才行。

面试高频题:「玩家名字、分数应该存在哪个类?」——答 PlayerState。为什么不是 PlayerController?因为 PC 只复制给它自己的客户端,别的玩家拿不到。这类问题的通用解法就是背下端存在性矩阵。

实现方案

1. 核心框架类

类职责
AGameModeBase / AGameMode规则、胜负、玩家进出、生成 Pawn。权威逻辑的家
AGameStateBase / AGameState全局比赛状态(比分、阶段、倒计时),给所有人看
APlayerController一个玩家的「大脑」,处理输入、拥有 Pawn,客户端与服务器的连接代表
APlayerState一个玩家的可公开数据(名字、分数、Ping、队伍)
APawn / ACharacter玩家控制的「身体」,Character 带 CMC 移动组件
AHUD / UUserWidget屏幕 UI,纯表现
UGameInstance跨关卡存活的进程级容器

2. 端存在性矩阵(核心)

类服务器所有客户端仅本地客户端存什么
GameMode✅❌❌规则、权威判定(客户端拿到 nullptr)
GameState✅✅ 复制—全场共享状态:比分、阶段
PlayerController✅(每玩家一个)只有自己那个✅输入、拥有关系;每客户端只看到自己的 PC
PlayerState✅(每玩家一个)✅ 全部复制—玩家公开数据:名字、分数、队伍
Pawn/Character✅✅ 相关的复制✅ 自己的角色实体、位置、动作
HUD/Widget❌(DS 无渲染)—✅纯本地 UI
GameInstance✅✅ 各端一个✅各端独立、不复制、跨关卡

三个最常考的结论

  1. GameMode 只在服务器。客户端 GetGameMode() 返回 nullptr。想让客户端知道的规则/状态,放 GameState。
  2. PlayerController 只复制给拥有它的客户端。每个客户端只能看到「自己的」PC,看不到别人的。所以别人也要看到的数据不能放这。
  3. PlayerState 复制给所有客户端。名字、分数、队伍这类「所有玩家都要看到的玩家数据」放这里。

3. Role:一个 Actor 在不同端的身份

同一个 Actor(比如你的角色)在不同端有不同「身份」,由两个字段描述:LocalRole(在本机的身份)与 RemoteRole(在对端的身份)。

enum ENetRole {
    ROLE_None,             // 无网络
    ROLE_SimulatedProxy,   // 模拟:这是「别人的」角色,靠复制+插值表现
    ROLE_AutonomousProxy,  // 自主:这是「我控制的」角色,可本地预测
    ROLE_Authority,        // 权威:这一份是说了算的(通常在服务器)
};

三种视角看同一个玩家角色:

在谁的机器上LocalRole表现方式
服务器ROLE_Authority权威计算,最终裁定
操控它的客户端ROLE_AutonomousProxy本地预测(立即响应输入),见 移动预测
其他客户端ROLE_SimulatedProxy只收复制来的状态,做插值平滑

判断用法:

if (HasAuthority())          // == GetLocalRole() == ROLE_Authority,服务器权威分支
    Health -= Damage;        // 只有服务器扣血

if (IsLocallyControlled())   // 本地玩家操控的
    ShowAimUI();             // 只给本人显示准星

HasAuthority() 的正确姿势:任何「改变游戏状态」的代码都要包在 HasAuthority() 里,只让服务器执行,靠复制同步到客户端。客户端只做预测和表现,最终以服务器为准。这是 UE 网络的服务器权威原则,详见 服务器权威与反作弊。

一句话记住三种 Role 的差别:同一个角色,三台机器上是三种身份。

Role一句话类比
ROLE_Authority这一份说了算,别人都得听它裁判手里的正式记分册
ROLE_AutonomousProxy我操控的那个,可以先动再对账你自己的记事本:先记下来,回头跟裁判核对
ROLE_SimulatedProxy别人操控的,我只照收来的状态画场边看板:只显示裁判播报的结果,还得补插值让它别一格一格跳

易错点:AutonomousProxy 和 SimulatedProxy 都在客户端、都不是权威,区别只在「是不是我操控的」——是我的才允许本地预测,别人的只能插值。而 Authority 通常在服务器,但 Listen Server 上房主自己的角色是 Authority(不需要预测,因为它就是权威),这也是「Listen Server 上房主手感天然更好」的原因。

4. 所有权(Ownership)链

RPC 和 COND_OwnerOnly 复制都依赖「所有权」:PlayerController 拥有它的 Pawn(Pawn->GetOwner() 链最终到 PC,PC 又对应一个 NetConnection)。判断「这个客户端能不能对这个 Actor 发 Server RPC」,就是看这条 owner 链是否连到它的连接。详见 RPC。

为什么这么做

按「可见范围」分类存储,是为了让复制最省、最安全:

  • 全场共享的(比分)放 GameState,一份复制给所有人。
  • 单个玩家公开的(名字)放 PlayerState,也复制给所有人(别人要看到你名字)。
  • 单个玩家私有的(背包明细、输入)放 PlayerController,只复制给本人,省带宽也防作弊(别人拿不到你的私有数据)。
  • 权威规则放 GameMode 且不下发,客户端根本碰不到,天然防篡改。

为什么别的选择不行

  • 全塞进一个 Actor 复制给所有人:带宽浪费(私有数据也全网广播)+ 安全隐患(作弊者能读到本不该看到的数据)。
  • 把分数存 PlayerController:别的客户端看不到(PC 只复制给 owner),做不了记分板。经典错误。
  • 在客户端访问 GameMode 写规则:拿到 nullptr 崩溃;即使能拿到,客户端权威 = 作弊天堂。规则必须服务器权威。

沉淀结论

存数据前先查端存在性矩阵:GameMode 只在服务器、PlayerController 只给 owner、PlayerState/GameState 给所有人。改状态用 HasAuthority() 锁到服务器,客户端只预测和表现。

记忆口诀

端存在:GameMode 仅服务器 / GameState 全体复制 / PlayerController 只给自己 / PlayerState 全体可见 / HUD 仅本地
存数据:全场状态→GameState / 玩家公开→PlayerState / 玩家私有→PlayerController
Role:Authority 权威(服务器)/ AutonomousProxy 我操控(预测)/ SimulatedProxy 别人(插值)
铁律:改状态包 HasAuthority / 客户端只预测表现 / 服务器最终裁定

内容来源

综合整理。主要参考:Epic 官方文档 Gameplay Framework、Actor Role and RemoteRole、Networking Overview、UE 源码 GameMode.cpp/PlayerController.cpp、社区分析 Cedric Neukirchen《Multiplayer Network Compendium》。

相关专题:Role 三态如何驱动预测与插值见 移动预测;所有权如何约束 RPC 见 RPC;服务器权威原则见 反作弊;局外后台与局内的整体差异见 游戏 vs 互联网后台。

自测:合上资料能说清楚吗?

玩家名字和分数应该存在哪个类?为什么不是 PlayerController?

参考答案

存 PlayerState。因为 PlayerState 复制给所有客户端,别的玩家能看到你的名字分数,才能做记分板。而 PlayerController 只复制给拥有它的那个客户端,别人拿不到——存 PC 里做不了记分板。

客户端调用 GetGameMode() 会返回什么?想让客户端知道规则/状态怎么办?

参考答案

返回 nullptr——GameMode 只存在于服务器。要让客户端知道的全局状态(比分、阶段、倒计时),放 GameState(复制给所有客户端);权威规则本身留在服务器不下发。

ROLE_Authority / AutonomousProxy / SimulatedProxy 三者分别对应什么?

参考答案

同一个角色 Actor 在不同端的身份:服务器上是 ROLE_Authority(权威,说了算);操控它的客户端上是 ROLE_AutonomousProxy(自主,本地预测立即响应输入);其他客户端上是 ROLE_SimulatedProxy(模拟,只收复制状态做插值平滑)。

为什么改游戏状态的代码要包在 HasAuthority() 里?

参考答案

服务器权威原则:只有服务器有权改变游戏状态(扣血、扣钱、判胜负),改完通过复制同步到客户端。客户端只做预测和表现。若客户端也能直接改状态,就是作弊天堂。HasAuthority()(等价 GetLocalRole()==ROLE_Authority)确保这些代码只在服务器执行。

最近更新: 2026/9/10 11:38
Prev
UObject / GC / 反射