加载中...
基于 ET 的双端战斗系统
发表于:2024-03-22 | 分类: 作品展示

基于 ET 的双端战斗系统

基于ET的双端战斗分布式架构

一款基于 ET 框架实现的双端战斗项目:服务端负责战斗结算,客户端负责表现与输入,通过消息通信保持同步。战斗系统采用配置驱动 + 服务器权威的设计——所有技能、Buff、野怪数值均可配置,命中/结算以服务器为准,客户端只做表现。


总体设计

战斗的核心矛盾是**”公平”与”手感”**:

  • 服务器权威:技能释放、命中判定、伤害计算都在服务器执行,防止客户端作弊;
  • 客户端表现:客户端根据服务器下发的战斗消息播放动画、特效、音效,追求流畅;
  • 消息驱动:双端通过一条战斗消息协议联动,服务器发”事件”,客户端播”表现”。

本文按 战斗系统 / Buff 系统 / 野怪数值 三个子系统,拆解服务端设计,再讲客户端如何配合。


服务端设计

1. 战斗系统

战斗系统围绕技能展开,核心是”技能的生命周期”与”命中结算”。

技能配置

技能不应写死在代码里,而应由配置表驱动(Excel/JSON):

配置项 说明
技能 ID / 名称 唯一标识
技能类型 主动 / 被动 / 触发
施放条件 蓝耗、冷却、距离
目标规则 单体 / 群体 / 指定范围
效果 伤害、治疗、Buff 引用
表现资源 动画 / 特效 / 音效

好处:调整数值、加新技能只改配置,不改代码。

技能生命周期

一次技能施放,在服务器端经历明确的阶段:

1
创建技能 → 施放(扣耗蓝/进入CD) → 飞行/延迟 → 命中判定 → 应用效果 → 释放结束

每个阶段都由服务器推进,阶段之间可插入事件回调(如”命中后触发 Buff”)。这种”状态机 + 事件”的结构让复杂技能(多段、引导、弹射)可组合实现。

▎真实代码(Hotfix/Server/MMO/Battle/Skill/SkillStatusComponentSystem.cs 精简)

技能施放被建模成显式状态机:New → Init(施放)→ Running(运行)→ Finish(结束),由 SkillStatusComponent 维护当前状态与冷却:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 施放前校验:单位存在、禁技能、冷却
public static int CanCastSkill(this SkillStatusComponent self, int castConfigId)
{
Unit unit = self.GetParent<Unit>();
if (unit == null) return ErrorCode.ERR_Cast_UnitIsNull;
NumericComponent numericComponent = unit.GetComponent<NumericComponent>();
if (numericComponent[NumericType.ForBidSkill] > 0) return ErrorCode.ERR_Cast_ForBidSkill;
if (self.CoolDowns.TryGetValue(castConfigId, out long tarTime))
if (TimeHelper.ServerNow() <= tarTime) return ErrorCode.ERR_CastCDDowns; // 冷却中
return ErrorCode.ERR_Success;
}

// 开始施放:记录冷却与起始时间,状态 → Init
public static bool StartSkill(this SkillStatusComponent self, Cast cast)
{
if (self.CanCastSkill(cast.ConfigId) != ErrorCode.ERR_Success) return false;
long now = TimeHelper.ServerNow();
self.CurSkillCastId = cast.ConfigId;
self.CurskillstartTime = now;
self.CurSkillStatusType = SkillStatusType.Init;
int coolDown = CastConfigCategory.Instance.Get(cast.ConfigId).CoolDown;
if (coolDown > 0) self.CoolDowns[cast.ConfigId] = now + coolDown; // 进冷却
Log.Console("开始释放技能");
return true;
}

// 运行 / 结束:状态迁移,非法状态则拒绝
public static bool RunningSkill(this SkillStatusComponent self, Cast cast)
{
if (self.CurSkillStatusType != SkillStatusType.Init || self.CurSkillCastInstanceId != cast.InstanceId) return false;
self.CurSkillStatusType = SkillStatusType.Running; return true;
}
public static bool FinishSkill(this SkillStatusComponent self, Cast cast)
{
if (self.CurSkillStatusType != SkillStatusType.Running || self.CurSkillCastInstanceId != cast.InstanceId) return false;
self.CurSkillStatusType = SkillStatusType.Finish; return true;
}
public static bool BreakSkill(this SkillStatusComponent self) { self.ClearSkillInfo(); return true; } // 打断

为什么用状态机:多段技能、引导、可打断技能在收到任意中间消息时,能通过 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 设置过期时间:注册一次性定时器,到时触发 BuffExirpTimer
public static void SetExoireTime(this Buff buff, long expireTime, bool isSendToClient = false)
{
if (expireTime == 0) { buff.ExirpTime = 0; return; }
if (buff.ExirpTime == expireTime) return;
if (buff.ExirpTime != 0) TimerComponent.Instance.Remove(ref buff.ExirpTime);
buff.ExirpTime = TimerComponent.Instance.NewOnceTimer(expireTime,
TimerInvokeType.BuffExirpTimer, buff);
}

// 设置间隔任务:注册循环定时器,每 Tick 执行一次
public static void SetTickTime(this Buff buff, int tickTime)
{
if (tickTime > 0)
{
buff.TickBeginTime = TimeHelper.ServerNow();
buff.TickTime = tickTime;
TimerComponent.Instance.Remove(ref buff.TickTimer);
buff.TickTimer = TimerComponent.Instance.NewRepeatedTimer(tickTime,
TimerInvokeType.BuffTickTimer, buff);
}
}

// 间隔跳结算:执行配置里的 TickActions,并同步给客户端
public static void TickActions(this Buff buff)
{
if (buff == null || buff.IsDisposed) return;
long buffInstanceId = buff.InstanceId;
foreach (var action in buff.Config.TickActions) // 例如持续伤害、回血
{
buff.CreatActions(action, ActionsRunType.BuffTick);
if (buff.InstanceId != buffInstanceId) break; // 执行中 Buff 被销毁则中断
}
// 同步 Buff 移除/刷新到客户端(M2C_BuffRemove / M2C_BuffUpdate)
M2C_BuffRemove msg = new M2C_BuffRemove() { BuffId = buff.Id, UnitId = buff.Owner.Id };
MMoMessageHelper.SendClient(buff.Owner, msg, (NoticeClienType)buff.Config.NoticToClientType);
}

过期与间隔分别对应两个定时器 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 项目架构笔记

上一篇:
基于 ET 框架的 MMO 项目架构笔记
下一篇:
基于 ET 的放置类网络游戏系统设计
本文目录
本文目录