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