用Puppeteer模拟游戏手柄吗?

时间:2020-07-31 12:21:13

标签: javascript puppeteer gamepad-api

我想写一些基于Puppeteer的测试来测试一些使用Gamepad API的逻辑,但是我找不到Puppeteer docs上的任何文档来说明如何模拟游戏手柄以及如何将按键发送到浏览器。

正确的方法是什么?

1 个答案:

答案 0 :(得分:2)

并没有真正的“适当”方法。

我所做的是使您可以使用的某种简洁方法。本质上,它涉及到创建自己的游戏控制器管理代码以在puppeteer中使用,并使用puppeteer evaluate API调用将控制器状态注入页面。

本质上,我们劫持了全局范围navigator.getGamepads函数并注入我们自己的实现。

我们首先使用代码对游戏控制器进行建模。控制器最基本的部分是按钮。因此,让我们这样做。

class Button {
  constructor() {
    this.value = 0.0
    this.pressed = false
  }
  press() {
    this.value = 1.0
    this.pressed = true
  }
  unpress() {
    this.value = 0.0
    this.pressed = false
  }
  toObject() {
    return {
      value: this.value,
      pressed: this.pressed,
    }
  }
}

接下来,根据W3C Gamepad Specification,游戏手柄具有两个模拟摇杆,因此我们将对其进行建模。

class AnalogStick {
  constructor() {
    this.button = new Button()
    this.xAxis = 0.0
    this.yAxis = 0.0
  }

  setXAxis(value) {
    this.xAxis = value
  }

  setYAxis(value) {
    this.yAxis = value
  }
}

最后,让我们创建一个代表游戏手柄的类。此类将具有一个辅助函数,该函数可以转换控制器的内部状态以匹配W3C Gamepad Interface signature

class GamePad {
  constructor(index = 0) {
    this.id = 'Standard Gamepad'
    this.displayId = null // this is used for VR
    this.connected = true
    this.index = index
    this.mapping = 'standard'

    this.dPad = {
      up: new Button(),
      right: new Button(),
      down: new Button(),
      left: new Button(),
    }

    this.select = new Button()
    this.home = new Button()
    this.start = new Button()

    this.actions = {
      top: new Button(),
      right: new Button(),
      bottom: new Button(),
      left: new Button(),
    }

    this.leftStick = new AnalogStick()
    this.rightStick = new AnalogStick()

    this.lButton = new Button()
    this.lTrigger = new Button()

    this.rButton = new Button()
    this.rTrigger = new Button()
  }

  getState() {
    return {
      axes: [
        this.leftStick.xAxis,
        this.leftStick.yAxis,
        this.rightStick.xAxis,
        this.rightStick.yAxis,
      ],
      buttons: [
        this.actions.bottom.toObject(),
        this.actions.right.toObject(),
        this.actions.left.toObject(),
        this.actions.top.toObject(),

        this.lButton.toObject(),
        this.rButton.toObject(),

        this.lTrigger.toObject(),
        this.rTrigger.toObject(),

        this.select.toObject(),
        this.start.toObject(),

        this.leftStick.button.toObject(),
        this.rightStick.button.toObject(),

        this.dPad.up.toObject(),
        this.dPad.down.toObject(),
        this.dPad.left.toObject(),
        this.dPad.right.toObject(),

        this.home.toObject(),
      ],
      connected: this.connected,
      displayId: this.displayId,
      id: this.id,
      index: this.index,
      mapping: this.mapping,
    }
  }
}

现在,我们有了一种方便的方式来用代码表示游戏控制器及其状态,我们可以劫持navigator.getGamepads并将其替换为返回虚拟控制器状态的函数。

现在,我们将定义几个辅助函数。设置游戏手柄状态为navigator.getGamepads将返回的状态。

const setGamepadsState = async (page, gamepads) => {
  const result = await page.evaluate((controllers) => {
    navigator.getGamepads = () => controllers
  }, gamepads)
  return result
}

现在我们已经完成了此操作,我们需要一种触发gamepadconnected事件的方法。我们可以使用伪指令page.emit函数调用来做到这一点。

const connectGamepad = async (page, gamepad) => {
  const connectedEvent = {
    gamepad,
  }
  page.emit('gamepadconnected', connectedEvent)
}

我们现在拥有所有的构建块,可以使用操纵up来模拟控制器!用法示例如下:

;(async () => {
  const controller1 = new GamePad(0)
  const controller2 = new GamePad(1)
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.goTo('https://www.yourgamepadpage.com')

  // Set the current gamepad state for both controllers in the puppeteer page.
  // We need to call this each time we change a controllers state
  await setGamepadsState(page, [controller1.getState(), controller2.getState()])

  // fires a 'gamepadconnected' event in the page for controller1
  await connectGamepad(page, controller1.getState())
  // fires a 'gamepadconnected' event in the page for controller2
  await connectGamepad(page, controller2.getState())

  // toggles the state of the bottom action button to pressed on controller1, 'X' on a playstation pad or 'A' on an xbox pad
  controller1.actions.bottom.press()
  await setGamepadsState(page, [controller1.getState(), controller2.getState()]) // passes controller1's current state into puppeteer's 'page.evaluate'

  // do a check here in your puppeteer based test!
  console.log('this should be whatever test code you need!')

  controller1.actions.bottom.unpress() // untoggles the state of the bottom action button on controller1

  // now lets simulate an analog stick axis shift, e.g. left analog stick on the horizontal axis all the way to the left.
  controller1.leftStick.setXAxis(-1.0)
  await setGamepadsState(page, [controller1.getState(), controller2.getState()]) // and now we pass it to the page context!

  await browser.close()
})()

希望这应该为您指明正确的方向。如果您有任何疑问,请随时在这里继续:)