@edv4h/usketch-plugin-character 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -11
- package/dist/character-input.d.ts +15 -4
- package/dist/character-input.d.ts.map +1 -1
- package/dist/character-input.js +174 -26
- package/dist/character-input.js.map +1 -1
- package/dist/character-key-choices.d.ts +14 -0
- package/dist/character-key-choices.d.ts.map +1 -0
- package/dist/character-key-choices.js +23 -0
- package/dist/character-key-choices.js.map +1 -0
- package/dist/character-layer.d.ts +1 -1
- package/dist/character-layer.d.ts.map +1 -1
- package/dist/character-layer.js +45 -14
- package/dist/character-layer.js.map +1 -1
- package/dist/character-physics.d.ts +156 -2
- package/dist/character-physics.d.ts.map +1 -1
- package/dist/character-physics.js +270 -22
- package/dist/character-physics.js.map +1 -1
- package/dist/character-runtime.d.ts +8 -1
- package/dist/character-runtime.d.ts.map +1 -1
- package/dist/character-runtime.js +74 -8
- package/dist/character-runtime.js.map +1 -1
- package/dist/character-service.d.ts +23 -3
- package/dist/character-service.d.ts.map +1 -1
- package/dist/character-service.js +7 -0
- package/dist/character-service.js.map +1 -1
- package/dist/character-store.d.ts +34 -1
- package/dist/character-store.d.ts.map +1 -1
- package/dist/character-store.js +86 -4
- package/dist/character-store.js.map +1 -1
- package/dist/character-sync.d.ts +11 -1
- package/dist/character-sync.d.ts.map +1 -1
- package/dist/character-sync.js +23 -1
- package/dist/character-sync.js.map +1 -1
- package/dist/character-types.d.ts +31 -2
- package/dist/character-types.d.ts.map +1 -1
- package/dist/character-types.js +2 -1
- package/dist/character-types.js.map +1 -1
- package/dist/character-virtual-input.d.ts +20 -0
- package/dist/character-virtual-input.d.ts.map +1 -0
- package/dist/character-virtual-input.js +34 -0
- package/dist/character-virtual-input.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +13 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/plugin.js.map +1 -1
- package/dist/register-character-hud.d.ts.map +1 -1
- package/dist/register-character-hud.js +88 -1
- package/dist/register-character-hud.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,9 @@ createCharacterPlugin({
|
|
|
23
23
|
keys: { up: ["KeyW", "ArrowUp"] }, // KeyboardEvent.code values
|
|
24
24
|
cameraMode: "rotate", // "fixed" | "rotate" | "free"
|
|
25
25
|
controlScheme: "vehicle", // "directional" | "vehicle"
|
|
26
|
-
drift: true, // vehicle:
|
|
26
|
+
drift: true, // vehicle: Shift + steer drifts (or tuning overrides)
|
|
27
|
+
jump: true, // Space jumps (or tuning overrides)
|
|
28
|
+
renderShadow: ({ altitude }) => <div className="shadow" style={{ scale: 1 / (1 + altitude / 60) }} />,
|
|
27
29
|
screenAnchor: { x: 0.5, y: 0.75 },
|
|
28
30
|
});
|
|
29
31
|
```
|
|
@@ -34,7 +36,9 @@ createCharacterPlugin({
|
|
|
34
36
|
as CSS `rotate()` and the viewport `rotation`.
|
|
35
37
|
- **Render props**: `heading` (world), `screenHeading` (= `heading + viewport.rotation`,
|
|
36
38
|
what to rotate a top-down sprite by), `moving`, `speed`, `steering`, `slip`,
|
|
37
|
-
`drifting`, `zoom`, `appearance`, `name`, `isSelf`, `id`, `elevation
|
|
39
|
+
`drifting`, `zoom`, `appearance`, `name`, `isSelf`, `id`, `elevation`, `z`, `altitude`,
|
|
40
|
+
`airborne`, `verticalSpeed`, `hovering`, `hoverGauge` (0 … 1, `null` with hovering
|
|
41
|
+
off). Each character is placed centered on its screen position
|
|
38
42
|
in one screen-space layer, so self and remote characters look the same under zoom
|
|
39
43
|
and camera rotation.
|
|
40
44
|
- **Control schemes**
|
|
@@ -44,7 +48,7 @@ createCharacterPlugin({
|
|
|
44
48
|
- `vehicle` — up/down throttle and reverse, left/right steer. Steering scales
|
|
45
49
|
with speed (a stopped car can't turn) and inverts in reverse. The held steering
|
|
46
50
|
(-1 … 1) is in the pose and render props, e.g. to turn the front wheels.
|
|
47
|
-
- **Drift** (`vehicle`, opt-in via `drift`): hold the drift key (
|
|
51
|
+
- **Drift** (`vehicle`, opt-in via `drift`): hold the drift key (Shift) and steer at
|
|
48
52
|
speed to start an arcade drift toward the steering. It turns tighter than normal
|
|
49
53
|
steering and slides the body by `slip` degrees (draw the car at `heading + slip`;
|
|
50
54
|
it keeps traveling along `heading`). Steering into the drift tightens it,
|
|
@@ -52,7 +56,55 @@ createCharacterPlugin({
|
|
|
52
56
|
`stopSpeed` or reversing ends it, and the slip eases back. Tuning (`DriftParams`):
|
|
53
57
|
`startSpeed` / `stopSpeed` (fractions of `moveSpeed`), `turnRate` / `steerRate`
|
|
54
58
|
(multiples of `turnRate`), `slipAngle` / `steerSlipAngle` (degrees), `recoveryRate`
|
|
55
|
-
(1/s).
|
|
59
|
+
(1/s). With `hop: true`, pressing the drift key on the ground hops `hopHeight`
|
|
60
|
+
(default 10) first, and the drift starts on landing if the key is still held and
|
|
61
|
+
steering (a car can't steer in the air). The drift key is only captured while drift
|
|
62
|
+
is on.
|
|
63
|
+
- **Jump** (opt-in via `jump`): press the jump key (Space) to jump. The arc is set by
|
|
64
|
+
its apex `height` (world units) and `timeToApex`; it falls `fallGravity` × faster than
|
|
65
|
+
it rises, releasing the key while rising cuts it short (`releaseCut`), a press within
|
|
66
|
+
`bufferTime` before landing jumps on touchdown, and a jump within `coyoteTime` of
|
|
67
|
+
walking off a ledge still counts. The pose carries the feet height `z` (absolute, like
|
|
68
|
+
the ground height), `vz` and `airborne`. Where the jump key is also the active drift
|
|
69
|
+
key (both can be rebound) the drift wins. `api.jump()` (and the HUD
|
|
70
|
+
action) jumps from code, as a full jump.
|
|
71
|
+
- **Multi-jump** (`jump.airJumps`, default 0): a fresh press in the air uses one of
|
|
72
|
+
`airJumps` extra jumps while any remain (1 = a double jump); each reaches
|
|
73
|
+
`airJumpRatio` (default 0.8) × `height` (take-off speed × √ratio). They come back on
|
|
74
|
+
landing.
|
|
75
|
+
- **Hover** (`jump.hover`, default off; `true` or a partial `HoverParams`): once the air
|
|
76
|
+
jumps are used up (or there are none), holding the jump key while falling fires a
|
|
77
|
+
thrust and drains a gauge that lasts `duration` (1.5 s); an empty gauge falls normally.
|
|
78
|
+
`thrust` is a multiple of gravity (default 0 = a glide): below 1 the character sinks,
|
|
79
|
+
at 1 it keeps its vertical speed, above 1 it climbs for as long as the key is held.
|
|
80
|
+
Sinking is capped at `fallSpeed` (default 15 world units / s, 0 = never sink), climbing
|
|
81
|
+
at `riseSpeed` (60). The gauge refills
|
|
82
|
+
on the ground over `rechargeTime` (0.8 s), not at once on landing. The pose carries
|
|
83
|
+
`hovering` and `hoverGauge` (stays 1 with hovering off); both are broadcast.
|
|
84
|
+
- **Keys**: the jump (Space) and the drift (Shift) have their own keys. The HUD offers
|
|
85
|
+
a few keys for each (`KEY_CHOICES`: Space, Shift, Z, X, C, J, K, L); `setKeys` takes
|
|
86
|
+
any `KeyboardEvent.code`s and `getKeys` returns the bindings.
|
|
87
|
+
- **Shadow**: pass `renderShadow` to draw a shadow; it is placed on the ground under
|
|
88
|
+
each character while the body stands at its feet height `z`, so under a 3D view
|
|
89
|
+
(Mode 7) a jump lifts the body off its shadow. `altitude` (`z - elevation`) lets it
|
|
90
|
+
shrink as the character rises.
|
|
91
|
+
- **Keyboard focus**: movement keys are captured on `window` while the character is
|
|
92
|
+
on, except where the page needs them: nothing is taken while a text field has focus
|
|
93
|
+
or focus is inside a dialog / menu (`dialog`, `role="dialog"`, `alertdialog`, `menu`;
|
|
94
|
+
a dialog that contains the board itself doesn't count), arrow keys are left to
|
|
95
|
+
widgets that use them (range / radio inputs, `role="slider"`, `radio`, `tab`,
|
|
96
|
+
`option`, `combobox`, `menubar` items, `grid`, `treeitem` …) while WASD keeps
|
|
97
|
+
driving, and Space is left to buttons, links and other controls it activates. Focus
|
|
98
|
+
is read through `composedPath()`, so web-component dialogs count too. Moving focus
|
|
99
|
+
to such a target releases the held keys it uses.
|
|
100
|
+
- **Touch / gamepad (virtual input)**: a host control feeds analog input with
|
|
101
|
+
`api.setVirtualInput({ throttle, steer, drift })` (`throttle` / `steer` in -1 … 1;
|
|
102
|
+
`directional` reads them as screen up / right). Send the current state on every
|
|
103
|
+
change and `null` on release. It adds to the held keys (capped at ±1), so keyboard
|
|
104
|
+
and stick can drive together; a partial `throttle` aims for that fraction of the top
|
|
105
|
+
speed and a partial `steer` turns by that fraction. Touching it resumes a paused
|
|
106
|
+
follow camera like a movement key. It is ignored while the character is off, and
|
|
107
|
+
turning the character off releases it.
|
|
56
108
|
- **Camera modes**
|
|
57
109
|
- `fixed` — follow, world stays upright.
|
|
58
110
|
- `rotate` — follow and turn the world by `-heading` (character always faces up).
|
|
@@ -69,8 +121,11 @@ createCharacterPlugin({
|
|
|
69
121
|
`@edv4h/usketch-shared`, e.g. the map plugin's raised terrain), moving onto
|
|
70
122
|
ground more than `motion.maxStep` (default 24 world units, HUD "登れる段差") higher
|
|
71
123
|
than where the character stands is blocked; it slides along the cliff instead.
|
|
72
|
-
|
|
73
|
-
|
|
124
|
+
The step is measured from the feet, so mid-jump taller cliffs are reachable; a ledge
|
|
125
|
+
within the step above the feet is landed on. Stepping down within the step snaps
|
|
126
|
+
down; a bigger drop falls (gravity applies even with jumping off). The render props
|
|
127
|
+
carry `elevation` (the ground) and `z` (the feet), and under a 3D view (Mode 7) the
|
|
128
|
+
character is lifted to its feet height.
|
|
74
129
|
- **Input**: held keys are tracked from window `keydown`/`keyup` (capture phase,
|
|
75
130
|
`event.code`), swallowed while the character is on so single-letter tool
|
|
76
131
|
shortcuts don't fire. Typing in text fields, and key chords with Ctrl/Cmd/Alt,
|
|
@@ -78,9 +133,9 @@ createCharacterPlugin({
|
|
|
78
133
|
|
|
79
134
|
## HUD
|
|
80
135
|
|
|
81
|
-
A "キャラクター" settings group (on/off, camera, scheme, drift,
|
|
82
|
-
climbable step, screen position, camera lag) plus actions to toggle the
|
|
83
|
-
screen position.
|
|
136
|
+
A "キャラクター" settings group (on/off, camera, scheme, drift, drift key, drift hop, jump, jump key, jump height, air
|
|
137
|
+
jumps, hover, hover time, hover thrust, speed, turn rate, climbable step, screen position, camera lag) plus actions to toggle the
|
|
138
|
+
character, to place it at the screen position, and to jump.
|
|
84
139
|
|
|
85
140
|
## Service
|
|
86
141
|
|
|
@@ -91,8 +146,14 @@ const api = getCharacterApi(app.services);
|
|
|
91
146
|
api?.enable();
|
|
92
147
|
api?.setCameraMode("rotate");
|
|
93
148
|
api?.setRenderer(myRenderer);
|
|
94
|
-
api?.setDrift({ slipAngle: 35 }); // true / false / tuning overrides
|
|
95
|
-
api?.
|
|
149
|
+
api?.setDrift({ slipAngle: 35, hop: true }); // true / false / tuning overrides
|
|
150
|
+
api?.setJump({ height: 60 }); // true / false / tuning overrides
|
|
151
|
+
api?.setJump({ airJumps: 1, hover: { duration: 2 } }); // double jump + hover (hover: true / false / partial)
|
|
152
|
+
api?.jump();
|
|
153
|
+
api?.setKeys({ ...api.getKeys(), drift: ["KeyX"] }); // KeyboardEvent.code values
|
|
154
|
+
api?.setShadowRenderer(myShadow);
|
|
155
|
+
api?.getPose(); // { x, y, heading, speed, moving, steering, slip, drift, z, vz, airborne, hovering, hoverGauge }
|
|
156
|
+
api?.setVirtualInput({ throttle: 0.8, steer: -0.3 }); // touch stick; null on release
|
|
96
157
|
```
|
|
97
158
|
|
|
98
159
|
## Limitations
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { MoveInput } from "./character-physics.js";
|
|
2
2
|
import type { CharacterKeys } from "./character-types.js";
|
|
3
3
|
export interface KeyInput {
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The currently held directions (and the drift / jump keys). `jumpPressed` /
|
|
6
|
+
* `driftPressed` report a press since the previous `read()`, so a tap between two
|
|
7
|
+
* frames isn't lost.
|
|
8
|
+
*/
|
|
5
9
|
read(): MoveInput;
|
|
10
|
+
/** The current key bindings. */
|
|
11
|
+
getKeys(): CharacterKeys;
|
|
6
12
|
/** Whether any bound key is held. */
|
|
7
13
|
anyHeld(): boolean;
|
|
8
14
|
/** Replace the key bindings (held state is cleared). */
|
|
@@ -11,10 +17,15 @@ export interface KeyInput {
|
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
19
|
* Listen for the bound keys on `window`. `isActive()` gates everything: while it is
|
|
14
|
-
* false no key is captured (and none is swallowed).
|
|
15
|
-
*
|
|
20
|
+
* false no key is captured (and none is swallowed). Keys are left alone while focus is
|
|
21
|
+
* in a text field or inside a dialog / menu, arrow keys while a widget that uses them
|
|
22
|
+
* (slider, radio, tab …) has focus, and the drift / jump keys on controls Space activates.
|
|
23
|
+
* Held keys are cleared on window blur and tab hide, and released when focus moves to
|
|
24
|
+
* a target that uses them, so a key released elsewhere can't get stuck "down".
|
|
16
25
|
*/
|
|
17
26
|
export declare function createKeyInput(initialKeys: CharacterKeys, isActive: () => boolean,
|
|
18
27
|
/** Whether the drift key does anything now (else it is left to the host, e.g. Space-pan). */
|
|
19
|
-
isDriftActive?: () => boolean
|
|
28
|
+
isDriftActive?: () => boolean,
|
|
29
|
+
/** Whether the jump key does anything now (else it is left to the host). */
|
|
30
|
+
isJumpActive?: () => boolean): KeyInput;
|
|
20
31
|
//# sourceMappingURL=character-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"character-input.d.ts","sourceRoot":"","sources":["../src/character-input.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACxB
|
|
1
|
+
{"version":3,"file":"character-input.d.ts","sourceRoot":"","sources":["../src/character-input.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACxB;;;;OAIG;IACH,IAAI,IAAI,SAAS,CAAC;IAClB,gCAAgC;IAChC,OAAO,IAAI,aAAa,CAAC;IACzB,qCAAqC;IACrC,OAAO,IAAI,OAAO,CAAC;IACnB,wDAAwD;IACxD,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;CAChB;AAkJD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC7B,WAAW,EAAE,aAAa,EAC1B,QAAQ,EAAE,MAAM,OAAO;AACvB,6FAA6F;AAC7F,aAAa,GAAE,MAAM,OAAqB;AAC1C,4EAA4E;AAC5E,YAAY,GAAE,MAAM,OAAqB,GACvC,QAAQ,CAkIV"}
|
package/dist/character-input.js
CHANGED
|
@@ -26,21 +26,99 @@ const SPACE_ROLES = new Set([
|
|
|
26
26
|
"tab",
|
|
27
27
|
"textbox",
|
|
28
28
|
]);
|
|
29
|
-
|
|
30
|
-
function isSpaceTarget(target) {
|
|
29
|
+
const asElement = (target) => {
|
|
31
30
|
const el = target;
|
|
32
|
-
|
|
31
|
+
return el && typeof el.tagName === "string" ? el : null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The focused element and its ancestors, innermost first. `composedPath()` reaches into
|
|
35
|
+
* (open) shadow roots, so a web-component dialog or slider is seen as itself rather than
|
|
36
|
+
* as its retargeted host; without it, the light-DOM parent chain.
|
|
37
|
+
*/
|
|
38
|
+
function focusPath(e) {
|
|
39
|
+
const raw = typeof e.composedPath === "function" ? e.composedPath() : [];
|
|
40
|
+
const path = raw.map(asElement).filter((el) => el !== null);
|
|
41
|
+
if (path.length > 0)
|
|
42
|
+
return path;
|
|
43
|
+
const chain = [];
|
|
44
|
+
for (let el = asElement(e.target); el; el = asElement(el.parentElement))
|
|
45
|
+
chain.push(el);
|
|
46
|
+
return chain;
|
|
47
|
+
}
|
|
48
|
+
const roleOf = (el) => el && typeof el.getAttribute === "function" ? el.getAttribute("role") : null;
|
|
49
|
+
/** Elements Space activates (or types into): the drift and jump keys must not steal it from them. */
|
|
50
|
+
function isSpaceTarget(el) {
|
|
51
|
+
if (!el)
|
|
33
52
|
return false;
|
|
34
53
|
if (["INPUT", "TEXTAREA", "SELECT", "BUTTON", "A", "SUMMARY"].includes(el.tagName))
|
|
35
54
|
return true;
|
|
36
55
|
if (el.isContentEditable === true)
|
|
37
56
|
return true;
|
|
38
|
-
const role =
|
|
57
|
+
const role = roleOf(el);
|
|
39
58
|
return role !== null && SPACE_ROLES.has(role);
|
|
40
59
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Overlays that own the keyboard while focus is inside them (a dialog or menu opened
|
|
62
|
+
* mid-drive). Persistent containers such as a menubar or a sidebar listbox are not
|
|
63
|
+
* here: their items are arrow-key widgets, which keep WASD driving.
|
|
64
|
+
*/
|
|
65
|
+
const KEY_OWNER_SELECTOR = 'dialog, [role="dialog"], [role="alertdialog"], [role="menu"]';
|
|
66
|
+
/** Marks the board's layers, to tell a host dialog that wraps the whole board apart. */
|
|
67
|
+
const BOARD_SELECTOR = "[data-layer-id]";
|
|
68
|
+
/** `<input type>`s that move their value or selection with the arrow keys. */
|
|
69
|
+
const ARROW_INPUTS = new Set(["range", "radio"]);
|
|
70
|
+
/** ARIA widgets (and composite containers that keep focus) that use the arrow keys. */
|
|
71
|
+
const ARROW_ROLES = new Set([
|
|
72
|
+
"combobox",
|
|
73
|
+
"grid",
|
|
74
|
+
"gridcell",
|
|
75
|
+
"listbox",
|
|
76
|
+
"menubar",
|
|
77
|
+
"menuitem",
|
|
78
|
+
"menuitemcheckbox",
|
|
79
|
+
"menuitemradio",
|
|
80
|
+
"option",
|
|
81
|
+
"radio",
|
|
82
|
+
"radiogroup",
|
|
83
|
+
"scrollbar",
|
|
84
|
+
"separator",
|
|
85
|
+
"slider",
|
|
86
|
+
"spinbutton",
|
|
87
|
+
"tab",
|
|
88
|
+
"tablist",
|
|
89
|
+
"toolbar",
|
|
90
|
+
"tree",
|
|
91
|
+
"treegrid",
|
|
92
|
+
"treeitem",
|
|
93
|
+
]);
|
|
94
|
+
/**
|
|
95
|
+
* Focus is inside a dialog or menu: every bound key is left to it. A dialog that holds
|
|
96
|
+
* the board itself (the host shows the board in a modal) doesn't count, or the
|
|
97
|
+
* character could never be driven there.
|
|
98
|
+
*/
|
|
99
|
+
function isInKeyOwner(path) {
|
|
100
|
+
const owner = path.find((el) => typeof el.matches === "function" && el.matches(KEY_OWNER_SELECTOR));
|
|
101
|
+
if (!owner)
|
|
102
|
+
return false;
|
|
103
|
+
return typeof owner.querySelector !== "function" || owner.querySelector(BOARD_SELECTOR) === null;
|
|
104
|
+
}
|
|
105
|
+
/** The focused element uses the arrow keys itself, so arrow bindings are left to it. */
|
|
106
|
+
function isArrowTarget(el) {
|
|
107
|
+
if (!el)
|
|
108
|
+
return false;
|
|
109
|
+
if (el.tagName === "INPUT" && ARROW_INPUTS.has((el.type ?? "").toLowerCase()))
|
|
110
|
+
return true;
|
|
111
|
+
const role = roleOf(el);
|
|
112
|
+
return role !== null && ARROW_ROLES.has(role);
|
|
113
|
+
}
|
|
114
|
+
/** Whether a key acts as an arrow (by code, or by key: numpad 4/6 with NumLock off). */
|
|
115
|
+
const isArrowKey = (code, key) => code.startsWith("Arrow") || (key ?? "").startsWith("Arrow");
|
|
116
|
+
/** Focus where no bound key may be taken: typing, or a dialog / menu. */
|
|
117
|
+
function blocksAllKeys(path) {
|
|
118
|
+
return isTextInput(path[0]) || isInKeyOwner(path);
|
|
119
|
+
}
|
|
120
|
+
function isTextInput(el) {
|
|
121
|
+
if (!el)
|
|
44
122
|
return false;
|
|
45
123
|
const tag = el.tagName;
|
|
46
124
|
if (tag === "INPUT")
|
|
@@ -49,14 +127,26 @@ function isTextInput(target) {
|
|
|
49
127
|
}
|
|
50
128
|
/**
|
|
51
129
|
* Listen for the bound keys on `window`. `isActive()` gates everything: while it is
|
|
52
|
-
* false no key is captured (and none is swallowed).
|
|
53
|
-
*
|
|
130
|
+
* false no key is captured (and none is swallowed). Keys are left alone while focus is
|
|
131
|
+
* in a text field or inside a dialog / menu, arrow keys while a widget that uses them
|
|
132
|
+
* (slider, radio, tab …) has focus, and the drift / jump keys on controls Space activates.
|
|
133
|
+
* Held keys are cleared on window blur and tab hide, and released when focus moves to
|
|
134
|
+
* a target that uses them, so a key released elsewhere can't get stuck "down".
|
|
54
135
|
*/
|
|
55
136
|
export function createKeyInput(initialKeys, isActive,
|
|
56
137
|
/** Whether the drift key does anything now (else it is left to the host, e.g. Space-pan). */
|
|
57
|
-
isDriftActive = () => false
|
|
138
|
+
isDriftActive = () => false,
|
|
139
|
+
/** Whether the jump key does anything now (else it is left to the host). */
|
|
140
|
+
isJumpActive = () => false) {
|
|
58
141
|
let keys = initialKeys;
|
|
59
|
-
|
|
142
|
+
/** Held codes → the `key` they produced (to tell numpad arrows from digits). */
|
|
143
|
+
const held = new Map();
|
|
144
|
+
/** Held codes that act as the jump key (bound when pressed, so a rebind can't strand one). */
|
|
145
|
+
const jumpHeld = new Set();
|
|
146
|
+
let jumpPressed = false;
|
|
147
|
+
/** Held codes that act as the drift key (as for the jump). */
|
|
148
|
+
const driftHeld = new Set();
|
|
149
|
+
let driftPressed = false;
|
|
60
150
|
const directionOf = (code) => {
|
|
61
151
|
for (const dir of ["up", "down", "left", "right"]) {
|
|
62
152
|
if (keys[dir].includes(code))
|
|
@@ -64,54 +154,111 @@ isDriftActive = () => false) {
|
|
|
64
154
|
}
|
|
65
155
|
if (keys.drift?.includes(code) && isDriftActive())
|
|
66
156
|
return "drift";
|
|
157
|
+
if (keys.jump?.includes(code) && isJumpActive())
|
|
158
|
+
return "jump";
|
|
67
159
|
return null;
|
|
68
160
|
};
|
|
161
|
+
/** Keys Space activates on buttons etc.: the drift and jump keys yield to those. */
|
|
162
|
+
const isSpaceLike = (code) => (keys.drift?.includes(code) ?? false) || (keys.jump?.includes(code) ?? false);
|
|
69
163
|
const onKeyDown = (e) => {
|
|
70
|
-
if (!isActive()
|
|
164
|
+
if (!isActive())
|
|
71
165
|
return;
|
|
72
166
|
if (e.ctrlKey || e.metaKey || e.altKey)
|
|
73
167
|
return; // leave chords (Cmd+S …) alone
|
|
74
168
|
const dir = directionOf(e.code);
|
|
75
169
|
if (!dir)
|
|
76
170
|
return;
|
|
77
|
-
|
|
171
|
+
const path = focusPath(e);
|
|
172
|
+
if (blocksAllKeys(path))
|
|
78
173
|
return;
|
|
79
|
-
|
|
174
|
+
if ((dir === "drift" || dir === "jump") && isSpaceTarget(path[0]))
|
|
175
|
+
return;
|
|
176
|
+
if (isArrowKey(e.code, e.key) && isArrowTarget(path[0]))
|
|
177
|
+
return;
|
|
178
|
+
held.set(e.code, e.key ?? "");
|
|
179
|
+
if (dir === "jump") {
|
|
180
|
+
// Auto-repeat keydowns are not new presses.
|
|
181
|
+
if (!e.repeat && !jumpHeld.has(e.code))
|
|
182
|
+
jumpPressed = true;
|
|
183
|
+
jumpHeld.add(e.code);
|
|
184
|
+
}
|
|
185
|
+
else if (dir === "drift") {
|
|
186
|
+
if (!e.repeat && !driftHeld.has(e.code))
|
|
187
|
+
driftPressed = true;
|
|
188
|
+
driftHeld.add(e.code);
|
|
189
|
+
}
|
|
80
190
|
e.preventDefault();
|
|
81
191
|
e.stopImmediatePropagation();
|
|
82
192
|
};
|
|
193
|
+
const release = (code) => {
|
|
194
|
+
jumpHeld.delete(code);
|
|
195
|
+
driftHeld.delete(code);
|
|
196
|
+
return held.delete(code);
|
|
197
|
+
};
|
|
83
198
|
const onKeyUp = (e) => {
|
|
84
|
-
if (!
|
|
199
|
+
if (!release(e.code))
|
|
85
200
|
return;
|
|
86
201
|
e.preventDefault();
|
|
87
202
|
e.stopImmediatePropagation();
|
|
88
203
|
};
|
|
89
|
-
const clear = () =>
|
|
204
|
+
const clear = () => {
|
|
205
|
+
held.clear();
|
|
206
|
+
jumpHeld.clear();
|
|
207
|
+
jumpPressed = false;
|
|
208
|
+
driftHeld.clear();
|
|
209
|
+
driftPressed = false;
|
|
210
|
+
};
|
|
90
211
|
const onVisibility = () => {
|
|
91
212
|
if (typeof document !== "undefined" && document.hidden)
|
|
92
213
|
clear();
|
|
93
214
|
};
|
|
215
|
+
// Focus moving to a target that uses a held key hands that key to it: release it, or
|
|
216
|
+
// the character keeps going and the key's keyup is swallowed (a button that Space
|
|
217
|
+
// activates on keyup would never fire). Keys the new target ignores stay held.
|
|
218
|
+
const onFocusIn = (e) => {
|
|
219
|
+
const path = focusPath(e);
|
|
220
|
+
if (blocksAllKeys(path)) {
|
|
221
|
+
clear();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const owned = [...held].filter(([code, key]) => (isArrowKey(code, key) && isArrowTarget(path[0])) ||
|
|
225
|
+
(isSpaceLike(code) && isSpaceTarget(path[0])));
|
|
226
|
+
for (const [code] of owned)
|
|
227
|
+
release(code);
|
|
228
|
+
};
|
|
94
229
|
const hasWindow = typeof window !== "undefined";
|
|
95
230
|
if (hasWindow) {
|
|
96
231
|
window.addEventListener("keydown", onKeyDown, true);
|
|
97
232
|
window.addEventListener("keyup", onKeyUp, true);
|
|
98
233
|
window.addEventListener("blur", clear);
|
|
99
234
|
document.addEventListener("visibilitychange", onVisibility);
|
|
235
|
+
document.addEventListener("focusin", onFocusIn);
|
|
100
236
|
}
|
|
101
237
|
const isHeld = (dir) => keys[dir]?.some((c) => held.has(c)) ?? false;
|
|
102
238
|
return {
|
|
103
|
-
read: () =>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
239
|
+
read: () => {
|
|
240
|
+
const pressed = jumpPressed;
|
|
241
|
+
jumpPressed = false;
|
|
242
|
+
const drifted = driftPressed && isDriftActive();
|
|
243
|
+
driftPressed = false;
|
|
244
|
+
return {
|
|
245
|
+
up: isHeld("up"),
|
|
246
|
+
down: isHeld("down"),
|
|
247
|
+
left: isHeld("left"),
|
|
248
|
+
right: isHeld("right"),
|
|
249
|
+
// A key bound to both acts as the drift while drift is on (directionOf).
|
|
250
|
+
drift: isDriftActive() && isHeld("drift"),
|
|
251
|
+
jump: jumpHeld.size > 0,
|
|
252
|
+
jumpPressed: pressed,
|
|
253
|
+
driftPressed: drifted,
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
getKeys: () => keys,
|
|
110
257
|
anyHeld: () => held.size > 0,
|
|
111
258
|
setKeys(next) {
|
|
112
|
-
// Rebinding movement keeps the drift
|
|
113
|
-
keys = { ...next, drift: next.drift ?? keys.drift };
|
|
114
|
-
|
|
259
|
+
// Rebinding movement keeps the drift / jump keys unless given (`[]` unbinds).
|
|
260
|
+
keys = { ...next, drift: next.drift ?? keys.drift, jump: next.jump ?? keys.jump };
|
|
261
|
+
clear();
|
|
115
262
|
},
|
|
116
263
|
dispose() {
|
|
117
264
|
if (!hasWindow)
|
|
@@ -120,6 +267,7 @@ isDriftActive = () => false) {
|
|
|
120
267
|
window.removeEventListener("keyup", onKeyUp, true);
|
|
121
268
|
window.removeEventListener("blur", clear);
|
|
122
269
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
270
|
+
document.removeEventListener("focusin", onFocusIn);
|
|
123
271
|
},
|
|
124
272
|
};
|
|
125
273
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"character-input.js","sourceRoot":"","sources":["../src/character-input.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"character-input.js","sourceRoot":"","sources":["../src/character-input.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CharacterKeys } from "./character-types.js";
|
|
2
|
+
export interface KeyChoice {
|
|
3
|
+
/** HUD option value. */
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** `KeyboardEvent.code` values it binds. */
|
|
7
|
+
codes: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const KEY_CHOICES: readonly KeyChoice[];
|
|
10
|
+
/** The choice whose codes are exactly `codes` (in any order), or `null`. */
|
|
11
|
+
export declare function keyChoiceOf(codes: readonly string[] | undefined): KeyChoice | null;
|
|
12
|
+
/** `keys` with the jump or drift key set to the choice `value` (unchanged for an unknown one). */
|
|
13
|
+
export declare function withKeyChoice(keys: CharacterKeys, action: "jump" | "drift", value: string): CharacterKeys;
|
|
14
|
+
//# sourceMappingURL=character-key-choices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"character-key-choices.d.ts","sourceRoot":"","sources":["../src/character-key-choices.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,SAAS;IACzB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAS3C,CAAC;AAEF,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,CAMlF;AAED,kGAAkG;AAClG,wBAAgB,aAAa,CAC5B,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GAAG,OAAO,EACxB,KAAK,EAAE,MAAM,GACX,aAAa,CAGf"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const KEY_CHOICES = [
|
|
2
|
+
{ value: "space", label: "Space", codes: ["Space"] },
|
|
3
|
+
{ value: "shift", label: "Shift", codes: ["ShiftLeft", "ShiftRight"] },
|
|
4
|
+
{ value: "z", label: "Z", codes: ["KeyZ"] },
|
|
5
|
+
{ value: "x", label: "X", codes: ["KeyX"] },
|
|
6
|
+
{ value: "c", label: "C", codes: ["KeyC"] },
|
|
7
|
+
{ value: "j", label: "J", codes: ["KeyJ"] },
|
|
8
|
+
{ value: "k", label: "K", codes: ["KeyK"] },
|
|
9
|
+
{ value: "l", label: "L", codes: ["KeyL"] },
|
|
10
|
+
];
|
|
11
|
+
/** The choice whose codes are exactly `codes` (in any order), or `null`. */
|
|
12
|
+
export function keyChoiceOf(codes) {
|
|
13
|
+
if (!codes)
|
|
14
|
+
return null;
|
|
15
|
+
const set = new Set(codes);
|
|
16
|
+
return (KEY_CHOICES.find((c) => c.codes.length === set.size && c.codes.every((k) => set.has(k))) ?? null);
|
|
17
|
+
}
|
|
18
|
+
/** `keys` with the jump or drift key set to the choice `value` (unchanged for an unknown one). */
|
|
19
|
+
export function withKeyChoice(keys, action, value) {
|
|
20
|
+
const choice = KEY_CHOICES.find((c) => c.value === value);
|
|
21
|
+
return choice ? { ...keys, [action]: [...choice.codes] } : keys;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=character-key-choices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"character-key-choices.js","sourceRoot":"","sources":["../src/character-key-choices.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,WAAW,GAAyB;IAChD,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE;IACpD,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE;IACtE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;CAC3C,CAAC;AAEF,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,KAAoC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,CACN,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAChG,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,aAAa,CAC5B,IAAmB,EACnB,MAAwB,EACxB,KAAa;IAEb,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Viewport } from "@edv4h/usketch-shared";
|
|
2
2
|
import type { HeightAt } from "./character-physics.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type CharacterStore } from "./character-store.js";
|
|
4
4
|
export declare function CharacterLayer({ store, viewport, heightAt, }: {
|
|
5
5
|
store: CharacterStore;
|
|
6
6
|
viewport: Viewport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"character-layer.d.ts","sourceRoot":"","sources":["../src/character-layer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"character-layer.d.ts","sourceRoot":"","sources":["../src/character-layer.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,QAAQ,EAA4C,MAAM,uBAAuB,CAAC;AAEhG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,sBAAsB,CAAC;AA6CzE,wBAAgB,cAAc,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,QAAQ,GACR,EAAE;IACF,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACpB,sCA4GA"}
|