微信的跳一跳大家应该都玩过吧,玩法很简单,但是各种细节做的很不错。据了解它是使用 three.js 实现的,那么不用 WebGL,使用简单的 2D canvas 能否实现这样的效果呢?我们试试看。
部分素材来自网上资源,仅供学习使用,如有问题请与我联系。
我们选用 Tiny.js 这款高效而强劲的 HTML5 2D 游戏引擎来实现整个游戏,那么项目初始化使用 tinyjs-cli 即可,参考文档:http://tinyjs.net/guide/start.html
执行 tiny init
,选择「标准版」,按照自己的情况填写项目内容,会自动生成项目的初始化脚本。然后 npm install
来安装依赖,执行 npm start
来启动开发服务器,一个完整的 demo 就跑起来了。
第一步,将蚂蚁的图片放到 res/images
目录中,然后在 resources.js 中导出一下。
const antPng = 'res/images/ant.png';
export {
antPng,
};
第二步,开始写代码喽。
在 StartLayer.js 的 constructor 中,创建一个小蚂蚁。
// 使用图片生成一个 sprite
const ant = Tiny.Sprite.fromImage(Tiny.resources.antPng);
// canvas 默认旋转中心是在左上角,这样定位起来比较麻烦,所以这里将蚂蚁的脚底作为定位中心
ant.setPivot(ant.width / 2, ant.height);
// 先随便定个位置吧
ant.setPosition(100, 300);
// 加到 container 中来渲染
this.addChild(ant);
使用初始化生成的项目架构,图片在执行这里的业务代码前已经加载好了,因此不用担心图片加载问题,直接用来生成 sprite 就好。
看看效果:
跟上面一样,使用盒子的图片生成一个 sprite,然后渲染出来。
const box = Tiny.Sprite.fromImage(Tiny.resources.boxPng);
box.setPivot(box.width / 2, box.height);
// 位置得调整一下,使得蚂蚁能够正好站在盒子上
box.setPosition(100, 400);
// 如果使用 addChild,会发现盒子将蚂蚁盖起来了,所以使用 addChildAt 来将盒子放到蚂蚁下面
this.addChildAt(box, 0);
看看效果:
我们要实现的效果是,当鼠标按下然后松开时,蚂蚁向右方跳一下。
首先,要响应鼠标事件。移动端跟桌面浏览器的响应事件有些差别,因此要兼容一下。
const canvas = Tiny.app.view;
const supportTouch = 'ontouchstart' in window;
canvas.addEventListener(supportTouch ? 'touchstart' : 'mousedown', () => {
// 鼠标按下了
});
canvas.addEventListener(supportTouch ? 'touchend' : 'mouseup', () => {
// 鼠标抬起来了
});
下一步要实现蚂蚁跳跃了。我们设计的跳跃动作是:蚂蚁要翻个跟头,同时向右移动距离 deltaX。我们使用 Tiny.TWEEN.Tween 来实现这个动作。
jump(deltaX, onComplete) {
const maxHeight = 200; // 跳的最高点
const ant = this.ant; // 上面创建的蚂蚁实例
const originX = ant.position.x;
const originY = ant.position.y;
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 个关键帧,一个是最高点,一个是最终点。效果就是蚂蚁跳起来然后落下
}, 1000).onUpdate(function() {
// 设置位置
ant.setPosition(this.x, this.y);
// 需要将角度转换为弧度,然后设置旋转
ant.setRotation(Tiny.deg2radian(this.rotation));
}).onComplete(function () {
// 动画结束的回调
onComplete();
});
// 动画开始
tween.start();
}
鼠标按下时,我们期望蚂蚁能有个动作来响应这个操作,所以加了一个压缩的动画来模拟,因为比较简单,直接使用 Action 来实现。
compress() {
// 动画 持续 1000ms,y 轴缩放到 0.7
this.ant.runAction(Tiny.ScaleTo(1000, {
scaleY: 0.7,
}));
}
如果鼠标还没有按 0.7s 就松开了,这时候应该马上终止动画,然后恢复蚂蚁大小。
compressRestore() {
this.ant.removeActions(); // 移除动画
this.ant.runAction(Tiny.ScaleTo(500, Tiny.scale(1)));
}
看看蚂蚁跳跃的效果:
用上面同样的方法,在原来位置向右 deltaX 的位置再绘制一个小盒子。但是这次我们想加一个从上面掉下来的动画,直接使用 Tiny.MoveBy
即可,为了使落地动画更加生动,可以使用 action.setEasing
增加一个回弹效果。
dropBox() {
var box = Tiny.Sprite.fromImage(Tiny.resources.boxPng);
box.setPivot(box.width / 2, box.height);
box.name = 'box';
const deltaY = 100; // 从 deltaY 开始掉落
const x = this.currentBox.x + this.deltaX;
const y = this.currentBox.y - deltaY;
box.setPosition(x, y); // 设置初始位置
// 下落动画
const action = Tiny.MoveBy(500, {
y: deltaY,
});
action.setEasing(Tiny.TWEEN.Easing.Bounce.Out); // 盒子落地有个弹性效果
box.runAction(action);
this.addChildAt(box, 0);
return box;
}
蚂蚁跳到了下一个盒子,因此整个屏幕也要进行滚动,使得下一个盒子成为屏幕中心。要实现这个效果,只要为整个 container 加个动画即可。下面代码中的 this 指的就是 container。
sceneMove() {
this.runAction(Tiny.MoveBy(500, {
x: -this.deltaX,
}));
}
canvas.addEventListener(supportTouch ? 'touchstart' : 'mousedown', () => {
// 蚂蚁被压效果
this.compress();
});
canvas.addEventListener(supportTouch ? 'touchend' : 'mouseup', () => {
// 蚂蚁恢复被压效果
this.compressRestore();
// 跳的过程中就不能再跳了
if (this.isJumping) return;
this.isJumping = true;
this.jump(this.deltaX, () => {
// 跳完后
this.isJumping = false;
this.currentBox = this.targetBox;
// 移动屏幕
this.sceneMove();
// 再来一个盒子
this.targetBox = this.dropBox();
});
});
整体效果:
这样就实现了一个最简单的「跳一跳」的原型,当然动画还有些粗糙,后面我们慢慢来优化,源码可见: https://github.com/stonelee/jump/tree/feat-1