资讯动态

Netcode for Entities ECS 网络同步深度解析:每一帧的权威从何处来

发布时间:2026/9/20 11:38:59 来源:尧图企业网站定制
Netcode for Entities ECS 网络同步深度解析每一帧的权威从何处来【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples本文以一次玩家输入的完整路径为主线解析 Netcode for EntitiesUnity 的 ECS 网络同步方案中 Ghost 组件同步、RPC 命令流程与客户端预测回滚机制为开发者提供从输入上行到渲染呈现的权威链路说明与调参参考。同步全景一次输入的六站旅程多人游戏的网络层要回答一个基本问题玩家按下方向键后屏幕上看到的那一格移动究竟由谁决定、凭什么可信。Netcode for Entities 给出的组织方式是服务器权威 客户端预测客户端先把输入写进本地数据并立即模拟出结果服务器用同一套逻辑重放输入、产出权威状态再以快照形式回传客户端对两者做比对与修正。以 Kickball 示例Dots101/Netcode101为例一个空格踢球动作要经过六个阶段本地输入写入、输入随预测 tick 上行、服务器权威模拟、快照下行、预测比对修正、插值渲染。整条链路复用的是同一批 ECS 系统——PlayerMovementSystem、BallKickingSystem都挂在PredictedSimulationSystemGroup预测模拟系统组下服务器和预测客户端各跑一份这正是同一套逻辑两端重放能成立的原因。值得记住的三个分层GhostInputSystemGroup负责采集输入PredictedSimulationSystemGroup负责可预测的逻辑模拟而带WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)的系统只在服务器世界执行。下文按这条链路逐站展开。本地输入生成把按键写进一个会同步的组件它解决什么问题网络同步最小化的单位不是事件而是数据。如果只上行第 37 帧踢了一次球这类离散事件两端状态稍有偏差就会永久分裂而持续上行结构化输入向量服务器就能靠确定性重放收敛状态。它怎么工作PlayerInputAuthoring.cs定义了输入组件本身——// We only need set this input component once per frame. // Netcode will append the value to the clients input buffer. [GhostComponent(PrefabType GhostPrefabType.AllPredicted)] public struct PlayerInput : IInputComponentData { public float Horizontal; public float Vertical; public InputEvent KickBall; public InputEvent SpawnBall; }关键在两个标注[GhostComponent(PrefabType GhostPrefabType.AllPredicted)]表示该组件属于所有预测端可见的同步数据实现IInputComponentData接口语义上声明这是一个输入组件框架会把它逐帧追加进输入缓冲input buffer而非按普通状态快照发送——这也是注释里每帧只需设置一次的含义。KickBall/SpawnBall是InputEvent位标记适合本帧发生这类脉冲式输入。PlayerInputSystem负责填充[UpdateInGroup(typeof(GhostInputSystemGroup))] public partial struct PlayerInputSystem : ISystem { public void OnUpdate(ref SystemState state) { var moveValue InputSystem.actions.FindAction(Move).ReadValueVector2(); foreach (var input in SystemAPI.QueryRefRWPlayerInput() .WithAllGhostOwnerIsLocal()) { input.ValueRW default; input.ValueRW.Horizontal moveValue.x; input.ValueRW.Vertical moveValue.y; if (Keyboard.current?.spaceKey.wasPressedThisFrame true) input.ValueRW.KickBall.Set(); } } }两个关键点查询用.WithAllGhostOwnerIsLocal()过滤保证只改本地玩家自己的输入不会误改其他客户端下行复制过来的输入副本系统挂在GhostInputSystemGroup确保写入发生在预测模拟之前。每帧先input.ValueRW default再覆盖是为了让松开按键也能被表达为零。如何配置新增一类输入时照此模式定义IInputComponentData结构体并在输入组内填充即可注意输入组件应挂在玩家实体上与逻辑消费方对齐。输入数据已经就位接下来要回答的是另一类上行通信——非周期性的命令比如我要进入游戏它走的是 RPC 通道。上行命令与权威响应用一次 RPC 完成加入流程它解决什么问题周期输入解决持续做什么但请求进入对局请求重生这类一次性事件若也塞进输入缓冲会造成语义混乱与带宽浪费。Netcode 用 RPCRemote Procedure Call远程过程调用承载它们而 RPC 在 ECS 里没有特殊运行时——它就是一组组件。它怎么工作一个 RPC 请求 一个实现IRpcCommand的组件 一个携带它的临时实体。发送端给实体加上SendRpcCommandRequest可带TargetConnection指定目标连接接收端会在自己的世界里看到带ReceiveRpcCommandRequest的对应实体。Kickball 的加入流程客户端侧代码public struct GoInGameRequest : IRpcCommand { } // ... var req ecb.CreateEntity(); ecb.AddComponentGoInGameRequest(req); ecb.AddComponent(req, new SendRpcCommandRequest { TargetConnection entity });GoInGameClientSystem用WithNoneNetworkStreamInGame()找出尚未进入对局的本地连接实体发一条单播给服务器TargetConnection留空则为广播。服务器侧GoInGameServerSystemWorldSystemFilterFlags.ServerSimulation按查询消费请求var ecb new EntityCommandBuffer(Allocator.Temp); foreach (var (requestSource, requestEntity) in SystemAPI.QueryRefROReceiveRpcCommandRequest() .WithAllGoInGameRequest().WithEntityAccess()) { ecb.AddComponentNetworkStreamInGame(requestSource.ValueRO.SourceConnection); var networkId SystemAPI.GetComponentNetworkId( requestSource.ValueRO.SourceConnection); var player ecb.Instantiate(playerPrefab); ecb.SetComponent(player, new GhostOwner { NetworkId networkId.Value }); ecb.AppendToBuffer(requestSource.ValueRO.SourceConnection, new LinkedEntityGroup { Value player }); ecb.DestroyEntity(requestEntity); }四个动作各司其职NetworkStreamInGame标记连接状态机进入对局GhostOwner把玩家实体与该连接绑定是后续这是谁的化身的判断依据LinkedEntityGroup缓冲记录关联实体断线时自动销毁防止僵尸实体泄漏最后的DestroyEntity消费请求实体——RPC 实体不销毁就会残留并被重复处理。如何配置请求型命令一律做成空IRpcCommand 组件数据实体定向发送时填TargetConnection广播时置零。这条连接 → 玩家实体的绑定关系正是下一站快照下行时的分发依据。Ghost 组件与快照下行服务器状态如何到达每个客户端它解决什么问题上行通道解决服务器知道客户端做了什么下行通道要解决客户端知道服务器判定之后世界是什么样。Netcode 的下行单位是 Ghost 实体快照服务器按组件标记决定哪些数据参与序列化客户端据此在本地长出可渲染、可预测的实体。它怎么工作以 Kickball 的玩家为例PlayerInputAuthoring.cs与PlayerAuthoring.cs展示了两类标记的分工。整组件同步用[GhostComponent(PrefabType GhostPrefabType.AllPredicted)]如PlayerInput组件内字段级同步用[GhostField]如// this attribute means the value will be passed to _BaseColor of the shader [MaterialProperty(_BaseColor)] public struct Color : IComponentData { [GhostField] public float4 Value; }[GhostField]让Color的Value单独参与快照且[MaterialProperty]直接把它接到渲染材质的_BaseColor——同步数据与渲染数据在这里合流。PrefabType决定同步范围AllPredicted让所有具备预测能力的端本地玩家 其他客户端都拿到该数据服务器权威数据则只从下行方向出现。快照应用时的两个关键行为来自服务器侧代码玩家由服务器实例化而非客户端客户端收到后按GhostOwner与本地连接对齐本地预测身份每帧写入输入缓冲的PlayerInput副本会随快照下发让其他客户端也能重放你的移动。消费预测逻辑的系统如PlayerMovementSystem同样运行在服务器上保证下行状态与上行输入的因果一致[UpdateInGroup(typeof(PredictedSimulationSystemGroup))] public partial struct PlayerMovementSystem : ISystem { [BurstCompile] public void OnUpdate(ref SystemState state) { var speed SystemAPI.Time.DeltaTime * playerConfig.Speed; foreach (var (playerTransform, input) in SystemAPI.QueryRefRWLocalTransform, RefROPlayerInput() .WithAllPlayer, Simulate()) { var move new float3(input.ValueRO.Horizontal, 0, input.ValueRO.Vertical) * speed; playerTransform.ValueRW.Position move; } } }注意它读的是输入组件而非位置目标两端用同一输入推出同一位移这是预测端与服务器状态能对得上的前提。如何配置需要预测参与的数据选AllPredicted整组件标记纯表现字段颜色、装饰用[GhostField]控制粒度断开连接清理一律交给LinkedEntityGroup而非手写销毁逻辑。数据下行后客户端面临最后一公里快照总比预测慢一拍如何让画面既即时又不跳变。预测与插值切换半径、迟滞边界与平滑过渡它解决什么问题预测能让本地操作零延迟呈现但每个远处的球都预测一遍会白烧 CPU且远端对象预测错误时的回滚跳变也会被玩家看到。PredictionSwitching示例NetcodeSamples/Assets/Samples/PredictionSwitching给出答案按距离在预测与插值两种模式间切换并用迟滞边界防止边界抖动。它怎么工作配置组件集中了全部可调参数public struct PredictionSwitchingSettings : IComponentData { public Entity Player; public float PlayerSpeed; public float TransitionDurationSeconds; public float PredictionSwitchingRadius; /// The margin must be large enough that moving from predicted time /// to interpolated time does not move the ghost back into the prediction sphere. public float PredictionSwitchingMargin; }PredictionSwitchingSystem用两个并行 Job 分别处理切进预测与切出预测两者使用不同半径构成迟滞环// 切进预测距离小于 PredictionSwitchingRadius if (math.distancesq(playerPos, transform.Position) enterRadiusSq) predictedQueue.Enqueue(new ConvertPredictionEntry { TargetEntity ent, TransitionDurationSeconds transitionDurationSeconds, }); // 切出预测距离大于 Radius MarginSwitchToInterpolatedGhostViaRange if (math.distancesq(playerPos, transform.Position) exitRadiusSq) interpolatedQueue.Enqueue(new ConvertPredictionEntry { /* ... */ });实体经GhostsPredictionSwitchingQueues的ConvertToPredictedQueue/ConvertToInterpolatedQueue进入框架的预测切换管线由TransitionDurationSeconds控制新旧模式间的时间平滑SwitchPredictionSmoothing组件标记正在过渡的实体避免被两个 Job 重复处理。如何配置半径按玩家必须能即时影响的对象范围设定——Kickball 的KickingRangeSQ踢球距离平方就是天然下限参考TransitionDurationSeconds决定切换平滑度过大则边界穿越后画面仍按旧模式呈现更久Margin不能小于一次过渡期间实体可移动的距离注释明确要求覆盖预测时间到插值时间的位移实践建议取PlayerSpeed × TransitionDurationSeconds的 1.2~1.5 倍否则实体在边界附近会反复切换。参数调好后真正考验的是长期运行时的资源与流量纪律。调参权衡与调试实践带宽、帧开销与连接泄漏它解决什么问题预测、插值、RPC 各自正确不等于整体健康——边界抖动、RPC 实体残留、输入缓冲膨胀都会在上线后以偶发卡顿的形式出现。怎么排查与权衡。三个高频问题对应三处检查点边界抖动表现为远处物体突然开始参与预测、CPU 锯齿式波动。查SwitchToPredictedGhostViaRange/SwitchToInterpolatedGhostViaRange两个 Job 的入队频率可临时把ballColorChangingEnabled打开用颜色变化目视切换事件再回查PredictionSwitchingMargin是否不足。RPC 残留服务器若忘记DestroyEntity请求实体查询会重复命中并重复执行副作用重复生成玩家。对照GoInGameServerSystem的收尾动作即可确认消费闭环。输入与逻辑时序错位输入必须在预测模拟之前写入若自行拆分系统组用 ECS 系统检查器核对GhostInputSystemGroup相对PredictedSimulationSystemGroup的顺序避免本帧输入下一帧才生效的漂移。主要参数的调参权衡如下参数调大的效果调小的代价实践建议PredictionSwitchingRadius可预测对象变多远端操作更跟手预测端 CPU 与回滚成本上升取玩家最大作用距离如踢球范围上浮 20% 左右PredictionSwitchingMargin边界切换更稳定过小则抖动过大则对象过早失去预测覆盖一次过渡期内实体位移见上节公式TransitionDurationSeconds切换更平滑状态切换感知延迟增大0.1~0.5 秒区间试错与帧时匹配调试工具侧Netcode 的统计窗口可观察连接级流量与 tick 状态对下行量异常的排查路径是先数 Ghost 标记字段数量再看PrefabType是否过宽本应仅服务器的数据被标成AllPredicted最后检查是否有高频变化的[GhostField]字段。以上阈值为实践建议需结合 Profiler 实测。从输入到渲染的链路至此闭合每一站的确定性都建立在同一逻辑、两端重放之上。总结本文沿一次输入的路径拆解了 Netcode for Entities 的同步模型IInputComponentData输入缓冲与GhostComponent标记构成上行与下行数据面RPC 以纯组件形式承载一次性命令GhostOwner/LinkedEntityGroup维护连接与实体的生命周期预测-插值迟滞切换则控制 CPU 与画面稳定性的平衡。该方案适合对公平性与一致性要求高的场景如竞技类实时对战与大型多人在线游戏在需要强反作弊的设计下应坚持关键判定仅由ServerSimulation系统执行。【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价