妖尾历经几年开发,终于在今年6月底顺利上线,至今运营两个多月,笔者从2017年初参与开发,主要负责妖尾战斗系统开发,一路解决了一些技术问题,踩了一些坑,感觉有不少点是值得记录和分享的,希望能借几篇文字,系统性总结MMORPG战斗系统的开发经验。
本文主要介绍战斗录像系统,战斗录像基本是所有MMORPG游戏的标配系统,它同时也能成为开发调试利器,在整个开发阶段扮演重要角色。

首先是调试利器

一些项目组开发战斗系统时,可能会优先开发涉及表现的相关功能,迭代的新增战斗表现,修复Bug,直到整个战斗表现看起来相当完整了,到了后期再应策划要求,补充战斗录像系统。笔者项目是在开发中期加入战斗录像功能的,在经历完整个战斗开发阶段后,得出的经验是尽可能在基础框架搭建、前后台开始联调阶段就同步开发战斗录像系统,利用战斗录像来辅助系统开发、调试。到了项目中后期,战斗录像会发挥更大的用处,此时战斗系统已经提交到SVN版本控制,项目组所有人都可以体验到战斗系统,所有人都或多或少地扮演测试人员的角色,项目群会频繁地反馈战斗系统的表现问题,诸如报错、卡死,单位诈尸等等,什么反馈都会有,总之发挥你的想象力。当时开发会频繁地奔波于各个项目组成员的电脑面前,沟通、查看日志,尝试弄清问题,有了战斗录像后,我们会让对方给录像文件,在本地环境重放战斗录像,重现现场慢慢定位问题。
战斗录像能多大程度的辅助开发调试,取决于相关工具链有多完善,下面介绍妖尾项目对于工具链的打造。首先简单看下战斗录像框架:


一般来说,网络底层往上还会有一层业务网络层,妖尾的业务网络层分成两个,一个负责普通业务逻辑,一个网络层供战斗专用。通过在战斗网络层接口插桩,战斗录像模块就能收集一场战斗的所有数据。战斗结束后,自动将该场战斗数据保存成本地录像文件,当然,我们还要提供手动保存录像接口,以便战斗中途卡死了也能保存录像。虽然有了战斗数据,还要配备一套完整功能的GUI工具才能提高调试效率,因此笔者基于Unity开发了战斗录像播放器工具。

上图是战斗录像播放器经过几次迭代后的截图,除了实现最基本的播放录像、查看数据功能,还有查看设备数据,上传/下载录像、生成战斗播报、差量构建指定回合战场包等功能。笔者觉得在开发初期,先实现播放录像、查看数据的功能就能满足大部分调试需求了,开发时间成本只有2-3天,但它会在之后1-2个月甚至更久的前期开发阶段帮你缩短调试定位时间,节省更多时间早(bu)点(cun)下(zai)班(de),或帮策划做更多需求,重要的是解放心态,不再疲于沟通Bug,构造现场,因为现场就在录像里。

简单描述这套调试工具的使用姿势:

  • 开发过程中遇到了战斗Bug,如果在第一时间无法判断Bug原因,先保存录像,再逐步分析问题。
  • 选择报错的战斗录像,通过时间戳/快速模式重跑战斗,逐步缩小问题范围:观察战斗录像播到第几个回合报错,是资源加载、选招还是表演阶段报错,通过报错前的日志,逐步定位是哪个类哪个接口的问题,再猜测并验证某行代码,直到问题解决。
  • 如果不是卡死报错,战斗也能跑完,但策划反馈某个技能/Buff表现与预期不同,就要查看关键表演包的数据,看是后台传的有问题,还是前台表现没做对。
  • 上面两类问题的排查通常是无法一步到位的,排查过程会不断追踪代码给可疑代码打Log,会临时修改某些变量,会临时修改某段代码逻辑,依靠不断重跑战斗来验证。
  • 解决Bug的过程也少不了跟后台的沟通,在这之前,后台重数据轻表现,前台重表现轻数据,导致一种现象就是后台找前台问表现,前台找后台问数据,沟通成本比较高。有了这套工具,前台开发对于这场战斗包括服务器、角色ID、战斗ID、战场ID,协议数据等信息都了如指掌,快速分析出是前台问题就直接修复,是后台问题就告诉对方去修复哪块数据。

这里另外分享1个Bug调试修复的经验。个人认为Bug修复总时间 = 问题沟通时间 + 问题定位时间 + 代码修改时间 + 编译验证时间,像战斗这类大型系统,可能会经历多轮问题定位、代码修改、编译验证才能修好1个Bug。Lua代码做好Hot reload开关,最好做到修改某处代码,重进战斗就能验证最新代码。每次重启游戏至少花费30+秒,1个Bug平均几次重启验证就是几分钟时间,做好Hot reload节省下的时间相当可观。

初期在项目组内推行用录像反馈战斗Bug时,我们让大家把保存下来的录像文件单发给战斗开发来调试,很快发现用户体验并不友好,不是所有人都是开发,大家不清楚录像保存到哪个目录了,找到目录,他们也弄不清楚要发哪个录像给开发。在忍受了一段时间的灵魂三连问后,笔者又加上了录像上传/下载功能。

上面两张图是录像上传/下载流程及录像下载页面。我们将Bug反馈操作简化成游戏内一键反馈,点击按钮就能自动保存录像文件,并将二进制文件数据Base64编码成字符串,利用魔方质管组帮忙搭建的Web服务,通过Http请求将数据上传到Web服务器保存数据库,开发通过Web页面就可以搜索/下载base64字符串格式的录像文件,最后录像播放接口做适配,支持二进制/base64字符串两种格式数据的录像播放,整个环节就打通了。

开发阶段我们自行开发了战斗录像来辅助调试,确实也是到了战斗系统基本稳定后,策划们才前后提了战斗录像的正式需求,先做了一版基于服务器保存的活动录像,又做了一版基于客户端保存的战斗录像大厅。

前后做这两版录像需求,虽然都是观看录像,但其实现大不相同,因此需要谨慎设计整个录像模块,让两套逻辑独立并行,能共用底层功能,并尽量保持外部接口一致性。上图是整个战斗录像的模块划分,可划分为实现战斗录像基础功能的核心模块,及涉及界面UI的两版业务功能模块。BattleReplayManager是核心类,它对外接收录像相关的控制请求,对内调度其他核心模块类,获取/保存/构造数据,控制录像播放流程,并通过给战斗网络层发送协议数据影响战斗表现。

服务器录像

基于服务器保存的活动录像,所有数据都由服务器提供。前台首先发送观看录像请求,接收录像概要数据包,获取战斗波次/回合等信息用于显示和跳回合。收到初始战场包后进入战斗,在每回合表演完后请求下一回合表演数据。正常播放录像时,收到的协议数据跟普通战斗是一样的,但如果在战斗中途跳回合,除了新回合的表演包,还会收到新回合的战场包,用于恢复新回合初的战场单位状态。这个过程跟战斗断线重连恢复战场是同一套逻辑,因此把战斗断线重连的坑填完,实现服务器录像基本没有难点。

客户端录像

相对服务器录像,实现基于客户端保存的录像功能要考虑比较多问题:

  1. 确定录像数据结构,用什么数据结构存储一场战斗的所有协议及相关信息较优?
  2. 保证录制数据完整性。网络抖动、切出游戏再切回来等场景可能会导致少了某回合表演数据怎么办?
  3. 如何实现跳回合。一场正常战斗的协议包,除了初始战场包,每个回合只有表演包,没有战场包,跳回合怎么恢复战场状态?
  4. 录像上传/下载的传输策略。协议收发有64kb限制,录像文件大小超过了怎么办?
  5. 保证用户体验。评估极限情况的录像文件大小,保证流畅的录像观看体验。

模块开发初期就考虑这些问题,就可以避免基础设计出错,后期积重难返的尴尬情况。

1. 录像文件结构

首先是确定录像文件格式,由于妖尾协议基于pb通信,录像文件一开始就没有打算自定义二进制格式,而是直接基于pb定义数据结构,这样有几点好处:

  1. pb传输效率高,而且开发熟悉pb,不像自定义格式还有理解成本,开发效率也高。
  2. 协议与录像文件采用同种格式,比较容易根据查看列表,上传/下载录像等业务去反推最优的录像文件数据结构。让每份录像文件既可以有战斗录像数据,也有关于录像大厅的业务数据,一次设计,解决两个问题。
  3. pb支持数据结构嵌套,列表,能做出录像头、录像数据块设计,上传/下载协议也容易切分录像文件做分块传输。

基于几点考虑,录像文件由BattleReplayFile录像头、BattleReplayFileBlock录像数据块两部分组成。BattleReplayFile的blocks字段用于存放BattleReplayFileBlock列表,BattleReplayFile其他字段是概要信息。这样查看录像列表时,后台只需要返回不带blocks数据的BattleReplayFile列表即可。上传/下载录像时也可以先传录像头、再批量分次传录像数据块。

message BattleReplayFile{    optional string name = 1;                       // 录像文件名    repeated BattleReplayFileBlock blocks = 2;      // 协议文件块    optional uint32 block_num = 3;                  // 协议文件总块数    repeated string ext_info_keys = 4;              // 录像额外信息参数Key    repeated string ext_info_values = 5;            // 录像额外信息参数Value    ... // id、时间、双方成员、回合、波次等录像概要信息    ... // 简介、点赞、收藏等录像大厅业务信息}

message BattleReplayFileBlock{    optional uint32 index = 1;                  // 协议块序号    optional string name = 2;                   // 协议类名    optional bytes data = 3;                    // 协议数据    ... //时间、回合等其他信息}

2. 录像文件校验

网络抖动、切出游戏再切回来等情况导致断线重连,可能导致战斗录像数据损坏,因此保存本地前先做录像文件校验,判断有没有丢关键协议包,包括初始战场包、入包表演包、各回合表演包及退出战场包,保证协议包序,通过校验才保存录像文件,不通过就提示玩家录像数据损坏无法保存。

3. 录像回合跳转

一场战斗录像单靠收到的协议包,可以正常顺序播放整个战斗,却不能跳转回合播放,因为中间跳过了几回合的表演演算,战斗逻辑层无法将战场数据修正成跳转回合的状态。服务器录像可以依靠后台发跳转回合战场包做恢复,客户端录像就要靠前台自己处理,用录像表演包演算出跳转回合的战场状态。

第一直觉是在战斗逻辑层处理跳出的表演包,只是跳过表演,直接做数据演算,但稍加思考会发现有很多问题:战斗逻辑层里,数据与表现基本耦合在一起,毕竟这样的编码实现方式最直观。想抽离表现只演算数据,只能在原有代码里加ifelse分支,重写数据演算逻辑。几十个表演类,新增这么多分支,编码再加调试,必然失去对代码的把控,也破坏了原有系统稳定性。即使哼哧哼哧硬写下来,也会发现只实现了向后跳转回合,没实现向前跳转回合,因为战斗逻辑层实现的是按回合往下演算的逻辑。

跳出这个误区,我们认为战斗录像数据应该要有每个回合的战场包,跳转时供战斗逻辑层重置回合战场,因此后台修改了战斗逻辑,每回合都会发当回合战场包,这些战场包做了特殊标记,只用于录像存储,不会影响战斗逻辑,实现起来很快,但也清楚有明显效率问题。

基本上,战场包都会比表演包大,甚至大很多,如果某个回合技能不太复杂,那表演包数据其实非常小,为了实现跳回合,由后台给每个回合加发战场包,会非常影响战斗的协议数据量,保存录像文件变大,也会增加上传/下载录像时的负担。这么实现不合理的点在于,每回合战场包其实是冗余数据,每回合状态是可以通过初始战场包加表演包推算出来的。为了优化这个问题,前台实现了一个战场包构建器,以初始战场包、回合1~n-1表演包为输入,输出目标回合n的战场包。这样在保存录像时不需要保存回合战场包,录像跳转回合时由构造器动态生成战场包即可。编写调试战场包构建器时,要注意检查前后台的战场包差异,我们会打印战场包数据,通过Beyond Compare查看差异,不断调整代码,直到构建的关键数据一致为止。战场包构建器调试好后,只要后续不新增表演类型,就可以保证构建器可信可用,即使新增表演,代码工作量也很少。

优化完做下简单测试,打了一场40回合的5v5 pvp战斗保存录像,比较两种方案的保存录像文件大小:优化后文件大小是优化前的65%,减少了252KB,由于5v5pvp表演复杂,因此回合表演包数据本身也非常多,换做是一般的战斗,数据优化比率会更高。

4. 录像上传/下载策略

妖尾一次协议收发有64KB大小限制,看前面的数据可知,回合数比较多的战斗录像文件大小肯定会超过64KB,我们既不希望上传/下载录像单次传输的数据量超过64KB,又不希望单次传输数据量太少,导致协议发送次数过多,浪费太多时间在RRT上,因此采用的录像传输策略是,首次传输单独发送录像头,后续传输录像数据块切块传输,保证每次传输的所有BattleReplayFileBlock的data总大小不超过50KB。采用这样的策略,5回合以内的小型战斗基本都能分2次传输完毕,像上面的5v5 pvp大型战斗则需要进行11次传输。这就引出了下个问题思考,大型战斗的录像观看会不会有体验问题。

5.流式传输及录像缓存

战斗录像大厅的设计初衷,是让玩家可以自主分享/观看他们觉得满意的战斗录像,所以我们猜测玩家会比较多的上传/下载/观看大型pvp战斗录像,对于上传而言并不会有什么问题,因为就是一次性操作,但对下载/观看场景就要尽量进行优化,我们不希望玩家每次看录像,都要有感知地等待一会,等上10次网络回包,下载完录像文件才能观看录像,也不希望玩家每次看录像都得重复下载文件,对玩家的手机流量也很不友好。

针对这两点问题,战斗录像参考网络视频的做法,加上了流式传输及录像缓存的特性。

如上图所示,流式传输的目的在于优化玩家观看新录像的体验,不管一个完整的录像有多大,需要多少次传输才能完成,只需要先获得部分头部数据,就能观看录像。前台只需要头2次回包,获取录像概况、初始回合战场包和表演包,就足以表演第1回合的战斗,进入录像战斗后,静默下载其余的录像数据,一般后续的录像数据下载速度远远快于战斗表演速度,这样完全不影响整场战斗的录像观看。假设网络环境极端恶劣,表演完当前回合战斗后,后续录像数据还没返回,BattleReplayManager会每帧轮询等待下个回合表演数据,即使网络断掉了拿不到数据,玩家仍然可以点击按钮退出战斗录像。

录像缓存的目的则在于优化玩家重复观看录像的体验,减少流量消耗。当看过一次录像,下载了完整的录像数据后,前台就会把录像保存到本地缓存起来了,尽管录像头里存储了部分战斗录像大厅的字段,比如点赞、收藏数等,这些字段数据会失效,但战斗数据是不会变的。查看大厅的录像列表时,后台会返回只有录像头BattleReplayFile,没有数据块BattleReplayFileBlock的列表,玩家请求观看时,判断本地缓存有没有该录像缓存,有就不再走原来的下载流程,直接读取缓存文件播放即可。

洋洋洒洒写了一些关于战斗录像的总结,也确实是因为录像系统对战斗开发调试有所帮助,作为一个功能系统,也需要在早期考虑一些问题,做设计和优化,希望本文能对MMORPG或其他类型游戏战斗的设计开发,提供一些借鉴经验。

附上我们的游戏官网[妖精的尾巴:魔导少年],快来玩吧~

转载于:https://www.cnblogs.com/leoin2012/p/11321889.html

记录战斗记录你,详解妖尾战斗录像系统相关推荐

  1. pe系统如何读取手机_图文详解怎么用pe重做系统

    上期小编讲解了小编教你笔记本电脑开不了机怎么办,本次正特手机网小编给大家讲解一下图文详解怎么用pe重做系统,最近有不少的小伙伴都问小编说,使用pe重做系统简单吗?对于大家提问pe重做电脑系统的问题,其 ...

  2. linux和win双系统删除,双系统删除教程详解:Windows(linux)双系统,教你如何删除其中一个!...

    原标题:双系统删除教程详解:Windows(linux)双系统,教你如何删除其中一个! 现在的电脑配置都十分不错,有很多电脑都可以支持双系统.你可以将双系统可以分别安装在不同的分区内,在使用的时候,根 ...

  3. Kafka详解(上)——消息系统分类、Kafka安装、两种启动、基本概念、两种架构、核心配置文件

    1 消息和消息系统 ​ 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象. 1-1 消息系统是什么 ​ 消息系统负责将数据从一个应用 ...

  4. Linux卸载搜狐,双系统删除教程详解:Windows(linux)双系统,教你如何删除其中一个!...

    3. 随后,点击"引导",可以设置开机的启动系统了 4. 假设我们要删除win10系统,那么我们就将win10操作系统开机引导删除,选择win10然后选择"删除" ...

  5. fifa15服务器维护,菜单界面详解:游戏设置界面_FIFA15系统教程图文攻略(完结)_FIFA15图文全攻略_单机攻略_跑跑车单机游戏网...

    第 4 页 菜单界面详解:游戏设置界面 游戏设置界面 赛事设置 赛事时长:选择3.4.5.6.7.8.9.10.15.20分钟作为比赛半场时间; 赛事难度:基于玩家的FIFA级数选择玩家的AI对手难度 ...

  6. 数据库学习记录9一文详解多表查询

    目录 多表关系 一对多 多对多 一对一 多表查询概述 数据准备 概述 分类 内连接 外连接 自连接 联合查询 子查询 列子查询 行子查询 表子查询 多表查询 多表关系 一对一 一对多(多对一) 多对多 ...

  7. 【Numpy学习记录】np.cov详解

    写在篇前   在概率论和统计学中,协方差用于衡量两个变量的总体误差.而方差是协方差的一种特殊情况,即当两个变量是相同的情况.其定义的数学形式是:Cov(X,Y)=E[(X−E(X))(Y−E(Y))] ...

  8. SpringCloud Alibaba史上最强详解与史上最系统框架搭建

    框架实现代码资源地址:springCloud_dataservice_bus.zip_springcloudalibaba搭建-Java文档类资源-CSDN下载 目录 一.官网集合: Springbo ...

  9. 苹果IOS,与windows Phone7,系统,内存,CPU处理,及后台程序运行,详解微软墓碑机制的系统...

    关于ios的多任务以及内存管理 看了很多人为自己的可用内存是350mb还是380mb纠结.为了多优化出一点可用内存费脑筋. ios的任务管理和内存管理,跟windows是有很大差别的.很多人习惯于用  ...

最新文章

  1. PHP网站工作流程图,在网站绘制工作流程图的教程分享(打工人必看)
  2. STL--Lambdas(二)
  3. Spring Boot 2.0 新特性(二):新增事件ApplicationStartedEvent
  4. 007_SpringBoot文件上传
  5. 微型计算机选用要点,微型计算机原理以及应用考试_new要点分析.doc
  6. Python第十二章-多进程和多线程01-多进程
  7. 饿了么element UIel-dialog弹出层/el-dialog修改默认样式不能在style scoped修改
  8. C++交换两个数组的前n个字节
  9. matlab在同一窗口中画多个三维图像
  10. 项目中集成阿里巴巴分布式定时任务
  11. 1946年第一台公认电子计算机,1946年诞生的世界上公认的第一台电子计算机是()。...
  12. MGTV提取Ticket-保姆级教程
  13. AntV X6源码简析
  14. tomcat启动后无法访问到8080页面的原因
  15. 亚马逊测评有哪些误解?
  16. douyin_xl,xa,xg,xk
  17. 生信学习——基于R的可视化习题30个(附详细答案解读)
  18. Android SystemUI 架构详解
  19. iOS开发微信支付的介绍与实现
  20. 忆阻蔡氏电路matlab,基于有源带通滤波器的忆阻蔡氏电路研究.doc

热门文章

  1. 操作系统形式化验证实践教程(7) - C代码的自动验证(转载)
  2. 可视化实例(三)Tableau基础绘图介绍——横向条形图、双轴折线图、直方图
  3. 网络研讨会的邀请:丑女大翻身——用bbed工具对Oracle进行微整形
  4. 磁带库和磁带机的区别
  5. 数据挖掘BUC算法计算冰山立方体的python实现
  6. 周期性行业是什么意思_聊聊周期性行业 1.什么是周期性行业 周期性行业,就是指受经济周期影响较大的行业,经济低迷,行业亦表现为低迷;经济繁荣,行业也会表现得高... - 雪球...
  7. Qt:29---QColorDialog、QFontDialog颜色字体对话框
  8. warning Replace `············` with `······`
  9. linux与python什么关系,如何处理Linux / Python依赖关系?
  10. AI大数据竞赛平台和网站