Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

2.5 长周期成长与异步交互型游戏问题模型

这类游戏的核心特征是:玩家行为的影响跨越很长时间维度。一次建筑升级可能要等12小时,一次出征可能要等数小时才返回,资源每分钟自动产出但需要数周积累。玩家不一定实时在线参与每个过程。

典型游戏:SLG(列王的纷争、万国觉醒、王国纪元)、卡牌养成(剑与远征、放置奇兵)、模拟经营(辐射避难所、模拟城市手游)。

与房间制游戏的核心区别

房间制游戏的状态生命周期是对局——开局创建、结束销毁。而长周期游戏的状态生命周期是数月甚至数年,玩家的所有行为(升级建筑、培养英雄、积累资源)都在持续地修改同一个不断增长的状态。

这带来了几个根本性的不同:

  • 数据只增不减:玩家数据随游戏进程不断膨胀。一个活跃一年的玩家,其数据量可能是新玩家的数十倍
  • 时间即资源:很多操作的真实成本是“等待时间“,而非玩家操作。服务器需要在指定时间点触发事件(建筑完成、资源产出、行军到达)
  • 异步交互为主:玩家A攻击玩家B的城池时,B可能不在线。整个攻击过程是服务器代为执行的,B上线后才看到结果

三个核心问题

问题1:长期数据管理——数据只增不减怎么办?

数据膨胀的现实

一个 SLG 玩家运行一年后的数据可能包括:

  • 数百个建筑的状态和升级历史
  • 数千场战斗记录
  • 数万条资源产出/消耗记录
  • 社交关系、联盟历史、邮件

这些数据的访问模式差异很大:

  • 建筑当前状态、英雄阵容——每次登录都要用(热数据)
  • 上周的战斗记录——偶尔查看(温数据)
  • 三个月前的资源消耗明细——几乎不会看(冷数据)

冷热分层策略

热数据(Redis / 内存)         温数据(MySQL/PostgreSQL)     冷数据(对象存储/归档库)
├── 角色当前状态               ├── 近30天战斗记录             ├── 90天前的日志
├── 资源当前数量               ├── 近30天交易记录             ├── 已结束的活动数据
├── 建筑当前等级               ├── 联盟近期动态               ├── 历史排行榜快照
└── 英雄当前属性                                              └── 已过期的邮件

分层的关键决策:

  • 热数据何时过期:通常与玩家在线状态绑定。在线时在内存中,下线后定时刷到数据库,内存中保留短时缓存(如24小时)
  • 温数据何时归档:通常按时间窗口(如30天)或数据量阈值触发
  • 归档数据是否可查:如果需要支持玩家查询历史记录(如“战绩回放“),归档数据需要保留可查接口,但可以接受较慢的响应速度

数据迁移与版本兼容

长周期运营的游戏不可避免地需要修改数据结构(加新英雄类型、改资源种类)。如果数据结构是硬编码的,每次改动都需要写迁移脚本。一种实践是用灵活的存储格式(如 JSON 字段或 protobuf 的 unknown fields 特性)来降低迁移的频率和成本。

问题2:异步战斗系统——玩家不在线时战斗怎么处理?

同步 vs 异步战斗的本质区别

同步战斗(如 MOBA、FPS):双方实时操作,服务器实时仲裁。房间制游戏的做法。

异步战斗(如 SLG 攻城):一方发起攻击后,服务器根据双方当前的属性配置自动计算结果。防守方不需要在线,战斗过程由服务器模拟。

异步战斗的流程

玩家A发起攻击
  │
  ├── 1. 扣除A的出征资源(立即)
  ├── 2. 行军阶段(等待 N 小时,地图上可见行军路线)
  ├── 3. 到达目标,服务器读取双方属性
  ├── 4. 服务器模拟战斗(根据属性和配置,确定性计算)
  ├── 5. 生成战报(战斗过程记录)
  ├── 6. 结算:扣除兵力、转移资源、改变领地归属
  └── 7. 通知双方(A即时推送,B上线时查看)

关键设计决策:

  • 战斗是否可撤回:行军阶段是否允许撤回?通常允许,但已消耗的资源不退
  • 战斗结果是否可重现:战报是只存结果,还是存完整的战斗过程?后者数据量大但可以做回放
  • 并发冲突:如果A和B同时攻击同一个目标怎么办?需要用锁或版本号来保证一致性

异步战斗的架构选择

  • 轻量级(战斗逻辑简单):直接在游戏服务器中同步计算,几毫秒内完成
  • 中量级(需要几秒计算):用消息队列(如 Redis Stream、Kafka)将战斗任务发给独立的计算服务
  • 重量级(复杂模拟,可能需要数十秒):用专门的战斗计算集群,支持批处理

问题3:定时任务系统——“时间即资源“如何管理?

SLG 游戏中大量操作是定时触发的:建筑升级8小时完成、资源每分钟产出、行军3小时到达。这些不是玩家触发的,而是时间触发的

实现方案对比

方案1:时间轮(Timing Wheel)

时间轮是一个环形数组,每个槽代表一个时间间隔(如1秒)。定时任务根据触发时间放入对应槽中。指针每秒前进一格,执行当前槽中的所有任务。

适用于:大量短周期定时任务(如资源每分钟产出)。时间轮的容量有限(槽数 × 间隔 = 最大延迟),超长延迟的任务需要多层时间轮或降级到其他方案。

方案2:延迟队列(Delayed Queue)

使用 Redis 的 Sorted Set(ZSet),以触发时间戳为 score。一个后台进程定期查询 score <= 当前时间 的任务并执行。

适用于:中长周期定时任务(建筑升级数小时、行军数小时)。实现简单,且 Redis 的持久化机制可以防止任务丢失。

方案3:数据库轮询

在数据库中存储任务的触发时间,一个后台进程定期(如每秒)查询“触发时间 <= 当前时间“的任务。

适用于:必须保证任务不丢失的场景(如付费建筑升级)。性能较差(频繁查询数据库),但可靠性最高。

实际产品通常混合使用

  • 时间轮处理高频短周期任务(资源产出、体力恢复)
  • Redis ZSet 处理中周期任务(建筑升级、行军)
  • 数据库保底处理关键任务(付费相关操作)

服务器重启时的任务恢复

内存中的定时任务在服务器重启后会丢失。必须在数据库中持久化所有未完成的定时任务,服务器启动时从数据库加载并重新注册到时间轮/延迟队列中。这个恢复过程的正确性至关重要——漏恢复一个付费建筑升级任务会导致玩家投诉。

问题4:离线产出——玩家不在线时资源怎么算?

很多长周期游戏有“离线收益“机制——玩家下线后,资源仍然按一定速率产出,上线时一次性领取。

计算方式:

  • 简单方式:当前时间 - 上次结算时间 × 每小时产出速率。问题是没有考虑产出上限(仓库满了应该停止产出)
  • 分段计算:考虑仓库上限、产出加速/减速事件(如被攻击导致产出降低)。需要记录关键时间点的状态变化

离线产出的数据一致性要求比异步战斗低——如果算错了一点资源,通常不会导致严重问题。但仍需注意防止负数(产出率被降到负值)和溢出(离线时间过长导致数值溢出)。

小结

  1. 数据只增不减 → 需要冷热分层策略,按访问频率分级存储
  2. 异步交互为主 → 战斗结果由服务器模拟,不需要双方在线
  3. 时间即资源 → 定时任务系统是核心组件,需要兼顾性能和可靠性
  4. 离线产出 → 基于时间差的计算,需考虑产出上限和状态变化

这类游戏的架构复杂度不在实时性,而在数据管理和定时任务的可靠性。

下一节(2.6)将学习:经济与平台型游戏问题模型


参考文献

本章内容为问题模型的分析框架,技术方案的详细实现和行业案例将在后续章节(第13章数据与数据库、第14章缓存与中间件)中展开讨论,届时将提供具体的技术引用。