上一篇文章中实现了一个简单的「跳一跳」,但是效果不算太好,现在我们来对其进行一下优化。
如果仔细观察微信的「跳一跳」,会发现手指按下时,不仅小瓶子会被压扁,小瓶子所在的小盒子也有一个压缩效果,这个应该如何实现呢?
方案其实很多,最简单的实现就是,将小盒子作为一个组,然后将蚂蚁放进去,然后整个组以底部为中心进行压缩,就能实现上面的效果了。
// 将蚂蚁放到 box 组中
this.currentBox.addChild(this.ant);
然而元素的位置计算跟其所在的组是相关的,如果组发生了变化,原本设置的 position 可能就会发生偏差,因此还需要调整下位置。
// 位置也得调整下
this.ant.setPosition(100, 50);
然后在每次跳到新盒子的一瞬间,将蚂蚁放到新的组中(会自动从原来的组中去掉)
// 因为要更换 ant 的 group,所以要计算 ant 相对 targetBox 的位置
var { x, y } = caleRelateTargetPos(this.ant, this.targetBox);
this.ant.setPosition(x, y);
this.targetBox.addChild(this.ant);
这里我们封装了个方法 caleRelateTargetPos
,可以计算一个元素相对目标组的 position。其原理是用全局坐标来计算相对位置,还要考虑缩放比率和目标组的中心点坐标。
/**
* 得到 obj 在 targetContainer 中的相对坐标
* @param {Tiny.DisplayObject} obj
* @param {Tiny.DisplayObject} targetContainer
*/
export function caleRelateTargetPos(obj, targetContainer) {
// 使用全局坐标来计算
const objGlobalPos = obj.getGlobalPosition();
const targetGlobalPos = targetContainer.getGlobalPosition();
const newX = objGlobalPos.x - targetGlobalPos.x;
const newY = objGlobalPos.y - targetGlobalPos.y;
// 还要考虑缩放比率,不同配置中拿到的宽度是不同的
const winW = Tiny.config.fixSize ? Tiny.config.width : window.innerWidth;
const rate = winW / Tiny.WIN_SIZE.width;
// 还要考虑 targetContainer 的中心点
const targetPivot = targetContainer.getPivot();
return {
x: newX / rate + targetPivot.x,
y: newY / rate + targetPivot.y,
};
}
解决了更换组引起的位置偏移问题后,盒子跟蚂蚁的整体动画就很容易实现了。只要将动画的执行者由「蚂蚁」更换为「盒子」(内部包含了蚂蚁)就好了。
compress() {
// 动画 持续 1000ms,y 轴缩放到 0.7
this.currentBox.runAction(Tiny.ScaleTo(1000, {
scaleY: 0.7,
}));
}
compressRestore() {
this.currentBox.removeActions(); // 移除动画
const action = Tiny.ScaleTo(500, Tiny.scale(1));
action.setEasing(Tiny.TWEEN.Easing.Bounce.Out); // 回弹效果
this.currentBox.runAction(action);
}
优化前 | 优化后 |
仔细观察蚂蚁的跳起动画,会发现它的旋转是以脚底为中心的,这跟我们普通的认知不太一样,空中翻滚的中心点应该更接近腰部,因此旋转中心换成腰部位置应该会更自然一些。
很多同学就会这样来写了
this.ant.setPivot(this.ant.width / 2, this.ant.height / 2);
然而这样是有问题的,中心点被修改后,又会导致位置计算的偏移问题。因此我们又封装了一个方法,来设置中心点,并自动重设其 position,使得视觉上的渲染位置保持不变。
/**
* 设置 obj 的中心点为 (x,y),会自动重设其 position,使得视觉上的渲染位置保持不变
* @param {Tiny.Sprite} obj
* @param {number} x
* @param {number} [y]
*/
export function setPivot(obj, x, y) {
const { x: originPivotX, y: originPivotY } = obj.getPivot();
obj.setPivot(x, y);
obj.setPositionX(obj.position.x + x - originPivotX);
obj.setPositionY(obj.position.y + y - originPivotY);
}
蚂蚁跳起的动画不应该是线性的,添加更多的关键帧,动作先快再慢,这样更自然一点。
可以在 Tween 动画中进行细节调整。
const tween = new Tiny.TWEEN.Tween({ // 起始值
rotation: 0,
x: originX,
y: originY,
}).to({ // 结束值
// rotation: 360, // 旋转 1 周
// x: originX + deltaX, // 向右移动 deltaX
// y: [originY - maxHeight, originY], // 设置 2 个关键帧,一个是最高点,一个是最终点。效果就是蚂蚁跳起来然后落下
rotation: [180, 320, 360], // 旋转 1 周
x: [originX + deltaX * 0.5, originX + deltaX * 0.8, originX + deltaX], // 向右移动 deltaX
y: [originY - maxHeight * 0.5, originY - maxHeight * 0.2, originY]
}, 1000)
看看对比效果
优化前 | 优化后 |
随着游戏的进行,我们发现页面的内存占用越来越多,怎么回事呢?原因很简单,盒子一直在增加,但是并没有销毁,自己手工处理一下就好了。
我们使用一个数组 boxes
来管理所有的盒子,然后在屏幕滚动动画结束之后,将屏幕外的盒子删掉。
for (var i = this.boxes.length - 1; i >= 0; i--) {
const box = this.boxes[i];
// 屏幕外的 box 都删掉,防止内存泄露
if ((box.x - box.pivot.x + box.width + scenePostion.x) < 0) {
box.parent.removeChild(box);
this.boxes.splice(i, 1);
}
}
经过上面的优化,游戏看起来舒服了许多,源码可见: https://github.com/stonelee/jump/tree/feat-2