基于 ET 的双端战斗系统

一款基于 ET 框架实现的双端战斗项目:服务端负责战斗结算,客户端负责表现与输入,通过消息通信保持同步。战斗系统采用配置驱动 + 服务器权威的设计——所有技能、Buff、野怪数值均可配置,命中/结算以服务器为准,客户端只做表现。
- 仓库地址:gitee.com/FengChen512/etskill
- 视频地址:bilibili 演示
总体设计
战斗的核心矛盾是**”公平”与”手感”**:
- 服务器权威:技能释放、命中判定、伤害计算都在服务器执行,防止客户端作弊;
- 客户端表现:客户端根据服务器下发的战斗消息播放动画、特效、音效,追求流畅;
- 消息驱动:双端通过一条战斗消息协议联动,服务器发”事件”,客户端播”表现”。
本文按 战斗系统 / Buff 系统 / 野怪数值 三个子系统,拆解服务端设计,再讲客户端如何配合。
服务端设计
1. 战斗系统
战斗系统围绕技能展开,核心是”技能的生命周期”与”命中结算”。
技能配置
技能不应写死在代码里,而应由配置表驱动(Excel/JSON):
| 配置项 | 说明 |
|---|---|
| 技能 ID / 名称 | 唯一标识 |
| 技能类型 | 主动 / 被动 / 触发 |
| 施放条件 | 蓝耗、冷却、距离 |
| 目标规则 | 单体 / 群体 / 指定范围 |
| 效果 | 伤害、治疗、Buff 引用 |
| 表现资源 | 动画 / 特效 / 音效 |
好处:调整数值、加新技能只改配置,不改代码。
技能生命周期
一次技能施放,在服务器端经历明确的阶段:
1 | 创建技能 → 施放(扣耗蓝/进入CD) → 飞行/延迟 → 命中判定 → 应用效果 → 释放结束 |
每个阶段都由服务器推进,阶段之间可插入事件回调(如”命中后触发 Buff”)。这种”状态机 + 事件”的结构让复杂技能(多段、引导、弹射)可组合实现。
▎真实代码(Hotfix/Server/MMO/Battle/Skill/SkillStatusComponentSystem.cs 精简)
技能施放被建模成显式状态机:New → Init(施放)→ Running(运行)→ Finish(结束),由 SkillStatusComponent 维护当前状态与冷却:
1 | // 施放前校验:单位存在、禁技能、冷却 |
为什么用状态机:多段技能、引导、可打断技能在收到任意中间消息时,能通过 CurSkillStatusType 快速判定”现在合不合法”,杜绝乱序请求导致的错乱。
创建技能
玩家发出技能请求后,服务器校验后创建技能实体(Actor),并分配技能实例 ID。技能实例持有其配置引用与运行状态,是生命周期的主体。
技能释放
释放阶段处理:施放者锁定、消耗校验(蓝量/冷却/距离)、广播”释放动作”。之后根据技能类型进入即时命中或飞行/引导流程。
释放结束与命中
命中判定的关键是服务器计算:目标是否在射程、是否被闪避/格挡、是否命中单位。命中后计算伤害并广播结果,客户端据此播放受击表现。
法球子弹
法球子弹(弹道类技能)是战斗系统的经典难题,本项目实现要点:
- 服务器维护子弹的运动轨迹与碰撞检测(不信任客户端位置);
- 子弹命中目标后触发对应效果并销毁;
- 客户端根据服务器下发的子弹事件做表现(导弹、光球、轨迹)。
战斗消息处理
战斗系统的所有交互都收敛到一套消息协议:
- 客户端 → 服务器:
释放技能请求 - 服务器 → 客户端:
技能释放广播、命中事件、伤害结算、战斗单位死亡
消息处理统一在战斗 Actor 内,保证并发安全与顺序一致。
2. Buff 系统
Buff(增益/减益)是战斗深度的核心。本项目 Buff 系统关注”何时施加、持续多久、每多久跳一次“。
Buff 配置
每个 Buff 由配置定义:
- 类型:增益 / 减益 / 控制(眩晕、减速、定身)
- 数值:攻击加成、移速变化、持续伤害等
- 持续方式:固定时长 / 永久 / 可叠加
- 触发:命中施加、技能触发、层数刷新
Buff 拥有者
Buff 挂在拥有者实体上(单位/角色),由拥有者统一管理其 Buff 列表与生效逻辑。施加 / 移除 / 刷新都走拥有者的统一入口,避免状态错乱。
时间与间隔
Buff 的两个关键维度:
- 总时长:Buff 何时过期移除;
- 生效间隔:多长时间的”一跳”(如每秒结算一次持续伤害/回血)。
定时器任务
间隔结算用定时器实现:Buff 进入时注册定时器,每到间隔执行一次效果,到期移除并触发”Buff 结束”事件。ET 的定时器机制天然适合这类周期任务。
▎真实代码(Hotfix/Server/MMO/Battle/Buff/BuffSystem.cs 精简)
Buff 的两个时间维度——过期(ExiprTime)与间隔(TickTime)——都用 ET 定时器实现;效果统一收敛到 BuffAction:
1 | // 设置过期时间:注册一次性定时器,到时触发 BuffExirpTimer |
过期与间隔分别对应两个定时器 handler:BuffExirpTime_TimerHandler(到时移除)与 BuffTickTimer_TimerHandler(到时跳结算)。所有”效果”都是配置里的 Action 列表——加个新 Buff 不改代码,只改配置。
BuffAction
Buff 的实际效果收敛为 BuffAction(可复用的动作单元):施加属性修改、执行伤害、施加子 Buff、触发特效等。通过”配置选择 Action + 参数”的方式,同一套 Action 可被多个 Buff 复用,扩展性强。
Buff 消息处理
Buff 的施加 / 移除 / 跳结算,都会通过消息同步给客户端:
- 客户端收到”Buff 施加”→ 播放头顶图标 / 状态栏更新;
- 收到”Buff 跳结算”→ 播放飘字 / 特效;
- 收到”Buff 移除”→ 清理表现。
3. 野怪数值
野怪(AI 敌人)是战斗内容的载体,本项目把野怪的”数值与生成”也做成配置驱动。
野怪配置
单个野怪类型定义:生命、攻击、防御、移速、技能 ID 列表、掉落等。数值全部走配置表,方便策划调参。
野怪群配置
将多个野怪按”群”组织(如一片区域的怪点):定义群包含的野怪类型、数量、刷新点、触发范围。战斗时按群加载与刷怪。
生成与死亡
- 生成:按野怪群配置在指定点位实例化野怪(服务器创建实体并同步给客户端);
- 死亡:血量归零后走死亡流程——广播死亡事件、掉落结算、清理实体、记录击杀。
定时器任务
野怪的行为节奏(巡逻、技能冷却、刷新重生)由定时器驱动,避免每帧轮询,降低服务端开销。
客户端设计
客户端不负责结算,只负责把服务器的”事件”变成”表现”。
1. 战斗表型(表现)
- 播放技能动画、特效、受击反馈;
- 法球子弹的飞行表现(轨迹、命中特效);
- 单位死亡表现(死亡动画、消失)。
客户端表现以服务器广播为触发,不做自主判定,保证与服务器结果一致。
2. Buff 表现
- 头顶状态图标、状态栏列表;
- 持续伤害 / 回血的飘字;
- 控制状态的视觉反馈(眩晕旋转、减速拖尾)。
3. 消息处理
- 客户端维护一套战斗消息处理器,把服务器消息映射到表现层;
- 消息按类型分发(技能 / Buff / 伤害 / 死亡),表现层订阅并响应;
- 对高频消息(伤害飘字、Buff 跳结算)做合并与队列化,避免卡顿。
小结
这套双端战斗的核心价值在于**”配置驱动 + 服务器权威 + 消息同步”**:
- 服务端:战斗 / Buff / 野怪三大系统全部配置化,生命周期清晰、定时器驱动、消息收敛;
- 客户端:专注表现,跟随服务器广播;
- 双端:通过一套战斗消息协议联动,兼顾公平与手感。
对于想用 ET 实现战斗的开发者,这套”技能状态机 + Buff Action + 野怪配置”的骨架可以直接复用与扩展。
延伸阅读:基于 ET 框架的 MMO 项目架构笔记