主题
射击游戏:服务端权威与瞄准同步
适用于顶视角射击、TPS、FPS,以及上下半身独立朝向的合作或对抗游戏。相机视角和模型动画可以不同,但共同使用以下链路:
text
客户端输入 -> 服务端校验与模拟 -> 权威状态下行
| |
+-> 本机即时瞄准/预测表现 +-> 远端角色平滑表现本篇定义通用同步配方。gamegraph_top_down_pve_template 已使用输入消息、服务端模拟、上下身子节点复制和视觉平滑实现顶视角合作,完成 Windows 编辑器普通单人及双人、四人调试,覆盖晚加入、独立控制、远端瞄准、共享战斗、个人重生和全队重开。本样例的持续瞄准输入使用 30 Hz、0.5 度变化阈值及 100 ms 静默刷新间隔;这些是该样例的起点,并非所有射击游戏的通用最佳值。FPS/TPS 的移动预测、历史快照插值、延迟补偿,以及网络断线、丢包和抖动仍需按项目实现与验收。
1. 分清输入和状态的方向
| 内容 | 方向 | 实现选择 |
|---|---|---|
| 移动意图、瞄准方向、开火/换弹请求 | 客户端 -> 服务端 | 跨端消息;高频输入优先紧凑二进制 |
| 权威位置、下半身朝向、上半身瞄准 | 服务端 -> 客户端 | replicated Node transform,或项目姿态快照 |
| 血量、弹药、死亡、装备状态 | 服务端 -> 客户端 | PropertyHostComponent;已有 Unit 项目用其属性体系 |
| 后坐力、步态、枪口闪光、音效 | 客户端本地 | 由输入预测或服务端确认事件驱动 |
PropertyHostComponent 和 Node.SetSyncVar(...) 的同步写入由服务端负责,不能作为客户端输入上行接口。客户端决定“想朝哪里瞄准”;服务端决定这一输入是否可接受,以及是否开枪、命中和扣血。服务器权威不要求本机瞄准等待回包。
保留项目已有路线:Unit/Ability 项目复用其移动、伤害和表现桥接;直接 GameGraph 项目可以继续使用 Node 和项目战斗逻辑。不要仅因有角色、血量或联机需求就重建项目架构。
2. 输入上行:表达意图,关联具体的一枪
现有通信入口见二进制消息、强类型消息和双端事件。代码项目也可使用 IProtocolClientTransient<T> 定义 unmanaged 输入协议,由 SendToServer() 发送,服务端在 Handle(Player player) 中取得实际发送者。消息接口本身没有提供完整的射击输入缓存、预测或确认协议。
以下字段是项目协议建议,不是 SDK 已定义的消息类型:
| 字段 | 用途 |
|---|---|
| LifeId / RoundId | 服务端分配的重生或回合代次,拒绝上一生命的滞留输入 |
| InputSequence | 对本次生命内的输入排序、去重,支持确认与预测回放 |
| MoveAxes | 移动意图;服务端限制长度并用自身时间步模拟 |
| AimYaw / AimPitch | 约定坐标系中的瞄准角;顶视角通常只需 yaw |
| Buttons / ShotSequence | 持续按键状态和需要单独确认、去重的开火事件 |
| SampleTime | 可选时间样本;需时钟映射和边界限制,不能直接信任客户端时间 |
在服务端按真实发送者找到其可控制角色,校验代次、序号、有限数值、pitch 范围、消息频率、存活状态和武器规则。客户端提交的位置、枪口、伤害或命中对象不能直接成为权威结果。自由鼠标瞄准通常允许快速甩枪;转向速度限制只在玩法明确要求时启用。
协议显式约定角度单位、零方向和正方向。新项目可以使用弧度,yaw 为 0 指向世界 +X,正 yaw 朝 +Y,正 pitch 朝 +Z;对应单位方向为 (cos(pitch)*cos(yaw), cos(pitch)*sin(yaw), sin(pitch))。已有项目可保留自己的约定,但在消息边界统一转换;模型前向和相机局部轴不能直接混入协议。
持续输入可以采样最新状态,不必对每次鼠标事件发送消息。开火、停火和换弹等边沿要有明确的送达/确认策略;不要因为瞄准角差未达阈值而漏发按钮变化。输入停止或失焦后发送中立状态,服务端也设置可调输入失效时间,避免断线后持续移动或开火。序号采用回绕安全比较,重生后重建其作用域。
具体的一枪需要关联其瞄准方向。 单次开火请求携带该枪的 aim 和 ShotSequence;按住连射时可以提交 fire-held,由服务端根据弹药、射速和最新有效 aim 生成各枪。释放 fire-held 不得被角度限流吞掉。服务端返回确认的 shot id、实际发射方向和结果,客户端据此合并预测的子弹/闪光,避免重复播放。
不要假定独立的移动、aim、开火消息总按理想顺序到达。输入流的序号和开火去重各自定义清楚;较新的移动状态不能无条件吞掉仍在允许时间窗口内、尚未处理的一枪。已有 SendToServer() 也不能被解释为自动提供“可丢弃旧输入”的网络通道。
发送返回值不等于服务端处理确认。涉及输入回放、换弹结果或重试去重时,仍需要协议中的 ack/确认事件。
3. 下行:控制节点与玩法属性分别承载
对于直接 GameGraph 的分离身体角色,优先考虑少量 replicated 控制节点:
text
EntityRoot (replicated,位置,Rotation 保持单位旋转)
+-- LowerPose (replicated,下半身朝向)
| +-- 下半身模型、步态关节 (client local)
+-- AimPose (replicated,上半身瞄准姿态)
+-- 上半身模型、武器、后坐力 (client local)Root 无旋转时,两条姿态分支可直接表达约定的世界方向;如果 Root 本身有旋转,则子节点需由世界目标姿态换算成父坐标系下的局部姿态。下半身停下时保留最后方向,不能每帧从零速度重算朝向。服务器逻辑姿态使用游戏坐标系;模型资源的 Y-up/Z-up、尺寸和前向修正放在客户端 local 模型层,避免把模型导入偏置误当瞄准状态。
服务端创建子节点时显式传 NodeReplication.Replicated,因为 Node.CreateChild(...) 默认是 local。服务端不需要加载角色模型或逐骨骼模拟,客户端根据控制姿态、移动状态与确认事件生成动画。枪口的权威偏移/姿态应由项目武器配置和服务端逻辑计算,不能依赖客户端模型骨骼查询。
replicated Node 的旋转已通过引擎复制并由客户端 SmoothedTransform 平滑到最新目标。属性组件支持把血量、弹药等挂到节点,并按属性指定同步范围;属性组件和节点是两条独立到达的同步流,读取须使用 TryGetPropertyHost / OnPropertyHostReady。详见节点复制和属性组件。
另一种合理路线是用属性或小型 SyncVar 保存语义上的 AimYaw/AimPitch,在客户端驱动骨骼瞄准、IK 或上半身节点。这些值不会自动获得 Node 的旋转平滑,需要项目按最短角度/四元数插值。需要时间戳、成组姿态或精确输入确认时,可使用专门的姿态消息;不要把其时间语义假定为普通属性同步已经提供。
同一个姿态只选一个权威来源。不要同时同步 AimPose.Rotation 和 AimYaw,再让两套数据竞争控制上半身。用于日志的镜像值可以存在,但应明确哪一项控制表现。多个 Node、属性和确认消息也不保证组成原子快照;需要位置、朝向和输入确认对应同一模拟 tick 时,应发送包含这些字段的成组快照。
独立发布图由服务端 Publish(player) 给两位玩家;已有 SCE 场景通过自身 sync root 同步,不对内置图调用 Publish。已有 Unit 项目可使用 UnitNodeBinding 等桥接,继承该 Unit 的可见性。静态场地/渲染资源尽量客户端 local,服务端持有对应的碰撞数据;只复制有运行时变化的状态。
4. 本机预测与远端平滑
本机相机和瞄准表现每帧立即更新;权威节点作为服务端状态来源,预测的本地表现通过独立节点或项目控制器渲染。不要每帧覆盖收到的 replicated transform,与其内置平滑争抢同一节点。也不要叠加两层未经设计的平滑,使操作额外迟滞。
移动预测需要明确的闭环:
- 本机按输入推进预测位置,缓存输入序号和模拟步长。
- 服务端使用相同移动规则,在自己的模拟时间步处理输入。
- 服务端返回权威状态、server tick 和已处理输入序号。
- 本机丢弃已确认输入,从该权威状态回放未确认输入,对显示偏差做纠正。
仅收到 Node 的平滑位置不等于已经具备这个闭环。若实现输入回放,确认快照需包含对应的权威位置/姿态,不能把独立到达的 ack 序号与当前平滑显示位置拼接成权威快照。刚体预测也不默认具有跨端确定性;小型合作游戏可先验证服务端模拟与本机即时瞄准,再按实际手感加入移动预测。
远端玩家可先使用现有 Node 平滑。它是向最新目标收敛,不是带 server tick 的历史快照插值缓冲,不自动提供外推、回滚或命中回溯。死亡、重生和传送需要明确的姿态重置策略,并重建输入/预测缓存;短距离重生也不能只依赖位置超过 snap 阈值。
5. 将角度阈值、发布频率和表现平滑分开
| 参数 | 作用 | 当前实现方式 |
|---|---|---|
| 输入发送角差 | 减少上行微小鼠标变化 | 项目发送策略 |
| 输入最短发送间隔、最大静默间隔 | 控制频率和状态新鲜度 | 项目发送策略 |
| 权威姿态发布角差/频率 | 减少下行姿态变化 | 项目发布姿态代理,或后续复制 API |
| 客户端收敛速度 | 远端 Node 追随网络目标的速度 | TransformSmoothingConstant |
| 客户端位置贴合距离 | 大幅位移时直接贴合 | TransformSnapThreshold,单位为世界距离 |
| 玩法最大转向速度 | 限制角色或炮塔实际转动 | 服务端玩法规则,与网络阈值独立 |
当前节点旋转通过压缩四元数变化检测复制,没有公开的“超过 N 度才同步”配置。压缩精度也不等于业务角度容忍度。ConfigureTransformSmoothing(...) 是客户端图级设置,不能用作发送频率或旋转角差设置。
角差必须相对上次实际发送值计算,并处理圆周边界。例如 yaw 由 179 度转到 -179 度时,角差是 2 度。四元数采用归一化后的最短旋转角,q 与 -q 表达同一个旋转。发布成功前不要更新 lastSent,以免发送失败后错误抑制重试。
一种项目策略是:在发送机会到来时,角差超过阈值或达到最大静默间隔则发送;按钮变化、开火和生命代次变化独立处理。顶视角原型可从 0.5 度、持续变化时 20-30 Hz、最大静默间隔 100 ms 开始测量。这不是 SDK 默认值或已验证推荐值,输入失效时间应留出合理的网络抖动余量。
角度误差在远处会放大:横向偏移约为 距离 * tan(角差),100 米处 0.5 度约为 0.87 米。远距离精密射击应缩小输入角差,且开火请求携带该枪的完整 aim;远端观看姿态可以采用更宽松阈值。时间延迟带来的误差仍需另外处理。
若第一版在项目里限流下行,用独立发布姿态承载低频显示目标,服务端逻辑始终保存完整精度的 aim。初始化、重生、停转及新订阅时确保目标状态就绪,避免阈值永久留下旧方向。不要降低权威旋转精度来实现省流。移动/物理需要的根节点也不能随意限流写入而改变实际模拟。
星火当前使用自己的传输链路承载引擎复制。不能假定开放 Urho Network、Controls 或 NetworkPriority,就能自动获得可配置的输入通道、快照丢弃策略、预测与延迟补偿。新增传输策略需沿实际客户端/Host 路径验证。
6. 顶视角、TPS 和 FPS 的不同点
| 视角 | 瞄准意图 | 本机表现 | 服务端射击判定关注点 |
|---|---|---|---|
| 顶视角 | 鼠标射线与游戏瞄准平面得到方向,通常 yaw | 上下身分离,固定或跟随相机 | 枪口偏移、子弹连续扫掠、停转与绕角 |
| TPS | 相机准星射线,yaw + pitch | 相机、身体/上身分离、肩位和遮挡 | 准星与枪口视差、近身掩体、可信的视点规则 |
| FPS | 视线 yaw + pitch | 本机相机与武器立即响应,远端身体单独驱动 | 视点高度、枪口规则、远距离精度、射线延迟补偿 |
TPS 的相机准星先选出期望瞄准点,再从枪口向该点确定发射方向。服务端按照自己的角色状态、武器偏移和视点规则重建或校验意图,并检查枪口前的遮挡。客户端相机绕过墙看到目标,不代表枪口也能穿墙射击。视点/目标点可以作为请求数据,但不能成为免校验的命中结果;准星应能反馈枪口路径被挡。
FPS 也要明确视线命中与枪口弹道的武器规则,而不是假定两者重合。自由观察、死亡观战或 TPS 的相机 yaw 不一定是武器 aim;协议按玩法区分 view、aim 和 body,不能把相机旋转直接当作所有身体姿态。
若射线命中需要延迟补偿,服务端保留带时间的 hitbox 历史,在限制窗口内映射发射时间并执行历史查询,规定移动掩体、死亡/重生边界和超时输入的处理。它是需要新增实现与验收的机制。飞行子弹先采用服务端当前位置连续扫掠,不自动套用射线武器的历史回溯策略。
7. 后续 API 收口与验收
以下是设计候选项,均不能写成现有能力:
- 输入采样/发送策略:角差、频率、keepalive、按钮边沿、代次与序号,尽量在托管层复用。
- 节点复制策略:位置/角度阈值、最大发布间隔,独立于权威 transform,并保证首次订阅和重生快照。
- 姿态消费接口:最新网络目标与显示值的区分、按对象平滑/立即重置,支持本机预测和远端观看采用不同策略。
- 预测确认快照:权威状态、server tick、ack 序号、同一步长回放;不承诺所有物理系统都能确定性回放。
- FPS 的 hitbox 历史与时间映射,仅在武器需求明确后实现。
优先用双人合作样例验证现有机制,再由真实重复代码决定 helper/API 的边界。普通编辑器调试需贯通服务端编译/上传、服务端开局和两客户端连接;纯客户端只能验证表现。最终验收至少覆盖:
- 两玩家横移、背向移动仍可独立瞄准,停下后姿态不归零。
- 持续小幅转向、跨 +/-180 度、快速甩枪、开火时方向一致;无重复子弹/音效。
- 延迟、抖动、丢包或重发条件下按钮释放有效,输入过期后停止控制;服务端射速/弹药/伤害保持权威。
- 本机输入确认与纠偏,远端平滑,重生/回合重开拒绝旧请求;后加入取得当前完整状态。
- TPS 相机与枪口视差、近身墙角遮挡;FPS 远距离精度和采用的命中时间策略。
记录实际发送率、消息大小、输入年龄、朝向误差、纠偏幅度和双方日志,用结果选择参数,不能只凭单客户端截图认定同步正确。