From 00be2ee747e5d916e7aca8b94927d1facb4102d6 Mon Sep 17 00:00:00 2001 From: Niklas Higi Date: Sat, 18 Apr 2020 16:46:17 +0200 Subject: [PATCH] feat: add `gamepad.vibrate()` method --- docs/components/gamepad.md | 18 ++++++++++++++++ src/apis.ts | 18 ++++++++++++++++ src/inputs/gamepad.spec.ts | 42 ++++++++++++++++++++++++++++++++++++++ src/inputs/gamepad.ts | 19 +++++++++++++++++ 4 files changed, 97 insertions(+) diff --git a/docs/components/gamepad.md b/docs/components/gamepad.md index 4b09bf7..3bc842c 100644 --- a/docs/components/gamepad.md +++ b/docs/components/gamepad.md @@ -30,8 +30,26 @@ When `.query()`-ed returns a `Vector2` of the current position of the stick. --- +#### `vibrate(duration, options)` + +Makes the gamepad vibrate with the specified magnitude for the specified duration. + +* `duration` is the duration in milliseconds +* `options` is an object with the following properties: + * `weakMagnitude` is the magnitude with which the weak motor will vibrate (defaults to `0`) + * `strongMagnitude` is the magnitude with which the strong motor will vibrate (defaults to `0`) + +Note that this feature: + +* **only works** with so-called "Dual Rumble" gamepads that have two vibrations motors +* **will not work** in all browsers since it's based on a [proposal][vibration-proposal] that, as of April 2020, isn't part of the official specification yet + +--- + #### `isConnected()` Returns whether a gamepad is currently connected. [gamepad-api]: https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API +[vibration-proposal]: https://github.com/w3c/gamepad/pull/68 + diff --git a/src/apis.ts b/src/apis.ts index 54835a1..359723e 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -28,4 +28,22 @@ export interface IGamepad { connected: boolean timestamp: number mapping: string + vibrationActuator?: IGamepadHapticActuator +} + +// Based on https://github.com/w3c/gamepad/pull/68 +// (just a proposal to the specification, but implemented in Chrome) +export interface IGamepadHapticActuator { + type: string + playEffect( + type: 'dual-rumble', + params: IGamepadEffectParameters, + ): Promise +} + +export interface IGamepadEffectParameters { + duration?: number + startDelay?: number + strongMagnitude?: number + weakMagnitude?: number } diff --git a/src/inputs/gamepad.spec.ts b/src/inputs/gamepad.spec.ts index 6f4e950..7556e1a 100644 --- a/src/inputs/gamepad.spec.ts +++ b/src/inputs/gamepad.spec.ts @@ -118,6 +118,16 @@ describe('The `Gamepad` class', () => { }) + + + describe('should have a `vibrate()` method that', () => { + + it("doesn't throw an error when called", async () => { + await gamepad.vibrate(1000) + }) + + }) + }) describe('in its connected state', () => { @@ -217,6 +227,38 @@ describe('The `Gamepad` class', () => { }) + describe('should have a `vibrate()` method that', () => { + + it('correctly passes along the provided options', () => { + let playEffectArgs: any[] = [] + nav.gamepads[0].vibrationActuator = { + type: 'dual-rumble', + playEffect: async (...args) => { playEffectArgs = args }, + } + + gamepad.vibrate(1000, { weakMagnitude: 0.5, strongMagnitude: 1 }) + + expect(playEffectArgs).to.deep.equal([ + 'dual-rumble', + { duration: 1000, weakMagnitude: 0.5, strongMagnitude: 1 }, + ]) + }) + + + + it('ignores actuators that are not of type `dual-rumble`', () => { + let playEffectCalled = false + nav.gamepads[0].vibrationActuator = { + type: 'not-dual-rumble', + playEffect: async () => { playEffectCalled = true }, + } + + gamepad.vibrate(1000) + expect(playEffectCalled).to.equal(false) + }) + + }) + }) }) diff --git a/src/inputs/gamepad.ts b/src/inputs/gamepad.ts index d474e5c..2d58228 100644 --- a/src/inputs/gamepad.ts +++ b/src/inputs/gamepad.ts @@ -120,4 +120,23 @@ export class Gamepad { } } + public async vibrate( + duration: number, + { weakMagnitude, strongMagnitude }: VibrationOptions = {}, + ): Promise { + if (!this.isConnected()) return + + const actuator = this.gamepad.vibrationActuator + if (!actuator || actuator.type !== 'dual-rumble') return + + await actuator.playEffect('dual-rumble', { + duration, strongMagnitude, weakMagnitude, + }) + } + +} + +interface VibrationOptions { + strongMagnitude?: number + weakMagnitude?: number }