技术闲聊
整个游戏开发中涉及的技术十分繁琐,在此我从游戏的整个大框架入手,主要介绍一些开发中前后端以及管理端我们涉及的技术问题。当然,由于篇幅原因,只是简单介绍,并不描述具体样例。
整体架构
- 后端
- 持久层——Mysql 数据库
- 剧情、玩家、卡牌、资源的数据结构设计
- 数据存储安全
- 缓存层——socket.io-redis与session
- 玩家状态:登录、逗留、对战、退出
- 对战与重连:网络波动环境下的访问
- 服务层——ThinkJS(based on Koa2)
- 数据埋点与分析:Middleware
- RPC接口功能稳定性:mockData与单元测试
- RPC接口性能可用性:并发压测
- 开发环境、预发环境、线上环境的数据隔离
- 持久层——Mysql 数据库
- 游戏客户端
- 跨平台开发框架——Egret与TypeScript
- 资源管理与打包
- 资源下载
- 资源缓存
- 资源迭代热更新
- 页面管理
- 页面注册
- 生命周期与事件监听
- 页面注销与异常处理
- 内存泄露
- 组件化开发与单一数据源
- 组件开发与复用(卡牌组件、弹窗组件等复用)
- 组件埋点(记录所有玩家操作、点击)
- 视图单向绑定数据(单一数据源渲染)
- 组件防抖与限流
- 数据请求:Https Request与SocketIO
- Egret 动画与逻辑
- 为什么tween.js很垃圾
- 如何克服异步动画问题
- 动画与逻辑同步:事件队列的执行与阻塞
- 流程交互体验
- 按钮反馈动画的重要性
- 交互提示,消息红点与任务提醒
- 加载提示
- 游戏新手引导
- 反作弊
- 全栈管理平台
- 项目开发管理——GitLab
- 代码部署与迭代——pm2
- 热更新迭代
- 服务节点崩溃重启
- 日志记录与分析
- CDN资源管理——七牛云
- 自主开发管理平台
- 数据管理
- 玩家数据监控
- 卡牌数据可视化管理
- 剧本、关卡数据可视化管理
- 资源数据管理
- RPC接口的开放与关闭管理
- 预发与线上隔离环境的数据同步
- 客户端组件埋点管理、应急管理(一键关闭组件入口)
- 资源管理(七牛云API)资源更新与CDN同步
- 线上版本控制(后端版本控制,前端需要微信审核,无法快速迭代/回滚)
- 数据管理
一、后端
-
持久层——Mysql 数据库
-
剧情、玩家、卡牌、资源的数据结构设计
在数据结构设计方面,最大程度上使得所有属性可以自由组合,这样设计大大提高了属性的复用性,卡牌数据的设计仿照《炉石传说》设计,为实现各种卡牌效果,我们为卡牌添加了元数据,使卡片可以执行特殊的命令。
-
数据存储安全
用户关键数据无明文存储,全部加密存储。
-
-
缓存层——socket.io-redis与session
-
玩家状态:登录、逗留、对战、退出
用户在登录、逗留状态时,client与server使用https进行数据传输,后端创建session记录用户登录状态。当玩家进入对战状态时,client与server使用socektIO进行数据传输,后端使用socket.io-redis记录用户状态,维护房间对战内容。
-
对战与重连:网络波动环境下的访问
当玩家因网络波动与后端断开socket链接时,client会尝试重连,重连之后会获取socket.io-redis缓存内容,加入之前仍在进行的对战。
-
-
服务层——ThinkJS(based on Koa2)
-
数据埋点与分析:Middleware
在C-like游戏中,通过数据解读玩家对游戏运营有着十分重要的意义,所以数据埋点是必要的。我们在中间层开发了部分数据埋点插件,所有前后端交互数据将先通过中间层的插件,这可以记录下我们分析所需的数据。前端组件也会绑定一些用户行为记录的事件,发送这些数据到后端,以便我们在管理端分析用户的数据。
-
RPC接口功能稳定性:mockData与单元测试
在游戏的开发中,后端接口的可用性检测消耗了我们的很大一部分时间。由于在卡牌游戏中,卡牌结算逻辑复杂,接口将整个逻辑内含,只暴露出输入和输出,最大程度简化客户端交互逻辑,但这也带来一个问题:单个接口的单元测试涉及的功能十分庞大。我们为了保证接口功能的稳定性和可用性,创建了很多为测试专用的虚拟对战接口,来让前端加载准备好的mockData在游戏客户端测试对应的卡牌或特殊的组合情况。
-
RPC接口性能可用性:并发压测
后端使用pm2部署,通过pm2,后端可以一键部署、迭代,还可以通过监控平台实时查看服务器性能。为保证我们接口的性能可用性,我们对每一个接口都进行了压力测试,使用Siege等工具对不同接口的性能进行了评估,针对有性能问题的接口进行分析和重构。
-
开发环境、预发环境、线上环境的数据隔离
后端将开发环境、预发环境(测试)、线上环境进行了数据隔离,目前这部分的管理端页面仍在开发中,我们只能手动切换环境。我们搭建了三个不同的环境,在新的接口上线时,将通过开发、预发环境的测试才能推送到线上环境。
-
二、游戏客户端
-
Egret与TypeScript
为了较快完成整个项目以及跨平台移植,我们选择了H5开发框架Egret。Egret Engine是全球领先的HTML5移动技术平台,它是遵循HTML5标准的2D、3D引擎,解决了HTML5性能问题及碎片化问题,灵活地满足开发者开发2D或3D游戏的需求,并有着较强的跨平台运行能力。它具有较高的开发效率,完整的游戏开发工作流,针对不同平台的性能优化以及简洁易用的组件化UI系统。通过Egret引擎开发的游戏,可以较为方便得移植到微信、QQ、OPPO、百度、Facebook等小游戏平台,为我们对此项目的进一步扩展提供了较好的帮助。
Egret Engine也同时支持TypeScript,JavaScript(ES6语法) 语言开发。TypeScript是由微软开发的开源编程语言。它被称为JavaScript的超集,在扩展了JavaScript的同时也兼容JavaScript。它从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展,支持静态输入、更便捷的重构以 及协作。
- 踩过的坑!
- API不足:一些项目需要的功能并没有被实现,我们用JavaScript实现封装了这些功能,也有一些API不能满足使用需求或者已被废弃需要更新,我们在引擎中找到对应代码并进行了一定的扩充。
- 屏幕适配:鉴于当前异形屏花样繁多,而Egret的适配为拉伸、裁剪等较为粗糙的方式。我们的游戏仍为非异形屏按比例铺满,异形屏按比例居中,导致左右有一定程度的黑边,略影响游戏体验。
- 微信小游戏适配不足:在微信更新某一版本后,Egret发布到微信的游戏会有卡顿。这部分需要引擎开发者对微信做更多的适配。
- 踩过的坑!
-
资源管理
对于微信小游戏,设计C-like游戏的一大难点就是资源管理,如何利用有限的缓存空间保存足够多的游戏资源,是提升游戏流畅性的关键。即使是Wifi状态下,下载数十上百M的资源也足以让游戏的用户体验直接死亡。为了更便捷得控制游戏内容,我们使用了第三种加载方式,实现资源缓存、版本控制、热更新。
-
资源缓存
Egret为我们提供了较为方便的接口,通过微信小游戏的API,资源可以直接缓存到本地,当下次游戏时会从本地加载,没有版本更新的时候用户只需要在第一次打开游戏的时候下载资源。
-
版本控制
使用版本控制,资源被加上版本号信息,同时通过对应的版本,控制文件资源进行更新。我们在管理端实现了资源版本管理、名称规范化模块来实现便捷的版本控制。
-
热更新
后台管理端可以管理资源文件的版本信息,每次用户打开游戏前拉去资源信息进行比对,得到更新下载的资源列表,根据列表下载并更新缓存资源文件到本地,实现热更新。
-
-
页面管理
在Typescript中自动GC十分容易造成内存泄露,为防止创建大量的重复页面造成内存占用过多,我们对不同量级的场景页面做了不同的处理。对于包含对象、监听、动画较多的页面,我们使用了单例模式以及对象池,对每个大型页面创建单一的对象并且不断复用,以此在切换游戏页面中,避免了频繁创建和删除操作,从而优化游戏性能。对于较小的页面或者弹窗提示,我们采用了卡片组件、弹窗组件来创造对象并且严格控制其销毁。我们对每个页面进行了大量的错误处理,保证其监听事件在任何情况下都可以正常运行,使玩家在页面切换中不会因为其他逻辑、数据错误而卡在一个页面。
-
组件化开发与数据渲染
组件化开发是前端工程中常用的一种开发模式,通过这种模式,我们的代码能够得到极大程度的复用,整个工程的可维护性也会大大提升。在我们的开发中,页面是最上层组件,页面内各个元素也是组件,组件的生命周期与父组件直接关联,我们只需要进行完善地页面管理就可以有效管理所有组件。组件大多使用不同的数据源,与常见的组件化开发不同的是,我们使用的是单一数据源,数据加载导致的组件更新并不会造成其父元素或子元素的更新。选择这个模式的原因主要是因为我们的游戏要避免频繁的组件更新,只在需要动画的区域进行更新,数据加载的更新影响较小,在游戏中一般也不会影响其他组件的更新,所以我们去除了这种组件更新依赖的关系,而是使用Sub/Pub模式让组件监听单一数据源并更新自身。
-
数据请求
作为一款功能模块复杂,兼具购买、聊天、任务、关卡、PVE对战、PVP对战的TCG C-like游戏,不同的游戏页面对前后端交互的时效性、数据量、安全性的需求不同。为此,我们使用混合型请求Https Request + SocketIO来进行前后端交互,保证了在不同需求下使用最合适的请求方式,无论是开发流程还是产品体验,都因此得到了提升。
在进入游戏到开始对战前,使用短连接由前端单向对后端进行数据请求,在这个过程中,交互不密集,客户端单向请求,Https Request足够胜任并且对后台有较小的压力。对战时,前后端交互使用时效性更强、双向通讯的SocketIO,在满足实时交互的同时,能更好的向客户端反馈数据交互状态,可以处理在各种网络情况下的极端情况。
-
Egret 动画与逻辑
在动画制作上,我们使用了tween.js来实现动画。相比unity动画,tween动画的开发更为繁琐,想要保证流畅的动画就对工程的需求量大大提高,但其好处是可以运行在多个平台上,兼容性更强。
tween的异步执行机制,使动画进行的顺序、时机难以准确控制,我们将动画封装在事件队列中,动画播放会阻塞该队列,当有动画执行完毕时,检查队列是否有未执行动画,接着执行下一条动画。当队列为空后,终止动画的执行。如此即可保证不同的动画可以按要求顺序进行。
逻辑代码只能向事件队列中添加需要播放的动画,而动画的播放只由事件队列控制,单一播放会阻塞队列,不会造成逻辑代码导致动画播放混乱的情况。逻辑代码执行结束后会监听动画事件播放完成的事件,直到动画播放完成才执行下一阶段逻辑代码。
-
流程交互体验
-
按钮反馈
按钮反馈是保证流畅的重要元素之一。在早期版本时,按钮点击之后便会执行下一步操作。但是我们发现,当按钮点击与下一步操作之间有较短间隔时,会导致玩家无法知道按钮是否已被触发,从而多次点击,带来较为糟糕的游戏体验。为此我们给按钮加上了统一的动画来展示点击反馈,动画阻塞了按钮的点击逻辑,提供了有效的防抖和限流,并且玩家也可以明确知道按钮是否被触发,从而给出一个我们期待的预期反应。
-
交互提示、吸引性标识
交互提示是对玩家行为的一种限制和提醒,为防止非预期操作的出现,我们在玩家所有操作后都添加了交互提示,但执行较快的操作会取消交互提示的显示,这样再玩家网络不稳定的情况下,玩家会对操作的响应时间给予更宽容的态度。加载提示是交互提示中数量最多的一种,在每次页面跳转时,页面内数据渲染完成前都会显示加载提示。
吸引性标识(消息红点)能够有效的增加玩家对自己数据的掌控能力,给玩家一个容易接受的时效性信息,并且能够显著增加用户活跃度和用户粘性。我们在邮件、任务、新解锁的物品上都添加了吸引性标识。
-
新手指引
这部分是一次性指引,用于玩家第一次进入时,通过文字和点击指引教会玩家游戏的基础玩法和游戏物品的使用,同时介绍游戏的背景、主题、世界观和价值观,帮助玩家快速上手游戏。
-
-
反作弊
- 为防止脚本或软件对客户端的修改,所有数据都在服务端严格控制,对战时客户端会计算部分数据来实现零等待响应时间,但所有的对战数据都会被服务端计算的数据直接替代,客户端计算数据只用于显示。
- 玩家资源数据在传输时加密,无法直接看到数据结构与数据内容。
三、全栈管理平台
-
项目开发管理——GitLab
在项目开发期间,我们使用gitlab管理项目的版本迭代与上线流程,在下一步开发中,我们会搭建私有的代码仓库,将版本迭代与上线流程添加入全栈管理平台,以图形化的方式管理整个项目流程。
-
代码部署与迭代——pm2
-
热更新迭代、服务节点崩溃重启、日志记录与分析
pm2支持nodejs代码一键多核部署,自动重启,版本平滑更新。我们使用pm2启动后端服务,服务器与后端服务的运行状况可以在管理平台实时监控,日志的图标分析也可以在管理上查看。
-
-
CDN资源管理——七牛云
为提高资源加载、下载速度,方便管理游戏内资源,我们使用了七牛云平台作为资源管理平台。通过七牛云提供的API,在管理平台上可以管理资源及其版本信息,CDN回源更新资源等。
目前在管理平台上我们实现的主要有以下功能:
-
玩家数据可视化分析
-
卡牌数据可视化管理
-
剧本、关卡数据可视化管理
-
资源数据管理
-
RPC接口的开放与关闭管理(迭代流程与应急处理)
-
客户端组件埋点管理、应急管理
-
资源管理(七牛云API)资源更新与CDN同步
-
线上版本控制(后端版本控制,前端需要微信审核,无法快速迭代/回滚)
-
动画视觉部分
很遗憾我对这部分所知甚少,负责原画与动画的同学最近十分忙碌,所以这部分只好暂时省略了,以后有机会再补上吧。
结尾
目前这款游戏的开发已经完成60%,这次参赛作品姑且算是展出了一个demo吧,剩下的开发很大一部分精力要投入到构建一个好的流程平台上去。在这款游戏的开发期间,我确实学习到了很多东西,自己的技术也成长了很多,希望在今后的时间,我能够完成这款TCG式C-like游戏。如果你有任何疑问或意见,欢迎和我交流。
厉害,大佬多写点游戏入门的文章,肯定受欢迎