@vibedgames/gamepad 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Kaiyu Hsu
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.
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # @vibedgames/gamepad
2
+
3
+ On-screen touch controls for browser games — a floating analog joystick plus
4
+ action buttons. Framework-agnostic core, with drop-in adapters that wire the
5
+ input and render the overlay for you: a [Phaser](https://phaser.io) adapter
6
+ (`@vibedgames/gamepad/phaser`) and a DOM adapter (`@vibedgames/gamepad/dom`)
7
+ for everything else — Three.js, raw canvas, plain pages.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @vibedgames/gamepad
13
+ ```
14
+
15
+ ## Phaser quickstart
16
+
17
+ ```ts
18
+ import { attachVirtualGamepad } from "@vibedgames/gamepad/phaser";
19
+
20
+ // in your scene's create():
21
+ this.gamepad = attachVirtualGamepad(this, {
22
+ // a "rest" button (no position) — any finger that isn't the stick fires:
23
+ buttons: [{ id: "fire" }],
24
+ onFirstTouch: () => this.hint?.remove(),
25
+ });
26
+
27
+ // in your scene's update():
28
+ this.gamepad.update(); // reconcile stale touches + redraw the overlay
29
+
30
+ const stick = this.gamepad.getStick();
31
+ if (stick.active && !stick.inDeadZone) {
32
+ this.steer(stick.angle, stick.magnitude); // radians, 0–1 thrust
33
+ }
34
+ if (this.gamepad.isButtonDown("fire")) this.shoot();
35
+ ```
36
+
37
+ The mouse is ignored on purpose, so a desktop build keeps whatever controls it
38
+ already had — the gamepad is purely the touch overlay. `isTouch` flips true the
39
+ first time a finger lands.
40
+
41
+ ## DOM quickstart (Three.js, canvas, anything)
42
+
43
+ Same controller, rendered as positioned DOM elements in a
44
+ `pointer-events: none` overlay — it never steals taps from your HUD. Touches
45
+ on interactive elements (`button`, `a`, inputs, `[data-gamepad-ignore]`) are
46
+ left to the page.
47
+
48
+ ```ts
49
+ import { attachDomGamepad } from "@vibedgames/gamepad/dom";
50
+
51
+ const gamepad = attachDomGamepad({
52
+ visible: "coarse", // pre-show buttons on touch devices (default: after first touch)
53
+ buttons: [
54
+ {
55
+ id: "jump",
56
+ label: "JUMP",
57
+ position: (v) => ({ x: v.width - 72 - v.inset.right, y: v.height - 72 - v.inset.bottom }),
58
+ },
59
+ ],
60
+ });
61
+
62
+ // in your game loop:
63
+ gamepad.update();
64
+ const stick = gamepad.getStick();
65
+ if (gamepad.justPressed("jump")) jump();
66
+ ```
67
+
68
+ Give your game surface `touch-action: none` so the browser delivers moves
69
+ instead of scrolling. `setTint` takes a CSS color here (`"#22d3ee"`).
70
+
71
+ ### Fixed buttons (e.g. a bomb button)
72
+
73
+ Give a button a `position` resolver to pin it on-screen; it re-anchors on
74
+ resize. The resolver's viewport includes safe-area insets (`inset.top/right/
75
+ bottom/left` — notch and home indicator, zeros elsewhere; requires
76
+ `viewport-fit=cover` in your viewport meta tag on iOS). Use `onButtonDown` or
77
+ `justPressed(id)` for edge-triggered actions (place a bomb once per tap) and
78
+ `isButtonDown` for held input.
79
+
80
+ ```ts
81
+ attachVirtualGamepad(this, {
82
+ buttons: [
83
+ {
84
+ id: "bomb",
85
+ label: "💣",
86
+ position: ({ width, height, inset }) => ({
87
+ x: width - 80 - inset.right,
88
+ y: height - 80 - inset.bottom,
89
+ }),
90
+ radius: 48,
91
+ },
92
+ ],
93
+ onButtonDown: (id) => {
94
+ if (id === "bomb") this.placeBomb();
95
+ },
96
+ });
97
+ ```
98
+
99
+ Buttons with a `label` render it (text in the DOM adapter, a `Text` object in
100
+ Phaser). Pass `visible: "coarse"` to either adapter to pre-show fixed buttons
101
+ on touch-capable devices instead of waiting for the first touch — an invisible
102
+ button is undiscoverable.
103
+
104
+ ### Grid movement
105
+
106
+ For tile-based games, snap the stick to a direction:
107
+
108
+ ```ts
109
+ import { stickDirection4 } from "@vibedgames/gamepad/phaser";
110
+
111
+ const dir = stickDirection4(this.gamepad.getStick()); // "up" | "down" | "left" | "right" | null
112
+ if (dir) this.step(dir);
113
+ ```
114
+
115
+ ### Rendering
116
+
117
+ The Phaser adapter draws the joystick + fixed buttons into a screen-fixed
118
+ `Graphics` object. Tune it with `render: { depth, tint, blendMode }`, recolor at
119
+ runtime with `setTint(0xff00aa)`, or pass `render: false` and draw it yourself
120
+ from `gamepad.pad` (see `getStickGeometry`, `getStick`, `getButtonLayout`).
121
+
122
+ ## Framework-agnostic core
123
+
124
+ The `VirtualGamepad` class has no engine dependency — feed it raw pointer
125
+ events in screen-space (CSS pixel) coordinates and read the state back. Most
126
+ non-Phaser games want `attachDomGamepad` instead; drive the core directly only
127
+ for custom event routing or a custom renderer.
128
+
129
+ ```ts
130
+ import { VirtualGamepad, safeAreaInset } from "@vibedgames/gamepad";
131
+
132
+ const pad = new VirtualGamepad({ buttons: [{ id: "jump" }] });
133
+ pad.setViewport(window.innerWidth, window.innerHeight, safeAreaInset());
134
+
135
+ // clientX/clientY, NOT offsetX or canvas.width — the pad works in CSS pixels,
136
+ // and a HiDPI canvas's buffer size is larger than its on-screen size.
137
+ window.addEventListener("pointerdown", (e) => pad.pointerDown(e.pointerId, e.clientX, e.clientY));
138
+ window.addEventListener("pointermove", (e) => pad.pointerMove(e.pointerId, e.clientX, e.clientY));
139
+ window.addEventListener("pointerup", (e) => pad.pointerUp(e.pointerId));
140
+ window.addEventListener("pointercancel", (e) => pad.pointerUp(e.pointerId));
141
+
142
+ // each frame:
143
+ pad.nextFrame(); // publish justPressed/justReleased edges
144
+ const stick = pad.getStick();
145
+ const jumping = pad.isButtonDown("jump");
146
+ ```
147
+
148
+ Touch `up` events can go missing (finger slides off the canvas, tab switch) —
149
+ call `pad.reconcile(liveIds)` each frame with the pointer ids you know are
150
+ down, or `pad.reset()` on blur, like the adapters do.
151
+
152
+ ### Touch routing
153
+
154
+ Each `pointerDown` is routed in order:
155
+
156
+ 1. **Fixed buttons** — a touch inside a button's circle presses it.
157
+ 2. **The stick** — the first free touch anchors the floating stick.
158
+ 3. **A "rest" button** (a button with no `position`) — catches everything else.
159
+
160
+ This covers both the twin-stick model (stick + a rest "fire" button: any
161
+ second finger shoots) and the d-pad-style model (stick + a fixed action
162
+ button).
163
+
164
+ ## Physical controllers
165
+
166
+ `PhysicalGamepad` reads real controllers (the
167
+ [Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API),
168
+ standard mapping, first connected pad wins) with the same read API as the
169
+ virtual pad, so one code path serves both. Bind your game's action ids to
170
+ physical buttons and poll once per frame:
171
+
172
+ ```ts
173
+ import { PhysicalGamepad, stickDirection4 } from "@vibedgames/gamepad";
174
+
175
+ const pad = new PhysicalGamepad({
176
+ bindings: { jump: ["a"], dash: ["b", "rb"] },
177
+ onConnect: () => showToast("🎮 controller connected"),
178
+ });
179
+
180
+ // in your game loop:
181
+ pad.update();
182
+ const dir = stickDirection4(pad.getStick()); // left stick; getStick("right") too
183
+ const jumped = vpad.justPressed("jump") || pad.justPressed("jump");
184
+ ```
185
+
186
+ Raw button names (`"a"`, `"rt"`, `"up"`, …) work without a binding, and
187
+ `buttonValue("rt")` exposes analog trigger values for driving games.
188
+ `isPadConnected()` is a standalone check for UI ("show controller hints only
189
+ when a pad is plugged in"). Everything is poll-based — no listeners, no DOM —
190
+ and the pad source is injectable (`poll`) for headless tests.
191
+
192
+ ## Related
193
+
194
+ - [`@vibedgames/multiplayer`](https://www.npmjs.com/package/@vibedgames/multiplayer) — real-time multiplayer for browser games (framework-agnostic core + React hooks). Pairs naturally: recolor the joystick/buttons per player with `setTint`.
195
+
196
+ ## License
197
+
198
+ MIT
@@ -0,0 +1 @@
1
+ {"version":"7.0.2","root":[[64,69],72],"packageJsons":["../../../node_modules/.pnpm/phaser@4.2.1/node_modules/phaser/package.json","../package.json"],"missingPackageJsons":["../../../node_modules/.pnpm/phaser@4.2.1/node_modules/phaser/types/package.json"],"fileNames":["lib.es5.d.ts","lib.es2015.d.ts","lib.es2016.d.ts","lib.es2017.d.ts","lib.es2018.d.ts","lib.es2019.d.ts","lib.es2020.d.ts","lib.es2021.d.ts","lib.es2022.d.ts","lib.es2023.d.ts","lib.dom.d.ts","lib.dom.iterable.d.ts","lib.es2015.core.d.ts","lib.es2015.collection.d.ts","lib.es2015.generator.d.ts","lib.es2015.iterable.d.ts","lib.es2015.promise.d.ts","lib.es2015.proxy.d.ts","lib.es2015.reflect.d.ts","lib.es2015.symbol.d.ts","lib.es2015.symbol.wellknown.d.ts","lib.es2016.array.include.d.ts","lib.es2016.intl.d.ts","lib.es2017.arraybuffer.d.ts","lib.es2017.date.d.ts","lib.es2017.object.d.ts","lib.es2017.sharedmemory.d.ts","lib.es2017.string.d.ts","lib.es2017.intl.d.ts","lib.es2017.typedarrays.d.ts","lib.es2018.asyncgenerator.d.ts","lib.es2018.asynciterable.d.ts","lib.es2018.intl.d.ts","lib.es2018.promise.d.ts","lib.es2018.regexp.d.ts","lib.es2019.array.d.ts","lib.es2019.object.d.ts","lib.es2019.string.d.ts","lib.es2019.symbol.d.ts","lib.es2019.intl.d.ts","lib.es2020.bigint.d.ts","lib.es2020.date.d.ts","lib.es2020.promise.d.ts","lib.es2020.sharedmemory.d.ts","lib.es2020.string.d.ts","lib.es2020.symbol.wellknown.d.ts","lib.es2020.intl.d.ts","lib.es2020.number.d.ts","lib.es2021.promise.d.ts","lib.es2021.string.d.ts","lib.es2021.weakref.d.ts","lib.es2021.intl.d.ts","lib.es2022.array.d.ts","lib.es2022.error.d.ts","lib.es2022.intl.d.ts","lib.es2022.object.d.ts","lib.es2022.string.d.ts","lib.es2022.regexp.d.ts","lib.es2023.array.d.ts","lib.es2023.collection.d.ts","lib.es2023.intl.d.ts","lib.decorators.d.ts","lib.decorators.legacy.d.ts","../src/types.ts","../src/core.ts","../src/safe-area.ts","../src/physical.ts","../src/dom.ts","../src/index.ts","../../../node_modules/.pnpm/phaser@4.2.1/node_modules/phaser/types/matter.d.ts","../../../node_modules/.pnpm/phaser@4.2.1/node_modules/phaser/types/phaser.d.ts","../src/phaser.ts"],"fileInfos":[{"version":"16934abaab7026ac114da441aabea1a0","affectsGlobalScope":true,"impliedNodeFormat":1},"d4306fb2e47f74835e8674ffac07d76f","e437c5c1302869326c3bb93da85bbbcf","e4324975a566567b21d350615f1fc6ac","333b1b9a2a9ac3b8497dba5c63b5ba50","6cffacd662b6eb5fa7a36aa2ea366bfa","b4c34f9c23304dbef2d23698637ed638","e5cb86a5fc491796ecd1d2dd348d208f","feb6c6fb19cdb246a5d8acb36a6901c7","9443a7f109277ffaa79d893ed2549995",{"version":"aae8996e8b5684814785a42cbbefcd79","affectsGlobalScope":true,"impliedNodeFormat":1},"abad6dd56cc8caf095c165df8124d237",{"version":"926204c28cd3d073865348473ae28d2e","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"f25e42c801b2cb3cf2b39792006a9beb","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"6344b55f26a4e81d9608777dbfb877dd","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"3c0ed28e53d3695b363e256ec1c023fd","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4c2761daba7f17141c25baa0821ac5da","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b87656acabd63e69379ff6ffcfe52fc7","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"597469522da047a5af5222cc6989f405","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"1708ea4d34dc37fadadc63ca01127e80","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"55d97a8c6fbf34a30450a7b1e5f7a298","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"0ee05eb59426d33e374226d8dcfa708b","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"e347c14030993906efcfbb88915b6a05","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b0231263857c9b6a03641acdc9280ceb","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"3b15c4a83b598cacb4067676e6f0abed","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b417d97b7934cef63b1889abec0bbfbf","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"09a6cf4032ebba60ce22a501e663f881","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"2b056277dd138e8f5650fc04e20eaa8d","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"e22cc07e3f3cc242ba52fa3f8ea1fc58","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"2c45da767a1bfbb220848df1bc4029e4","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b44c3e0fbaf2130cdcf6ac38b120ffa1","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b612fb5cf8e5d964b92063a75207632a","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"02705151a5e1551b9162a9ed8ab763f7","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"41025e398be9215d32e4337335da8f0b","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"52684c2b1f353a5538e4f275182a54cd","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"6dedb6a4f90d1df3a6fbe5693e44886c","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"ca3f36fe3562c07e0f0d71c2bebd3f6d","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"409974d6129befbb8226ddd1c6558568","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4d9cfde2a1ae1b4925f1f9bc10848e5d","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"7e1daecc66dd564144e3bb1a0266b5fd","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a8e1d9bb35fd0637f2f9fd2b2a54f2ec","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4f168501772a6543182765bfd5f2fbfe","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a19c80aad1b2162103496f5ba293a732","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b69afa63cd5d059851c78adb2856ee09","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"ae2fc5d954e9b0f5feee3d481b953c27","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"1cfd3091a071d8b6feec15277643bafe","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"1da2c1f258970fd3cc91184f69e91a9d","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a0d87491913d843139e0c993650a3235","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4ef72aa378127e7b7abba915b0110b1e","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"3ec74c6a7d4463f0254db3a74cf75646","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"84c2bdfa470d075526cce6322d81b0b6","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"0b3844c2b8c73e4e1ab91431411cad11","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4fc71cf4a15b8d99675a31df77f26e07","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"d1b49564ddaeca3df5b6dbae925d2242","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"6a25be566d60ccfc2d6e8b7bfdeefa83","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"8a20e27646985dfd58c57ca6566553dd","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"9969f02ad3cdcbf4f709405cda44167f","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"9c9be4792a7a4f42c15ae7360bf28779","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"8cf777fe00349b71ee9c4b6f3fe7fd19","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"74f96bc192530c9723f572bfff3d3078","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"1df91c56b25955c56387426f378173c5","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"f64453cbf9671f28158677fa5c43967a","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"33f317af5428801f944a478d2c1e38e5","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"fb0d43d1359d1f88a6bde7d39c0c0763","signature":"07fa22ee2a03ba5222b020ea2a1a050c","impliedNodeFormat":1},{"version":"e887af32abe4f8af09e83c3ae2893fef","signature":"f41e5e474d41815834538f2a4f808e15","impliedNodeFormat":1},{"version":"9f2d2c36d2a7d24894e79e0479612bdd","signature":"f2a553836ae92146c350d78e4a9e0bfd","impliedNodeFormat":1},{"version":"be7299012871bef43093a1e97b56ba9a","signature":"830729e01c3e59efc49d3fd05c17e0bf","impliedNodeFormat":1},{"version":"a524cfbde4f7f1591ff05904d8ff33cc","signature":"0db4593aebcae3733c0c244907abfc76","impliedNodeFormat":1},{"version":"4f38e8bb42215bb606648119679821bb","signature":"6eb7f9c4c06903088e98a642e2c442a7","impliedNodeFormat":1},{"version":"698438032222f74334da71cf72a280d4","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"505522b3445c3a9c7a1fd275fcd4a2ca","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"ec122422c154102cae57d02c266acf9f","signature":"bf0f45bde95e87d27f7053f1c576301d","impliedNodeFormat":1}],"fileIdsList":[[70,71],[71],[70],[64,70,71],[64,65,66,67,70,71]],"options":{"allowJs":true,"checkJs":true,"declaration":true,"declarationMap":true,"module":99,"noUncheckedIndexedAccess":true,"outDir":"./","rootDir":"../src","skipLibCheck":true,"strict":true,"sourceMap":true,"target":9,"tsBuildInfoFile":"./.tsbuildinfo","esModuleInterop":true},"referencedMap":[[62,1],[63,1],[11,1],[12,1],[14,1],[13,1],[2,1],[15,1],[16,1],[17,1],[18,1],[19,1],[20,1],[21,1],[22,1],[3,1],[23,1],[24,1],[4,1],[25,1],[29,1],[26,1],[27,1],[28,1],[30,1],[31,1],[32,1],[5,1],[33,1],[34,1],[35,1],[36,1],[6,1],[40,1],[37,1],[38,1],[39,1],[41,1],[7,1],[42,1],[47,1],[48,1],[43,1],[44,1],[45,1],[46,1],[8,1],[52,1],[49,1],[50,1],[51,1],[53,1],[9,1],[54,1],[55,1],[56,1],[58,1],[57,1],[59,1],[60,1],[10,1],[61,1],[1,1],[70,2],[71,3],[65,4],[68,5],[69,5],[72,5],[67,4],[66,4],[64,1]]}
package/dist/core.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { ButtonLayout, Dir4, Dir8, Inset, StickGeometry, StickState, VirtualGamepadOptions } from "./types";
2
+ /**
3
+ * Framework-agnostic on-screen controls. You feed it raw pointer events
4
+ * (`pointerDown/Move/Up`) in screen-space pixels; it tracks a floating analog
5
+ * stick plus any number of action buttons and exposes their state for the game
6
+ * loop to read. No DOM, no engine — pair it with `attachVirtualGamepad` from
7
+ * `@vibedgames/gamepad/phaser`, or drive it yourself.
8
+ *
9
+ * Touch routing on each `pointerDown`, in order:
10
+ * 1. fixed buttons (a touch inside a button's circle presses it),
11
+ * 2. the stick (the first free touch anchors the floating stick),
12
+ * 3. a "rest" button, if any (catches every other touch — the
13
+ * "any finger fires" model).
14
+ */
15
+ export declare class VirtualGamepad {
16
+ private readonly stickOpts;
17
+ private readonly buttons;
18
+ private readonly onButtonDown?;
19
+ private readonly onButtonUp?;
20
+ private viewport;
21
+ private stick;
22
+ /** button id -> set of pointer ids currently pressing it. */
23
+ private readonly pressed;
24
+ /** pointer id -> what it's bound to. */
25
+ private readonly binding;
26
+ private downAccum;
27
+ private upAccum;
28
+ private frameDown;
29
+ private frameUp;
30
+ constructor(options?: VirtualGamepadOptions);
31
+ /** Update the canvas size so fixed buttons re-anchor (call on resize).
32
+ * Pass safe-area insets (see `safeAreaInset()`) so position resolvers can
33
+ * keep buttons clear of the notch / home indicator. */
34
+ setViewport(width: number, height: number, inset?: Inset): void;
35
+ pointerDown(id: number, x: number, y: number): void;
36
+ pointerMove(id: number, x: number, y: number): void;
37
+ pointerUp(id: number): void;
38
+ /**
39
+ * Drop any tracked pointer that's no longer physically down. Touch `up`
40
+ * events go missing when a finger slides off the canvas, so call this each
41
+ * frame with the engine's live pointer ids.
42
+ */
43
+ reconcile(activeIds: Iterable<number>): void;
44
+ /** Release everything (e.g. on respawn or scene reset). */
45
+ reset(): void;
46
+ /** True while any finger is touching the gamepad. */
47
+ get isTouching(): boolean;
48
+ isButtonDown(id: string): boolean;
49
+ /** Button went up→down since the previous `nextFrame()` — edge-triggered
50
+ * polling for fixed-timestep games (tap = one action). The callback
51
+ * alternative is `onButtonDown`. */
52
+ justPressed(id: string): boolean;
53
+ /** Last finger left the button since the previous `nextFrame()`. */
54
+ justReleased(id: string): boolean;
55
+ /**
56
+ * Publish accumulated edges for `justPressed`/`justReleased` and start a new
57
+ * accumulation window. The adapters call this from their `update()`; call it
58
+ * once per frame yourself only when driving the core directly.
59
+ */
60
+ nextFrame(): void;
61
+ /** Ids of all buttons with at least one finger down. */
62
+ buttonsDown(): string[];
63
+ /** A snapshot of the stick. Idle when no finger owns it. */
64
+ getStick(): StickState;
65
+ /** Resolved stick tuning, or null if the stick is disabled. */
66
+ getStickGeometry(): StickGeometry | null;
67
+ /** Resolved button geometry + state, for renderers. */
68
+ getButtonLayout(): ButtonLayout[];
69
+ private bindButton;
70
+ }
71
+ /** Snap a stick reading to a 4-way direction, or null in the dead zone. */
72
+ export declare function stickDirection4(stick: StickState): Dir4 | null;
73
+ /** Snap a stick reading to an 8-way direction, or null in the dead zone. */
74
+ export declare function stickDirection8(stick: StickState): Dir8 | null;
75
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAEZ,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,aAAa,EAEb,UAAU,EACV,qBAAqB,EAEtB,MAAM,SAAS,CAAC;AA+BjB;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAuB;IAEnD,OAAO,CAAC,QAAQ,CAAwD;IACxE,OAAO,CAAC,KAAK,CAMG;IAChB,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,wCAAwC;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAItD,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,OAAO,CAAqB;IAEpC,YAAY,OAAO,GAAE,qBAA0B,EAY9C;IAED;;4DAEwD;IACxD,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,KAAkB,GAAG,IAAI,CAE1E;IAED,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAoBlD;IAED,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAKlD;IAED,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAgB1B;IAED;;;;OAIG;IACH,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAM3C;IAED,2DAA2D;IAC3D,KAAK,IAAI,IAAI,CAEZ;IAED,qDAAqD;IACrD,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhC;IAED;;yCAEqC;IACrC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/B;IAED,oEAAoE;IACpE,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhC;IAED;;;;OAIG;IACH,SAAS,IAAI,IAAI,CAKhB;IAED,wDAAwD;IACxD,WAAW,IAAI,MAAM,EAAE,CAItB;IAED,4DAA4D;IAC5D,QAAQ,IAAI,UAAU,CAuBrB;IAED,+DAA+D;IAC/D,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvC;IAED,uDAAuD;IACvD,eAAe,IAAI,YAAY,EAAE,CAahC;IAED,OAAO,CAAC,UAAU;CAWnB;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAO9D;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAc9D"}
package/dist/core.js ADDED
@@ -0,0 +1,258 @@
1
+ const ZERO_INSET = { top: 0, right: 0, bottom: 0, left: 0 };
2
+ const DEFAULT_STICK = { radius: 64, deadZone: 8, knobRadius: 26 };
3
+ const DEFAULT_BUTTON_RADIUS = 44;
4
+ const IDLE_STICK = {
5
+ active: false,
6
+ anchorX: 0,
7
+ anchorY: 0,
8
+ curX: 0,
9
+ curY: 0,
10
+ dx: 0,
11
+ dy: 0,
12
+ distance: 0,
13
+ angle: 0,
14
+ magnitude: 0,
15
+ inDeadZone: true,
16
+ };
17
+ /**
18
+ * Framework-agnostic on-screen controls. You feed it raw pointer events
19
+ * (`pointerDown/Move/Up`) in screen-space pixels; it tracks a floating analog
20
+ * stick plus any number of action buttons and exposes their state for the game
21
+ * loop to read. No DOM, no engine — pair it with `attachVirtualGamepad` from
22
+ * `@vibedgames/gamepad/phaser`, or drive it yourself.
23
+ *
24
+ * Touch routing on each `pointerDown`, in order:
25
+ * 1. fixed buttons (a touch inside a button's circle presses it),
26
+ * 2. the stick (the first free touch anchors the floating stick),
27
+ * 3. a "rest" button, if any (catches every other touch — the
28
+ * "any finger fires" model).
29
+ */
30
+ export class VirtualGamepad {
31
+ stickOpts;
32
+ buttons;
33
+ onButtonDown;
34
+ onButtonUp;
35
+ viewport = { width: 0, height: 0, inset: ZERO_INSET };
36
+ stick = null;
37
+ /** button id -> set of pointer ids currently pressing it. */
38
+ pressed = new Map();
39
+ /** pointer id -> what it's bound to. */
40
+ binding = new Map();
41
+ // Edge state is double-buffered: events accumulate into *Accum, and
42
+ // nextFrame() publishes them for justPressed/justReleased to read — so an
43
+ // edge is visible for exactly one frame no matter when the event landed.
44
+ downAccum = new Set();
45
+ upAccum = new Set();
46
+ frameDown = new Set();
47
+ frameUp = new Set();
48
+ constructor(options = {}) {
49
+ this.stickOpts = options.stick === false ? null : { ...DEFAULT_STICK, ...options.stick };
50
+ this.buttons = (options.buttons ?? []).map((b) => ({
51
+ id: b.id,
52
+ position: b.position,
53
+ radius: b.radius ?? DEFAULT_BUTTON_RADIUS,
54
+ label: b.label,
55
+ rest: !b.position,
56
+ }));
57
+ for (const b of this.buttons)
58
+ this.pressed.set(b.id, new Set());
59
+ this.onButtonDown = options.onButtonDown;
60
+ this.onButtonUp = options.onButtonUp;
61
+ }
62
+ /** Update the canvas size so fixed buttons re-anchor (call on resize).
63
+ * Pass safe-area insets (see `safeAreaInset()`) so position resolvers can
64
+ * keep buttons clear of the notch / home indicator. */
65
+ setViewport(width, height, inset = ZERO_INSET) {
66
+ this.viewport = { width, height, inset };
67
+ }
68
+ pointerDown(id, x, y) {
69
+ if (this.binding.has(id))
70
+ return;
71
+ // 1. Fixed buttons claim a touch that lands inside their circle.
72
+ for (const b of this.buttons) {
73
+ if (b.rest || !b.position)
74
+ continue;
75
+ const c = b.position(this.viewport);
76
+ if (Math.hypot(x - c.x, y - c.y) <= b.radius) {
77
+ this.bindButton(b.id, id);
78
+ return;
79
+ }
80
+ }
81
+ // 2. The first free touch anchors the floating stick.
82
+ if (this.stickOpts && !this.stick) {
83
+ this.stick = { pointerId: id, anchorX: x, anchorY: y, curX: x, curY: y };
84
+ this.binding.set(id, { kind: "stick" });
85
+ return;
86
+ }
87
+ // 3. A "rest" button (if defined) catches everything else.
88
+ const rest = this.buttons.find((b) => b.rest);
89
+ if (rest)
90
+ this.bindButton(rest.id, id);
91
+ }
92
+ pointerMove(id, x, y) {
93
+ if (this.stick && this.stick.pointerId === id) {
94
+ this.stick.curX = x;
95
+ this.stick.curY = y;
96
+ }
97
+ }
98
+ pointerUp(id) {
99
+ const b = this.binding.get(id);
100
+ if (!b)
101
+ return;
102
+ this.binding.delete(id);
103
+ if (b.kind === "stick") {
104
+ this.stick = null;
105
+ return;
106
+ }
107
+ const set = this.pressed.get(b.id);
108
+ if (set) {
109
+ set.delete(id);
110
+ if (set.size === 0) {
111
+ this.upAccum.add(b.id);
112
+ this.onButtonUp?.(b.id);
113
+ }
114
+ }
115
+ }
116
+ /**
117
+ * Drop any tracked pointer that's no longer physically down. Touch `up`
118
+ * events go missing when a finger slides off the canvas, so call this each
119
+ * frame with the engine's live pointer ids.
120
+ */
121
+ reconcile(activeIds) {
122
+ const live = activeIds instanceof Set ? activeIds : new Set(activeIds);
123
+ // Snapshot keys: pointerUp mutates `binding` mid-loop.
124
+ for (const id of Array.from(this.binding.keys())) {
125
+ if (!live.has(id))
126
+ this.pointerUp(id);
127
+ }
128
+ }
129
+ /** Release everything (e.g. on respawn or scene reset). */
130
+ reset() {
131
+ for (const id of Array.from(this.binding.keys()))
132
+ this.pointerUp(id);
133
+ }
134
+ /** True while any finger is touching the gamepad. */
135
+ get isTouching() {
136
+ return this.binding.size > 0;
137
+ }
138
+ isButtonDown(id) {
139
+ return (this.pressed.get(id)?.size ?? 0) > 0;
140
+ }
141
+ /** Button went up→down since the previous `nextFrame()` — edge-triggered
142
+ * polling for fixed-timestep games (tap = one action). The callback
143
+ * alternative is `onButtonDown`. */
144
+ justPressed(id) {
145
+ return this.frameDown.has(id);
146
+ }
147
+ /** Last finger left the button since the previous `nextFrame()`. */
148
+ justReleased(id) {
149
+ return this.frameUp.has(id);
150
+ }
151
+ /**
152
+ * Publish accumulated edges for `justPressed`/`justReleased` and start a new
153
+ * accumulation window. The adapters call this from their `update()`; call it
154
+ * once per frame yourself only when driving the core directly.
155
+ */
156
+ nextFrame() {
157
+ [this.frameDown, this.downAccum] = [this.downAccum, this.frameDown];
158
+ this.downAccum.clear();
159
+ [this.frameUp, this.upAccum] = [this.upAccum, this.frameUp];
160
+ this.upAccum.clear();
161
+ }
162
+ /** Ids of all buttons with at least one finger down. */
163
+ buttonsDown() {
164
+ const out = [];
165
+ for (const [id, set] of this.pressed)
166
+ if (set.size > 0)
167
+ out.push(id);
168
+ return out;
169
+ }
170
+ /** A snapshot of the stick. Idle when no finger owns it. */
171
+ getStick() {
172
+ const o = this.stickOpts;
173
+ const s = this.stick;
174
+ if (!o || !s)
175
+ return { ...IDLE_STICK }; // fresh copy — never hand out the shared singleton
176
+ const dx = s.curX - s.anchorX;
177
+ const dy = s.curY - s.anchorY;
178
+ const distance = Math.hypot(dx, dy);
179
+ // Guard against a misconfigured deadZone >= radius (span <= 0 → NaN/∞).
180
+ const span = Math.max(1, o.radius - o.deadZone);
181
+ const magnitude = Math.min(1, Math.max(0, (distance - o.deadZone) / span));
182
+ return {
183
+ active: true,
184
+ anchorX: s.anchorX,
185
+ anchorY: s.anchorY,
186
+ curX: s.curX,
187
+ curY: s.curY,
188
+ dx,
189
+ dy,
190
+ distance,
191
+ angle: Math.atan2(dy, dx),
192
+ magnitude,
193
+ inDeadZone: distance <= o.deadZone,
194
+ };
195
+ }
196
+ /** Resolved stick tuning, or null if the stick is disabled. */
197
+ getStickGeometry() {
198
+ return this.stickOpts ? { ...this.stickOpts } : null;
199
+ }
200
+ /** Resolved button geometry + state, for renderers. */
201
+ getButtonLayout() {
202
+ return this.buttons.map((b) => {
203
+ const c = b.rest || !b.position ? { x: 0, y: 0 } : b.position(this.viewport);
204
+ return {
205
+ id: b.id,
206
+ x: c.x,
207
+ y: c.y,
208
+ radius: b.radius,
209
+ label: b.label,
210
+ pressed: this.isButtonDown(b.id),
211
+ rest: b.rest,
212
+ };
213
+ });
214
+ }
215
+ bindButton(buttonId, pointerId) {
216
+ const set = this.pressed.get(buttonId);
217
+ if (!set)
218
+ return;
219
+ const wasDown = set.size > 0;
220
+ set.add(pointerId);
221
+ this.binding.set(pointerId, { kind: "button", id: buttonId });
222
+ if (!wasDown) {
223
+ this.downAccum.add(buttonId);
224
+ this.onButtonDown?.(buttonId);
225
+ }
226
+ }
227
+ }
228
+ /** Snap a stick reading to a 4-way direction, or null in the dead zone. */
229
+ export function stickDirection4(stick) {
230
+ if (!stick.active || stick.inDeadZone)
231
+ return null;
232
+ const deg = ((((stick.angle * 180) / Math.PI) % 360) + 360) % 360;
233
+ if (deg >= 45 && deg < 135)
234
+ return "down";
235
+ if (deg >= 135 && deg < 225)
236
+ return "left";
237
+ if (deg >= 225 && deg < 315)
238
+ return "up";
239
+ return "right";
240
+ }
241
+ /** Snap a stick reading to an 8-way direction, or null in the dead zone. */
242
+ export function stickDirection8(stick) {
243
+ if (!stick.active || stick.inDeadZone)
244
+ return null;
245
+ const deg = ((((stick.angle * 180) / Math.PI) % 360) + 360) % 360;
246
+ const sectors = [
247
+ "right",
248
+ "down-right",
249
+ "down",
250
+ "down-left",
251
+ "left",
252
+ "up-left",
253
+ "up",
254
+ "up-right",
255
+ ];
256
+ return sectors[Math.round(deg / 45) % 8] ?? "right";
257
+ }
258
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}