本文首发于微信公众号“Shopee 技术团队”。
摘要
款式繁多的“消消乐”游戏想必大家都不生疏,通关秘籍就是将三个或更多雷同的元素配对打消,通常咱们称这类游戏为“三消”游戏。Shopee 购物平台内嵌的三消游戏 Shopee Candy 也受到了不少用户的青睐,这篇文章将带你从我的项目起源、游戏架构和我的项目工具集等方面理解如何打造一款这样的三消小游戏。
1. 我的项目起源
1.1 游戏简介
Shopee Candy 是一款面向多地区市场的三消类休闲 H5 游戏,用户能够在游戏中取得 Shopee Coins、商家购物券等优惠处分,既能够加强用户粘性,激励用户生产,也能够为商家引流。同时,分享领奖、好友排行榜等机制加强了游戏的社交性,起到了为平台拉新的作用。此外,H5 游戏简略、轻量、高适配的个性与 Shopee 用户的应用习惯十分符合,能够即点即玩,参加门槛低的同时兼顾了趣味性。
2020 年 6 月,Shopee Candy 在 Shopee 全市场上线,推出 iOS 和安卓正式版本。自上线以来,Shopee Candy 在日常和大促流动中体现夺目,用户的活跃度和在线时长屡翻新高。
Shopee Candy 在格调上应用了缤纷可恶的糖果元素主题;玩法上次要是在限度操作步数的同时,设定收集元素数量作为通关条件。关卡内,用户通过替换相邻糖果,使三个或以上雷同色彩的糖果连接起来,便可触发打消;依据打消糖果的数量和形态还能够生成有非凡打消能力的道具元素;模式上无关卡模式及无尽模式等。
1.2 我的项目简介
随着我的项目一直倒退,Shopee Candy 的性能迭代能够被清晰地划分为四类。首先是各类业务功能模块(道具商城、工作零碎和兑换商店等);其次是负责打消逻辑算法,计算分数、关卡进度的算法库(Algorithm SDK)和负责打消成果的动画零碎;最初是服务游戏的各种工具,包含解放设计关卡生产力的 Map Editor 关卡编辑器,能够量化关卡难度的 Score Runner 跑分器以及能进行操作复盘的 Replayer 回放工具等。
2. 游戏架构
2.1 算法库(Algorithm SDK)
作为一款元素打消品种丰盛的三消游戏,Shopee Candy 的算法局部十分重要和简单。早在我的项目之初,算法和动画是耦合在一起的。随着产品的上线和打消品种的减少,咱们缓缓发现我的项目保护老本越来越高;同时算法自身是独立的,跟动画和业务没有依赖,所以将算法局部抽离了进去。
2.1.1 Algorithm SDK 实现
Algorithm SDK 的实现次要有三大部分:Map、Operator 以及 Logic Processing。
地图(Map)
Map 治理了游戏关卡中的地图对象和元素对象。咱们依据每个元素的个性对元素进行了上、中、下三层的治理,这种元素分层的架构模式可能满足不同特效新元素的退出。每一层元素互相制约、相互影响,在打消流程中相辅相成,实现游戏中富丽的打消成果。
export default class Grid {
public middle: CellModel;
public upper: Upper;
public bottom: Bottom;
constructor(info: ICellInfo) {const { type} = info;
this.upper = new Upper(/* ... */);
this.middle = new CellModel(/* ... */);
this.bottom = new Bottom(/* ... */);
}
}
操作(Operator)
Operator 对算法的所有可行操作进行治理,是整个 Algorithm SDK 与内部通信的桥梁,负责接管内部的替换、双击等操作信号,进行相应算法的调用。在 Shopee Candy 主流程的调用中,Operator 会收集动画数据,并以此与动画零碎进行通信。
// 元素替换
export function exchange(startPos, endPos): IAnimationData {
// ... logic process
// returns animation data
}
// 元素双击
export function doubleClick(pos): IAnimationData {
// ... logic process
// returns animation data
}
逻辑运算(Logic Processing)
Algorithm 对 Algorithm SDK 的所有逻辑运算进行治理,包含:开局有解保障、有解检测、打消、掉落等,是整个 Algorithm SDK 的外围。
流程中元素打消屡次循环时,可能会呈现逻辑执行用时过长的问题,导致用户操作时丢帧。为了防止这种状况,咱们将逻辑运算分成了多段,让计算异步化,提前将数据发送到动画执行,后续操作别离在不同帧中实现。
2.1.2 Algorithm SDK 单元测试
在实现上咱们曾经做到了尽可能的拆散和解耦,然而对于宏大的算法库来说,光是惯例的 Code Review 远远不够,前端测试就显得十分重要了。
单元测试介绍
很多人说前端测试浪费时间且收效甚微,惯例的前端业务的确会常常变动,包含 Shopee Candy 的业务视图也是常常变动。然而得益于算法库的拆散和独立,咱们能够对它进行无 UI、纯逻辑的单元测试。
单元测试利用
人工测试只能保障操作后代码不报错以及布局不错乱,但无奈发现打消元素数量或分数不正确等多种状况。我的项目中用户的一次挪动或者双击的操作,管制同样的布局下,最初得出雷同的后果。这个后果包含了最终地图的数据、用户取得的分数、收集或者销毁元素的数量等,保障在屡次迭代中的稳定性。
describe('BOMB', () => {it('Exchange each other should be the same crush', () => {const source = { row: 0, col: 3};
const target = {row: 1, col: 3};
const wrapper = mapDecorator(operator);
const data1 = wrapper({key: CRUSH_TYPE.BOMB}, source, target);
const data2 = wrapper({key: CRUSH_TYPE.BOMB}, target, source);
// 地图比拟
expect(JSON.stringify(data1.map)).equal(JSON.stringify(data2.map)).equal('xxx');
// 分数比拟
expect(data1.score).equal(data2.score).equal(150);
// 步数比拟
expect(data1.passStep).equal(data2.passStep).equal(14);
});
});
2.2 动画零碎(Animation)
动画与算法拆散后,动画独自作为一个零碎,这样有以下长处:
- 高内聚低耦合:算法与动画均具备较高复杂度,拆散后升高了零碎复杂度,同时模块更加内聚;
- 高效率:算法的执行不必期待动画,进步了计算效率;
- 高灵活性:算法去掉对动画的依赖后,能够很好地反对跳过 Bonus、跑分器等需要。
2.2.1 方案设计
拆散动画零碎后,须要与算法建设通信机制,来保障算法执行的打消后果有对应的动画播放。通信的实现形式如下:
- 建设事件机制,算法与动画通过事件进行互相通信;
- 定义动画数据结构,通过定义不同的动画类型来辨别动画,例如打消和着落动画,同时定义残缺的动画信息,动画零碎解析后播放对应动画。
针对动画的播放,咱们引入了一套「动画队列」的流程。将算法解析后的动画数据增加到队列中,递归播放队列,直至队列为空,完结动画播放。
从动画队列中播放单个动画时,为了确保各个元素动画的播放彼此之间不相互影响,动画零碎采纳「策略模式 」进行设计,依据动画类型执行不同的打消策略,将元素的动画「 内聚」到各自的策略办法中。
// 策略配置
const animStrategyCfg: Map<TElementType, IElementStrategy> = new Map([[AElement, AStrategy],
[BElement, BStrategy],
[CElement, CStrategy],
]);
// 获取策略
function getStrategy(elementType):IElementStrategy{return animStrategyCfg.get(elementType);
}
// 执行策略
function executeStrategy(elementType){const strategy = getStrategy(elementType);
return strategy.execute();}
算法负责计算打消逻辑,动画零碎负责播放对应动画,除了播放龙骨等特效,还会操作棋盘元素的大小、地位和显示。失常状况下动画播放完结后棋盘的状态和算法状态应该是统一的,但极少数状况下可能会因为设施性能等起因,造成时序、定时器异样等问题,进而导致两者状态不统一,比方元素不显示或地位错位等。
所以,动画完结后,须要「兜底逻辑」实时获取算法状态,校验修改棋盘状态,使之与其匹配,防止展现上的谬误。同时,为了防止性能问题,这里并非全量校验修改,而是只针对容易出错的中层元素。
2.2.2 解决回调天堂
游戏引擎自带的动画库采纳回调形式执行动画实现后的逻辑,在动画较为简单的状况下,经常会呈现回调嵌套的写法,使得逻辑难以了解和保护。为了解决回调天堂问题,咱们在动画库的原型链上封装了 promise 办法,这样就能够应用 async/await 同步的写法。
function animation(callback: Function) {tweenA.call(() => {tweenB.call(() => {tweenC.call(() => {sleep.call(callback);
});
});
});
}
async function animation() {await tweenA.promise();
await tweenB.promise();
await tweenC.promise();
await sleep.promise();}
下面展现了从回调写法改成同步写法的成果,能够看到同步写法更加直观,解决了回调天堂的问题,更易于代码的保护。
3. 我的项目工具集
后面介绍了 Shopee Candy 的算法库和动画零碎,实际上咱们团队还做了很多工具。
3.1 Map Editor
在 Shopee Candy 游戏建设之初,咱们须要制作一个既能灵便配置关卡也能测试关卡可玩性与通关率的工具。
在日常的工作中,通过拖拽、键盘快捷键的形式能够疾速配置关卡棋盘元素。
目前棋盘元素已倒退到 30 多个,各个元素之间存在简单的互斥共存规定:
- 共存:A 元素和 B 元素能够同时呈现在一个格子上;
- 互斥:A 元素和 B 元素不能同时呈现在一个格子上;
- 大互斥:A 元素和 B 元素不能同时呈现在一个棋盘上。
面对这么多元素之间的关系,单纯依附策动在配置关卡的时候人工解决互斥关系是不适合的,
所以须要一个关系互斥表去保护元素之间的关系。咱们通过 Map Editor 服务端拉取这个表格的数据,将它返回给 Web 端,在做好关系限度的同时给出肯定的谬误反馈。
3.2 Score Runner
在上线后期,遇到的问题之一就是关卡策动速度追不上用户通关的速度,经常出现用户催更关卡的状况。一款三消游戏动辄几千关的设计量,对于任何团队来说都是研发工作中的一个微小难点。其中,策动关卡最头疼和耗时是关卡的难度测试和调整,每一次调整都要人工反复试玩屡次,而后统计通关率。Score Runner 正是一个能够解决图中红色干燥费时流程的工具。
Score Runner 实现
先来看其中一关的布局,场上能够打消的操作有多种可能性,如果把这些可能性看成一张网状结构的数据,模仿用户的操作无外乎就只有这些可能性。
那么,下一步操作呢?在这些可能性操作前面有不同的布局,就会有更多的不同的可能性。流程大抵如下:
- 遍历地图获取每一种操作的可能性;
- 依据每一种操作,持续获取下一步的可能性直到最初一步(通过还是失败);
- 失去所有的操作步骤,就能得出最多和起码通关分数。
从图中能够看到,每一种可能性都能走到最初完结,然而实际上很多可能性用户是不会去操作的,比方得分少的,没有打消意义的操作等,对于通关率来说不迷信,而且对于计算效率来说十分低下。
于是咱们退出了 聪慧度策略,对可能性的打消的数据集做计算得出操作权重程序。以最高权重的操作为最佳可能性,对以后宏大的可能性网状结构进行剪枝,得出更合乎的通关率。
例:
关卡 | 均匀通关率(100 次) | 通关记录(%) |
---|---|---|
341 | 65% | 63/62/71 |
342 | 68% | 65/63/76 |
343 | 60% | 56/57/67 |
344 | 60% | 63/64/56 |
345 | 47% | 46/47/47 |
346 | 51% | 51/52/49 |
347 | 50% | 50/52/42 |
348 | 47% | 51/38/51 |
349 | 63% | 65/61/62 |
通过剖析均匀通关成功率,能够缩小策动对于关卡难度的验证工夫。
3.3 Replayer
在 Score Runner 之后,咱们应用 Replayer 对跑分过程进行回放,以验证跑分的正确性。
回放时,面临的首要问题就是保障每次随机的后果与跑分时是统一的。如何保障?
随机种子 是这个问题的答案。随机种子是一种以随机数作为对象的,以真随机数(种子)为初始条件的随机数。简略来说就是设置固定的种子,输入的后果、程序完全相同的伪随机办法。
为了接入随机种子,咱们采纳了新的随机数策略,该策略能够对随机种子进行设置,且咱们每一次随机数都是基于上一次随机数后果作为种子计算得出的后果。
这样的策略保障了整一局游戏中的每一次随机数都被记录,每一次随机的后果能够随时拿到。
同时这一策略也有以下播种:
- 逻辑算法执行的可回溯性;
- 断线重连后随机数的可追溯性;
- 复盘线上用户整局游戏步骤的可行性;
- 极少数据存储量。
4. 将来布局
本文从我的项目起源、游戏架构和我的项目工具集等方面介绍了 Shopee Candy 这款游戏。
“Rome wasn’t built in a day”,通过“需要——重构——上线”的循环能力造就更加欠缺的我的项目,后续咱们将从以下几方面对 Shopee Candy 进行欠缺和优化:
1)针对新元素实现配置化开发,缩小开发成本
目前新元素的开发还是须要投入较多的人力,打算联合算法库(Algorithm SDK)与 Map Editor 实现元素属性分类、分层等配置化开发,仅需新增配置化算法及元素动画即可实现新元素的开发。
2)针对低端设施提供更晦涩的游戏体验
Shopee Candy 是一款强游戏性我的项目,特地关注游戏性能和操作手感。因为市面上挪动设施性能参差不齐,则更加须要关注低端设施的用户体验。后续会针对低端设施制订逻辑和视图性能的优化策略,为用户提供更加晦涩的游戏体验。
3)操作行为验证,防止舞弊景象
前端依附混同或者加密等伎俩减少了破解老本,但无奈齐全防备舞弊行为。目前,我的项目正在开发操作行为验证服务,联合现有的 Replyer 性能对可疑结算行为进行二次操作校验,从而保障游戏的公平性。
4)利用机器学习进行关卡设计
目前,Shopee Candy 曾经开发了上千关卡。为进步关卡配置效率,后续打算联合机器学习和 Score Runner 统计的通关率进行模型训练,仅需提供大量的人工配置即可主动生成关卡。
本文作者
Shopee Games – Candy 前端团队。