簡體   English   中英

用 Puppeteer 模擬游戲手柄?

[英]Simulate gamepad with Puppeteer?

我想編寫一些基於 Puppeteer 的測試來測試一些使用Gamepad API的邏輯,但我在Puppeteer 文檔上找不到任何文檔來解釋如何模擬游戲手柄以及如何將按鈕按下發送到瀏覽器。

正確的方法是什么?

沒有真正的“正確”方法來做到這一點。

我所做的是一種你應該能夠使用的有點干凈的方式。 It essentially involves creating your own game controller management code to use in puppeteer, and injecting the controller state into a page using the puppeteer evaluate API call.

本質上,我們劫持了全局 scope navigator.getGamepads function 並注入我們自己的實現。

我們首先在代碼中對游戲 controller 進行建模。 controller 最基本的部分是一個按鈕。 所以讓我們這樣做。

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 游戲手柄規范,一個游戲手柄有兩個模擬搖桿,所以我們將 model 那些。

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

最后,讓我們創建一個 class 來表示游戲手柄。 這個 class 將有一個助手 function 轉換控制器內部 state 以匹配W3C 游戲手柄接口簽名

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,
    }
  }
}

現在我們有了一種在代碼中表示游戲控制器及其 state 的便捷方式,我們可以劫持navigator.getGamepads並將其替換為我們自己的 function,它返回我們的虛擬控制器的 Z9ED39E2EA931586B6A985A64E。

現在我們將定義幾個輔助函數。 設置navigator.getGamepads將返回的游戲手柄 state 的一個。

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

現在我們已經完成了,我們需要一種方法來觸發gamepadconnected事件。 我們可以使用page.emit function 調用來做到這一點。

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

我們現在擁有使用 puppeteer 模擬 controller 的所有構建塊:示例用法如下:

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

希望這應該為您指明正確的方向。 如果您有任何問題,請隨時在此處跟進:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM