├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── MergedInput.js ├── MergedInput.js.map ├── MergedInput.min.js ├── MergedInput.min.js.map └── MergedInput.min.map ├── main.d.ts ├── package-lock.json ├── package.json ├── src ├── ButtonCombo.js ├── configs │ ├── bearings.js │ ├── pad_dualshock.js │ ├── pad_generic.js │ ├── pad_unlicensedSNES.js │ └── pad_xbox360.js ├── controlManager.js ├── demo │ ├── assets │ │ ├── gamepad.json │ │ └── gamepad.png │ ├── button.js │ ├── debug.js │ ├── demo.js │ ├── index.html │ ├── inputController.js │ ├── main.js │ └── merged-input-demo.gif └── main.js ├── webpack.build.config.js └── webpack.demo.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = false 9 | indent_style = tab 10 | indent_size = 4 11 | tab_width = 4 12 | 13 | [*.{md,markdown}] 14 | trim_trailing_whitespace = false 15 | insert_final_newline = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # System and IDE files 2 | Thumbs.db 3 | .DS_Store 4 | *.bak* 5 | dev/ 6 | 7 | # Vendors 8 | node_modules/ 9 | 10 | # Build 11 | build/ 12 | /npm-debug.log 13 | *.code-workspace 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gary Stanton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Merged input plugin for Phaser 3 2 | A Phaser 3 plugin to map input from keyboard, gamepad & mouse to player actions. 3 | 4 | The merged input plugin listens to input from keyboard, connected gamepads and the mouse pointer, updating ‘player’ objects that you may interrogate instead of writing separate input handlers in your game. 5 | Each player object contains direction and button actions. These are updated by the corresponding gamepad input, as well as any keys that you assign to the action. 6 | 7 | ## Benefits 8 | . Single place to handle all your input. 9 | . Keyboard, Gamepad & Mouse input is amalgamated. 10 | . Handle input for multiple player objects to easily create multiplayer games. 11 | . Assign and reassign keys to actions for each player, allowing for ‘redefine keys’ function. 12 | . Assign multiple keys to a single action. 13 | . Interrogate current state of all buttons. 14 | . Global events emitted on button down/up. 15 | · (v1.7.0) Plugin specific events for button/keyboard/mouse presses, as well as device changes 16 | . Check for gamepad button presses (i.e. ‘justDown()’ functionality for gamepads) 17 | . Check the last device type used for interaction. 18 | · (v1.4.0) Button mapping to consistent names such as 'RC_X' for the right cluster of buttons 19 | · (v1.4.0) Normalising of gamepad devices, including generating dpad events for gamepads that map them as axis internally 20 | · (v1.8.0) 'ButtonCombos' mimic Phaser's 'KeyCombo' functionality for gamepads. 21 | 22 | ## Installation 23 | 24 | ``` 25 | npm install phaser3-merged-input 26 | ``` 27 | 28 | Then you can either add the plugin to Phaser 3's global configuration: 29 | 30 | ```javascript 31 | const config = { 32 | plugins: { 33 | scene: [ 34 | { 35 | key: "mergedInput", 36 | plugin: MergedInput, 37 | mapping: "mergedInput", 38 | }, 39 | ], 40 | } 41 | }; 42 | ``` 43 | 44 | Or using a scene's local configuration: 45 | 46 | ```javascript 47 | class InputController extends Phaser.Scene { 48 | preload() { 49 | this.load.scenePlugin('mergedInput', MergedInput); 50 | } 51 | ``` 52 | 53 | 54 | ### TypeScript 55 | 56 | If you're using TypeScript, you will also need to add a class member to the scene so TypeScript knows how to type it. 57 | 58 | **Example**: 59 | 60 | ```typescript 61 | class InputController extends Phaser.Scene { 62 | private mergedInput?: MergedInput; 63 | ``` 64 | 65 | If you're using the Phaser global config for the plugin, the member name **must** have the same name as the value the `mapping` property specified in the Phaser configuration above, or the plugin won't work. 66 | 67 | If you're using the scene local plugin, the member name **must** match the key specified in `scenePlugin(key, ...)`. 68 | 69 | --- 70 | 71 | ## Setup 72 | Set up a player object for each player in your game with `addPlayer()`. 73 | Then assign keys to each action with the `defineKey()` function, e.g. 74 | ```javascript 75 | var player1 = mergedInput.addPlayer(0); 76 | mergedInput.defineKey(0, 'UP', 'W') 77 | .defineKey(0, 'DOWN', 'S') 78 | .defineKey(0, 'LEFT', 'A') 79 | .defineKey(0, 'RIGHT', 'D') 80 | .defineKey(0, 'B0', 'U') 81 | .defineKey(0, 'B1', 'I') 82 | .defineKey(0, 'B2', 'O') 83 | .defineKey(0, 'B3', 'P') 84 | 85 | var player2 = mergedInput.addPlayer(1); 86 | mergedInput.defineKey(1, 'UP', 'UP') 87 | .defineKey(1, 'DOWN', 'DOWN') 88 | .defineKey(1, 'LEFT', 'LEFT') 89 | .defineKey(1, 'RIGHT', 'RIGHT') 90 | .defineKey(1, 'B0', 'NUMPAD_0') 91 | .defineKey(1, 'B1', 'NUMPAD_1') 92 | .defineKey(1, 'B2', 'NUMPAD_2') 93 | .defineKey(1, 'B3', 'NUMPAD_3') 94 | ``` 95 | 96 | ### NEW in v1.4.0 97 | You may now choose to use 'mapped button names' to define keys, instead of button numbers. 98 | The plugin will attempt to map each button to the corresponding number, depending on the type of joypad entered. 99 | So, instead of using B0, which is the 'A' button on an Xbox controller, but the 'B' button on an 8-bit Do controller, and the 'X' button on a GeeekPi controller, you can now use 'RC_S' for 'Right cluster: South' - for a more consistent approach. 100 | ```javascript 101 | var player1 = mergedInput.addPlayer(0); 102 | mergedInput.defineKey(0, 'UP', 'W') 103 | .defineKey(0, 'DOWN', 'S') 104 | .defineKey(0, 'LEFT', 'A') 105 | .defineKey(0, 'RIGHT', 'D') 106 | .defineKey(0, 'RC_S', 'U') 107 | .defineKey(0, 'RC_E', 'I') 108 | .defineKey(0, 'RC_W', 'O') 109 | .defineKey(0, 'RC_N', 'P') 110 | ``` 111 | 112 | Then, interrogate your player objects to check for the state of the _action_, rather than the key, e.g. 113 | ```javascript 114 | if(player1.direction.DOWN) { 115 | // Move your player down. This will remain true for as long as the down button is depressed. 116 | } 117 | 118 | if(player2.buttons.B0 > 0) { 119 | // Player two is pressing the first button. This will remain true for as long as B0 is depressed. 120 | } 121 | 122 | if(player1.buttons_mapped.RC_W > 0) { 123 | // Player one is pressing left button in the right cluster. This will remain true for as long as the button is depressed. 124 | } 125 | 126 | if(player1.buttons_mapped.START > 0) { 127 | // Player one is pressing what the plugin considers to be the 'start' button - depending on the controller config. 128 | } 129 | 130 | if(player1.interaction.device == 'gamepad') { 131 | // Player one is using a gamepad, you may wish to update your prompts accordingly. 132 | } 133 | 134 | if (['B8', 'B9', 'B0'].filter(x => player1.interaction.pressed.includes(x)).length) { 135 | // Player one has just pressed one of the following buttons - B8, B9 or B0. 136 | // The 'pressed' interaction flag differs from interrogating the buttons directly. It will contain the button(s) pressed for a single update tick, as it happens. 137 | // Here we're comparing an array of button names to the array of buttons pressed in the step. 138 | } 139 | 140 | // NEW in v1.6.0 141 | if (player1.interaction.isPressed(['RC_S', 'LC_E'])) { 142 | // Player one has just pressed one of the following buttons - Right cluster: South or Left cluster (DPad): East. 143 | // Instead of comparing arrays directly as above, we're using the included helper function here, which will return any matching buttons that were pressed in this update step. 144 | } 145 | ``` 146 | 147 | ### New in v1.7.0 148 | A new plugin specific eventEmitter instance exists at `mergedInput.events`. 149 | You may use this across your game to listen for keypresses, button presses and device changes (i.e. moving from using the keyboard to a gamepad). 150 | 151 | 152 | ### New in v1.8.0 153 | BUTTON COMBOS ARE HERE!! 154 | A new 'ButtonCombo' exists in the merged input plugin to mimic Phaser's native KeyCombos for gamepad/player combinations. 155 | Button combos emit `buttoncombomatch` events. 156 | Setting them up is easy: 157 | ```javascript 158 | let combos_konami = mergedInput.createButtonCombo(player1, ['UP', 'UP', 'DOWN', 'DOWN', 'LEFT', 'RIGHT', 'LEFT', 'RIGHT', 'RC_E', 'RC_S'], { resetOnMatch: true }); 159 | combos_konami.name = 'Konami Code'; 160 | 161 | mergedInput.events.on('buttoncombomatch', event => { 162 | console.log(`Player: ${event.player.index} entered: ${event.combo.name}!`); 163 | }); 164 | ``` 165 | 166 | Note that combo checking only occurrs on gamepad actions. Keyboard combos are still handled by Phaser. 167 | 168 | 169 | 170 | 171 | ## Demo / Dev 172 | A demo scene is included in the repository. 173 | The demo has been updated to incorporate the mapped buttons and interactions included in v1.4.0 and the helper functions added in v1.6.0 174 | 175 |  176 | 177 | Install with `npm install`, then use `npm run dev` to spin up a development server and run the demo scene. 178 | 179 | 180 | ## Build plugin 181 | Build the plugin including minified version. Targets the dist folder. 182 | `npm run build` 183 | 184 | ## Changelog 185 | 186 | v1.9.0 - 2025-04-20 187 | Updates to pointer handling, in relation to player position. 188 | Updates to build dependancies. 189 | 190 | v1.8.6 - 2024-10-06 191 | Added axis threshold, below which an analogue stick will not generate a value. 192 | Previously this was hardcoded at 0.5 to avoid drift, but you may now change this via `setAxisThreshold(0.2)` 193 | 194 | v1.8.5 - 2024-05-15 195 | Bugfix: Gamepad button combo events were missing timestamps. 196 | Bugfix: Incorrect keyboard event states firing. (Thanks to @brntns) 197 | 198 | v1.8.4 - 2023-11-26 199 | Bugfix: Mouse pointer checkDown function timers were handled incorrrectly. 200 | 201 | v1.8.3 - 2023-11-18 202 | Bugfix: When using a joypad that maps direction buttons to the left axis, the fake DPad functionality was not mimicking button number value changes for `buttons` and `buttons_mapped`. 203 | 204 | v1.8.2 - 2023-11-13 205 | Bugfix: Gamepad button release was not freeing the timer's tick var. 206 | 207 | v1.8.1 - 2023-10-29 208 | Added mouse pointers to checkDown function. 209 | Fixed issue with generic player helper functions when player not fully initialised. 210 | 211 | v1.8.0 - 2023-10-29 212 | Added new ButtonCombos, to mimic Phaser's KeyCombos with gamepad buttons. 213 | Added timers to button presses, we're now able to retrieve a pressed, released, and duration value. 214 | Added extra helper functions to the player object, including `isDown` and `checkDown` to mimic Phaser's keyboard handling with merged input. 215 | Player helper objects are now able to be called directly on the player object and will accept either mapped or unmapped button actions. 216 | 217 | v1.7.0 - 2023-10-15 218 | Added a new plugin specific instance of the event emitter. 219 | The old 'mergedInput' events continue to fire on the scene's emitter; however as they are all the same event with extra data, you need to listen to all every 'mergedInput' event and filter for the ones you need. 220 | The new plugin specific instance allows you to listen only to the events you need. 221 | 222 | v1.6.1 - 2023-06-01 223 | Updated pointer events to only be set when adding the first player. 224 | Pointer events now check for player object. 225 | Updated typings 226 | With many thanks to @Dan-Mizu for help with this release. 227 | 228 | v1.6.0 - 2022-12-05 229 | Improved handling of the 'pressed' and 'released' events. Previously it was possible to miss a press event if two happened within the same update step. 230 | **IMPORTANT:** The `pressed` & `released` properties under the player's `interaction` object has changed from a string to an array, to allow for multiple values in an update step. 231 | Any code that checks these properties should be updated to expect an array of one or more values. 232 | New helper functions `isPressed()` and `isReleased()` have been added to the `interaction` and `interaction_mapped` properties of the player object. 233 | Use these to check if one or more buttons were pressed/released in the current update step. See the demo for more details. 234 | 235 | v1.5.0 - 2022-08-22 236 | When the game loses focus, the plugin will now reset each of the defined keys to avoid them getting stuck when returning to the game. 237 | 238 | v1.4.0 - 2022-07-03 239 | Added normalisation of gamepad devices, using mapping files located in the new `configs` folder. 240 | Added friendly mapped button names, and a new batch of properties under `interaction_mapped` and `buttons_mapped`. 241 | Added fake DPad functionality to better handle joypads that map their DPads to the left axis, instead of the standard buttons 12-15. 242 | Added a debug scene to the demo. 243 | 244 | v1.3.1 - 2022-03-11 245 | Fixed missing code caused by bad merge! 246 | Added keywords 247 | Clean up readme.md 248 | 249 | v1.3.0 - 2022-03-10 250 | Migrated keyboard interaction flags from the `justDown` and `justUp` key functions, to instead use the keyboard's `keyDown` and `keyUp` events. 251 | This way we maintain consistancy between keyboard and gamepad interactions, as events trigger before the scene's update call. 252 | Added a new `released` key to the interaction object to indicate when a button has been released. 253 | Added a new `lastPressed` and `lastReleased` key, to replace the existing `pressed` key - the old `pressed` key remains for backwards compatability. 254 | Added TypeScript support. 255 | With many thanks to @zewa666 and @bbugh for help with this release. 256 | 257 | v1.2.8 - 2021-07-23 258 | Added gamepad directions to interaction buffer/presses to match keyboard interactions. 259 | 260 | v1.2.7 - 2021-07-06 261 | Changed the order of buffer/pressed checking in update loop. 262 | 263 | v1.2.6 - 2021-05-04 264 | Guess who forgot to build again?? 265 | 266 | v1.2.5 - 2021-05-04 267 | Updated buttondown and buttonup event listeners from per pad, to per input system. 268 | It seems the per pad listeners weren't firing for pad 2 and this method works around the problem. 269 | Also added an addPlayer call if the corresponding player is missing. 270 | Updated phaser dependancy 271 | 272 | v1.2.4 - 2020-05-08 273 | And again, remembering to include the built files would be a bonus. 274 | 275 | v1.2.3 - 2020-05-08 276 | Added extra handling for 'null' gamepads. 277 | 278 | v1.2.2 - 2020-05-03 279 | Added secondary direction key detection, so that secondary directions may be instigated through a keypress as well as the right stick of a gamepad. 280 | Added timestamps to interactions making it possible to tell which was last used, e.g. keyboard vs mouse. 281 | 282 | v1.2.1 - 2020-04-27 283 | Actually added the build files. 284 | 285 | v1.2.0 - 2020-04-27 286 | You are now able to pass a player's X/Y position to a player object, whereupon the position of the mouse in relation to that player will be used to determine mouse bearings and degrees 287 | 288 | v1.1.0 - 2020-04-19 289 | Plugin now handles secondary directional movement from the second stick on a gamepad. 290 | Bearings and degrees have been added to direction objects. 291 | 292 | 293 | ## Credits 294 | Written by [Gary Stanton](https://garystanton.co.uk) 295 | Built from the [Plugin Starter Kit](https://github.com/nkholski/phaser-plugin-starter) by Niklas Berg 296 | Demo sprites by [Nicolae Berbece](https://opengameart.org/content/free-keyboard-and-controllers-prompts-pack) 297 | 298 | --- 299 | 300 | ## Functions 301 | 302 |
Add a new player object to the players array.
If an index is provided and a player object at that index already exists, this will be returned instead of another object created
Get player object
308 |Returns a struct to hold input control information 311 | Set up a struct for each player in the game 312 | Direction and Buttons contain the input from the devices 313 | The keys struct contains arrays of keyboard characters or mouse buttons that will trigger the action
314 |Define a key for a player/action combination
317 |A ButtonCombo will listen for a specific combination of buttons from the given player's gamepad, and when it receives them it will emit a buttoncombomatch event.
320 |Pass one or more button names to check whether one or more buttons were pressed during an update tick.
323 |Pass one or more button names to check whether one or more buttons were released during an update tick.
326 |Pass one or more button names to check whether one or more buttons are held down during an update tick.
329 |Pass one or more button names to check whether one or more buttons are held down during an update tick. You may provide a duration to this method and it will return true every X milliseconds.
332 |number
|
343 |
344 |
345 |
346 |
347 | ### getPlayer(index)
348 | Get player object
349 |
350 | | Param | Type |
351 | | --- | --- |
352 | | thisPlayer | number
|
353 |
354 |
355 |
356 |
357 | ### defineKey(player, action, value, append)
358 | Define a key for a player/action combination
359 | | Param | Type | |
360 | | --- | --- | --- |
361 | | player | number
| The player ID on which we're defining a key |
362 | | action | string
| The action to define |
363 | | value | string
| The key to use |
364 | | append | boolean
| When true, this key definition will be appended to the existing key(s) for this action |
365 |
366 |
367 |
368 | ### createButtonCombo(player, buttons, [config])
369 | A ButtonCombo will listen for a specific combination of buttons from the given player's gamepad, and when it receives them it will emit a buttoncombomatch event.
370 |
371 | | Param | Type | |
372 | | --- | --- | --- |
373 | | player | object
| The player object on which we're defining a key |
374 | | buttons | array
| An array of buttons to act as the combo. You may use directions ['UP'], button IDs ['B12'] or mapped buttons ['LC_N'] |
375 | | config | Phaser.Types.Input.Keyboard.KeyComboConfig
| A Key Combo configuration object. Uses the same config as Phaser's native KeyCombo classes |
376 |
377 |
378 |
379 |
380 | ### {player}.isPressed(button)
381 | Check if button(s) were pressed during an update tick
382 |
383 | | Param | Type |
384 | | --- | --- |
385 | | button | string/array
|
386 |
387 |
388 |
389 |
390 | ### {player}.isReleased(button)
391 | Check if button(s) were released during an update tick
392 |
393 | | Param | Type |
394 | | --- | --- |
395 | | button | string/array
|
396 |
397 |
398 |
399 | ### {player}.isDown(button)
400 | Check if button(s) were held down during an update tick
401 |
402 | | Param | Type |
403 | | --- | --- |
404 | | button | string/array
|
405 |
406 |
407 |
408 | ### {player}.checkDown(button)
409 | Check if button(s) were held down during an update tick
410 | You may provide a duration to this method and it will return true every X milliseconds.
411 |
412 | | Param | Type |
413 | | --- | --- |
414 | | button | string/array
|
415 | | duration | number
| The duration which must have elapsed before this button is considered as being down.
416 | | includeFirst | boolean
| When true, include the first press of a button, otherwise wait for the first passing of the duration.
417 |
418 |
419 |
420 |
421 | ## Events
422 |
423 | | Event | Description | Data |
424 | | --- | --- | --- |
425 | | gamepad_connected | Gamepad is connected | gamepad instance |
426 | | device_changed | The last input device has changed | last device used (keyboard/gamepad/mouse) |
427 | | keyboard_keydown | Keyboard key pressed | player: player instance, key: keycode pressed |
428 | | keyboard_keyup | Keyboard key released | player: player instance, key: keycode pressed |
429 | | gamepad_buttondown | Gamepad button pressed | player: player instance, button: button number pressed |
430 | | gamepad_buttonup | Gamepad button released | player: player instance, button: button number released |
431 | | gamepad_directiondown | Gamepad D-Pad pressed | player: player instance, direction: D-Pad direction pressed |
432 | | gamepad_directionup | Gamepad D-Pad released | player: player instance, direction: D-Pad direction released |
433 | | gamepad_directionup | Gamepad D-Pad released | player: player instance, direction: D-Pad direction released |
434 | | pointer_down | Mouse button pressed | button number pressed |
435 | | pointer_up | Mouse button released | button number released |
436 | | buttoncombomatch | A button combo match has occurred | player: player instance, combo: The ButtonCombo object that matched |
--------------------------------------------------------------------------------
/main.d.ts:
--------------------------------------------------------------------------------
1 | declare module "phaser3-merged-input" {
2 | import * as Phaser from "phaser";
3 |
4 | export type KeyCode = keyof typeof Phaser.Input.Keyboard.KeyCodes;
5 | export type Bearing =
6 | | ""
7 | | "W"
8 | | "NW"
9 | | "N"
10 | | "NE"
11 | | "E"
12 | | "SE"
13 | | "S"
14 | | "SW";
15 | export interface Player {
16 | direction: {
17 | UP: 0 | 1;
18 | DOWN: 0 | 1;
19 | LEFT: 0 | 1;
20 | RIGHT: 0 | 1;
21 | BEARING: Bearing;
22 | BEARING_LAST: Bearing;
23 | DEGREES: number;
24 | DEGREES_LAST: number;
25 | TIMESTAMP: number;
26 | };
27 |
28 | direction_secondary: {
29 | UP: 0 | 1;
30 | DOWN: 0 | 1;
31 | LEFT: 0 | 1;
32 | RIGHT: 0 | 1;
33 | BEARING: Bearing;
34 | BEARING_LAST: Bearing;
35 | DEGREES: number;
36 | DEGREES_LAST: number;
37 | TIMESTAMP: number;
38 | };
39 |
40 | buttons: {
41 | B1: 0 | 1;
42 | B2: 0 | 1;
43 | B3: 0 | 1;
44 | B4: 0 | 1;
45 | B5: 0 | 1;
46 | B6: 0 | 1;
47 | B7: 0 | 1;
48 | B8: 0 | 1;
49 | B9: 0 | 1;
50 | B10: 0 | 1;
51 | B11: 0 | 1;
52 | B12: 0 | 1;
53 | B13: 0 | 1;
54 | B14: 0 | 1;
55 | B15: 0 | 1;
56 | B16: 0 | 1;
57 | };
58 |
59 | pointer: {
60 | M1: 0 | 1;
61 | M2: 0 | 1;
62 | M3: 0 | 1;
63 | M4: 0 | 1;
64 | M5: 0 | 1;
65 | BEARING: Bearing;
66 | BEARING_DEGREES: number;
67 | ANGLE: number;
68 | TIMESTAMP: number;
69 | };
70 |
71 | position: {};
72 | interaction: {
73 | buffer: string[];
74 | device: string;
75 | pressed: string[];
76 | released: string[];
77 | lastPressed: string;
78 | lastReleased: string;
79 | };
80 | interaction_mapped: {
81 | isPressed: (button: string | string[]) => boolean;
82 | };
83 | gamepad: {
84 | id: number;
85 | index: number;
86 | };
87 | keys: {
88 | UP: [];
89 | DOWN: [];
90 | LEFT: [];
91 | RIGHT: [];
92 | B1: [];
93 | B2: [];
94 | B3: [];
95 | B4: [];
96 | B5: [];
97 | B6: [];
98 | B7: [];
99 | B8: [];
100 | B9: [];
101 | B10: [];
102 | B11: [];
103 | B12: [];
104 | B13: [];
105 | B14: [];
106 | B15: [];
107 | B16: [];
108 | };
109 | setPosition: (x: number, y: number) => void;
110 | internal: {
111 | fakedpadBuffer: string[];
112 | fakedpadPressed: string[];
113 | fakedpadReleased: string[];
114 | };
115 | }
116 |
117 | export default class MergedInput {
118 | /**
119 | * The Merged Input plugin is designed to run in the background and handle input.
120 | * Upon detecting a keypress or gamepad interaction, the plugin will update a player object and emit global events.
121 | *
122 | * @extends Phaser.Scene
123 | * @param {*} scene
124 | * @param {*} pluginManager
125 | */
126 | constructor(scene: any, pluginManager: any);
127 | scene: any;
128 | players: Player[];
129 | gamepads: any[];
130 | keys: {};
131 | bearings: {
132 | "-180": string;
133 | "-168.75": string;
134 | "-157.5": string;
135 | "-146.25": string;
136 | "-135": string;
137 | "-123.75": string;
138 | "-112.5": string;
139 | "-101.25": string;
140 | "-90": string;
141 | "-78.75": string;
142 | "-67.5": string;
143 | "-56.25": string;
144 | "-45": string;
145 | "-33.75": string;
146 | "-22.5": string;
147 | "-11.25": string;
148 | "0": string;
149 | "11.25": string;
150 | "22.5": string;
151 | "33.75": string;
152 | "45": string;
153 | "56.25": string;
154 | "67.5": string;
155 | "78.75": string;
156 | "90": string;
157 | "101.25": string;
158 | "112.5": string;
159 | "123.75": string;
160 | "135": string;
161 | "146.25": string;
162 | "157.5": string;
163 | "168.75": string;
164 | "180": string;
165 | };
166 | refreshGamepads(): void;
167 | boot(): void;
168 | eventEmitter: any;
169 | preupdate(): void;
170 | postupdate(): void;
171 | /**
172 | * Set up the gamepad and associate with a player object
173 | */
174 | setupGamepad(thisGamepad: any): void;
175 | /**
176 | * Add a new player object to the players array
177 | * @param {number} index Player index - if a player object at this index already exists, it will be returned instead of creating a new player object
178 | */
179 | addPlayer(index: number): Player;
180 | /**
181 | * Get player object
182 | * @param {number} index Player index
183 | */
184 | getPlayer(index: number): any;
185 | getPlayerIndexFromKey(key: any): any;
186 | getPlayerButtonFromKey(key): any;
187 | /**
188 | * Returns a struct to hold input control information
189 | * Set up a struct for each player in the game
190 | * Direction and Buttons contain the input from the devices
191 | * The keys struct contains arrays of keyboard characters that will trigger the action
192 | */
193 | setupControls(): {
194 | direction: {
195 | UP: number;
196 | DOWN: number;
197 | LEFT: number;
198 | RIGHT: number;
199 | BEARING: string;
200 | BEARING_LAST: string;
201 | DEGREES: number;
202 | DEGREES_LAST: number;
203 | TIMESTAMP: number;
204 | };
205 | direction_secondary: {
206 | UP: number;
207 | DOWN: number;
208 | LEFT: number;
209 | RIGHT: number;
210 | BEARING: string;
211 | DEGREES: number;
212 | BEARING_LAST: string;
213 | DEGREES_LAST: number;
214 | TIMESTAMP: number;
215 | };
216 | buttons: {};
217 | pointer: {
218 | M1: number;
219 | M2: number;
220 | M3: number;
221 | M4: number;
222 | M5: number;
223 | BEARING: string;
224 | BEARING_DEGREES: number;
225 | ANGLE: number;
226 | TIMESTAMP: number;
227 | };
228 | position: {};
229 | interaction: {
230 | buffer: string[];
231 | device: string;
232 | pressed: string[];
233 | released: string[];
234 | lastPressed: string;
235 | lastReleased: string;
236 | };
237 | gamepad: {};
238 | keys: {
239 | UP: any[];
240 | DOWN: any[];
241 | LEFT: any[];
242 | RIGHT: any[];
243 | };
244 | };
245 | /**
246 | * Define a key for a player/action combination
247 | * @param {number} player The player on which we're defining a key
248 | * @param {string} action The action to define
249 | * @param {string} value The key to use
250 | * @param {boolean} append When true, this key definition will be appended to the existing key(s) for this action
251 | */
252 | defineKey(
253 | player: number,
254 | action: string,
255 | value: KeyCode,
256 | append?: boolean
257 | ): MergedInput;
258 | /**
259 | * Iterate through players and check for interaction with defined keys
260 | */
261 | checkKeyboardInput(): void;
262 | /**
263 | * When a keyboard button is pressed down, this function will emit a mergedInput event in the global registry.
264 | * The event contains a reference to the player assigned to the key, and passes a mapped action and value
265 | */
266 | keyboardKeyDown(event: KeyboardEvent): void;
267 | /**
268 | * When a keyboard button is released, this function will emit a mergedInput event in the global registry.
269 | * The event contains a reference to the player assigned to the key, and passes a mapped action and value
270 | */
271 | keyboardKeyUp(event: KeyboardEvent): void;
272 | /**
273 | * Iterate through players and check for interaction with defined pointer buttons
274 | */
275 | checkPointerInput(): void;
276 | /**
277 | * When a gamepad button is pressed down, this function will emit a mergedInput event in the global registry.
278 | * The event contains a reference to the player assigned to the gamepad, and passes a mapped action and value
279 | * @param {number} index Button index
280 | * @param {number} value Button value
281 | * @param {Phaser.Input.Gamepad.Button} button Phaser Button object
282 | */
283 | gamepadButtonDown(
284 | pad: any,
285 | button: Phaser.Input.Gamepad.Button,
286 | value: number
287 | ): void;
288 | /**
289 | * When a gamepad button is released, this function will emit a mergedInput event in the global registry.
290 | * The event contains a reference to the player assigned to the gamepad, and passes a mapped action and value
291 | * @param {number} index Button index
292 | * @param {number} value Button value
293 | * @param {Phaser.Input.Gamepad.Button} button Phaser Button object
294 | */
295 | gamepadButtonUp(
296 | pad: any,
297 | button: Phaser.Input.Gamepad.Button,
298 | value: number
299 | ): void;
300 | /**
301 | * Iterate through gamepads and handle interactions
302 | */
303 | checkGamepadInput(): void;
304 | /**
305 | * Function to run on pointer move.
306 | * @param {*} pointer - The pointer object
307 | */
308 | pointerMove(pointer: any, threshold: any): void;
309 | /**
310 | * Function to run on pointer down. Indicates that Mx has been pressed, which should be listened to by the player object
311 | * @param {*} pointer - The pointer object
312 | */
313 | pointerDown(pointer: any): void;
314 | /**
315 | * Function to run on pointer up. Indicates that Mx has been released, which should be listened to by the player object
316 | * @param {*} pointer - The pointer object
317 | */
318 | pointerUp(pointer: any): void;
319 | /**
320 | * Get the bearing from a given angle
321 | * @param {float} angle - Angle to use
322 | * @param {number} numDirections - Number of possible directions (e.g. 4 for N/S/E/W)
323 | */
324 | getBearingFromAngle(
325 | angle: number,
326 | numDirections: number,
327 | threshold: any
328 | ): any;
329 | /**
330 | * Given a bearing, return a direction object containing boolean flags for the four directions
331 | * @param {*} bearing
332 | */
333 | mapBearingToDirections(bearing: any): {
334 | UP: number;
335 | DOWN: number;
336 | LEFT: number;
337 | RIGHT: number;
338 | BEARING: any;
339 | };
340 | /**
341 | * Given a directions object, return the applicable bearing (8 way only)
342 | * @param {*} directions
343 | */
344 | mapDirectionsToBearing(directions: any, threshold: any): Bearing;
345 | /**
346 | * Given a bearing, return the snapped angle in degrees
347 | * @param {*} bearing
348 | */
349 | mapBearingToDegrees(bearing: any): any;
350 | destroy(): void;
351 | /**
352 | * Return debug object
353 | */
354 | debug(): {
355 | input: {};
356 | };
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phaser3-merged-input",
3 | "version": "1.9.0",
4 | "description": "A Phaser 3 plugin to handle input from keyboard, gamepad & mouse, allowing for easy key definition and multiplayer input",
5 | "main": "src/main.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.build.config.js",
8 | "demo": "webpack serve --config webpack.demo.config.js",
9 | "dev": "webpack serve --config webpack.demo.config.js",
10 | "test": "echo \"No test specified\""
11 | },
12 | "types": "main.d.ts",
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/garystanton/phaser3-merged-input.git"
16 | },
17 | "keywords": [
18 | "phaser",
19 | "phaser-plugin",
20 | "phaser3",
21 | "phaser3-plugin",
22 | "phaser3-gamepad",
23 | "gamepad"
24 | ],
25 | "author": {
26 | "name": "Gary Stanton",
27 | "url": "https://sleepyada.com"
28 | },
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/garystanton/phaser3-merged-input/issues"
32 | },
33 | "homepage": "https://github.com/garystanton/phaser3-merged-input#readme",
34 | "devDependencies": {
35 | "@babel/core": "^7.22.0",
36 | "@babel/preset-env": "^7.22.0",
37 | "babel-loader": "^8.3.0",
38 | "copy-webpack-plugin": "^13.0.0",
39 | "core-js": "^3.41.0",
40 | "dat.gui": "^0.7.9",
41 | "html-webpack-plugin": "^5.5.0",
42 | "phaser": "^3.60.0",
43 | "regenerator-runtime": "^0.14.1",
44 | "terser-webpack-plugin": "^5.3.14",
45 | "webpack": "^5.88.0",
46 | "webpack-cli": "^5.1.4",
47 | "webpack-dev-server": "^5.2.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/ButtonCombo.js:
--------------------------------------------------------------------------------
1 | import AdvanceKeyCombo from 'phaser/src/input/keyboard/combo/AdvanceKeyCombo.js';
2 | import ResetKeyCombo from 'phaser/src/input/keyboard/combo/ResetKeyCombo.js';
3 |
4 | export default class ButtonCombo extends Phaser.Input.Keyboard.KeyCombo {
5 | constructor(mergedInput, player, buttons, config) {
6 | super(mergedInput.systems.input.keyboard, buttons, config);
7 |
8 | this.player = player;
9 | this.mergedInput = mergedInput;
10 | this.keyCodes = buttons; // KeyCombo expects this to be an array of keycodes, we'll be checking against button names
11 |
12 | mergedInput.events.on('gamepad_buttondown', this.onButtonDown, this);
13 | this.current = this.keyCodes[0];
14 | }
15 |
16 | onButtonDown(event) {
17 | if (this.matched || !this.enabled) {
18 | return;
19 | }
20 |
21 | var matched = this.ProcessButtonCombo(event, this);
22 | if (matched) {
23 | this.mergedInput.eventEmitter.emit('mergedInput', { combo: this, player: this.player, action: 'Button combo matched' });
24 | this.mergedInput.events.emit('buttoncombomatch', { player: this.player, combo: this });
25 |
26 | if (this.resetOnMatch) {
27 | ResetKeyCombo(this);
28 | }
29 | else if (this.deleteOnMatch) {
30 | this.destroy();
31 | }
32 | }
33 | }
34 |
35 | ProcessButtonCombo (event, combo) {
36 | // Set a timestamp from the gamepad
37 | event.timeStamp = this.mergedInput.systems.time.now
38 |
39 | // Don't check buttons on a different pad
40 | if (combo.player.index !== event.player) {
41 | return false;
42 | }
43 |
44 | // Check matched
45 | if (combo.matched) {
46 | return true;
47 | }
48 |
49 | // Compare the current action with the button pressed
50 | let buttonMatch = false;
51 | if (event.button === combo.current) {
52 | buttonMatch = true;
53 | }
54 |
55 | let mappedButton = this.mergedInput.getMappedButton(combo.player, event.button);
56 | if (mappedButton === combo.current) {
57 | buttonMatch = true;
58 | }
59 |
60 | let unMappedButton = this.mergedInput.getUnmappedButton(combo.player, mappedButton);
61 | if (unMappedButton === combo.current) {
62 | buttonMatch = true;
63 | }
64 |
65 | var comboMatched = false;
66 | var keyMatched = false;
67 |
68 | if (buttonMatch) {
69 | // Button was correct
70 |
71 | if (combo.index > 0 && combo.maxKeyDelay > 0) {
72 | // We have to check to see if the delay between
73 | // the new key and the old one was too long (if enabled)
74 |
75 | var timeLimit = combo.timeLastMatched + combo.maxKeyDelay;
76 |
77 | // Check if they pressed it in time or not
78 | if (event.timeStamp <= timeLimit) {
79 | keyMatched = true;
80 | comboMatched = AdvanceKeyCombo(event, combo);
81 | }
82 | }
83 | else {
84 | keyMatched = true;
85 |
86 | // We don't check the time for the first key pressed, so just advance it
87 | comboMatched = AdvanceKeyCombo(event, combo);
88 | }
89 | }
90 |
91 | if (!keyMatched && combo.resetOnWrongKey) {
92 | // Wrong key was pressed
93 | combo.index = 0;
94 | combo.current = combo.keyCodes[0];
95 | }
96 |
97 | if (comboMatched) {
98 | combo.timeLastMatched = event.timeStamp;
99 | combo.matched = true;
100 | combo.timeMatched = event.timeStamp;
101 | }
102 |
103 | return comboMatched;
104 | };
105 |
106 |
107 | destroy() {
108 | this.mergedInput.events.off('gamepad_buttondown', this.onButtonDown);
109 | super.destroy();
110 | }
111 | }
--------------------------------------------------------------------------------
/src/configs/bearings.js:
--------------------------------------------------------------------------------
1 | const bearings = {
2 | '-180': 'W',
3 | '-168.75': 'WBN',
4 | '-157.5': 'WNW',
5 | '-146.25': 'NWBW',
6 | '-135': 'NW',
7 | '-123.75': 'NWBN',
8 | '-112.5': 'NNW',
9 | '-101.25': 'NBW',
10 | '-90': 'N',
11 | '-78.75': 'NBE',
12 | '-67.5': 'NNE',
13 | '-56.25': 'NEBN',
14 | '-45': 'NE',
15 | '-33.75': 'NEBE',
16 | '-22.5': 'ENE',
17 | '-11.25': 'EBN',
18 | '0': 'E',
19 | '11.25': 'EBS',
20 | '22.5': 'ESE',
21 | '33.75': 'SEBE',
22 | '45': 'SE',
23 | '56.25': 'SEBS',
24 | '67.5': 'SSE',
25 | '78.75': 'SBE',
26 | '90': 'S',
27 | '101.25': 'SBW',
28 | '112.5': 'SSW',
29 | '123.75': 'SWBS',
30 | '135': 'SW',
31 | '146.25': 'SWBW',
32 | '157.5': 'WSW',
33 | '168.75': 'WBS',
34 | '180': 'W'
35 | };
36 |
37 | module.exports = bearings;
--------------------------------------------------------------------------------
/src/configs/pad_dualshock.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Dualshock mapping
3 | */
4 | module.exports = {
5 | padID: 'Dualshock',
6 | padType: 'Sony',
7 | gamepadMapping: {
8 | RC_S: 0,
9 | RC_E: 1,
10 | RC_W: 2,
11 | RC_N: 3,
12 | START: 9, // Options
13 | SELECT: 8, // Share
14 | LB: 4,
15 | RB: 5,
16 | LT: 6,
17 | RT: 7,
18 | LS: 10,
19 | RS: 11,
20 | LC_N: 12,
21 | LC_S: 13,
22 | LC_W: 14,
23 | LC_E: 15,
24 | MENU: 16,
25 | TOUCH: 17
26 | },
27 | }
--------------------------------------------------------------------------------
/src/configs/pad_generic.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generic pad mapping
3 | */
4 | module.exports = {
5 | padID: 'Generic',
6 | padType: 'generic',
7 | gamepadMapping: {
8 | RC_S: 0,
9 | RC_E: 1,
10 | RC_W: 2,
11 | RC_N: 3,
12 | START: 9,
13 | SELECT: 8,
14 | LB: 4,
15 | RB: 5,
16 | LT: 6,
17 | RT: 7,
18 | LS: 10,
19 | RS: 11,
20 | LC_N: 12,
21 | LC_S: 13,
22 | LC_W: 14,
23 | LC_E: 15
24 | },
25 | }
--------------------------------------------------------------------------------
/src/configs/pad_unlicensedSNES.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 081f-e401 - UnlicensedSNES
3 | */
4 | module.exports = {
5 | padID: '081f-e401',
6 | padType: 'snes',
7 | gamepadMapping : {
8 | RC_S: 2,
9 | RC_E: 1,
10 | RC_W: 3,
11 | RC_N: 0,
12 | START: 9,
13 | SELECT: 8,
14 | LB: 4,
15 | RB: 5,
16 | LC_N: 12,
17 | LC_S: 13,
18 | LC_W: 14,
19 | LC_E: 15
20 | }
21 | }
--------------------------------------------------------------------------------
/src/configs/pad_xbox360.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generic pad mapping
3 | */
4 | module.exports = {
5 | padID: 'Xbox 360 controller (XInput STANDARD GAMEPAD)',
6 | padType: 'xbox',
7 | gamepadMapping: {
8 | RC_S: 0,
9 | RC_E: 1,
10 | RC_W: 2,
11 | RC_N: 3,
12 | START: 9,
13 | SELECT: 8,
14 | LB: 4,
15 | RB: 5,
16 | LT: 6,
17 | RT: 7,
18 | LS: 10,
19 | RS: 11,
20 | LC_N: 12,
21 | LC_S: 13,
22 | LC_W: 14,
23 | LC_E: 15,
24 | MENU: 16
25 | },
26 | }
--------------------------------------------------------------------------------
/src/controlManager.js:
--------------------------------------------------------------------------------
1 | import pad_generic from './configs/pad_generic'
2 | import pad_unlicensedSNES from './configs/pad_unlicensedSNES'
3 | import pad_xbox360 from './configs/pad_xbox360'
4 | import pad_dualshock from './configs/pad_dualshock'
5 |
6 | export default class controlManager {
7 | constructor (){
8 | }
9 |
10 | mapGamepad(id) {
11 | id = id.toLowerCase();
12 | let padConfig = pad_generic;
13 |
14 | if (id.includes('081f') && id.includes('e401')) {
15 | padConfig = pad_unlicensedSNES;
16 | }
17 | else if (id.includes('xbox') && id.includes('360')) {
18 | padConfig = pad_xbox360;
19 | }
20 | else if (id.includes('054c')) {
21 | padConfig = pad_dualshock;
22 | }
23 | else {
24 |
25 | }
26 |
27 | return padConfig;
28 | }
29 |
30 | getBaseControls() {
31 | return {
32 | 'direction': {
33 | 'UP': 0,
34 | 'DOWN': 0,
35 | 'LEFT': 0,
36 | 'RIGHT': 0,
37 | 'BEARING': '',
38 | 'BEARING_LAST': '',
39 | 'DEGREES': 0,
40 | 'DEGREES_LAST': 0,
41 | 'TIMESTAMP': 0
42 | },
43 | 'direction_secondary': {
44 | 'UP': 0,
45 | 'DOWN': 0,
46 | 'LEFT': 0,
47 | 'RIGHT': 0,
48 | 'BEARING': '',
49 | 'DEGREES': 0,
50 | 'BEARING_LAST': '',
51 | 'DEGREES_LAST': 0,
52 | 'TIMESTAMP': 0
53 | },
54 | 'buttons': {},
55 | 'timers' : {},
56 | 'gamepadMapping': {
57 | RC_S: 0,
58 | RC_E: 1,
59 | RC_W: 2,
60 | RC_N: 3,
61 | START: 9,
62 | SELECT: 8,
63 | LB: 4,
64 | RB: 5,
65 | LT: 6,
66 | RT: 7,
67 | LS: 10,
68 | RS: 11,
69 | LC_N: 12,
70 | LC_S: 13,
71 | LC_W: 14,
72 | LC_E: 15,
73 | MENU: 16
74 | },
75 | 'pointer': {
76 | 'M1': 0,
77 | 'M2': 0,
78 | 'M3': 0,
79 | 'M4': 0,
80 | 'M5': 0,
81 | 'BEARING': '',
82 | 'BEARING_DEGREES': 0,
83 | 'ANGLE': 0,
84 | 'TIMESTAMP': 0
85 | },
86 | 'position': {x:0,y:0},
87 | 'position_last': {x:0,y:0},
88 | 'gamepad': {},
89 | 'keys': {
90 | 'UP': [],
91 | 'DOWN': [],
92 | 'LEFT': [],
93 | 'RIGHT': [],
94 | },
95 | 'internal': {
96 | 'fakedpadBuffer': [],
97 | 'fakedpadPressed': [],
98 | 'fakedpadReleased': [],
99 | },
100 | 'interaction': {
101 | 'buffer': [],
102 | 'pressed': [],
103 | 'released': [],
104 | 'last': '',
105 | 'lastPressed': '',
106 | 'lastReleased': '',
107 | 'device': '',
108 | },
109 | 'interaction_mapped': {
110 | 'pressed': [],
111 | 'released': [],
112 | 'last': '',
113 | 'lastPressed': '',
114 | 'lastReleased': '',
115 | 'gamepadType': '',
116 | },
117 | 'buttons_mapped': {
118 | RC_S: 0,
119 | RC_E: 0,
120 | RC_W: 0,
121 | RC_N: 0,
122 | START: 0,
123 | SELECT: 0,
124 | MENU: 0,
125 | LB: 0,
126 | RB: 0,
127 | LT: 0,
128 | RT: 0,
129 | LS: 0,
130 | RS: 0,
131 | LC_N: 0,
132 | LC_S: 0,
133 | LC_W: 0,
134 | LC_E: 0,
135 | }
136 | }
137 | }
138 |
139 |
140 |
141 | /**
142 | * Returns a struct to hold input control information
143 | * Set up a struct for each player in the game
144 | * Direction and Buttons contain the input from the devices
145 | * The keys struct contains arrays of keyboard characters that will trigger the action
146 | */
147 | setupControls(numberOfButtons) {
148 | numberOfButtons = numberOfButtons || 16;
149 |
150 | let controls = this.getBaseControls();
151 |
152 | // Add buttons
153 | for (let i = 0; i <= numberOfButtons; i++) {
154 | controls.buttons['B' + i] = 0;
155 | controls.keys['B' + i] = [];
156 | }
157 |
158 | // Add timers
159 | for (let i = 0; i <= numberOfButtons; i++) {
160 | controls.timers['B' + i] = {
161 | 'pressed': 0,
162 | 'released': 0,
163 | 'duration': 0
164 | };
165 | }
166 | for (let thisDirection of ['UP', 'DOWN', 'LEFT', 'RIGHT', 'ALT_UP', 'ALT_DOWN', 'ALT_LEFT', 'ALT_RIGHT']) {
167 | controls.timers[thisDirection] = {
168 | 'pressed': 0,
169 | 'released': 0,
170 | 'duration': 0
171 | };
172 | }
173 |
174 | for (let thisPointer of ['M1', 'M2', 'M3', 'M4', 'M5']) {
175 | controls.timers[thisPointer] = {
176 | 'pressed': 0,
177 | 'released': 0,
178 | 'duration': 0
179 | };
180 | }
181 |
182 |
183 | controls.setPosition = function(x,y) {
184 | this.position.x = x;
185 | this.position.y = y;
186 | }
187 |
188 |
189 | return controls;
190 | }
191 |
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/src/demo/assets/gamepad.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "gamepad.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 96,
8 | "h": 1454
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "XboxOne_Dpad",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 100,
18 | "h": 100
19 | },
20 | "spriteSourceSize": {
21 | "x": 3,
22 | "y": 3,
23 | "w": 94,
24 | "h": 94
25 | },
26 | "frame": {
27 | "x": 1,
28 | "y": 1,
29 | "w": 94,
30 | "h": 94
31 | }
32 | },
33 | {
34 | "filename": "XboxOne_Dpad_Down",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 100,
39 | "h": 100
40 | },
41 | "spriteSourceSize": {
42 | "x": 3,
43 | "y": 3,
44 | "w": 94,
45 | "h": 94
46 | },
47 | "frame": {
48 | "x": 1,
49 | "y": 97,
50 | "w": 94,
51 | "h": 94
52 | }
53 | },
54 | {
55 | "filename": "XboxOne_Dpad_Left",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 100,
60 | "h": 100
61 | },
62 | "spriteSourceSize": {
63 | "x": 3,
64 | "y": 3,
65 | "w": 94,
66 | "h": 94
67 | },
68 | "frame": {
69 | "x": 1,
70 | "y": 193,
71 | "w": 94,
72 | "h": 94
73 | }
74 | },
75 | {
76 | "filename": "XboxOne_Dpad_Right",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 100,
81 | "h": 100
82 | },
83 | "spriteSourceSize": {
84 | "x": 3,
85 | "y": 3,
86 | "w": 94,
87 | "h": 94
88 | },
89 | "frame": {
90 | "x": 1,
91 | "y": 289,
92 | "w": 94,
93 | "h": 94
94 | }
95 | },
96 | {
97 | "filename": "XboxOne_Dpad_Up",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 100,
102 | "h": 100
103 | },
104 | "spriteSourceSize": {
105 | "x": 3,
106 | "y": 3,
107 | "w": 94,
108 | "h": 94
109 | },
110 | "frame": {
111 | "x": 1,
112 | "y": 385,
113 | "w": 94,
114 | "h": 94
115 | }
116 | },
117 | {
118 | "filename": "XboxOne_Left_Stick",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 100,
123 | "h": 100
124 | },
125 | "spriteSourceSize": {
126 | "x": 4,
127 | "y": 5,
128 | "w": 92,
129 | "h": 91
130 | },
131 | "frame": {
132 | "x": 1,
133 | "y": 481,
134 | "w": 92,
135 | "h": 91
136 | }
137 | },
138 | {
139 | "filename": "XboxOne_Right_Stick",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 100,
144 | "h": 100
145 | },
146 | "spriteSourceSize": {
147 | "x": 4,
148 | "y": 5,
149 | "w": 92,
150 | "h": 91
151 | },
152 | "frame": {
153 | "x": 1,
154 | "y": 574,
155 | "w": 92,
156 | "h": 91
157 | }
158 | },
159 | {
160 | "filename": "XboxOne_LB",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 100,
165 | "h": 100
166 | },
167 | "spriteSourceSize": {
168 | "x": 5,
169 | "y": 26,
170 | "w": 90,
171 | "h": 49
172 | },
173 | "frame": {
174 | "x": 1,
175 | "y": 667,
176 | "w": 90,
177 | "h": 49
178 | }
179 | },
180 | {
181 | "filename": "XboxOne_RB",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 100,
186 | "h": 100
187 | },
188 | "spriteSourceSize": {
189 | "x": 5,
190 | "y": 26,
191 | "w": 90,
192 | "h": 49
193 | },
194 | "frame": {
195 | "x": 1,
196 | "y": 718,
197 | "w": 90,
198 | "h": 49
199 | }
200 | },
201 | {
202 | "filename": "XboxOne_A",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 100,
207 | "h": 100
208 | },
209 | "spriteSourceSize": {
210 | "x": 8,
211 | "y": 8,
212 | "w": 84,
213 | "h": 84
214 | },
215 | "frame": {
216 | "x": 1,
217 | "y": 769,
218 | "w": 84,
219 | "h": 84
220 | }
221 | },
222 | {
223 | "filename": "XboxOne_B",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 100,
228 | "h": 100
229 | },
230 | "spriteSourceSize": {
231 | "x": 8,
232 | "y": 8,
233 | "w": 84,
234 | "h": 84
235 | },
236 | "frame": {
237 | "x": 1,
238 | "y": 855,
239 | "w": 84,
240 | "h": 84
241 | }
242 | },
243 | {
244 | "filename": "XboxOne_X",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 100,
249 | "h": 100
250 | },
251 | "spriteSourceSize": {
252 | "x": 8,
253 | "y": 8,
254 | "w": 84,
255 | "h": 84
256 | },
257 | "frame": {
258 | "x": 1,
259 | "y": 941,
260 | "w": 84,
261 | "h": 84
262 | }
263 | },
264 | {
265 | "filename": "XboxOne_Y",
266 | "rotated": false,
267 | "trimmed": true,
268 | "sourceSize": {
269 | "w": 100,
270 | "h": 100
271 | },
272 | "spriteSourceSize": {
273 | "x": 8,
274 | "y": 8,
275 | "w": 84,
276 | "h": 84
277 | },
278 | "frame": {
279 | "x": 1,
280 | "y": 1027,
281 | "w": 84,
282 | "h": 84
283 | }
284 | },
285 | {
286 | "filename": "XboxOne_LT",
287 | "rotated": false,
288 | "trimmed": true,
289 | "sourceSize": {
290 | "w": 100,
291 | "h": 100
292 | },
293 | "spriteSourceSize": {
294 | "x": 9,
295 | "y": 6,
296 | "w": 82,
297 | "h": 88
298 | },
299 | "frame": {
300 | "x": 1,
301 | "y": 1113,
302 | "w": 82,
303 | "h": 88
304 | }
305 | },
306 | {
307 | "filename": "XboxOne_RT",
308 | "rotated": false,
309 | "trimmed": true,
310 | "sourceSize": {
311 | "w": 100,
312 | "h": 100
313 | },
314 | "spriteSourceSize": {
315 | "x": 9,
316 | "y": 6,
317 | "w": 82,
318 | "h": 88
319 | },
320 | "frame": {
321 | "x": 1,
322 | "y": 1203,
323 | "w": 82,
324 | "h": 88
325 | }
326 | },
327 | {
328 | "filename": "XboxOne_Menu",
329 | "rotated": false,
330 | "trimmed": true,
331 | "sourceSize": {
332 | "w": 100,
333 | "h": 100
334 | },
335 | "spriteSourceSize": {
336 | "x": 10,
337 | "y": 11,
338 | "w": 80,
339 | "h": 79
340 | },
341 | "frame": {
342 | "x": 1,
343 | "y": 1293,
344 | "w": 80,
345 | "h": 79
346 | }
347 | },
348 | {
349 | "filename": "XboxOne_Windows",
350 | "rotated": false,
351 | "trimmed": true,
352 | "sourceSize": {
353 | "w": 100,
354 | "h": 100
355 | },
356 | "spriteSourceSize": {
357 | "x": 10,
358 | "y": 11,
359 | "w": 80,
360 | "h": 79
361 | },
362 | "frame": {
363 | "x": 1,
364 | "y": 1374,
365 | "w": 80,
366 | "h": 79
367 | }
368 | }
369 | ]
370 | }
371 | ],
372 | "meta": {
373 | "app": "https://www.codeandweb.com/texturepacker",
374 | "version": "3.0",
375 | "smartupdate": "$TexturePacker:SmartUpdate:f5f80bd0d6597954858ec9f87b373854:fe77e1ba7f34b0c980b2916d93901cd0:1c9586ece4449c8026f8e901f073f4a8$"
376 | }
377 | }
378 |
--------------------------------------------------------------------------------
/src/demo/assets/gamepad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GaryStanton/phaser3-merged-input/7366b5aa1be9241b841a93bbe274130442c2fb5b/src/demo/assets/gamepad.png
--------------------------------------------------------------------------------
/src/demo/button.js:
--------------------------------------------------------------------------------
1 | export default class circleButton extends Phaser.GameObjects.Container {
2 | /**
3 | * Generic button graphic
4 | *
5 | * @constructor
6 | * @param {object} config - The configuration for the object
7 | * @param {Phaser.Scene} config.scene - The scene on which to add the button
8 | * @param {number} config.x - The horizontal coordinate relative to the scene viewport
9 | * @param {number} config.y - The vertical coordinate relative to the scene viewport
10 | * @param {number} config.text - Text to sit inside the button
11 | */
12 |
13 | constructor(config) {
14 | // Assign some defaults for anything not passed
15 | let defaults = {
16 | x: 0
17 | , y: 0
18 | , text: ''
19 | };
20 | config = Object.assign({}, defaults, config);
21 |
22 | super(config.scene, config.x, config.y, []);
23 | this.config = config;
24 |
25 | this.create();
26 | }
27 |
28 | create() {
29 | // circle
30 | this.circle = this.scene.add.circle(0, 0, 20, 0x6666ff);
31 | this.add(this.circle);
32 |
33 | // Text
34 | if (this.config.text.toString.length > 0) {
35 | this.setText(this.config.text)
36 | }
37 | }
38 |
39 | /**
40 | * Set the text content of this button
41 | * @param {string} text
42 | */
43 | setText(text) {
44 | if (typeof this.text !== 'undefined') {
45 | this.text.setText(text)
46 | }
47 | else {
48 | var style = {
49 | fontSize: '18px',
50 | fontFamily: 'Arial',
51 | color: '#ffffff',
52 | };
53 | // The same again but specified in an object
54 | this.text = this.scene.add.text(0,0, text, style).setPadding({ x: 10, y: 10 }).setOrigin(0.5, 0.5)
55 | this.add(this.text)
56 | }
57 |
58 | return this;
59 | }
60 | }
--------------------------------------------------------------------------------
/src/demo/debug.js:
--------------------------------------------------------------------------------
1 | import * as dat from 'dat.gui';
2 | export default class Debug extends Phaser.Scene {
3 | /**
4 | * Debug scene
5 | *
6 | * @extends Phaser.Scene
7 | */
8 | constructor() {
9 | super({ key: 'Debug' });
10 | }
11 |
12 | /**
13 | * Init
14 | * @param {*} data
15 | */
16 | init(data) {
17 |
18 | }
19 |
20 | preload() {
21 |
22 | }
23 |
24 | /**
25 | * @protected
26 | * @param {object} data Initialization parameters.
27 | */
28 | create() {
29 | // Debug gui object
30 | this.gui = new dat.GUI();
31 |
32 | // Reference to systems events emitter
33 | this.eventEmitter = this.scene.events;
34 |
35 | // Input
36 | this.inputController = this.scene.get('InputController');
37 | this.inputType = '';
38 |
39 | // Add controls to dat.gui
40 | this.buildGUI(this.inputController.mergedInput.debug().players[0], 'Player 1');
41 | this.buildGUI(this.inputController.mergedInput.debug().players[1], 'Player 2');
42 |
43 | // Merged input events occur on the scene that the plugin is associated with
44 | this.inputController.events.on('mergedInput', function(MIEvent){
45 | if (MIEvent.action == 'Connected') {
46 | this.buildGUI(this.inputController.mergedInput.debug().input.gamepads[MIEvent.player], `Gamepad ${MIEvent.player}`);
47 | }
48 | }, this)
49 | }
50 |
51 |
52 | update() {
53 | }
54 |
55 | buildGUI(thisObject, folderName) {
56 | this.guiFolder = this.gui.addFolder(folderName);
57 | this.gui.remember(thisObject);
58 | this.addToGui(thisObject, this.guiFolder);
59 | }
60 |
61 | addToGui(obj, folder, parent) {
62 | if (parent !== undefined) {
63 | folder = parent.addFolder(folder);
64 | }
65 | for (const key in obj) { //for each key in your object
66 | if (obj.hasOwnProperty(key)) {
67 | let val = obj[key];
68 | if (typeof val == 'number') { //if the value of the object key is a number, establish limits and step
69 | const numDigits = this.getNumDigits(val);
70 | let step, limit;
71 | if (val > -1 && val < 1) { //if it's a small decimal number, give it a GUI range of -1,1 with a step of 0.1...
72 | step = 0.1;
73 | limit = 1;
74 | } else { //otherwise, calculate the limits and step based on # of digits in the number
75 | const numDigits = this.getNumDigits(Math.round(val)); //to establish a step and limit, we'll use a base number that is an integer
76 | limit = Math.pow(10, numDigits); //make the limit one digit higher than the number of digits of the itself, i.e. '150' would have a range of -1000 to 1000...
77 | step = Math.pow(10, numDigits - 2); //...with a step one less than the number of digits, i.e. '10'
78 | }
79 | folder.add(obj, key, -limit, limit).step(step).listen(); //add the value to your GUI folder
80 | } else if (typeof val === 'object') {
81 | this.addToGui(val, key, folder); //if the key is an object itself, call this function again to loop through that subobject, assigning it to the same folder
82 | } else {
83 | folder.add(obj, key).listen(); //...this would include things like boolean values as checkboxes, and strings as text fields
84 | }
85 | }
86 | }
87 | }
88 |
89 | getNumDigits(val) {
90 | return (`${val}`.match(/\d/g) || []).length //a regex to compute the number of digits in a number. Note that decimals will get counted as digits, which is why to establish our limit and step we rounded
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/demo/demo.js:
--------------------------------------------------------------------------------
1 | import circlebutton from './button';
2 | export default class Demo extends Phaser.Scene {
3 |
4 | preload() {
5 | // Input controller scene
6 | this.scene.launch('InputController')
7 | this.load.multiatlas('gamepad', 'assets/gamepad.json', 'assets');
8 | }
9 |
10 | create() {
11 | // Get the input controller scene, which contains our player objects
12 | this.inputController = this.scene.get('InputController');
13 |
14 | // Get our player objects
15 | this.player1 = this.inputController.player1;
16 | this.player2 = this.inputController.player2;
17 |
18 | // Set up an array of player objects
19 | this.players = [this.player1, this.player2]
20 |
21 |
22 | // Set up some gamepad sprites for testing
23 | // We can check for button numbers, or mapped button names. For the sprites, we'll use mapped button names.
24 | this.player1.sprites = {
25 | 'dpad': this.add.image(100, 350, 'gamepad', 'XboxOne_Dpad').setScale(2),
26 | 'RC_S': this.add.image(370, 420, 'gamepad', 'XboxOne_A'),
27 | 'RC_E': this.add.image(440, 350, 'gamepad', 'XboxOne_B'),
28 | 'RC_W': this.add.image(300, 350, 'gamepad', 'XboxOne_X'),
29 | 'RC_N': this.add.image(370, 280, 'gamepad', 'XboxOne_Y'),
30 | 'LB': this.add.image(70, 200, 'gamepad', 'XboxOne_LB'),
31 | 'RB': this.add.image(430, 200, 'gamepad', 'XboxOne_RB'),
32 | 'LT': this.add.image(70, 130, 'gamepad', 'XboxOne_LT'),
33 | 'RT': this.add.image(430, 130, 'gamepad', 'XboxOne_RT')
34 | }
35 |
36 | this.player2.sprites = {
37 | 'dpad': this.add.image(800, 350, 'gamepad', 'XboxOne_Dpad').setScale(2),
38 | 'RC_S': this.add.image(1070, 420, 'gamepad', 'XboxOne_A'),
39 | 'RC_E': this.add.image(1140, 350, 'gamepad', 'XboxOne_B'),
40 | 'RC_W': this.add.image(1000, 350, 'gamepad', 'XboxOne_X'),
41 | 'RC_N': this.add.image(1070, 280, 'gamepad', 'XboxOne_Y'),
42 | 'LB': this.add.image(770, 200, 'gamepad', 'XboxOne_LB'),
43 | 'RB': this.add.image(1130, 200, 'gamepad', 'XboxOne_RB'),
44 | 'LT': this.add.image(770, 130, 'gamepad', 'XboxOne_LT'),
45 | 'RT': this.add.image(1130, 130, 'gamepad', 'XboxOne_RT')
46 | }
47 |
48 | // Now we'll add some graphics to represent the actual button numbers.
49 | // How these map to friendly button names may differ per device, so the plugin attempts to map them according to the pad ID.
50 | this.player1.buttonGraphics = {}
51 | for (let i=0; i<=16; i++) {
52 | this.player1.buttonGraphics['B' + i] = new circlebutton({scene: this, x:100 + (i > 7 ? (i - 8.5) * 50 : (i * 50)), y:i > 7 ? 550 : 500, text:i});
53 | this.add.existing(this.player1.buttonGraphics['B' + i])
54 | }
55 |
56 | this.player2.buttonGraphics = {}
57 | for (let i=0; i<=16; i++) {
58 | this.player2.buttonGraphics['B' + i] = new circlebutton({scene: this, x:790 + (i > 7 ? (i - 8.5) * 50 : (i * 50)), y:i > 7 ? 550 : 500, text:i});
59 | this.add.existing(this.player2.buttonGraphics['B' + i])
60 | }
61 |
62 |
63 | // Set up some debug text
64 | this.player1Text = this.add.text(50, 600, '', {
65 | fontFamily: 'Arial',
66 | fontSize: 14,
67 | color: '#00ff00'
68 | });
69 |
70 | this.player1Combos = this.add.text(50, 730, '', {
71 | fontFamily: 'Arial',
72 | fontSize: 16,
73 | color: '#00ff00'
74 | });
75 |
76 | this.player2Text = this.add.text(50, 800, '', {
77 | fontFamily: 'Arial',
78 | fontSize: 14,
79 | color: '#00ff00'
80 | });
81 |
82 | this.player2Combos = this.add.text(50, 930, '', {
83 | fontFamily: 'Arial',
84 | fontSize: 16,
85 | color: '#00ff00'
86 | });
87 |
88 |
89 | // Instructions
90 | this.instructions1 = this.add.text(50, 20, ['Directions: WASD', 'Buttons: 1-0'], {
91 | fontFamily: 'Arial',
92 | fontSize: 14,
93 | color: '#00ff00'
94 | });
95 | this.instructions1 = this.add.text(740, 20, ['Directions: Cursors', 'Buttons: Numpad 1-0'], {
96 | fontFamily: 'Arial',
97 | fontSize: 14,
98 | color: '#00ff00'
99 | });
100 |
101 | // Set a position for the player
102 | this.player1.setPosition(this.cameras.main.centerX, this.cameras.main.centerY);
103 |
104 |
105 |
106 | /**
107 | * Some examples of creating button combos
108 | */
109 | this.input.keyboard.createCombo([38, 38, 40, 40, 37, 39, 37, 39, 66, 65], { resetOnMatch: true }).name = 'Konami code - Keyboard';
110 | this.inputController.mergedInput.createButtonCombo(this.player1, ['UP', 'UP', 'DOWN', 'DOWN', 'LEFT', 'RIGHT', 'LEFT', 'RIGHT', 'RC_E', 'RC_S'], { resetOnMatch: true }).name = 'Konami code - Gamepad';
111 | this.inputController.mergedInput.createButtonCombo(this.player1, ['B12', 'B13'], { resetOnMatch: true, maxKeyDelay: 1000, }).name = 'Button ID test';
112 |
113 | this.input.keyboard.on('keycombomatch', event => {
114 | this.player1Combos.setText(`KEY COMBO: ${event.name}`)
115 | console.log(`${event.name} entered!`);
116 | });
117 |
118 | this.inputController.mergedInput.events.on('buttoncombomatch', event => {
119 | this[`player${event.player.index + 1}Combos`].setText(`BUTTON COMBO: ${event.combo.name}`)
120 | console.log(`${event.combo.name} entered! - Player: ${event.player.index}`);
121 | });
122 |
123 | }
124 |
125 | update() {
126 | // Loop through player objects
127 | for (let thisPlayer of this.players) {
128 | // Reset dpad frame
129 | thisPlayer.sprites.dpad.setFrame('XboxOne_Dpad');
130 |
131 | // Show dpad frame for direction input. (Diagonal input is supported, but can't easily be shown with these sprites)
132 | if (thisPlayer.direction.UP > 0) {
133 | thisPlayer.sprites.dpad.setFrame('XboxOne_Dpad_Up');
134 | }
135 | if (thisPlayer.direction.RIGHT > 0) {
136 | thisPlayer.sprites.dpad.setFrame('XboxOne_Dpad_Right');
137 | }
138 | if (thisPlayer.direction.DOWN > 0) {
139 | thisPlayer.sprites.dpad.setFrame('XboxOne_Dpad_Down');
140 | }
141 | if (thisPlayer.direction.LEFT > 0) {
142 | thisPlayer.sprites.dpad.setFrame('XboxOne_Dpad_Left');
143 | }
144 |
145 |
146 | // Check the button NUMBER values to correspond with the button graphics
147 | for (let thisButton in thisPlayer.buttons) {
148 | if (typeof thisPlayer.buttonGraphics[thisButton] !== 'undefined') {
149 | if (thisPlayer.buttons[thisButton] > 0) {
150 | thisPlayer.buttonGraphics[thisButton].circle.setFillStyle(0xcc0000, 1)
151 | }
152 | else {
153 | thisPlayer.buttonGraphics[thisButton].circle.setFillStyle(0x6666ff, 1)
154 | }
155 | }
156 | }
157 |
158 | // Check the MAPPED button values to correspond with the sprites we created
159 | for (let thisButton in thisPlayer.buttons_mapped) {
160 | if (typeof thisPlayer.sprites[thisButton] !== 'undefined') {
161 | if (thisPlayer.buttons_mapped[thisButton] > 0) {
162 | this.tintButton(thisPlayer, thisButton);
163 | }
164 | else {
165 | thisPlayer.sprites[thisButton].clearTint();
166 | }
167 | }
168 | }
169 | }
170 |
171 | this.player1Text.setText([
172 | 'Player 1', 'Gamepad: ' + (typeof this.player1.gamepad.index === 'undefined' ? 'Press a button to connect' : this.player1.gamepad.id),
173 | 'Directions: ' + JSON.stringify(this.player1.direction),
174 | 'Buttons: ' + JSON.stringify(this.player1.buttons),
175 | 'Mouse: ' + JSON.stringify(this.player1.pointer),
176 | 'Timers: ' + JSON.stringify(this.player1.timers),
177 | 'Interaction: ' + JSON.stringify(this.player1.interaction),
178 | `isDown: ${this.player1.isDown(Object.keys(this.player1.buttons_mapped))}, ${this.player1.isDown(Object.keys(this.player1.buttons))}`,
179 | 'Internal: ' + JSON.stringify(this.player1.internal)
180 | ]);
181 | this.player2Text.setText([
182 | 'Player 2', 'Gamepad: ' + (typeof this.player2.gamepad.index === 'undefined' ? 'Press a button to connect' : this.player2.gamepad.id),
183 | 'Directions: ' + JSON.stringify(this.player2.direction),
184 | 'Buttons: ' + JSON.stringify(this.player2.buttons),
185 | 'Mouse: ' + JSON.stringify(this.player2.pointer),
186 | 'Timers: ' + JSON.stringify(this.player2.timers),
187 | 'Interaction: ' + JSON.stringify(this.player2.interaction),
188 | `isDown: ${this.player2.isDown(Object.keys(this.player2.buttons_mapped))}, ${this.player2.isDown(Object.keys(this.player2.buttons))}`,
189 | 'Internal: ' + JSON.stringify(this.player2.internal)
190 | ]);
191 |
192 |
193 | /**
194 | * Some logging of player helper functions
195 | */
196 | /*
197 | // Here we check if certain buttons were pressed in this update step.
198 | if (this.player1.interaction_mapped.isPressed(['LC_N','START','RC_S','RC_N'])) {
199 | console.log(`mapped - isPressed: ${this.player1.interaction_mapped.isPressed(['LC_N', 'START', 'RC_S', 'RC_N'])}`)
200 | }
201 |
202 | // Here we check if certain buttons are held down in this update step.
203 | if (this.player1.interaction_mapped.isDown(['LC_N', 'RC_S', 'RC_N'])) {
204 | console.log(`mapped - isDown: ${this.player1.interaction_mapped.isDown(['LC_N', 'RC_S', 'RC_N'])}`)
205 | }
206 |
207 | // Here we check if certain buttons are held down for a given duration in this update step.
208 | if (this.player1.interaction_mapped.checkDown(['LC_N'], 1000)) {
209 | console.log(`mapped checkDown: ${this.player1.interaction_mapped.checkDown(['LC_N'], 1000)}`)
210 | }
211 |
212 | // Here we check if certain buttons are held down in this update step.
213 | if (this.player1.interaction.isPressed(['DOWN', 'B1'])) {
214 | console.log(`raw - isPressed: ${this.player1.interaction.isPressed(['DOWN', 'B1'])}`)
215 | }
216 |
217 | // Here we check if certain buttons are held down in this update step.
218 | if (this.player1.interaction.isDown(['DOWN', 'B1'])) {
219 | console.log(`raw - isDown: ${this.player1.interaction.isDown(['DOWN', 'B1'])}`)
220 | }
221 |
222 | // Here we check if certain buttons are held down for a given duration in this update step.
223 | if (this.player1.interaction.checkDown(['DOWN'], 1000, true)) {
224 | console.log(`raw checkDown: ${this.player1.interaction.checkDown(['DOWN'], 1000, true)}`)
225 | }
226 |
227 |
228 | // Generic button (mapped / unmapped) isPressed function
229 | if (this.player1.isPressed(['RIGHT', 'LC_W', 'B2'])) {
230 | console.log(`generic - isPressed: ${this.player1.isPressed(['RIGHT', 'LC_W', 'B2'])}`)
231 | }
232 |
233 | // Generic button (mapped / unmapped) isDown function
234 | if (this.player1.isDown(['RIGHT', 'LC_W', 'B2'])) {
235 | console.log(`generic - isDown: ${this.player1.isDown(['RIGHT', 'LC_W', 'B2'])}`)
236 | }
237 |
238 | // Generic button (mapped / unmapped) isReleased function
239 | if (this.player1.isReleased(['RIGHT', 'LC_W', 'B2'])) {
240 | console.log(`generic - isReleased: ${this.player1.isReleased(['RIGHT', 'LC_W', 'B2'])}`)
241 | }
242 |
243 | */
244 |
245 | // Here we check if certain buttons are held down for a given duration in this update step.
246 | if (this.player1.checkDown(['M1','LEFT'], 1000, false)) {
247 | console.log(`generic checkDown: LEFT`)
248 | }
249 |
250 | // Mouse pointer check
251 | /*
252 | if (this.player1.isPressed(['M1', 'M2'])) {
253 | console.log(`isPressed: ${this.player1.interaction.isPressed(['M1', 'M2'])}`)
254 | }
255 | */
256 |
257 |
258 |
259 |
260 |
261 | // this.debugView.value = this.inputController.mergedInput.debug().input;
262 | }
263 |
264 | tintButton(player, button){
265 | player.sprites[button].setTint(0xff0000);
266 | }
267 |
268 | }
269 |
--------------------------------------------------------------------------------
/src/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |