pixijs-input-devices 0.1.3 → 0.2.1

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  # đŸ•šī¸ pixijs-input-devices  [![NPM version](https://img.shields.io/npm/v/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices) [![Minzipped](https://badgen.net/bundlephobia/minzip/pixijs-input-devices@latest)](https://bundlephobia.com/package/pixijs-input-devices) [![Downloads per month](https://img.shields.io/npm/dm/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices) [![Tests](https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml/badge.svg)](https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml) [![License](https://badgen.net/npm/license/pixijs-input-devices)](https://github.com/reececomo/pixijs-input-devices/blob/main/LICENSE)
2
2
 
3
- WIP
3
+ 🚧 WIP - This API is a work in progress, and is subject to change.
4
4
 
5
5
  - Adds support for âŒ¨ī¸ **Keyboard**, 🎮 **Gamepads**, and other human-interface devices
6
- - A simple `Navigation` API which hooks devices into existing pointer events
7
- - A powerful event-based API for event-driven interaction
6
+ - A simple `Navigation` API which hooks devices into existing pointer/mouse events
7
+ - A powerful event-based API for event-driven interactions
8
8
  - and of course, a high-performance API for real-time applications
9
9
 
10
10
  <hr/>
@@ -15,628 +15,205 @@ WIP
15
15
  npm i pixijs-input-devices
16
16
  ```
17
17
 
18
- Then inste
18
+ #### Setup
19
19
 
20
20
  ```ts
21
- // 1. set up ticker (or put in update loop)
22
- Ticker.shared.add(() =>
23
- InputDevice.shared.update()
24
- );
21
+ import { InputDevice, Navigation } from "pixijs-input-devices"
25
22
 
26
- // 2. register container mixin
27
- registerPixiJSInputDeviceMixin( Container );
23
+ // register mixin
24
+ registerPixiJSInputDeviceMixin( Container )
28
25
 
29
- // 3. (optional) enable auto-navigation
30
- Navigation.shared.stageRoot = app.stage;
31
- ```
32
-
33
- ### Event API
26
+ // add update loop
27
+ Ticker.shared.add( () => InputDevice.update() )
34
28
 
35
- ```ts
36
- // use the button offsets
37
- InputDevice.gamepads[0].on( Button.A, () =>
38
- alert("I've been pressed!")
39
- );
40
-
41
- // or the common labels
42
- InputDevice.gamepads[0].on( "Back", () =>
43
- alert("Let's get out of here.")
44
- );
29
+ // enable navigation
30
+ Navigation.stage = app.stage
45
31
  ```
46
32
 
47
33
  ### Realtime API
48
34
 
49
- Simple example:
35
+ Iterate through `InputDevice.devices`, or access devices directly:
50
36
 
51
37
  ```ts
52
- let jump = false;
53
- let hide = false;
54
- let moveX = 0.0;
38
+ let jump = false, hide = false, moveX = 0.0
55
39
 
56
- for ( const device of InputDevice.shared.devices )
57
- {
58
- if ( device.type === "keyboard" )
59
- {
60
- if ( device.key.ArrowUp ) jump = true
61
- if ( device.key.ArrowDown ) hide = true
62
- if ( device.key.ArrowLeft ) moveX = -1.0
63
- if ( device.key.ArrowRight ) moveX = 1.0
64
- }
65
-
66
- else if ( device.type === "gamepad" )
67
- {
68
- if ( device.button.A ) jump = true
69
- if ( device.rightTrigger > 0.25 ) hide = true
70
- if ( device.leftJoystick.x !== 0.0 ) moveX = device.leftJoystick.x
71
-
72
- device.playVibration({ duration: 100 });
73
- }
74
- }
75
- ```
40
+ const keyboard = InputDevice.keyboard
41
+ if ( keyboard.key.ArrowUp ) jump = true
42
+ if ( keyboard.key.ArrowDown ) hide = true
43
+ if ( keyboard.key.ArrowLeft ) moveX = -1.0
44
+ else if ( keyboard.key.ArrowRight ) moveX = 1.0
76
45
 
77
- #### Assigning devices
78
-
79
- ```ts
80
- InputDevice.shared.on("deviceconnected", ({ device }) => {
81
- device.meta.localPlayerId = 123;
82
- });
83
-
84
- for ( const device of InputDevice.shared.devices )
85
- {
86
- if ( device.meta.localPlayerId === 123 )
87
- {
88
- // do something
89
- }
46
+ for ( const gamepad of InputDevice.gamepads ) {
47
+ if ( gamepad.button.A ) jump = true
48
+ if ( gamepad.rightTrigger > 0.25 ) hide = true
49
+ if ( gamepad.leftJoystick.x !== 0.0 ) moveX = gamepad.leftJoystick.x
90
50
  }
91
51
  ```
92
52
 
93
- > [!NOTE]
94
- > You may also use `device.button.RightTrigger` to detect triggers as a button press.
95
-
96
- > [!NOTE]
97
- > You can use `device.pressingAll([...])` and `device.pressingAny([...])` to check groups of keys/buttons.
98
-
99
- > [!NOTE]
100
- > You can use `device.assignee` to store any device assignment-related data (such as a user ID).
101
-
102
-
103
- ### Event-driven API
53
+ ### Event API
104
54
 
105
55
  ```ts
106
- InputDevice.keyboard.bind( "KeyW", (device, code, label) => {
107
- console.log( "key in W position was pressed. label:", label );
108
- });
109
-
110
- InputDevice.gamepads.bind({
111
- Back: () => {
112
-
113
- }
114
- })
115
-
116
- InputDevice.gamepad[0].bind({
117
- KeyA: () => {},
118
- Space: {
119
- keydown: () => {},
120
- keyup: () => {},
121
- }
122
- })
123
- ```
56
+ // global events
57
+ InputDevice.on( "deviceconnected", ({ device }) =>
58
+ console.debug( "A new " + device.type + " device connected!" )
59
+ )
124
60
 
125
- By default, any element with "mousedown" or "pointerdown" will
61
+ // device events
62
+ InputDevice.keyboard.on( "layoutdetected", ({ layout }) =>
63
+ console.debug( "layout detected as " + layout );
64
+ )
126
65
 
127
- Container events | description
128
- ------------------|--------------------------------------------------------
129
- `focus` | Target became focused. args: direction: { x, y }
130
- `blur` | Target lost focus. args: direction: { x, y }
131
- `focusHinted` | Target may be about to become focused. args: direction: { x, y }
132
- `blurHinted` | Target may be about to lose focus. args: direction: { x, y }
133
-
134
- Container properties | description
135
- ---------------------|-----------------------------------------------------
136
- `focusable` | boolean?, default: `undefined`
137
- `isFocusGroup` | boolean?, default: `false`
138
- `focusPriority` | number?, default: `0`
139
- `focusGroup` | PIXI.Container?, default: `app.stage ?? undefined`
140
- `focusTransform` | `{ x, y }`, default: `{ x: 5, y: 5 }`
141
- `isFocusable` | get () => boolean, returns true if "focusable" = true, or InputDevice.defaultOptions.autoFocusable and has "mousedown", "pointerdown"
142
-
143
- Device gesture | Keyboard (primary) | Keyboard (alt) | Gamepad
144
- -----------------|--------------------|-----------------|------------------
145
- `"moveFocusLeft"` | Left Arrow Key | A Key | Left Stick Left
146
- `"moveFocusRight"` | Right Arrow Key | D Key | Left Stick Right
147
- `"moveFocusUp"` | Up Arrow Key | W Key | Left Stick Up
148
- `"moveFocusDown"` | Down Arrow Key | S Key | Left Stick Down
149
- `"focusover"` | Return Key | Space | A Button
150
- `"moveUpOneLevel"` | Escape Key | Backspace | B Button
151
-
152
-
153
- ### Global defaults:
154
-
155
- ```ts
156
- InputDevice.defaultOptions = {
157
- /** Globally enable/disable the navigation API. (i.e. if the browser loses focus). */
158
- focusEnabled: true,
159
- /**
160
- * (default: true) When enabled, any element with "mousedown" or "pointerdown"
161
- * is considered focusable. The "focusableTransform" will also apply.
162
- */
163
- autoFocusable: true,
164
- focusableTransform: { x: 5, y: 5 },
165
- keyboard: {
166
- enabled: true,
167
- layouts: [ "qwerty", "azerty", "jcuken", "qwertz" ],
168
- autoLanguageLayout: true, // fr*/nl-BE* -> azerty, uk*/ru* -> jcuken
169
- internationalization: {
170
- /** when true, labels are mapped to their layout */
171
- mapLabels: true,
172
- }
173
- },
174
- gamepad: {
175
- enabled: true,
176
- layouts: [ "standard", "nintendo" ],
177
- firstIntentCooldownMs: 400,
178
- intentCooldownMs: 100,
179
- },
180
- }
66
+ // bind keys/buttons
67
+ InputDevice.keyboard.on( "Escape", () => showMenu() )
68
+ InputDevice.gamepads[0].on( "Back", () => showMenu() )
181
69
  ```
182
70
 
183
- ```ts
184
- class InputDevice {
185
-
186
- }
71
+ #### Global Events
187
72
 
188
- ```
73
+ | Event | Description | Payload |
74
+ |---|---|---|
75
+ | `"deviceconnected"` | `{device}` | A device has become available. |
76
+ | `"devicedisconnected"` | `{device}` | A device has been removed. |
189
77
 
78
+ #### Keyboard Device Events
190
79
 
191
- <hr/>
80
+ | Event | Description | Payload |
81
+ |---|---|---|
82
+ | `"layoutdetected"` | `{layout,layoutSource,device}` | The keyboard layout (`"QWERTY"`, `"QWERTZ"`, `"AZERTY"`, or `"JCUKEN"`) has been detected, either from the native API or from keypresses. |
83
+ | **Key presses:** | | |
84
+ | `"KeyA"` \| `"KeyB"` \| ... 103 more ... | `{event,keyCode,keyLabel,device}` | `"KeyA"` was pressed. |
192
85
 
193
- <hr/>
86
+ #### Gamepad Device Events
194
87
 
88
+ | Event | Description | Payload |
89
+ |---|---|---|
90
+ | **Button presses:** | | |
91
+ | `"A"` \| `"B"` \| `"X"` \| ... 13 more ... | `{button,buttonCode,device}` | Button `"A"` was pressed. Equivalent to `0`. |
92
+ | ... | ... | ... |
93
+ | **Button presses (no label):** | | |
94
+ | `0` \| `1` \| `2` \| ... 13 more ... | `{button,buttonCode,device}` | Button `0` was pressed. Equivalent to `"A"`. |
95
+ | ... | ... | ... |
195
96
 
196
- ⚡ Powerful, high-performance animations for PixiJS
97
+ > [!TIP]
98
+ > **Multiplayer:** For multiple players, consider assigning devices
99
+ > using `device.meta` (e.g. `device.meta.player = 1`) and use
100
+ > `InputDevice.devices` to iterate through devices instead.
197
101
 
198
- | | |
199
- | ------ | ------ |
200
- | 🔮 Simple, declarative API | đŸŽŦ Based on [Cocos2d](https://docs.cocos2d-x.org/cocos2d-x/v3/en/actions/getting_started.html)/[SKActions](https://developer.apple.com/documentation/spritekit/getting_started_with_actions) |
201
- | 🚀 35+ [built-in actions](#action-initializers), 30+ [timing modes](#timing-modes) | 🔀 Reuseable, chainable & reversible |
202
- | 🍃 No dependencies & tree-shakeable | ⌚ Full speed/pausing control |
203
- | 🤏 `~4.3kb` minzipped | ✨ Supports PixiJS 8+, 7+, 6.3+ |
102
+ ### Navigation API
204
103
 
104
+ By default, any element with `"mousedown"` or `"pointerdown"` handlers is navigatable.
205
105
 
206
- ## Sample Usage
106
+ Container properties | type | default | description
107
+ ---------------------|------|---------|--------------
108
+ `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.
109
+ `isNavigatable` | `get () => boolean` | `false` | returns `true` if `navigationMode` is `"target"`, or `"auto"` and a `"pointerdown"` or `"mousedown"` event handler is registered.
110
+ `navigationPriority` | `number` | `0` | The priority relative to other navigation items in this group.
207
111
 
208
- *Create, configure, and run animations & actions.*
112
+ Navigation intent | Keyboard | Gamepads
113
+ ------------------|------------------------|-----------------------------------
114
+ `"navigateLeft"` | `ArrowLeft`, `KeyA` | Left Joystick (Left), `DPadLeft`
115
+ `"navigateRight"` | `ArrowRight`, `KeyD` | Left Joystick (Right), `DPadRight`
116
+ `"navigateUp"` | `ArrowUp`, `KeyW` | Left Joystick (Up), `DPadDown`
117
+ `"navigateDown"` | `ArrowDown`, `KeyS` | Left Joystick (Down), `DPadUp`
118
+ `"navigateBack"` | `Escape`, `Backspace` | `B`, `Back`
119
+ `"trigger"` | `Enter,` `Space` | `A`
209
120
 
210
- ```ts
211
- // Define an action
212
- const spinAndRemove = Action.sequence([
213
- Action.rotateByDegrees(360, 0.2).easeInOut(),
214
- Action.fadeOut(0.2).easeIn(),
215
- Action.removeFromParent(),
216
- Action.run(() => console.info('✨ done!'))
217
- ]);
218
-
219
- // Run an action
220
- mySprite.run(spinAndRemove);
221
- ```
121
+ Container events | description
122
+ ------------------|--------------------------------------------------------
123
+ `focus` | Target became focused.
124
+ `blur` | Target lost focus.
222
125
 
223
- ## Getting Started with PixiJS Actions
126
+ > [!TIP]
127
+ > Modify `device.options.navigation.binds` to override which keys/buttons are used for navigation.
128
+ >
129
+ > Or set `device.options.navigation.enabled = false` to disable navigation.
224
130
 
225
- *Everything you need to quickly build beautiful animations.*
226
131
 
227
- **PixiJS Actions** is based off the idiomatic and expressive [**SKActions API**](https://developer.apple.com/documentation/spritekit/getting_started_with_actions), extending `Container` to add first-class support for running and managing actions.
132
+ ### Devices
228
133
 
229
- The core concepts are:
134
+ #### Gamepads
230
135
 
231
- 1. **Nodes:** _Any container (e.g. `Container`, `Sprite`, `Graphics`)_
232
- 2. **Actions:** _Stateless, reusable recipes_ (e.g. animations, triggers, and more)
233
- 3. **TimingMode & speed:** _Controls for the speed & smoothness of actions and animations_
136
+ ##### Gamepad Layouts
234
137
 
235
138
  > [!NOTE]
236
- > _See [Timing Modes](#timing-modes) and [Manipulating Action Speed](#manipulating-action-speed) for more information._
237
-
238
-
239
- ## Installation
240
-
241
- *Quick start guide.*
242
-
243
- **1.** Install the latest `pixijs-input-devices` package:
244
-
245
- ```sh
246
- # npm
247
- npm install pixijs-input-devices -D
248
-
249
- # yarn
250
- yarn add pixijs-input-devices --dev
251
- ```
252
-
253
- **2.** Register the mixin & ticker:
254
-
255
- ```ts
256
- import * as PIXI from 'pixi.js';
257
- import { Action, registerPixiJSActionsMixin } from 'pixijs-input-devices';
258
-
259
- // register container mixin
260
- registerPixiJSActionsMixin(PIXI.Container);
261
-
262
- // register `Action.tick(...)` with shared ticker
263
- Ticker.shared.add(ticker => Action.tick(ticker.elapsedMS));
264
- ```
265
-
266
- > [!TIP]
267
- > **PixiJS 7 / 6.3+:**
139
+ > **Gamepad Labels:** The gamepad buttons are aliased with generic standard controller buttons in a Logitech/Xbox/Steam controller layout.
140
+
141
+ | Button | ButtonCode | Name | Generic | Nintendo<br/>(*physical) | Playstation | Xbox |
142
+ |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
143
+ | `0` | `"A"` | **A** | A | B | Cross | A |
144
+ | `1` | `"B"` | **B** | B | A | Circle | B |
145
+ | `2` | `"X"` | **X** | X | Y | Square | X |
146
+ | `3` | `"Y"` | **Y** | Y | X | Triangle | Y |
147
+ | `4` | `"LeftShoulder"` | **Left Shoulder** | LeftShoulder | L | L1 | LB |
148
+ | `5` | `"RightShoulder"` | **Right Shoulder** | RightShoulder | R | R1 | RB |
149
+ | `6` | `"LeftTrigger"` | **Left Trigger** | LeftTrigger | L2 | ZL | LT |
150
+ | `7` | `"RightTrigger"` | **Right Trigger** | RightTrigger | R2 | ZR | RT |
151
+ | `8` | `"Back"` | **Back** | Back | Minus | Options | Back |
152
+ | `9` | `"Start"` | **Start** | Start | Plus | Select | Start |
153
+ | `10` | `"LeftStick"` | **Left Stick** | LeftStick | L3 | LeftStick | LSB |
154
+ | `11` | `"RightStick"` | **Right Stick** | RightStick | R3 | RightStick | RSB |
155
+ | `12` | `"DPadUp"` | **D-Pad Up** | DPadUp | DPadUp | DPadUp | DPadUp |
156
+ | `13` | `"DPadDown"` | **D-Pad Down** | DPadDown | DPadDown | DPadDown | DPadDown |
157
+ | `14` | `"DPadLeft"` | **D-Pad Left** | DPadLeft | DPadLeft | DPadLeft | DPadLeft |
158
+ | `15` | `"DPadRight"` | **D-Pad Right** | DPadRight | DPadRight | DPadRight | DPadRight |
159
+
160
+ > [!CAUTION]
161
+ > ***Nintendo:** Both the labels and physical positions of the A,B,X,Y buttons are different
162
+ > on Nintendo controllers.
163
+ >
164
+ > Set `GamepadDevice.defaultOptions.remapNintendoMode` to apply the remapping as required.
165
+ >
166
+ > - `"physical"` _(default)_ &ndash; A,B,X,Y refer the physical layout of a standard controller (Left=X,Top=Y,Bottom=A,Right=B).
167
+ > - `"accurate"` &ndash; A,B,X,Y refer to the exact Nintendo labels (Left=Y,Top=X,Bottom=B,Right=A).
168
+ > - `"none"` &ndash; A,B,X,Y refer to the button indices 0,1,2,3 (Left=Y,Top=B,Bottom=X,Right=A).
169
+ >
170
+ > ```
171
+ > standard nintendo nintendo nintendo
172
+ > layout "physical" "accurate" "none"
173
+ > reference (default)
268
174
  >
269
- > ```ts
270
- > Ticker.shared.add(() => Action.tick(Ticker.shared.elapsedMS));
271
- > // or
272
- > Ticker.shared.add((dt) => Action.tick(dt));
175
+ > Y Y X B
176
+ > X B X B Y A Y A
177
+ > A A B X
178
+ >
179
+ > 3 3 2 1
180
+ > 2 1 2 1 3 0 3 0
181
+ > 0 0 1 2
273
182
  > ```
274
183
 
275
- > [!NOTE]
276
- > _If not using a PixiJS ticker, then just put `Action.tick(elapsedMs)` in the appropriate equivalent place (i.e. your `requestAnimationFrame()` render loop)._
184
+ #### Device assignment
277
185
 
278
- **3.** Done!
279
-
280
- ✨ You are now ready to run your first action!
281
-
282
- ## Running Actions
283
-
284
- *Running actions in your canvas.*
186
+ You can assign IDs and other meta data using the `device.meta` dictionary.
285
187
 
286
188
  ```ts
287
- // Hide me instantly!
288
- mySprite.run(Action.hide(), () => {
289
- console.log('where did I go?');
290
- });
291
- ```
292
-
293
- Nodes are extended with a few new methods and properties to make it easy to interact with actions.
294
-
295
- | Property | Description |
296
- | :----- | :------ |
297
- | `speed` | A speed modifier applied to all actions executed by the node and its descendants. Defaults to `1.0`. |
298
- | `isPaused` | A boolean value that determines whether actions on the node and its descendants are processed. Defaults to `false`. |
299
-
300
- | Method | Description |
301
- | :----- | :------ |
302
- | `run(action)` | Run an action. |
303
- | `run(action, completion)` | Run an action with a completion handler. |
304
- | `runWithKey(action, withKey)` | Run an action, and store it so it can be retrieved later. |
305
- | `runAsPromise(action): Promise<void>` | Run an action as a promise. |
306
- | `action(forKey): Action \| undefined` | Return an action associated with a specified key. |
307
- | `hasActions(): boolean` | Return a boolean indicating whether the node is executing actions. |
308
- | `removeAllActions(): void` | End and removes all actions from the node. |
309
- | `removeAction(forKey): void` | Remove an action associated with a specified key. |
310
-
311
- ### Running Identifiable Actions
189
+ InputDevice.on("deviceconnected", ({ device }) =>
190
+ // assign!
191
+ device.meta.localPlayerId = 123
192
+ )
312
193
 
313
- ```ts
314
- // Repeat an action forever!
315
- const spin = Action.repeatForever(Action.rotateBy(5, 1.0));
316
- mySprite.runWithKey(spin, 'spinForever');
317
-
318
- // Or remove it later.
319
- mySprite.removeAction('spinForever');
320
- ```
321
-
322
- ### Pausing All Actions
323
-
324
- ```ts
325
- mySprite.isPaused = true;
326
- // All actions will stop running.
327
- ```
328
-
329
- ### Manipulating Action Speed
330
-
331
- Speed can be manipulated on both actions and nodes.
332
-
333
- ```ts
334
- const moveAction = Action.moveByX(10, 4.0);
335
- moveAction.speed = 2.0;
336
- // moveAction will now take only 2 seconds instead of 4.
337
-
338
- const repeatAction = Action.repeat(moveAction, 5);
339
- repeatAction.speed = 2.0;
340
- // Each moveAction will only take 1 second, for a total of 5 seconds.
341
-
342
- mySprite.run(moveAction);
343
- mySprite.speed = 2.0;
344
- // mySprite is running at 2x speed!
345
- // The entire action should now take ~2.5 seconds.
346
-
347
- mySprite.parent!.speed = 1 / 4;
348
- // Now we've slowed down mySprite's parent.
349
- // The entire action will now take ~10 seconds.
350
- ```
351
-
352
- > [!NOTE]
353
- > Changes to nodes' `speed` will take effect immediately, however changes to an `Action` initializer's `speed` or `timingMode` will not affect any actions that have already begun running.
354
-
355
- ## Action Initializers
356
-
357
- *Combine these initializers to create expressive animations and behaviors.*
358
-
359
- Most actions implement specific predefined animations that are ready to use. If your animation needs fall outside of the suite provided here, then you should implement a custom action (see [Creating Custom Actions](#creating-custom-actions)).
360
-
361
- | Action | Description | Reversible? |
362
- | :----- | :---------- | :---------- |
363
- |***Chaining Actions***|||
364
- | `Action.group(actions)` | Run multiple actions in parallel. | Yes |
365
- | `Action.sequence(actions)` | Run multiple actions sequentially. | Yes |
366
- | `Action.repeat(action, count)` | Repeat an action a specified number of times. | Yes |
367
- | `Action.repeatForever(action)` | Repeat an action indefinitely. | Yes |
368
- |***Animating a Node's Position in a Linear Path***|||
369
- | `Action.moveBy(vector, duration)` | Move a node by a relative vector `{ x, y }` (e.g. `PIXI.Point`). | Yes |
370
- | `Action.moveBy(dx, dy, duration)` | Move a node by relative values. | Yes |
371
- | `Action.moveByX(dx, duration)` | Move a node horizontally by a relative value. | Yes |
372
- | `Action.moveByY(dy, duration)` | Move a node vertically by a relative value. | Yes |
373
- | `Action.moveTo(position, duration)` | Move a node to a specified position `{ x, y }` (e.g. `PIXI.Point`, `PIXI.Container`). | _*No_ |
374
- | `Action.moveTo(x, y, duration)` | Move a node to a specified position. | _*No_ |
375
- | `Action.moveToX(x, duration)` | Move a node to a specified horizontal position. | _*No_ |
376
- | `Action.moveToY(y, duration)` | Move a node to a specified vertical position. | _*No_ |
377
- |***Animating a Node's Position Along a Custom Path***|||
378
- | `Action.follow(path, duration)` | Move a node along a path, optionally orienting the node to the path. | Yes | Yes |
379
- | `Action.followAtSpeed(path, speed)` | Move a node along a path at a specified speed, optionally orienting the node to the path. | Yes |
380
- |***Animating the Rotation of a Node***|||
381
- | `Action.rotateBy(delta, duration)` | Rotate a node by a relative value (in radians). | Yes |
382
- | `Action.rotateByDegrees(delta, duration)` | Rotate a node by a relative value (in degrees). | Yes |
383
- | `Action.rotateTo(radians, duration)` | Rotate a node to a specified value (in radians). | _*No_ |
384
- | `Action.rotateToDegrees(degrees, duration)` | Rotate a node to a specified value (in degrees). | _*No_ |
385
- |***Animating the Scaling of a Node***|||
386
- | `Action.scaleBy(vector, duration)` | Scale a node by a relative vector `{ x, y }` (e.g. `PIXI.Point`). | Yes |
387
- | `Action.scaleBy(scale, duration)` | Scale a node by a relative value. | Yes |
388
- | `Action.scaleBy(dx, dy, duration)` | Scale a node in each axis by relative values. | Yes |
389
- | `Action.scaleByX(dx, duration)` | Scale a node horizontally by a relative value. | Yes |
390
- | `Action.scaleByY(dy, duration)` | Scale a node vertically by a relative value. | Yes |
391
- | `Action.scaleTo(size, duration)` | Scale a node to achieve a specified size `{ width, height }` (e.g. `PIXI.ISize`, `PIXI.Container`). | _*No_ |
392
- | `Action.scaleTo(scale, duration)` | Scale a node to a specified value. | _*No_ |
393
- | `Action.scaleTo(x, y, duration)` | Scale a node in each axis to specified values. | _*No_ |
394
- | `Action.scaleToX(x, duration)` | Scale a node horizontally to a specified value. | _*No_ |
395
- | `Action.scaleToY(y, duration)` | Scale a node vertically to a specified value. | _*No_ |
396
- |***Animating the Transparency of a Node***|||
397
- | `Action.fadeIn(duration)` | Fade the alpha to `1.0`. | Yes |
398
- | `Action.fadeOut(duration)` | Fade the alpha to `0.0`. | Yes |
399
- | `Action.fadeAlphaBy(delta, duration)` | Fade the alpha by a relative value. | Yes |
400
- | `Action.fadeAlphaTo(alpha, duration)` | Fade the alpha to a specified value. | _*No_ |
401
- |***Controlling a Node's Visibility***|||
402
- | `Action.unhide()` | Set a node's `visible` property to `true`. | Yes |
403
- | `Action.hide()` | Set a node's `visible` property to `false`. | Yes |
404
- |***Removing a Node from the Canvas***|||
405
- | `Action.removeFromParent()` | Remove a node from its parent. | _†Identical_ |
406
- |***Running Actions on Children***|||
407
- | `Action.runOnChild(nameOrLabel, action)` | Add an action to a child node. | Yes |
408
- |***Delaying Actions***|||
409
- | `Action.waitForDuration(duration)` | Idle for a specified period of time. | _†Identical_ |
410
- | `Action.waitForDurationWithRange(duration, range)` | Idle for a randomized period of time. | _†Identical_ |
411
- |***Triggers and Custom Actions***|||
412
- | `Action.run(callback)` | Execute a block (i.e. trigger another action). | _†Identical_ |
413
- | `Action.customAction(duration, stepHandler)` | Execute a custom stepping function over the action duration. | _†Identical_ |
414
- |***Manipulating the Action Speed of a Node***|||
415
- | `Action.speedBy(delta, duration)` | Change how fast a node executes its actions by a relative value. | Yes |
416
- | `Action.speedTo(speed, duration)` | Set how fast a node executes actions to a specified value. | _*No_ |
417
-
418
- > [!IMPORTANT]
419
- > #### Reversing Actions
420
- > Every action initializer has a `.reversed()` method which will return a new action. Some actions are **not reversible**, and these cases are noted in the table above:
421
- > - _**†Identical**_ &mdash; The reversed action is identical to the original action.
422
- > - _**\*No**_ &mdash; The reversed action will idle for the equivalent duration.
423
-
424
- ### Action Chaining
425
-
426
- Many actions can be joined together using `Action.sequence()`, `Action.group()`, `Action.repeat()` and `Action.repeatForever()` to quickly create complex animations:
427
-
428
- ```ts
429
- import { Action } from 'pixijs-input-devices';
430
-
431
- // Expand and contract smoothly over 2 seconds
432
- const pulsate = Action.sequence([
433
- Action.scaleTo(1.5, 1.0).easeOut(),
434
- Action.scaleTo(1, 1.0).easeIn()
435
- ]);
436
-
437
- // Follow a complex path (e.g. a bezier curve)
438
- const path = [
439
- { x: 0, y: 0 },
440
- { x: 100, y: 0 },
441
- { x: 100, y: 100 },
442
- { x: 200, y: 200 }
443
- ];
444
- const followPath = Action.follow(path, 5.0);
445
-
446
- // Create a 10 second loop that goes back and forth
447
- const moveBackAndForthWhilePulsating = Action.group([
448
- Action.repeat(pulsate, 5),
449
- Action.sequence([followPath, followPath.reversed()]),
450
- ]);
451
-
452
- // ✨ Animate continuously
453
- mySprite.run(Action.repeatForever(moveBackAndForthWhilePulsating));
454
- ```
455
-
456
- ## Timing Modes
457
-
458
- Every action has a `timingMode` which controls the timing curve of its execution.
459
-
460
- The default timingMode for all actions is `TimingMode.linear`, which causes an animation to occur evenly over its duration.
461
-
462
- You can customize the speed curve of actions in many ways:
463
-
464
- ```ts
465
- // Default easings:
466
- Action.fadeIn(0.3).easeIn();
467
- Action.fadeIn(0.3).easeOut();
468
- Action.fadeIn(0.3).easeInOut();
469
-
470
- // Set a specific TimingMode:
471
- Action.fadeIn(0.3).setTimingMode(TimingMode.easeInOutCubic);
472
-
473
- // Set a custom timing function:
474
- Action.fadeIn(0.3).setTimingMode(x => x * x);
475
- ```
476
-
477
- > [!IMPORTANT]
478
- > **Timing Mutators:** The `.easeIn()`, `.easeOut()`, `.easeInOut()`, `setTimingMode(â€Ļ)`, `setSpeed(â€Ļ)` methods mutate the underlying action.
479
-
480
- ### Built-in TimingMode Options
481
-
482
- See the following table for default `TimingMode` options.
483
-
484
- | Pattern | Ease In, Ease Out | Ease In | Ease Out | Description |
485
- | --------------- | ----- | -- | --- | ----------- |
486
- | **Linear** | `linear` | - | - | Constant motion with no acceleration or deceleration. |
487
- | **Sine** | `easeInOutSine` | `easeInSine` | `easeOutSine` | Gentle start and end, with accelerated motion in the middle. |
488
- | **Circular** | `easeInOutCirc` | `easeInCirc` | `easeOutCirc` | Smooth start and end, faster acceleration in the middle, circular motion. |
489
- | **Cubic** | `easeInOutCubic` | `easeInCubic` | `easeOutCubic` | Gradual acceleration and deceleration, smooth motion throughout. |
490
- | **Quadratic** | `easeInOutQuad` | `easeInQuad` | `easeOutQuad` | Smooth acceleration and deceleration, starts and ends slowly, faster in the middle. |
491
- | **Quartic** | `easeInOutQuart` | `easeInQuart` | `easeOutQuart` | Slower start and end, increased acceleration in the middle. |
492
- | **Quintic** | `easeInOutQuint` | `easeInQuint` | `easeOutQuint` | Very gradual start and end, smoother acceleration in the middle. |
493
- | **Exponential** | `easeInOutExpo` | `easeInExpo` | `easeOutExpo` | Very slow start, exponential acceleration, slow end. |
494
- | **Back** | `easeInOutBack` | `easeInBack` | `easeOutBack` | Starts slowly, overshoots slightly, settles into final position. |
495
- | **Bounce** | `easeInOutBounce` | `easeInBounce` | `easeOutBounce` | Bouncy effect at the start or end, with multiple rebounds. |
496
- | **Elastic** | `easeInOutElastic` | `easeInElastic` | `easeOutElastic` | Stretchy motion with overshoot and multiple oscillations. |
497
-
498
- ### Default Timing Modes
499
-
500
- The `.easeIn()`, `.easeOut()`, `.easeInOut()`, and `.linear()` mutator methods on `Action` instances will set the timing mode of that action to the global default timing mode for that curve type.
501
-
502
- | TimingMode mutator | Global setting | Default value |
503
- | :--- | :--- | :--- |
504
- | `action.easeIn()` | `Action.DefaultTimingModeEaseIn` | `TimingMode.easeInSine` |
505
- | `action.easeOut()` | `Action.DefaultTimingModeEaseOut` | `TimingMode.easeOutSine` |
506
- | `action.easeInOut()` | `Action.DefaultTimingModeEaseInOut` | `TimingMode.easeInOutSine` |
507
- | `action.linear()` | _(n/a)_ | `TimingMode.linear` |
508
-
509
- Global default timing modes can be set like so:
510
-
511
- ```ts
512
- // set default
513
- Action.DefaultTimingModeEaseIn = TimingMode.easeInQuad;
514
-
515
- // apply
516
- myNode.run(myAction.easeIn());
517
-
518
- myAction.timingMode
519
- // TimingMode.easeInQuad
520
- ```
521
-
522
- ## Creating Custom Actions
523
-
524
- Beyond combining chaining actions like `sequence()`, `group()`, `repeat()` and `repeatForever()`, you can provide code that implements your own action.
525
-
526
- ### Composite Actions
527
-
528
- Actions are stateless and reusable, so you can create complex animations once, and then run them on many nodes.
529
-
530
- ```ts
531
- /** A nice gentle rock back and forth. */
532
- const rockBackAndForth = Action.repeatForever(
533
- Action.group([
534
- Action.sequence([
535
- Action.moveByX(5, 0.33).easeOut(),
536
- Action.moveByX(-10, 0.34).easeInOut(),
537
- Action.moveByX(5, 0.33).easeIn(),
538
- ]),
539
- Action.sequence([
540
- Action.rotateByDegrees(-2, 0.33).easeOut(),
541
- Action.rotateByDegrees(4, 0.34).easeInOut(),
542
- Action.rotateByDegrees(-2, 0.33).easeIn(),
543
- ]),
544
- ])
545
- );
546
-
547
- // Run it over here
548
- someSprite.run(rockBackAndForth);
549
-
550
- // Run it somewhere else
551
- someOtherContainer.run(rockBackAndForth);
552
- ```
553
-
554
- You can combine these with dynamic actions for more variety:
555
-
556
- ```ts
557
- const MyActions = {
558
- squash: (amount: number, duration: number = 0.3) => Action.sequence([
559
- Action.scaleTo(amount, 1 / amount, duration / 2).easeOut(),
560
- Action.scaleTo(1, duration / 2).easeIn()
561
- ]),
562
- stretch: (amount: number, duration: number = 0.3) => Action.sequence([
563
- Action.scaleTo(1 / amount, amount, duration / 2).easeOut(),
564
- Action.scaleTo(1, duration / 2).easeIn()
565
- ]),
566
- squashAndStretch: (amount: number, duration: number = 0.3) => Action.sequence([
567
- MyActions.squash(amount, duration / 2),
568
- MyActions.stretch(amount, duration / 2),
569
- ]),
570
- };
571
-
572
- // Small squish!
573
- mySprite.run(MyActions.squashAndStretch(1.25));
574
-
575
- // Big squish!
576
- mySprite.run(MyActions.squashAndStretch(2.0));
577
- ```
578
-
579
- ### Custom Action (Basic)
580
-
581
- You can use the built-in `Action.customAction(duration, stepHandler)` to provide custom actions:
582
-
583
- ```ts
584
- const rainbowColors = Action.customAction(5.0, (target, t, dt) => {
585
- // Calculate color based on time "t".
586
- const colorR = Math.sin(0.3 * t + 0) * 127 + 128;
587
- const colorG = Math.sin(0.3 * t + 2) * 127 + 128;
588
- const colorB = Math.sin(0.3 * t + 4) * 127 + 128;
589
-
590
- // Apply random color with time-based variation.
591
- target.tint = (colorR << 16) + (colorG << 8) + colorB;
592
- });
593
-
594
- // Start rainbow effect
595
- mySprite.runWithKey(Action.repeatForever(rainbowColors), 'rainbow');
596
-
597
- // Stop rainbow effect
598
- mySprite.removeAction('rainbow');
194
+ for ( const device of InputDevice.devices )
195
+ {
196
+ if ( device.meta.localPlayerId === 123 )
197
+ {
198
+ // use assigned input device!
199
+ }
200
+ }
599
201
  ```
600
202
 
601
- > **Step functions:**
602
- > - `target` = The node the aciton is runnning against.
603
- > - `t` = Progress of time from 0 to 1, which has been passed through the `timingMode` function.
604
- > - `dt` = delta/change in `t` since last step. Use for relative actions.
605
- >
606
- > _Note: `t` can be outside of 0 and 1 in timing mode functions which overshoot, such as `TimingMode.easeInOutBack`._
607
-
608
- This function will be called as many times as the renderer asks over the course of its duration.
609
-
610
- ### Custom Action (with State)
203
+ #### Custom devices
611
204
 
612
- Here is a practical example:
205
+ You can add custom devices to the device manager:
613
206
 
614
207
  ```ts
615
- // Create a custom action that relies on
616
- // state (radius, inital target position).
617
- const makeOrbitAction = (
618
- radius: number,
619
- duration: number
620
- ): Action => {
621
- let startPos: PIXI.IPointData;
622
-
623
- return Action.customAction(duration, (target, t, td) => {
624
- if (!startPos) {
625
- // Capture on first run
626
- startPos = { x: target.x, y: target.y };
208
+ // 1. subclass CustomDevice
209
+ class MySpecialDevice extends CustomDevice
210
+ {
211
+ constructor() {
212
+ super( "special-device" )
627
213
  }
214
+ }
628
215
 
629
- const angle = Math.PI * 2 * t;
630
-
631
- target.position.set(
632
- startPos.x + radius * Math.cos(angle),
633
- startPos.y + radius * Math.sin(angle)
634
- );
635
- });
636
- };
637
-
638
- // Run the custom action
639
- mySprite.run(
640
- Action.repeatForever(makeOrbitAction(10, 15.0))
641
- );
216
+ // 2. add the device
217
+ const device = new MySpecialDevice();
218
+ InputDevice.add( device )
642
219
  ```