仿微信「跳一跳」- 1. 简单实现

微信的跳一跳大家应该都玩过吧,玩法很简单,但是各种细节做的很不错。据了解它是使用 three.js 实现的,那么不用 WebGL,使用简单的 2D canvas 能否实现这样的效果呢?我们试试看。

部分素材来自网上资源,仅供学习使用,如有问题请与我联系。

1. 初始化项目

我们选用 Tiny.js 这款高效而强劲的 HTML5 2D 游戏引擎来实现整个游戏,那么项目初始化使用 tinyjs-cli 即可,参考文档:http://tinyjs.net/guide/start.html

执行 tiny init,选择「标准版」,按照自己的情况填写项目内容,会自动生成项目的初始化脚本。然后 npm install 来安装依赖,执行 npm start 来启动开发服务器,一个完整的 demo 就跑起来了。

2. 渲染一个小蚂蚁

第一步,将蚂蚁的图片放到 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 就好。

看看效果:

3. 给蚂蚁加个小盒子

跟上面一样,使用盒子的图片生成一个 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);

看看效果:

4. 让蚂蚁跳起来

我们要实现的效果是,当鼠标按下然后松开时,蚂蚁向右方跳一下。

首先,要响应鼠标事件。移动端跟桌面浏览器的响应事件有些差别,因此要兼容一下。

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)));
}

看看蚂蚁跳跃的效果:

5. 再来一个小盒子

用上面同样的方法,在原来位置向右 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;
}

6. 屏幕滚动

蚂蚁跳到了下一个盒子,因此整个屏幕也要进行滚动,使得下一个盒子成为屏幕中心。要实现这个效果,只要为整个 container 加个动画即可。下面代码中的 this 指的就是 container。

sceneMove() {
  this.runAction(Tiny.MoveBy(500, {
    x: -this.deltaX,
  }));
}

7. 通过用户行为来控制上面的动作

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();
  });
});

整体效果:

8. 总结

这样就实现了一个最简单的「跳一跳」的原型,当然动画还有些粗糙,后面我们慢慢来优化,源码可见: https://github.com/stonelee/jump/tree/feat-1