pixijs-input-devices 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +431 -164
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +54 -28
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,223 +2,218 @@
|
|
|
2
2
|
|
|
3
3
|
🚧 WIP - This API is a work in progress, and is subject to change.
|
|
4
4
|
|
|
5
|
-
- Adds support for ⌨️ **Keyboard**, 🎮 **Gamepads**, and other human-interface devices
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
5
|
+
- Adds comprehensive support for ⌨️ **Keyboard**, 🎮 **Gamepads**, and other human-interface devices
|
|
6
|
+
- High-performance, easy-to-use, sensible defaults
|
|
7
|
+
- Supports either real-time or event driven APIs
|
|
8
|
+
- Built-in `Navigation` API to navigate pointer/mouse based menus _(optional)_
|
|
9
9
|
|
|
10
10
|
<hr/>
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## 💿 Install
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
npm i pixijs-input-devices
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
### Setup
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
|
-
import { InputDevice
|
|
21
|
+
import { InputDevice } from "pixijs-input-devices"
|
|
22
22
|
|
|
23
|
-
// register mixin
|
|
24
|
-
registerPixiJSInputDeviceMixin( Container )
|
|
25
|
-
|
|
26
|
-
// add update loop
|
|
27
23
|
Ticker.shared.add( () => InputDevice.update() )
|
|
24
|
+
```
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
_(Optional)_ Enable the Navigation API:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Navigation } from "pixijs-input-devices"
|
|
30
|
+
|
|
31
|
+
// set root node
|
|
30
32
|
Navigation.stage = app.stage
|
|
33
|
+
|
|
34
|
+
// register mixin
|
|
35
|
+
registerPixiJSInputDeviceMixin( Container )
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
## Overview
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
There are a few very simple themes:
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
crouch: [ "ArrowDown","KeyS" ],
|
|
41
|
-
moveSlow: [ "ShiftLeft", "ShiftRight" ],
|
|
42
|
-
left: [ "ArrowLeft", "KeyA" ],
|
|
43
|
-
right: [ "ArrowRight", "KeyD" ],
|
|
42
|
+
- All devices are accessed through the `InputDevice` manager
|
|
43
|
+
- There are three supported device types: ⌨️ `"keyboard"`, 🎮 `"gamepad"` and 👻 `"custom"`
|
|
44
|
+
- Inputs can be accessed directly, or configured by [Named Groups](#named-input-groups)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
// other...
|
|
47
|
-
};
|
|
46
|
+
### InputDevice Manager
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
jump: [ "A" ],
|
|
51
|
-
crouch: [ "B", "X", "RightTrigger" ],
|
|
48
|
+
The `InputDevice` singleton controls all device discovery.
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
```ts
|
|
51
|
+
InputDevice.keyboard // KeyboardDevice
|
|
52
|
+
InputDevice.gamepads // Array<GamepadDevice>
|
|
53
|
+
InputDevice.custom // Array<CustomDevice>
|
|
56
54
|
```
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
You can access all **active/connected** devices using `.devices`:
|
|
59
57
|
|
|
60
58
|
```ts
|
|
61
|
-
//
|
|
62
|
-
|
|
59
|
+
for ( const device of InputDevice.devices ) { // ...
|
|
60
|
+
```
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
InputDevice.gamepads[0].onGroup( "jump", ( event ) => doJump() );
|
|
62
|
+
#### InputDevice - properties
|
|
66
63
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
| Property | Type | Description |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `InputDevice.isMobile` | `boolean` | Whether the context is mobile (including tablets). |
|
|
67
|
+
| `InputDevice.isTouchCapable` | `boolean` | Whether the context has touchscreen capability. |
|
|
68
|
+
| `InputDevice.lastInteractedDevice` | `Device?` | The most recently interacted device (or first if multiple). |
|
|
69
|
+
| `InputDevice.devices` | `Device[]` | All active, connected devices. |
|
|
70
|
+
| `InputDevice.keyboard` | `KeyboardDevice` | The global keyboard. |
|
|
71
|
+
| `InputDevice.gamepads` | `GamepadDevice[]` | Connected gamepads. |
|
|
72
|
+
| `InputDevice.custom` | `CustomDevice[]` | Custom devices. |
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
#### InputDevice - on() Events
|
|
75
|
+
|
|
76
|
+
Access global events directly through the manager:
|
|
72
77
|
|
|
73
78
|
```ts
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
InputDevice.on( "deviceconnected", ({ device }) => {
|
|
80
|
+
// a device was connected
|
|
81
|
+
// do additional setup here, show a dialog, etc.
|
|
82
|
+
})
|
|
77
83
|
|
|
78
|
-
//
|
|
79
|
-
InputDevice.gamepads[0].on( "A", ( event ) => doJump() );
|
|
84
|
+
InputDevice.off( "deviceconnected" ) // stop listening
|
|
80
85
|
```
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
| Event | Description | Payload |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `"deviceconnected"` | `{device}` | A device has become available. |
|
|
90
|
+
| `"devicedisconnected"` | `{device}` | A device has been removed. |
|
|
83
91
|
|
|
84
|
-
Iterate through `InputDevice.devices`, or access devices directly:
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
let jump = false, crouch = false, moveX = 0
|
|
93
|
+
### KeyboardDevice
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
if ( keyboard.groupPressed( "jump" ) ) jump = true
|
|
91
|
-
if ( keyboard.groupPressed( "crouch" ) ) crouch = true
|
|
92
|
-
if ( keyboard.groupPressed( "left" ) ) moveX -= 1
|
|
93
|
-
if ( keyboard.groupPressed( "right" ) ) moveX += 1
|
|
94
|
-
if ( keyboard.groupPressed( "moveSlow" ) ) moveX *= 0.5
|
|
95
|
+
Unlike gamepads & custom devices, there is a single global keyboard device.
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if ( gamepad.groupPressed( "crouch" ) ) crouch = true
|
|
97
|
+
```ts
|
|
98
|
+
let keyboard = InputDevice.keyboard
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
// we're going to apply these only if touched
|
|
102
|
-
if ( gamepad.leftJoystick.x != 0 ) moveX = gamepad.leftJoystick.x
|
|
103
|
-
if ( gamepad.leftTrigger > 0 ) moveX *= ( 1 - gamepad.leftTrigger )
|
|
104
|
-
}
|
|
100
|
+
if ( keyboard.key.ControlLeft ) { // ...
|
|
105
101
|
```
|
|
106
102
|
|
|
107
|
-
|
|
103
|
+
> [!NOTE]
|
|
104
|
+
> **Detection:** On mobiles/tablets the keyboard will not appear in `InputDevice.devices` until
|
|
105
|
+
> a keyboard is detected. See `keyboard.detected`.
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
#### Keyboard Layout - detection
|
|
110
108
|
|
|
111
109
|
```ts
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
console.debug( "A new " + device.type + " device connected!" )
|
|
115
|
-
)
|
|
110
|
+
keyboard.layout // "AZERTY" | "JCUKEN" | "QWERTY" | "QWERTZ"
|
|
111
|
+
```
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
113
|
+
> [!NOTE]
|
|
114
|
+
> **Layout support:** Detects the **"big four"** (AZERTY, JCUKEN, QWERTY and QWERTZ).
|
|
115
|
+
> Almost every keyboard is one of these four (or a regional derivative – e.g. Hangeul,
|
|
116
|
+
> Kana). There is no built-in detection for specialist or esoteric layouts (e.g. Dvorak, Colemak, BÉPO).
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
InputDevice.keyboard.on( "Escape", () => showMenu() )
|
|
124
|
-
InputDevice.gamepads[0].on( "Back", () => showMenu() )
|
|
118
|
+
The keyboard layout is automatically detected from (in order):
|
|
125
119
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
})
|
|
130
|
-
```
|
|
120
|
+
1. Browser API <sup>[(browser support)](https://caniuse.com/mdn-api_keyboardlayoutmap)</sup>
|
|
121
|
+
2. Keypresses
|
|
122
|
+
3. Browser Language
|
|
131
123
|
|
|
132
|
-
|
|
124
|
+
You can also manually force the layout:
|
|
133
125
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
```ts
|
|
127
|
+
// force layout
|
|
128
|
+
InputDevice.keyboard.layout = "JCUKEN"
|
|
129
|
+
|
|
130
|
+
InputDevice.keyboard.keyLabel( "KeyW" ) // "Ц"
|
|
131
|
+
InputDevice.keyboard.layoutSource // "manual"
|
|
132
|
+
```
|
|
138
133
|
|
|
139
|
-
####
|
|
134
|
+
#### KeyboardDevice Events
|
|
140
135
|
|
|
141
136
|
| Event | Description | Payload |
|
|
142
137
|
|---|---|---|
|
|
143
138
|
| `"layoutdetected"` | `{layout,layoutSource,device}` | The keyboard layout (`"QWERTY"`, `"QWERTZ"`, `"AZERTY"`, or `"JCUKEN"`) has been detected, either from the native API or from keypresses. |
|
|
144
|
-
| `"group"` | `{groupName,event,keyCode,keyLabel,device}` | A named input group key was pressed. |
|
|
139
|
+
| `"group"` | `{groupName,event,keyCode,keyLabel,device}` | A **named input group** key was pressed. |
|
|
145
140
|
| **Key presses:** | | |
|
|
146
|
-
| `"KeyA"`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
| Event | Description | Payload |
|
|
151
|
-
|---|---|---|
|
|
152
|
-
| `"group"` | `{groupName,button,buttonCode,device}` | A named input group button was pressed. |
|
|
153
|
-
| **Button presses:** | | |
|
|
154
|
-
| `"A"` \| `"B"` \| `"X"` \| ... 13 more ... | `{button,buttonCode,device}` | Button `"A"` was pressed. Equivalent to `0`. |
|
|
155
|
-
| ... | ... | ... |
|
|
156
|
-
| **Button presses (no label):** | | |
|
|
157
|
-
| `0` \| `1` \| `2` \| ... 13 more ... | `{button,buttonCode,device}` | Button `0` was pressed. Equivalent to `"A"`. |
|
|
141
|
+
| `"KeyA"` | `{event,keyCode,keyLabel,device}` | The `"KeyA"` was pressed. |
|
|
142
|
+
| `"KeyB"` | `{event,keyCode,keyLabel,device}` | The `"KeyB"` was pressed. |
|
|
143
|
+
| `"KeyC"` | `{event,keyCode,keyLabel,device}` | The `"KeyC"` was pressed. |
|
|
158
144
|
| ... | ... | ... |
|
|
159
145
|
|
|
160
|
-
> [!TIP]
|
|
161
|
-
> **Multiplayer:** For multiple players, consider assigning devices
|
|
162
|
-
> using `device.meta` (e.g. `device.meta.player = 1`) and use
|
|
163
|
-
> `InputDevice.devices` to iterate through devices.
|
|
164
146
|
|
|
165
|
-
###
|
|
147
|
+
### GamepadDevice
|
|
166
148
|
|
|
167
|
-
|
|
149
|
+
Gamepads are automatically detected via the browser API when first interacted with <sup>[(read more)](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API)</sup>.
|
|
168
150
|
|
|
169
|
-
|
|
170
|
-
---------------------|------|---------|--------------
|
|
171
|
-
`isNavigatable` | `boolean` | `false` | returns `true` if `navigationMode` is set to `"target"`, or is `"auto"` and a `"pointerdown"` or `"mousedown"` event handler is registered.
|
|
172
|
-
`navigationMode` | `"auto"` \| `"disabled"` \| `"target"` | `"auto"` | When set to `"auto"`, a `Container` can be navigated to if it has a `"pointerdown"` or `"mousedown"` event handler registered.
|
|
173
|
-
`navigationPriority` | `number` | `0` | The priority relative to other navigation items in this group.
|
|
151
|
+
Gamepad accessors are modelled around the "Standard Controller Layout":
|
|
174
152
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`"navigateLeft"` | `ArrowLeft`, `KeyA` | Left Joystick (Left), `DPadLeft`
|
|
178
|
-
`"navigateRight"` | `ArrowRight`, `KeyD` | Left Joystick (Right), `DPadRight`
|
|
179
|
-
`"navigateUp"` | `ArrowUp`, `KeyW` | Left Joystick (Up), `DPadDown`
|
|
180
|
-
`"navigateDown"` | `ArrowDown`, `KeyS` | Left Joystick (Down), `DPadUp`
|
|
181
|
-
`"navigateBack"` | `Escape`, `Backspace` | `B`, `Back`
|
|
182
|
-
`"trigger"` | `Enter,` `Space` | `A`
|
|
153
|
+
```ts
|
|
154
|
+
let gamepad = InputDevice.gamepads[0]
|
|
183
155
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
156
|
+
if ( gamepad.button.Start ) { // ...
|
|
157
|
+
if ( gamepad.leftTrigger > 0.25 ) { // ...
|
|
158
|
+
if ( gamepad.leftJoystick.x > 0.5 ) { // ...
|
|
159
|
+
```
|
|
188
160
|
|
|
189
161
|
> [!TIP]
|
|
190
|
-
>
|
|
191
|
-
>
|
|
192
|
-
> Or set `device.options.navigation.enabled = false` to disable navigation.
|
|
162
|
+
> **Special requirements?** You can always access `gamepad.source` and reference the
|
|
163
|
+
> underlying API directly as needed.
|
|
193
164
|
|
|
165
|
+
#### Vibration & Haptics
|
|
194
166
|
|
|
195
|
-
|
|
167
|
+
Use the `playVibration()` method to play a haptic vibration, in supported browsers.
|
|
196
168
|
|
|
197
|
-
|
|
169
|
+
```ts
|
|
170
|
+
gamepad.playVibration()
|
|
198
171
|
|
|
199
|
-
|
|
172
|
+
gamepad.playVibration({
|
|
173
|
+
duration: 150,
|
|
174
|
+
weakMagnitude: 0.25,
|
|
175
|
+
strongMagnitude: 0.65,
|
|
176
|
+
})
|
|
177
|
+
```
|
|
200
178
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
179
|
+
#### Gamepad Button Codes
|
|
180
|
+
|
|
181
|
+
The gamepad buttons reference **Standard Controller Layout**:
|
|
182
|
+
|
|
183
|
+
| Button # | ButtonCode | Standard | Nintendo* | Playstation | Xbox |
|
|
184
|
+
|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
185
|
+
| `0` | `"A"` | **A** | A | Cross | A |
|
|
186
|
+
| `1` | `"B"` | **B** | X | Circle | B |
|
|
187
|
+
| `2` | `"X"` | **X** | B | Square | X |
|
|
188
|
+
| `3` | `"Y"` | **Y** | Y | Triangle | Y |
|
|
189
|
+
| `4` | `"LeftShoulder"` | **Left Shoulder** | L | L1 | LB |
|
|
190
|
+
| `5` | `"RightShoulder"` | **Right Shoulder** | R | R1 | RB |
|
|
191
|
+
| `6` | `"LeftTrigger"` | **Left Trigger** | L2 | ZL | LT |
|
|
192
|
+
| `7` | `"RightTrigger"` | **Right Trigger** | R2 | ZR | RT |
|
|
193
|
+
| `8` | `"Back"` | **Back** | Minus | Options | Back |
|
|
194
|
+
| `9` | `"Start"` | **Start** | Plus | Select | Start |
|
|
195
|
+
| `10` | `"LeftStick"` | **Left Stick (click)** | L3 | L3 | LSB |
|
|
196
|
+
| `11` | `"RightStick"` | **Right Stick (click)** | R3 | R3 | RSB |
|
|
197
|
+
| `12` | `"DPadUp"` | **D-Pad Up** | ⬆️ | ⬆️ | ⬆️ |
|
|
198
|
+
| `13` | `"DPadDown"` | **D-Pad Down** | ⬇️ | ⬇️ | ⬇️ |
|
|
199
|
+
| `14` | `"DPadLeft"` | **D-Pad Left** | ⬅️ | ⬅️ | ⬅️ |
|
|
200
|
+
| `15` | `"DPadRight"` | **D-Pad Right** | ➡️ | ➡️ | ➡️ |
|
|
201
|
+
|
|
202
|
+
*See [Nintendo Layout Remapping](#gamepad---nintendo-layout-remapping) for more context
|
|
203
|
+
|
|
204
|
+
#### Gamepad Layouts
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
gamepad.layout // "nintendo" | "xbox" | "playstation" | "logitech" | "steam" | "generic"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Layout detection is **highly non-standard** across major browsers, it should generally be used for aesthetic
|
|
211
|
+
improvements (e.g. showing [device-specific icons](https://thoseawesomeguys.com/prompts/)).
|
|
212
|
+
|
|
213
|
+
There is some limited layout remapping support built-in for Nintendo controllers, which appear to be the
|
|
214
|
+
only major brand controller that deviates from the standard.
|
|
215
|
+
|
|
216
|
+
##### Gamepad - Nintendo Layout Remapping
|
|
222
217
|
|
|
223
218
|
> [!CAUTION]
|
|
224
219
|
> ***Nintendo:** Both the labels and physical positions of the A,B,X,Y buttons are different
|
|
@@ -226,9 +221,9 @@ Container events | description
|
|
|
226
221
|
>
|
|
227
222
|
> Set `GamepadDevice.defaultOptions.remapNintendoMode` to apply the remapping as required.
|
|
228
223
|
>
|
|
229
|
-
> - `"physical"` _(default)_ – A,B,X,Y refer the physical layout of a standard controller (Left=X,Top=Y,Bottom=A,Right=B).
|
|
230
|
-
> - `"accurate"` – A,B,X,Y
|
|
231
|
-
> - `"none"` – A,B,X,Y
|
|
224
|
+
> - `"physical"` _**(default)**_ – The A,B,X,Y button codes will refer the physical layout of a standard controller (Left=X, Top=Y, Bottom=A, Right=B).
|
|
225
|
+
> - `"accurate"` – The A,B,X,Y button codes will correspond to the exact Nintendo labels (Left=Y, Top=X, Bottom=B, Right=A).
|
|
226
|
+
> - `"none"` – The A,B,X,Y button codes mapping stay at the default indices (Left=Y, Top=B, Bottom=X, Right=A).
|
|
232
227
|
>
|
|
233
228
|
> ```
|
|
234
229
|
> standard nintendo nintendo nintendo
|
|
@@ -244,9 +239,222 @@ Container events | description
|
|
|
244
239
|
> 0 0 1 2
|
|
245
240
|
> ```
|
|
246
241
|
|
|
247
|
-
|
|
242
|
+
You can manually override this per-gamepad, or for all gamepads:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
gamepad.options.remapNintendoMode = "none"
|
|
246
|
+
GamepadDevice.defaultOptions.remapNintendoMode = "none"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### GamepadDevice Events
|
|
250
|
+
|
|
251
|
+
| Event | Description | Payload |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `"group"` | `{groupName,button,buttonCode,device}` | A **named input group** button was pressed. |
|
|
254
|
+
| **Button presses:** | | |
|
|
255
|
+
| `"A"` | `{button,buttonCode,device}` | Standard layout button `"A"` was pressed. Equivalent to `0`. |
|
|
256
|
+
| `"B"` | `{button,buttonCode,device}` | Standard layout button `"B"` was pressed. Equivalent to `1`. |
|
|
257
|
+
| `"X"` | `{button,buttonCode,device}` | Standard layout button `"X"` was pressed. Equivalent to `2`. |
|
|
258
|
+
| ... | ... | ... |
|
|
259
|
+
| **Button presses (no label):** | | |
|
|
260
|
+
| `0` or `Button.A` | `{button,buttonCode,device}` | Button at offset `0` was pressed. |
|
|
261
|
+
| `1` or `Button.B` | `{button,buttonCode,device}` | Button at offset `1` was pressed. |
|
|
262
|
+
| `2` or `Button.X` | `{button,buttonCode,device}` | Button at offset `2` was pressed. |
|
|
263
|
+
| ... | ... | ... |
|
|
264
|
+
|
|
265
|
+
### Custom Devices
|
|
266
|
+
|
|
267
|
+
You can add custom devices to the device manager so it will be polled togehter and included in `InputDevice.devices`.
|
|
248
268
|
|
|
249
|
-
|
|
269
|
+
```ts
|
|
270
|
+
import { type CustomDevice, InputDevice } from "pixijs-input-devices"
|
|
271
|
+
|
|
272
|
+
export const myDevice: CustomDevice = {
|
|
273
|
+
id: "on-screen-buttons",
|
|
274
|
+
type: "custom",
|
|
275
|
+
meta: {},
|
|
276
|
+
|
|
277
|
+
update: ( now: number ) => {
|
|
278
|
+
// polling update
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
InputDevice.add( myDevice )
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Named Input Groups
|
|
286
|
+
|
|
287
|
+
Use named "groups" to create named inputs that can be referenced.
|
|
288
|
+
|
|
289
|
+
This allows you to change the keys/buttons later (e.g. allow users to override inputs).
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
// keyboard:
|
|
293
|
+
InputDevice.keyboard.options.namedGroups = {
|
|
294
|
+
jump: [ "ArrowUp", "Space", "KeyW" ],
|
|
295
|
+
crouch: [ "ArrowDown", "KeyS" ],
|
|
296
|
+
toggleGraphics: [ "KeyB" ],
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// all gamepads:
|
|
300
|
+
GamepadDevice.defaultOptions.namedGroups = {
|
|
301
|
+
jump: [ "A" ],
|
|
302
|
+
crouch: [ "B", "X", "RightTrigger" ],
|
|
303
|
+
toggleGraphics: [ "RightStick" ],
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
These can then be used with either the real-time and event-based APIs.
|
|
308
|
+
|
|
309
|
+
#### Event-based:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
// listen to all devices:
|
|
313
|
+
InputDevice.onGroup( "toggleGraphics", ( e ) => toggleGraphics() )
|
|
314
|
+
|
|
315
|
+
// listen to specific devices:
|
|
316
|
+
InputDevice.keyboard.onGroup( "jump", ( e ) => doJump() )
|
|
317
|
+
InputDevice.gamepads[0].onGroup( "jump", ( e ) => doJump() )
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### Real-time:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
let jump = false, crouch = false, moveX = 0
|
|
324
|
+
|
|
325
|
+
const keyboard = InputDevice.keyboard
|
|
326
|
+
if ( keyboard.groupPressed( "jump" ) ) jump = true
|
|
327
|
+
if ( keyboard.groupPressed( "crouch" ) ) crouch = true
|
|
328
|
+
if ( keyboard.key.ArrowLeft ) moveX = -1
|
|
329
|
+
else if ( keyboard.key.ArrowRight ) moveX = 1
|
|
330
|
+
|
|
331
|
+
for ( const gamepad of InputDevice.gamepads ) {
|
|
332
|
+
if ( gamepad.groupPressed( "jump" ) ) jump = true
|
|
333
|
+
if ( gamepad.groupPressed( "crouch" ) ) crouch = true
|
|
334
|
+
|
|
335
|
+
// gamepads have additional analog inputs
|
|
336
|
+
// we're going to apply these only if touched
|
|
337
|
+
if ( gamepad.leftJoystick.x != 0 ) moveX = gamepad.leftJoystick.x
|
|
338
|
+
if ( gamepad.leftTrigger > 0 ) moveX *= ( 1 - gamepad.leftTrigger )
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Navigation API
|
|
343
|
+
|
|
344
|
+
Automatically traverse existing pointer/mouse based menus using the `Navigation` API.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
Navigation.stage = app.stage
|
|
348
|
+
|
|
349
|
+
const button = new ButtonSprite()
|
|
350
|
+
button.on( "mousedown", () => button.run( clickAnimation ) )
|
|
351
|
+
button.on( "mouseout", () => button.run( resetAnimation ) )
|
|
352
|
+
button.on( "mouseover", () => button.run( hoverAnimation ) )
|
|
353
|
+
|
|
354
|
+
app.stage.addChild( button )
|
|
355
|
+
|
|
356
|
+
button.isNavigatable // true
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
> [!NOTE]
|
|
360
|
+
> **isNavigatable:** By default, any element with `"mousedown"` or `"pointerdown"` handlers is navigatable.
|
|
361
|
+
|
|
362
|
+
> [!WARNING]
|
|
363
|
+
> **Fallback Hover Effect:** If there is no `"pointerover"` or `"mouseover"` handler detected on a container, `Navigation`
|
|
364
|
+
> will apply abasic alpha effect to the selected item to indicate which container is currently the navigation target. This
|
|
365
|
+
> can be disabled by setting `Navigation.options.useFallbackHoverEffect` to `false`.
|
|
366
|
+
|
|
367
|
+
### Disable Navigation
|
|
368
|
+
|
|
369
|
+
You can **disable** the navigation API - either permanently or temporarily - like so:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
Navigation.options.enabled = false
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Navigation Hierarchy
|
|
376
|
+
|
|
377
|
+
UIs can be complex! The Navigation API allows you to take over some - or all - of the navigation elements.
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
class MyVerticalMenu implements NavigationResponder
|
|
381
|
+
{
|
|
382
|
+
becameFirstResponder() {
|
|
383
|
+
console.log( "I'm in charge now!" )
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
resignedAsFirstResponder() {
|
|
387
|
+
console.log( "Nooo! My power is gone!" )
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
handledNavigationIntent( intent, device ): boolean {
|
|
391
|
+
if ( intent === "navigateUp" ) this.moveCursorUp()
|
|
392
|
+
else if ( intent === "navigateDown" ) this.moveCursorDown()
|
|
393
|
+
else if ( intent === "navigateBack" ) this.loseFocus()
|
|
394
|
+
else if ( intent === "trigger" ) this.clickCursorItem()
|
|
395
|
+
|
|
396
|
+
// we are going to return false here, which will propagates unhandled
|
|
397
|
+
// intents ("navigateLeft", "navigateRight") up to the next responder
|
|
398
|
+
// in the stack - which could be a parent view, etc.
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const myMenu = new MyVerticalMenu()
|
|
404
|
+
Navigation.pushResponder( myMenu )
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
In a game, you might use this to disable navigation outside of menus:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
class GameScene implements NavigationResponder
|
|
411
|
+
{
|
|
412
|
+
handledNavigationIntent( intent, device ) {
|
|
413
|
+
// ignore navigation intents, but allow other navigatable
|
|
414
|
+
// views to be pushed on top of me - e.g. a dialog window:
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Default Navigation Binds
|
|
421
|
+
|
|
422
|
+
Keyboard and gamepad devices are configured with a few default binds for navigation.
|
|
423
|
+
|
|
424
|
+
The default binds are below:
|
|
425
|
+
|
|
426
|
+
Navigation Intent | Keyboard | Gamepad
|
|
427
|
+
------------------|------------------------|-----------------------------------
|
|
428
|
+
`"navigateLeft"` | `ArrowLeft`, `KeyA` | Left Joystick (Left), `DPadLeft`
|
|
429
|
+
`"navigateRight"` | `ArrowRight`, `KeyD` | Left Joystick (Right), `DPadRight`
|
|
430
|
+
`"navigateUp"` | `ArrowUp`, `KeyW` | Left Joystick (Up), `DPadDown`
|
|
431
|
+
`"navigateDown"` | `ArrowDown`, `KeyS` | Left Joystick (Down), `DPadUp`
|
|
432
|
+
`"navigateBack"` | `Escape`, `Backspace` | `B`, `Back`
|
|
433
|
+
`"trigger"` | `Enter,` `Space` | `A`
|
|
434
|
+
|
|
435
|
+
These can be manually configured in `<device>.options.navigation.binds`.
|
|
436
|
+
|
|
437
|
+
#### Container Mixin
|
|
438
|
+
|
|
439
|
+
Container properties | type | default | description
|
|
440
|
+
---------------------|------|---------|--------------
|
|
441
|
+
`isNavigatable` | `boolean` | `false` | returns `true` if `navigationMode` is set to `"target"`, or is `"auto"` and a `"pointerdown"` or `"mousedown"` event handler is registered.
|
|
442
|
+
`navigationMode` | `"auto"` \| `"disabled"` \| `"target"` | `"auto"` | When set to `"auto"`, a `Container` can be navigated to if it has a `"pointerdown"` or `"mousedown"` event handler registered.
|
|
443
|
+
`navigationPriority` | `number` | `0` | The priority relative to other navigation items in this group.
|
|
444
|
+
|
|
445
|
+
Container events | description
|
|
446
|
+
------------------|--------------------------------------------------------
|
|
447
|
+
`focus` | Target became focused.
|
|
448
|
+
`blur` | Target lost focus.
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
## Advanced usage
|
|
452
|
+
|
|
453
|
+
### Local Player Assignment
|
|
454
|
+
|
|
455
|
+
Use the `<device>.meta` property to set assorted meta data on devices as needed.
|
|
456
|
+
|
|
457
|
+
You lose TypeScript's nice strong types, but its very handy for things like user assignment in multiplayer games.
|
|
250
458
|
|
|
251
459
|
```ts
|
|
252
460
|
InputDevice.on("deviceconnected", ({ device }) =>
|
|
@@ -263,20 +471,79 @@ for ( const device of InputDevice.devices )
|
|
|
263
471
|
}
|
|
264
472
|
```
|
|
265
473
|
|
|
266
|
-
|
|
474
|
+
### On-Screen Inputs
|
|
267
475
|
|
|
268
|
-
You can
|
|
476
|
+
You can easily map an on-screen input device using the `CustomDevice` interface.
|
|
269
477
|
|
|
270
478
|
```ts
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
479
|
+
export class OnScreenInputContainer extends Container implements CustomDevice {
|
|
480
|
+
id = "onscreen";
|
|
481
|
+
type = "custom" as const;
|
|
482
|
+
meta: Record<string, any> = {};
|
|
483
|
+
|
|
484
|
+
inputs = {
|
|
485
|
+
moveX: 0.0
|
|
486
|
+
jump: false,
|
|
276
487
|
}
|
|
488
|
+
|
|
489
|
+
update( now )
|
|
490
|
+
{
|
|
491
|
+
this.moveX = this._virtualJoystick.x
|
|
492
|
+
this.jump = this._jumpButton.isTouching()
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const onscreen = new OnScreenInputContainer();
|
|
497
|
+
|
|
498
|
+
InputDevice.add( onscreen )
|
|
499
|
+
InputDevice.remove( onscreen )
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Two Users; One Keyboard
|
|
503
|
+
|
|
504
|
+
You could set up multiple named inputs:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
InputDevice.keyboard.options.namedGroups = {
|
|
508
|
+
jump: [ "ArrowUp", "KeyW" ],
|
|
509
|
+
defend: [ "ArrowDown", "KeyS" ],
|
|
510
|
+
left: [ "ArrowLeft", "KeyA" ],
|
|
511
|
+
right: [ "ArrowRight", "KeyD" ],
|
|
512
|
+
|
|
513
|
+
p1_jump: [ "KeyW" ],
|
|
514
|
+
p1_defend: [ "KeyS" ],
|
|
515
|
+
p1_left: [ "KeyA" ],
|
|
516
|
+
p1_right: [ "KeyD" ],
|
|
517
|
+
|
|
518
|
+
p2_jump: [ "ArrowUp" ],
|
|
519
|
+
p2_defend: [ "ArrowDown" ],
|
|
520
|
+
p2_left: [ "ArrowLeft" ],
|
|
521
|
+
p2_right: [ "ArrowRight" ],
|
|
277
522
|
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
and then switch groups depending on the mode:
|
|
278
526
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
527
|
+
```ts
|
|
528
|
+
if ( gameMode === "2p" )
|
|
529
|
+
{
|
|
530
|
+
// multiplayer
|
|
531
|
+
player1.jump = device.pressedGroup( "p1_jump" )
|
|
532
|
+
player1.defend = device.pressedGroup( "p1_defend" )
|
|
533
|
+
player1.moveX += device.pressedGroup( "p1_left" ) ? -1 : 0
|
|
534
|
+
player1.moveX += device.pressedGroup( "p1_right" ) ? 1 : 0
|
|
535
|
+
player2.jump = device.pressedGroup( "p2_jump" )
|
|
536
|
+
player2.defend = device.pressedGroup( "p2_defend" )
|
|
537
|
+
player2.moveX += device.pressedGroup( "p2_left" ) ? -1 : 0
|
|
538
|
+
player2.moveX += device.pressedGroup( "p2_right" ) ? 1 : 0
|
|
539
|
+
}
|
|
540
|
+
else
|
|
541
|
+
{
|
|
542
|
+
// single player
|
|
543
|
+
player1.jump = device.pressedGroup( "jump" )
|
|
544
|
+
player1.defend = device.pressedGroup( "defend" )
|
|
545
|
+
player1.moveX += device.pressedGroup( "left" ) ? -1 : 0
|
|
546
|
+
player1.moveX += device.pressedGroup( "right" ) ? 1 : 0
|
|
547
|
+
player2.updateComputerPlayer()
|
|
548
|
+
}
|
|
282
549
|
```
|