pixijs-input-devices 0.8.1-0 → 0.9.0

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,45 +1,57 @@
1
- # 🎮 PixiJS Input Devices  [![License](https://badgen.net/npm/license/pixijs-input-devices)](https://github.com/reececomo/pixijs-input-devices/blob/main/LICENSE) [![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) [![Downloads per month](https://img.shields.io/npm/dm/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices) [![NPM version](https://img.shields.io/npm/v/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices)
2
-
3
- Simple & powerful input device support for a variety of devices in PixiJS
4
-
5
- | | |
6
- | ------ | ------ |
7
- | 🎮 Enable [keyboard](#keyboard), [gamepads](#gamepaddevice), [and more](#custom-devices) | 🪄 Input [binding](#named-binds) |
8
- | ⚡ Performance [optimized](https://web.dev/articles/inp) | 🚀 Simple APIs ([realtime](#real-time), [events](#keyboarddevice-events)) |
9
- | 🔮 Configurable (and sensible defaults) | 🧭 Navigate [pointer-based UIs](#uinavigation-api) |
10
- | ✅ Cross-platform &amp; mobile-friendly <sup>[[1]](https://caniuse.com/mdn-api_keyboardlayoutmap) [[2]](https://caniuse.com/mdn-api_gamepad_vibrationactuator) [[3]](https://chromestatus.com/feature/5989275208253440)</sup> | 🌐 International layout [support](#keyboard-layout---detection) |
11
- | 🍃 Zero dependencies, tree-shakeable | ✨ Supports PixiJS v8, v7, v6.3+ |
12
-
13
-
14
- #### Configure binds
1
+ <h1 align="center">PixiJS Input Devices</h1>
2
+
3
+ <p align="center"><i>Device management, navigation for pointer-based UIs, and named input bindings.</i></p>
4
+
5
+ <p align="center"><a href="https://github.com/reececomo/pixijs-input-devices/blob/main/LICENSE"><img src="https://badgen.net/npm/license/pixijs-input-devices" alt="License"></a> <a href="https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml"><img src="https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml/badge.svg" alt="Tests"></a> <a href="https://www.npmjs.com/package/pixijs-input-devices"><img src="https://img.shields.io/npm/dm/pixijs-input-devices.svg" alt="Downloads per month"></a> <a href="https://www.npmjs.com/package/pixijs-input-devices"><img src="https://img.shields.io/npm/v/pixijs-input-devices.svg" alt="NPM version"></a></p>
6
+
7
+ <p align="center"><img src="./hero.png" /></p>
8
+
9
+ <table align="center">
10
+ <thead>
11
+ <tr>
12
+ <th></th>
13
+ <th></th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <tr>
18
+ <td>🎮 <a href="#keyboard">Keyboard</a>, <a href="#gamepaddevice">gamepads</a>, <a href="#custom-devices">or custom</a></td>
19
+ <td>✨ Configurable <a href="#named-binds">binds</a></td>
20
+ </tr>
21
+ <tr>
22
+ <td>🔮 Completely customizable</td>
23
+ <td>🧭 Navigate <a href="#uinavigation-api">pointer-based UIs</a></td>
24
+ </tr>
25
+ <tr>
26
+ <td>🎼 Haptic-feedback support<sup><a href="https://caniuse.com/mdn-api_gamepad_vibrationactuator">[1]</a></td>
27
+ <td>🌐 Fully <a href="#keyboard-layout---detection">international</a></td>
28
+ </tr>
29
+ <tr>
30
+ <td>🍃 Lightweight, zero dependencies</td>
31
+ <td>✨ Supports PixiJS v8+</td>
32
+ </tr>
33
+ </tbody>
34
+ </table>
35
+
36
+
37
+ #### Set default binds
15
38
 
16
39
  ```ts
17
- import { InputDevice, GamepadDevice } from "pixijs-input-devices";
40
+ import { KeyboardDevice, GamepadDevice } from "pixijs-input-devices";
18
41
 
19
- // ⌨️ keyboard
20
- InputDevice.keyboard.configureBinds({
21
- jump: ["Space"],
22
- left: ["KeyA", "ArrowLeft"],
23
- right: ["KeyD", "ArrowRight"]
42
+ KeyboardDevice.configureBinds({
43
+ Jump : [ "Space" ],
44
+ Left : [ "KeyA", "ArrowLeft" ],
45
+ Right : [ "KeyD", "ArrowRight" ],
24
46
  });
25
47
 
26
- // 🎮 all gamepads
27
48
  GamepadDevice.configureDefaultBinds({
28
- left: ["DpadLeft"],
29
- right: ["DpadRight"],
30
- jump: ["Face1"]
31
- });
32
-
33
- // 🎮 individual gamepad
34
- InputDevice.gamepads[0].configurBinds({
35
- left: ["LeftStickLeft"],
36
- right: ["LeftStickRight"]
49
+ Jump: [ "Face1" ],
50
+ Left: [ "LeftStickLeft", "DpadLeft" ],
51
+ Right: [ "LeftStickRight", "DpadRight" ],
37
52
  });
38
53
  ```
39
54
 
40
- > [!TIP]
41
- > See [**Named binds**](#named-binds) for more information on configuring devices.
42
-
43
55
  ## Basic Usage
44
56
 
45
57
  ```ts
@@ -48,14 +60,14 @@ let moveX = 0;
48
60
 
49
61
  for (let device of InputDevice.devices)
50
62
  {
51
- if (device.bindDown("jump")) jump = true;
52
- if (device.bindDown("left")) moveX = -1;
53
- if (device.bindDown("right")) moveX = 1;
63
+ if (device.bindDown("Jump")) jump = true;
64
+ if (device.bindDown("Left")) moveX = -1;
65
+ if (device.bindDown("Right")) moveX = 1;
54
66
 
55
- // 🎮 analog
56
- if (device.type === "gamepad" && device.leftJoystick.x)
67
+ if (device.type === "gamepad")
57
68
  {
58
- moveX = device.leftJoystick.x;
69
+ // 🎮 dual analog
70
+ if (device.leftJoystick.x) moveX = device.leftJoystick.x;
59
71
  }
60
72
  }
61
73
  ```
@@ -64,10 +76,42 @@ for (let device of InputDevice.devices)
64
76
 
65
77
  ```ts
66
78
  // targeted
67
- device.onBindDown("menu", ({ device }) => { });
79
+ device.onBindDown("Menu", ({ device }) => { });
68
80
 
69
81
  // global
70
- InputDevice.onBindDown("menu", ({ device }) => { });
82
+ InputDevice.onBindDown("Menu", ({ device }) => { });
83
+ ```
84
+
85
+ #### Setup bind names
86
+
87
+ Add the following snippet to set your bind names. Interface keys are ignored, but may be
88
+ used for grouping.
89
+
90
+ ```ts
91
+ // my-binds.ts
92
+
93
+ export {};
94
+
95
+ declare module "pixijs-input-devices"
96
+ {
97
+ interface BindValues
98
+ {
99
+ inGame:
100
+ | "Jump"
101
+ | "Left"
102
+ | "Right"
103
+ | "Crouch";
104
+
105
+ menu:
106
+ | "Mute"
107
+ | "Pause";
108
+ }
109
+
110
+ interface DeviceMetadata
111
+ {
112
+ playerID?: number;
113
+ }
114
+ }
71
115
  ```
72
116
 
73
117
  ## 💿 Install
@@ -117,15 +161,13 @@ Ticker.shared.add(() => InputDevice.update())
117
161
  **3.** (Optional) enable the UINavigation API
118
162
 
119
163
  ```ts
120
- import * as PIXI from 'pixi.js'
121
- import { UINavigation, registerPixiJSNavigationMixin } from 'pixijs-input-devices'
164
+ const app = new PIXI.Application({ /* … */ });
122
165
 
166
+ // Register navigation mixin
167
+ registerPixiJSNavigationMixin(PIXI.Container);
123
168
 
124
- const app = new PIXI.Application(/*…*/)
125
-
126
- // enable the navigation API
127
- UINavigation.enable(app.stage)
128
- registerPixiJSNavigationMixin(PIXI.Container)
169
+ // Configure the navigation API on the root container
170
+ UINavigation.enable(app.stage);
129
171
  ```
130
172
 
131
173
  ✨ You are now ready to use inputs!
@@ -188,14 +230,15 @@ InputDevice.off("deviceadded") // stop listening
188
230
  | `"lastdevicechanged"` | `{device}` | The _last interacted device_ has changed. |
189
231
 
190
232
 
191
- #### InputDevice - onBindDown() Events
233
+ #### InputDevice - onBind*() Events
192
234
 
193
235
  You may also subscribe globally to **named bind** events:
194
236
 
195
237
  ```ts
196
- InputDevice.onBindDown("my_custom_bind", (event) => {
197
- // a bound input waas triggered
198
- })
238
+ InputDevice
239
+ .onBindDown("MyBind", e => console.debug(e.name + " pressed"))
240
+ .onBindUp("MyBind", e => console.debug(e.name + " released"))
241
+ .onBind("MyBind", e => console.debug(e.name + e.pressed ? " pressed" : " released"));
199
242
  ```
200
243
 
201
244
  ### Keyboard
@@ -451,12 +494,11 @@ These can then be used with either the real-time and event-based APIs.
451
494
  #### Event-based:
452
495
 
453
496
  ```ts
454
- // listen to all devices:
497
+ // listen to ANY device:
455
498
  InputDevice.onBindDown("toggleGraphics", (e) => toggleGraphics())
456
499
 
457
500
  // listen to specific devices:
458
- InputDevice.keyboard.onBindDown("jump", (e) => doJump())
459
- InputDevice.gamepads[0].onBindDown("jump", (e) => doJump())
501
+ device.onBindDown("Jump", (e) => doJump())
460
502
  ```
461
503
 
462
504
  #### Real-time:
@@ -465,13 +507,13 @@ InputDevice.gamepads[0].onBindDown("jump", (e) => doJump())
465
507
  let jump = false, crouch = false, moveX = 0
466
508
 
467
509
  const keyboard = InputDevice.keyboard
468
- if (keyboard.bindDown("jump")) jump = true
510
+ if (keyboard.bindDown("Jump")) jump = true
469
511
  if (keyboard.bindDown("crouch")) crouch = true
470
512
  if (keyboard.key.ArrowLeft) moveX = -1
471
513
  else if (keyboard.key.ArrowRight) moveX = 1
472
514
 
473
515
  for (const gamepad of InputDevice.gamepads) {
474
- if (gamepad.bindDown("jump")) jump = true
516
+ if (gamepad.bindDown("Jump")) jump = true
475
517
  if (gamepad.bindDown("crouch")) crouch = true
476
518
 
477
519
  // gamepads have additional analog inputs
@@ -496,12 +538,19 @@ registerPixiJSNavigationMixin(PIXI.Container)
496
538
 
497
539
  Navigation should now work automatically if your buttons handle these events:
498
540
 
499
- - `"pointerdown"` &ndash; i.e. Trigger / show press effect
541
+ - `"pointerdown"`, `"pointerup"` or `"pointertap"` &ndash; i.e. Trigger / show press effect
542
+ - `"pointerenter"` or `"pointerover"` &ndash; i.e. Select / show hover effect
543
+ - `"pointerleave"` or `"pointerout"` &ndash; i.e. Deselect / reset
500
544
 
501
- But in order to really make use, you should also set:
545
+ You can override these mappings manually:
502
546
 
503
- - `"pointerover"` &ndash; i.e. Select / show hover effect
504
- - `"pointerout"` &ndash; i.e. Deselect / reset
547
+ ```ts
548
+ // defaults:
549
+ UINavigation.options.events.focus = [ "pointerenter", "pointerover" ];
550
+ UINavigation.options.events.blur = [ "pointerleave", "pointerout" ];
551
+ UINavigation.options.events.press = [ "pointerdown" ];
552
+ UINavigation.options.events.release = [ "pointerup", "pointertap" ];
553
+ ```
505
554
 
506
555
  > [!TIP]
507
556
  > 🖱️ **Seamless navigation:** Manually set `UINavigation.focusTarget = <target>`
@@ -512,6 +561,19 @@ But in order to really make use, you should also set:
512
561
  > **Auto-focus:** Set a container's `navigationPriority` to a value above `0`
513
562
  > to become the default selection in a context.
514
563
 
564
+ ### Spatial navigation
565
+
566
+ By default the "NavigateUp", "NavigateLeft", "NavigateRight" and "NavigateDown" events
567
+ use global screen space to move to the nearest UI in that direction, using a heuristic.
568
+
569
+ ### Explicit navigation links:
570
+
571
+ However for tricky UIs you can manually bind navigation links for containers:
572
+
573
+ ```ts
574
+ button1.navigationLinks.up = button2;
575
+ ```
576
+
515
577
  ### How it works
516
578
 
517
579
  The Navigation API is centered around the **UINavigation** manager, which
@@ -526,24 +588,14 @@ responsible for asking the **first responder** whether it can handle the intent.
526
588
  If it returns `false`, any other responders are checked (if they exist),
527
589
  otherwise the default global navigation behavior kicks in.
528
590
 
529
- ### Default Global Navigation Behaviors
530
-
531
- When a navigation action is **not** handled manually by a responder, it is handled in one of the following ways:
532
-
533
- | Action | Behavior |
534
- |---|---|
535
- |`"NavigateBack"`|<ul><li>No action.</li></ul>|
536
- |`"NavigateLeft"`, `"NavigateRight"`, `"NavigateUp"`, `"NavigateDown"`|<ul><li>Looks for the nearest `Container` where `container.isNavigatable` in the direction given, and if found, receives a `"pointerover"` event.</li><li>Additionally, if the newly focused container has registered an event handler for either `"pointerover"`, it will fire that too.</li><li>If we were previously focused on a container, that previous container receives a `"pointerout"` event.</li></ul>|
537
- |`"NavigateActivate"`|<ul><li>Checks if we are currently focused on a container, and then issue a `"pointerdown"` and `"pointerup"` event.</li><li>If the focused container has registered an event handler for either `"pointerdown"`, that event handler will be fired too.</li></ul>|
538
-
539
591
  ### Container Navigatability
540
592
 
541
593
  Containers are extended with a few properties/accessors:
542
594
 
543
595
  | Container properties | type | default | description
544
596
  |---------------------|------|---------|--------------
545
- | `isNavigatable` | `readonly boolean` | `false` | returns `true` if `navigationMode` is set to `"pointer"`, |or is `"auto"` and is interactive.
546
- | `navigationMode` | `"auto"` \| `"pointer"` | `"none"` \| `"auto"` | When set to `"auto"`, a `Container` can be navigated to if it is int
597
+ | `navigatable` | `readonly boolean` | `false` | returns `true` if `navigationMode` is set to `"always"`, |or is `"auto"` and is interactive with at least one of "pointertap", "pointerup" or "pointerdown".
598
+ | `navigationMode` | `"auto"` \| `"always"` | `"none"` \| `"auto"` | When set to `"auto"`, a `Container` can be navigated to if it is int
547
599
  | `navigationPriority` | `number` | `0` | The priority relative to other navigation items in this group.
548
600
 
549
601
  ### Default Binds
@@ -599,30 +651,63 @@ for (const device of InputDevice.devices)
599
651
  You can easily map an on-screen input device using the `CustomDevice` interface.
600
652
 
601
653
  ```ts
602
- export class OnScreenInputContainer extends Container implements CustomDevice {
603
- id = "onscreen"
604
- type = "custom" as const
605
- meta: Record<string, any> = {}
654
+ import { CustomDevice, NamedBind } from "pixijs-input-devices";
606
655
 
607
- inputs = {
608
- moveX: 0.0
609
- jump: false,
610
- }
656
+ export class VirtualInputDevice extends Container implements CustomDevice
657
+ {
658
+ type = "custom" as const;
659
+ id = "virtual-gamepad";
660
+ meta = {};
661
+
662
+ // example game input:
663
+ input = { moveX: 0, jump: false };
664
+
665
+ // views:
666
+ joystick = new GameJoystick({ parent: this, x: -200 });
667
+ button1 = new GameButton({ parent: this, x: 300 });
611
668
 
612
669
  update(now)
613
670
  {
614
- this.inputs.moveX = this._virtualJoystick.x
615
- this.inputs.jump = this._jumpButton.isTouching()
671
+ this.input.moveX = this.joystick.x;
672
+ this.input.jump = this.button1.pressed;
616
673
  }
617
674
 
618
- // e.g. disable named binds for onscreen joysticks:
619
- bindDown(name){ return false }
675
+ // (Optional) integrate named binds
676
+ bindDown(name: NamedBind): boolean
677
+ {
678
+ switch (name) {
679
+ case "Jump":
680
+ return this.button1.pressed;
681
+
682
+ case "Left":
683
+ return this.joystick.x < 0.5;
684
+
685
+ case "Right":
686
+ return this.joystick.x > 0.5;
687
+
688
+ default:
689
+ return false;
690
+ }
691
+ }
620
692
  }
621
693
 
622
- const onscreen = new OnScreenInputContainer()
694
+ const myDevice = new VirtualInputDevice();
695
+
696
+ // enable device
697
+ InputDevice.add(myDevice);
698
+ myDevice.on("destroyed", () => InputDevice.remove(myVirtualDevice))
699
+
700
+ // (Optional) participate in named binds, including navigation:
701
+ myDevice.joystick
702
+ .on("pointermove", debounce(50, () => {
703
+ if (myDevice.joystick.x < 0.5) InputDevice.emitBind("NavigateLeft", myDevice);
704
+ if (myDevice.joystick.x > 0.5) InputDevice.emitBind("NavigateRight", myDevice);
705
+ if (myDevice.joystick.y < 0.5) InputDevice.emitBind("NavigateUp", myDevice);
706
+ if (myDevice.joystick.y > 0.5) InputDevice.emitBind("NavigateDown", myDevice);
707
+ }));
623
708
 
624
- InputDevice.add(onscreen)
625
- InputDevice.remove(onscreen)
709
+ myDevice.button1
710
+ .on("pointertap", () => InputDevice.emitBind("NavigateActivate", myDevice));
626
711
  ```
627
712
 
628
713
  ### Two Users; One Keyboard
@@ -631,45 +716,45 @@ You could set up multiple named inputs:
631
716
 
632
717
  ```ts
633
718
  InputDevice.keyboard.configureBinds({
634
- jump: [ "ArrowUp", "KeyW" ],
635
- defend: [ "ArrowDown", "KeyS" ],
636
- left: [ "ArrowLeft", "KeyA" ],
637
- right: [ "ArrowRight", "KeyD" ],
638
-
639
- p1_jump: [ "KeyW" ],
640
- p1_defend: [ "KeyS" ],
641
- p1_left: [ "KeyA" ],
642
- p1_right: [ "KeyD" ],
643
-
644
- p2_jump: [ "ArrowUp" ],
645
- p2_defend: [ "ArrowDown" ],
646
- p2_left: [ "ArrowLeft" ],
647
- p2_right: [ "ArrowRight" ]
719
+ Jump : [ "ArrowUp", "KeyW" ],
720
+ Crouch : [ "ArrowDown", "KeyS" ],
721
+ Left : [ "ArrowLeft", "KeyA" ],
722
+ Right : [ "ArrowRight", "KeyD" ],
723
+
724
+ P1_Jump : [ "KeyW" ],
725
+ P1_Crouch : [ "KeyS" ],
726
+ P1_Left : [ "KeyA" ],
727
+ P1_Right : [ "KeyD" ],
728
+
729
+ P2_Jump : [ "ArrowUp" ],
730
+ P2_Crouch : [ "ArrowDown" ],
731
+ P2_Left : [ "ArrowLeft" ],
732
+ P2_Right : [ "ArrowRight" ]
648
733
  })
649
734
  ```
650
735
 
651
- and then switch groups depending on the mode:
736
+ and then switch groups depending on the mode/player:
652
737
 
653
738
  ```ts
654
- if (gameMode === "multiplayer")
739
+ if (SINGLE_PLAYER_MODE)
655
740
  {
656
- player1.jump = device.bindDown("p1_jump")
657
- player1.defend = device.bindDown("p1_defend")
658
- player1.moveX += device.bindDown("p1_left") ? -1 : 0
659
- player1.moveX += device.bindDown("p1_right") ? 1 : 0
660
-
661
- player2.jump = device.bindDown("p2_jump")
662
- player2.defend = device.bindDown("p2_defend")
663
- player2.moveX += device.bindDown("p2_left") ? -1 : 0
664
- player2.moveX += device.bindDown("p2_right") ? 1 : 0
741
+ player.jump = device.bindDown("Jump");
742
+ player.defend = device.bindDown("Crouch");
743
+ player.moveX += device.bindDown("Left") ? -1 : 0;
744
+ player.moveX += device.bindDown("Right") ? 1 : 0;
745
+ }
746
+ else if (player.id === 1)
747
+ {
748
+ player.jump = device.bindDown("P1_Jump");
749
+ player.defend = device.bindDown("P1_Crouch");
750
+ player.moveX += device.bindDown("P1_Left") ? -1 : 0;
751
+ player.moveX += device.bindDown("P1_Right") ? 1 : 0;
665
752
  }
666
753
  else
667
754
  {
668
- player1.jump = device.bindDown("jump")
669
- player1.defend = device.bindDown("defend")
670
- player1.moveX += device.bindDown("left") ? -1 : 0
671
- player1.moveX += device.bindDown("right") ? 1 : 0
672
-
673
- updateComputerPlayerInput(player2)
755
+ player.jump = device.bindDown("P2_Jump");
756
+ player.defend = device.bindDown("P2_Crouch");
757
+ player.moveX += device.bindDown("P2_Left") ? -1 : 0;
758
+ player.moveX += device.bindDown("P2_Right") ? 1 : 0;
674
759
  }
675
760
  ```