十四、游戏手柄

我们差不多完成了。在我们分道扬镳之前,我认为尝试一下游戏手柄会很有趣。只有少数浏览器通过 JavaScript 支持它们,但它们使用起来确实很有趣!

处理事件

游戏手柄事件的工作方式与我们目前看到的键盘和鼠标事件略有不同。由于它们的实验支持,我们需要以某种方式捕捉它们,在Game.animate内部:

constructor(w, h) {
  this.w = w
  this.h = h

  this.state = {
    "game": this,
    "keys": {},
    "clicks": {},
    "mouse": {},
    "buttons": {},
    "objects": [],
    "player": null,
    "crosshair": null,
  }

  this.animate = this.animate.bind(this)
}

// ...later

animate() {
  requestAnimationFrame(this.animate)

  this.state.renderer = this.renderer
  this.state.stage = this.stage
  this.state.player = this.player
  this.state.crosshair = this.crosshair

  let gamepads = []

  if (navigator.getGamepads) {
    gamepads = navigator.getGamepads()
  }

  if (navigator.webkitGetGamepads) {
    gamepads = navigator.webkitGetGamepads
  }

  if (gamepads) {
    const gamepad = gamepads[0]

    gamepad.buttons.forEach((button, i) => {
      this.state.buttons[i] = button.pressed
    })
  }

  // ...remaining animation code
}

这是出自 http://codepen.io/assertchris/pen/WGzYgA

在找到我们使用的浏览器支持的游戏手柄列表之前,我们需要尝试一些不同的方法。我用的是 Chrome 的现代版本,支持 JavaScript Gamepad API。

我们捕获每个按钮的按下状态,并将其存储在state.buttons对象中(我们在constructor中初始化了该对象)。考虑到我们给Player. animate增加了多少复杂性,我认为是时候对其进行一点重构了:

animate(state) {
  const leftKey = state.keys[37] || state.keys[65]
  const leftButton = state.buttons && state.buttons[13]
  const rightKey = state.keys[39] || state.keys[68]
  const rightButton = state.buttons && state.buttons[14]
  const upKey = state.keys[38] || state.keys[87]
  const upButton = state.buttons && state.buttons[11]
  const jumpKey = state.keys[32]
  const jumpButton = state.buttons && state.buttons[0]

  if (leftKey || leftButton) {
    this.velocityX = Math.max(
      this.velocityX - this.accelerationX,
      this.maximumVelocityX * -1,
    )
  }

  if (rightKey || rightButton) {
    this.velocityX = Math.min(
      this.velocityX + this.accelerationX,
      this.maximumVelocityX,
    )
  }

  this.velocityX *= this.frictionX

  this.velocityY = Math.min(
    this.velocityY + this.accelerationY,
    this.maximumVelocityY,

  )

  state.objects.forEach((object) => {
    if (object === this) {
      return
    }

    const me = this.rectangle
    const you = object.rectangle
    const collides = object.collides

    if (me.x < you.x + you.width &&
        me.x + me.width > you.x &&
        me.y < you.y + you.height &&
        me.y + me.height > you.y) {

      if (object.constructor.name === "Ladder") {
        if (upKey || upButton) {
          this.rectangle.y -= this.climbingSpeed
          this.isOnLadder = true
          this.isOnGround = false
          this.velocityY = 0
          this.velocityX = 0
        }

        if (me.y <= you.x - me.height) {
          this.isOnLadder = false
        }

        return
      }

      // ...remaining collision detection code
    }
  })

  if ((jumpKey || jumpButton) && this.isOnGround) {
      this.velocityY = this.jumpVelocity
      this.isOnGround = false
      this.isOnSlope = false

  }

  // ...remaining movement code
}

这是出自 http://codepen.io/assertchris/pen/WGzYgA

这里我们定义了一些常量(代表按下的键盘按键和游戏手柄按钮)。假设键盘存在是很容易的,但是游戏手柄就不那么确定了。

这就是为什么我们将几个不同的键盘按键合并到一个检查中,但是每个游戏手柄按钮检查都要求我们首先确保已经定义了任何游戏手柄按钮。

通过这段代码,我们将方向按钮(D-Pad)和跳转按钮(PS 兼容控制器上的一个)映射到相应的播放器动作。

触发器和操纵杆

触发器和操纵杆比按钮要困难得多(而且容易在游戏手柄设计上产生差异)。我使用的是兼容 PS 的 Logitech 游戏手柄,触发器被映射到gamepad.axes对象,操纵杆也是如此。

在所有 6 个轴上,捕捉值的范围从-11。静止时,操纵杆不完全是0,这意味着我们需要使用一些ε值( https://en.wikipedia.org/wiki/Epsilon )或舍入来确定轴是否静止。

我们还需要修改三角方程,以考虑输入值/比例的差异。我想我要说的是,我们需要考虑我们想要支持哪些游戏手柄,以及我们的玩家需要哪些浏览器来使用它们。

摘要

在这一章中,我们尝试了游戏手柄的海洋。我们很幸运生活在一个浏览器越来越支持使用带有 JavaScript 的游戏手柄的时代,但是在它变得简单或普遍之前还有很多工作要做。

如果你觉得自己很勇敢,也许你会尝试将游戏手柄的触发器和操纵杆映射到游戏动作上(比如瞄准和发射射弹)。

我想花点时间感谢您阅读这本书。对我来说,这是一个短暂但激动人心的项目,我希望你和我一样(如果不是更多的话)了解并喜欢它。

正如我在开头所说的:如果有任何问题或改进建议,请随时联系我。鉴于示例的动态性质,我将能够修复错误并添加关于如何改进它们的评论。

我的推特账号是 https://twitter.com/assertchris

有时,我也会边编码边流式传输。你可以加入进来,问问题(实时),和我一起学习。

我的抽搐通道是 https://www.twitch.tv/assertchris