水排序是一款益智类的小游戏,前两年大热,游戏的主要目标是将不同颜色的水倒入对应颜色的水杯中,通过移动水的位置来达到排序的目的。玩到这个游戏的时候还挺好奇倒水这个过程到底是怎么实现的,说简单也非常简单,但也涉及到一些数学计算,本期就来扒一扒这里面的细节。
核心玩法演示
开始之前先看下最后的效果,虽说不是倒水过程很逼真,但已经有模有样。
玩法逻辑拆解
在写代码之前先拆解下这个玩法的一些基本组成部分。
游戏由多个不同颜色的水和水杯组成
多个不同颜色水混合在一个水杯里
需要通过移动水的位置,倒入正确颜色的桶中,最终实现使每个杯子都只包含一种颜色的水
倒水的颜色必须是与杯中顶部水颜色一致
水杯有上限,满了就不能倒了
倒水是一个杯减少,一个杯增加
倒水过程
所以在游戏中,需要定义3个类:
杯子:长度属性
水:颜色、高度属性
水柱:长度属性
杯子/水/水柱
遮罩的妙用
水这里是简单用一个单纯色块来模拟,要实现杯子装水的效果,用遮罩就能满足。
给容器节点添加一个mask组件,类型选图片,然后选杯子的纹理。要是不设置遮罩就是另外一个效果了。
实现难点
实现的难点我想了下主要有以下两个方面,一个是杯子倒出水,尤其是倒出去要怎么模拟才尽量真实。首先杯子在倒水的过程中要倾斜,这个好办,给杯子设定一个旋转角度就行,角度随着倒水越多而越大,最后要与另外一个杯子成 90 角。另外需要注意是杯中的水也得跟着一起倾斜,而且最上面的水高度要不断减少。总体效果如下图所示。
需要注意的是锚点要设置到杯子的右上角,因为动画都是围绕这个点进行旋转。然后设定一个水杯倒水的初始角度initialAngle以及最终角度finalAngle,水杯新的位置X就是接收杯子的X坐标,Y坐标可以设定为其上方一点位置。代码如下:
cc.tween(this.node).to(.3, { angle: initialAngle, position: cc.v2(target.x, target.y + self.node.height / 3 * self.node.scale) // 移动节点至新的位置})
然后旋转到最终的角度,时间取决于水的长度
cc.tween(this.node).to(.9 * waterLength, { angle: finalAngle // 在指定时间内旋转至最终角度})
倒完水回归原始的位置和角度即可
cc.tween(this.node).to(.3, { angle: 0, position: self.originPos // 将节点移动至原始位置})
水杯中的水块处理会稍微复杂点,处理逻辑就是水位下降,那么就是整体减小所有水块的Y坐标,这里每个多下降25个单位,保证不漏出。X坐标同样发生了位移,因为杯子横过来了,可以稍微模拟一下偏移,这里定了一个hack值,每个水块根据Y坐标偏移一定的像素,水块的宽度可以设置尽量大一些,我这里设置为1000。
// 计算新的 x 和 y 坐标var childY = child.y;var newX = -(146 === childY ? 10 : 72 === childY ? 20 : -4 === childY ? 40 : 48);var newY = child.y - child.height - 25;cc.tween(child).delay(.1 * (self.container.children.length - index)).to(.9 * t, { x: newX, y: newY // 在指定时间内移动子节点至新的坐标位置})
另外水总是相对地面把持水平,所以角度改变要与杯子反向,相当于始终保持角度不变。angle是负值
// 创建并执行子节点的动画序列cc.tween(child).to(.3, { angle: -initialAngle // 旋转子节点至初始角度}).parallel( cc.tween().to(.9 * t, { angle: -finalAngle // 在指定时间内旋转至最终角度 }), .... // 旋转角度的同时位置也改变)
如果颜色断层了,那么就终止倒水。
另外一个问题就是水柱效果,就是水在倒入过程中的样子,这个可以用一长条状来模拟水柱。只要改变长度就行,刚开始倒水是从 0 不断变长,最后水柱又逐渐减为 0。
这里水柱的属性要设置为filled,因为改变高度是从端到另外一端,用fiilled模式更好处理,改变fillStart就行。
// 设置水滴结束 this.setDropletEnd = setTimeout(function () { // 定义水滴动画函数 self.droplet_func = function () { if (dropletSprite.fillStart >= 1) { self.unschedule(self.droplet_func); self.droplet.active = false; dropletSprite.fillStart = 0; } dropletSprite.fillStart += 0.065; }; // 执行水滴动画计划任务 self.schedule(self.droplet_func); }, 600 * number + (number === 4 ? 2000 : number === 3 ? 1000 : number === 2 ? 500 : 250));
接水的杯子比较好处理,主要是水的高度增加就行。如果水接满了,就设置完成状态。
游戏体验地址
长按二维码体验
游戏源码交流请私信。
欢迎关注我的公众号,获取更多游戏开发知识和游戏源码,手把手教你做游戏
更多精彩文章
【源码】游戏弯道设计:曲线运动的关键技巧与策略
如何开发《完蛋了!我被美女包围了!》游戏?
玩转合成游戏:从0到1的合成游戏开发指南
手把手教你10分钟接入微信小游戏的好友排行榜功能