Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chrome 小恐龙游戏源码探究二 -- 让地面动起来 #5

Open
liuyib opened this issue Apr 15, 2019 · 0 comments
Open

Chrome 小恐龙游戏源码探究二 -- 让地面动起来 #5

liuyib opened this issue Apr 15, 2019 · 0 comments

Comments

@liuyib
Copy link
Owner

liuyib commented Apr 15, 2019

前言

上一篇文章:《Chrome 小恐龙游戏源码探究一 -- 绘制静态地面》 中定义了游戏的主体类 Runner,并实现了静态地面的绘制。这一篇文章中,将实现效果:1、地面无限滚动。2、刚开始地面不动,按下空格后地面滚动。

地面无限滚动

要实现地面的移动就要不断更新地面的 x 坐标。定义如下方法:

HorizonLine.prototype = {
  /**
   * 更新地面的 x 坐标
   * @param {Number} pos 地面的位置
   * @param {Number} incre 移动距离
   */
  updateXPos: function (pos, incre) {
    var line1 = pos;
    var line2 = pos === 0 ? 1 : 0;

    // 第一段地面向左移动,第二段地面随之
    this.xPos[line1] -= incre;
    this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;

    // 第一段地面移出了 canvas
    if (this.xPos[line1] <= -this.dimensions.WIDTH) {
      // 将第一段地面放到 canvas 右侧
      this.xPos[line1] += this.dimensions.WIDTH * 2;
      // 此时第二段地面的 x 坐标刚好和 canvas 的 x 坐标对齐
      this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
      // 给放到 canvas 后面的地面随机地形
      this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
    }
  },
  // 获取随机的地形
  getRandomType: function () {
    return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
  },
};

其中 updateXPos 实现了地面 x 坐标的更新。当第一段地面移出 canvas 时,会将第一段地面 x 坐标乘 2 放到 canvas 后面,然后为其随机地形。这时,原来的第二段地面就变成了第一段地面继续向前移动,以此类推。这样就实现了地面的不断移动和更新。

上面的函数只是实现了地面移动相关的逻辑,真正让地面动起来还需要调用这个函数。添加方法:

HorizonLine.prototype = {
  /**
   * 更新地面
   * @param {Number} deltaTime 间隔时间
   * @param {Number} speed 速度
   */
  update: function (deltaTime, speed) {
    // 计算地面每次移动的距离(距离 = 速度 x 时间)时间由帧率和间隔时间共同决定
    var incre = Math.floor(speed * (FPS / 1000) * deltaTime);

    if (this.xPos[0] <= 0) {
      this.updateXPos(0, incre);
    } else {
      this.updateXPos(1, incre);
    }
    this.draw();
  },
};

然后需要通过 Horizon 上的 update 方法来调用 HorizonLine 上的 update 方法:

Horizon.prototype = {
  // 更新背景
  update: function (deltaTime, currentSpeed) {
    this.horizonLine.update(deltaTime, currentSpeed);
  },
};

同理,按照上面的逻辑,现在应该在 Runner 上定义 update 方法来调用 Horizon 上的 update 方法:

Runner.prototype = {
  // 更新游戏帧并进行下一次更新
  update: function () {
    this.updatePending = false; // 等待更新

    var now = getTimeStamp();
    var deltaTime = now - (this.time || now);

    this.time = now;

    this.clearCanvas();
    this.horizon.update(deltaTime, this.currentSpeed);

    // 进行下一次更新
    this.scheduleNextUpdate();
  },
  // 清空 canvas
  clearCanvas: function () {
    this.ctx.clearRect(0, 0, this.dimensions.WIDTH,
      this.dimensions.HEIGHT);
  },
  // 进行下一次更新
  scheduleNextUpdate: function () {
    if (!this.updatePending) {
      this.updatePending = true;
      this.raqId = requestAnimationFrame(this.update.bind(this));
    }
  },
};

//  获取时间戳
function getTimeStamp() {
  return performance.now();
}

最后在 Runnerinit 方法中调用它的 update 方法:

Runner.prototype = {
  init: function () {
    // ...

+   // 更新 canvas
+   this.update();
  },
};

由于类层层抽象的原因,方法的也需要层层调用。

现在地面就可以进行无限滚动了,效果如下:

move-horizonline-1

你可以通过查看我的 commit 信息,来查看添加或修改的代码:戳这里

响应空格键

下面来实现地面对空格键的响应,具体效果就是,初始地面不动,按下空格键后地面移动。

修改 Runner 原型链中的 update 方法:

Runner.prototype = {
  update: function () {
    var now = getTimeStamp();
    var deltaTime = now - (this.time || now);

    this.time = now;

+   if (this.playing) {
      this.clearCanvas();
      this.horizon.update(deltaTime, this.currentSpeed);
+   }

+   if (this.playing) {
      // 进行下一次更新
      this.scheduleNextUpdate();
+   }
  },
};

监听键盘事件:

Runner.prototype = {
  startListening: function () {
    document.addEventListener(Runner.events.KEYDOWN, this);
    document.addEventListener(Runner.events.KEYUP, this);
  },
  stopListening: function () {
    document.removeEventListener(Runner.events.KEYDOWN, this);
    document.removeEventListener(Runner.events.KEYUP, this);
  },
};

添加数据:

Runner.events = {
  // ...

+ KEYDOWN: 'keydown',
+ KEYUP: 'keyup',
};

然后在 Runnerinit 方法中调用 startListening 方法,来监听键盘的 keydownkeyup 事件:

Runner.prototype = {
  init: function () {
    // ...
    
+   // 开始监听用户动作
+   this.startListening();
  },
};

当浏览器监听到用户按下键盘时,会执行 handleEvent 方法来处理键盘事件:

Runner.prototype = {
  // 用来处理 EventTarget(这里就是 Runner 类) 上发生的事件
  // 当事件被发送到 EventListener 时,浏览器就会自动调用这个方法
  handleEvent: function (e) {
    return (function (eType, events) {
      switch (eType) {
        case events.KEYDOWN:
          this.onKeyDown(e);
          break;
        default:
          break;
      }
    }.bind(this))(e.type, Runner.events);
  },
};

上面用到的 onKeyDown 方法定义如下:

Runner.prototype = {
  onKeyDown: function (e) {
    if (!this.crashed && !this.paused) {
      if (Runner.keyCodes.JUMP[e.keyCode]) {
        e.preventDefault();

        if (!this.playing) {
          this.setPlayStatus(true);
          this.update();
        }
      }
    }      
  },
  // 设置游戏是否为进行状态
  setPlayStatus: function (isPlaying) {
    this.playing = isPlaying;
  },
};

这里需要提一下,当按下空格键后, 为什么浏览器会执行 handleEvent 事件:原因是当使用 addEventListener 监听某个对象上的事件时,只要被监听的事件触发了,就会执行该对象上名字为 handleEvent 的方法(如果有)。MDN 上有对 handleEvent 事件的解释。

到这里,就实现了地面对空格键的响应,效果如下:

move-when-down-space

查看添加或修改的代码,戳这里

Demo 体验地址:https://liuyib.github.io/demo/game/google-dino/horizonline-move/

上一篇 下一篇
Chrome 小恐龙游戏源码探究一 -- 绘制静态地面 Chrome 小恐龙游戏源码探究三 -- 进入街机模式
@liuyib liuyib changed the title Chrome 小恐龙游戏源码探究一 -- 让地面动起来 Chrome 小恐龙游戏源码探究二 -- 让地面动起来 Apr 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant