minimojs 1.0.0-alpha.1 → 1.0.0-alpha.2
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/dist/animations.js +30 -0
- package/dist/audio.js +17 -0
- package/dist/game.js +1105 -0
- package/dist/input.js +185 -0
- package/dist/internal-types.js +4 -0
- package/dist/minimo.d.ts +123 -0
- package/dist/minimo.js +308 -39
- package/dist/physics.js +10 -0
- package/dist/pointer-info.js +1 -0
- package/dist/render.js +75 -0
- package/dist/sprite.js +149 -0
- package/dist/timers.js +23 -0
- package/package.json +1 -1
package/dist/input.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export function createInputState() {
|
|
2
|
+
return {
|
|
3
|
+
keysDown: new Set(),
|
|
4
|
+
keysPressed: new Set(),
|
|
5
|
+
pointerDown: false,
|
|
6
|
+
pointerPressed: false,
|
|
7
|
+
pointerX: 0,
|
|
8
|
+
pointerY: 0,
|
|
9
|
+
mouseDown: false,
|
|
10
|
+
mousePressed: false,
|
|
11
|
+
mouseX: 0,
|
|
12
|
+
mouseY: 0,
|
|
13
|
+
touchPointers: new Map(),
|
|
14
|
+
primaryTouchId: null,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function bindInputEvents(canvas, input) {
|
|
18
|
+
window.addEventListener("keydown", (e) => {
|
|
19
|
+
if (!input.keysDown.has(e.key)) {
|
|
20
|
+
input.keysPressed.add(e.key);
|
|
21
|
+
}
|
|
22
|
+
input.keysDown.add(e.key);
|
|
23
|
+
});
|
|
24
|
+
window.addEventListener("keyup", (e) => {
|
|
25
|
+
input.keysDown.delete(e.key);
|
|
26
|
+
});
|
|
27
|
+
const getCanvasPoint = (clientX, clientY) => {
|
|
28
|
+
const rect = canvas.getBoundingClientRect();
|
|
29
|
+
const scaleX = canvas.width / rect.width;
|
|
30
|
+
const scaleY = canvas.height / rect.height;
|
|
31
|
+
return {
|
|
32
|
+
x: (clientX - rect.left) * scaleX,
|
|
33
|
+
y: (clientY - rect.top) * scaleY,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
canvas.addEventListener("mousedown", (e) => {
|
|
37
|
+
const p = getCanvasPoint(e.clientX, e.clientY);
|
|
38
|
+
input.mouseX = p.x;
|
|
39
|
+
input.mouseY = p.y;
|
|
40
|
+
input.mouseDown = true;
|
|
41
|
+
input.mousePressed = true;
|
|
42
|
+
syncPrimaryPointer(input);
|
|
43
|
+
});
|
|
44
|
+
canvas.addEventListener("mouseup", () => {
|
|
45
|
+
input.mouseDown = false;
|
|
46
|
+
syncPrimaryPointer(input);
|
|
47
|
+
});
|
|
48
|
+
canvas.addEventListener("mousemove", (e) => {
|
|
49
|
+
const p = getCanvasPoint(e.clientX, e.clientY);
|
|
50
|
+
input.mouseX = p.x;
|
|
51
|
+
input.mouseY = p.y;
|
|
52
|
+
syncPrimaryPointer(input);
|
|
53
|
+
});
|
|
54
|
+
canvas.addEventListener("touchstart", (e) => {
|
|
55
|
+
for (const touch of Array.from(e.changedTouches)) {
|
|
56
|
+
const p = getCanvasPoint(touch.clientX, touch.clientY);
|
|
57
|
+
input.touchPointers.set(touch.identifier, {
|
|
58
|
+
x: p.x,
|
|
59
|
+
y: p.y,
|
|
60
|
+
pressed: true,
|
|
61
|
+
});
|
|
62
|
+
if (input.primaryTouchId === null) {
|
|
63
|
+
input.primaryTouchId = touch.identifier;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
syncPrimaryPointer(input);
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
}, { passive: false });
|
|
69
|
+
const clearTouches = (e) => {
|
|
70
|
+
for (const touch of Array.from(e.changedTouches)) {
|
|
71
|
+
input.touchPointers.delete(touch.identifier);
|
|
72
|
+
if (input.primaryTouchId === touch.identifier) {
|
|
73
|
+
input.primaryTouchId = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
syncPrimaryPointer(input);
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
};
|
|
79
|
+
canvas.addEventListener("touchend", clearTouches, { passive: false });
|
|
80
|
+
canvas.addEventListener("touchcancel", clearTouches, { passive: false });
|
|
81
|
+
canvas.addEventListener("touchmove", (e) => {
|
|
82
|
+
for (const touch of Array.from(e.changedTouches)) {
|
|
83
|
+
const p = getCanvasPoint(touch.clientX, touch.clientY);
|
|
84
|
+
const existing = input.touchPointers.get(touch.identifier);
|
|
85
|
+
if (existing) {
|
|
86
|
+
existing.x = p.x;
|
|
87
|
+
existing.y = p.y;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
input.touchPointers.set(touch.identifier, {
|
|
91
|
+
x: p.x,
|
|
92
|
+
y: p.y,
|
|
93
|
+
pressed: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
syncPrimaryPointer(input);
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
}, { passive: false });
|
|
100
|
+
}
|
|
101
|
+
export function syncPrimaryPointer(input) {
|
|
102
|
+
if (input.primaryTouchId !== null) {
|
|
103
|
+
const primaryTouch = input.touchPointers.get(input.primaryTouchId);
|
|
104
|
+
if (primaryTouch) {
|
|
105
|
+
input.pointerX = primaryTouch.x;
|
|
106
|
+
input.pointerY = primaryTouch.y;
|
|
107
|
+
input.pointerDown = true;
|
|
108
|
+
input.pointerPressed = primaryTouch.pressed;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
input.primaryTouchId = null;
|
|
112
|
+
}
|
|
113
|
+
if (input.touchPointers.size > 0) {
|
|
114
|
+
const firstTouchEntry = input.touchPointers.entries().next().value;
|
|
115
|
+
if (firstTouchEntry) {
|
|
116
|
+
const [touchId, touch] = firstTouchEntry;
|
|
117
|
+
input.primaryTouchId = touchId;
|
|
118
|
+
input.pointerX = touch.x;
|
|
119
|
+
input.pointerY = touch.y;
|
|
120
|
+
input.pointerDown = true;
|
|
121
|
+
input.pointerPressed = touch.pressed;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
input.primaryTouchId = null;
|
|
126
|
+
input.pointerX = input.mouseX;
|
|
127
|
+
input.pointerY = input.mouseY;
|
|
128
|
+
input.pointerDown = input.mouseDown;
|
|
129
|
+
input.pointerPressed = input.mousePressed;
|
|
130
|
+
}
|
|
131
|
+
export function collectPointers(input) {
|
|
132
|
+
const pointers = [];
|
|
133
|
+
if (input.mouseDown) {
|
|
134
|
+
pointers.push({
|
|
135
|
+
id: "mouse",
|
|
136
|
+
kind: "mouse",
|
|
137
|
+
x: input.mouseX,
|
|
138
|
+
y: input.mouseY,
|
|
139
|
+
pressed: input.mousePressed,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
for (const [id, pointer] of input.touchPointers.entries()) {
|
|
143
|
+
pointers.push({
|
|
144
|
+
id,
|
|
145
|
+
kind: "touch",
|
|
146
|
+
x: pointer.x,
|
|
147
|
+
y: pointer.y,
|
|
148
|
+
pressed: pointer.pressed,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return pointers;
|
|
152
|
+
}
|
|
153
|
+
export function isScreenPointOverSprite(x, y, sprite, radiusScale, scrollX, scrollY) {
|
|
154
|
+
const safeScale = Math.max(0, radiusScale);
|
|
155
|
+
const radius = sprite.size * safeScale;
|
|
156
|
+
const drawX = sprite.ignoreScroll ? sprite.x : sprite.x - scrollX;
|
|
157
|
+
const drawY = sprite.ignoreScroll ? sprite.y : sprite.y - scrollY;
|
|
158
|
+
const dx = x - drawX;
|
|
159
|
+
const dy = y - drawY;
|
|
160
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
161
|
+
}
|
|
162
|
+
export function isPointerDownOverSprite(input, sprite, radiusScale, scrollX, scrollY) {
|
|
163
|
+
if (!sprite)
|
|
164
|
+
return false;
|
|
165
|
+
return collectPointers(input).some((pointer) => isScreenPointOverSprite(pointer.x, pointer.y, sprite, radiusScale, scrollX, scrollY));
|
|
166
|
+
}
|
|
167
|
+
export function isPointerPressedOverSprite(input, sprite, radiusScale, scrollX, scrollY) {
|
|
168
|
+
if (!sprite)
|
|
169
|
+
return false;
|
|
170
|
+
return collectPointers(input).some((pointer) => pointer.pressed &&
|
|
171
|
+
isScreenPointOverSprite(pointer.x, pointer.y, sprite, radiusScale, scrollX, scrollY));
|
|
172
|
+
}
|
|
173
|
+
export function getPointersOverSprite(input, sprite, radiusScale, scrollX, scrollY) {
|
|
174
|
+
if (!sprite)
|
|
175
|
+
return [];
|
|
176
|
+
return collectPointers(input).filter((pointer) => isScreenPointOverSprite(pointer.x, pointer.y, sprite, radiusScale, scrollX, scrollY));
|
|
177
|
+
}
|
|
178
|
+
export function resetTransientInput(input) {
|
|
179
|
+
input.keysPressed.clear();
|
|
180
|
+
input.mousePressed = false;
|
|
181
|
+
for (const pointer of input.touchPointers.values()) {
|
|
182
|
+
pointer.pressed = false;
|
|
183
|
+
}
|
|
184
|
+
syncPrimaryPointer(input);
|
|
185
|
+
}
|
package/dist/minimo.d.ts
CHANGED
|
@@ -155,6 +155,32 @@ export declare class Sprite {
|
|
|
155
155
|
*/
|
|
156
156
|
constructor(sprite: string, x?: number, y?: number);
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Snapshot of an active pointer tracked by the engine.
|
|
160
|
+
*
|
|
161
|
+
* Coordinates are in canvas/screen space, matching {@link Game.pointerX} and
|
|
162
|
+
* {@link Game.pointerY}. For world-space comparisons, use helpers such as
|
|
163
|
+
* {@link Game.isPointerDownOverSprite} or {@link Game.getPointersOverSprite}.
|
|
164
|
+
*/
|
|
165
|
+
export interface PointerInfo {
|
|
166
|
+
/**
|
|
167
|
+
* Unique pointer id.
|
|
168
|
+
* - Mouse always uses `"mouse"`.
|
|
169
|
+
* - Touch pointers use the browser touch identifier.
|
|
170
|
+
*/
|
|
171
|
+
id: "mouse" | number;
|
|
172
|
+
/** Pointer source kind. */
|
|
173
|
+
kind: "mouse" | "touch";
|
|
174
|
+
/** Current X position in canvas/screen space, in pixels. */
|
|
175
|
+
x: number;
|
|
176
|
+
/** Current Y position in canvas/screen space, in pixels. */
|
|
177
|
+
y: number;
|
|
178
|
+
/**
|
|
179
|
+
* `true` only on the frame this pointer started pressing.
|
|
180
|
+
* For held pointers after the first frame, this becomes `false`.
|
|
181
|
+
*/
|
|
182
|
+
pressed: boolean;
|
|
183
|
+
}
|
|
158
184
|
/**
|
|
159
185
|
* # MinimoJS v1 — AI Agent Integration Guide
|
|
160
186
|
*
|
|
@@ -685,6 +711,103 @@ export declare class Game {
|
|
|
685
711
|
* ```
|
|
686
712
|
*/
|
|
687
713
|
isPointerPressed(): boolean;
|
|
714
|
+
/**
|
|
715
|
+
* Returns `true` while any active pointer is held down over the target sprite.
|
|
716
|
+
* Works with both mouse input and multiple simultaneous touches.
|
|
717
|
+
*
|
|
718
|
+
* Pointer hit testing uses a circular area centered on the sprite. The radius
|
|
719
|
+
* is `sprite.size * radiusScale`. World-space sprites are tested against the
|
|
720
|
+
* current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
|
|
721
|
+
* screen space.
|
|
722
|
+
*
|
|
723
|
+
* Use this for continuous virtual buttons such as touch movement controls.
|
|
724
|
+
*
|
|
725
|
+
* @param sprite - Target sprite to test. If `null` / `undefined`, returns `false`.
|
|
726
|
+
* @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
|
|
727
|
+
* Default: `0.5`.
|
|
728
|
+
* @returns `true` if any currently held pointer overlaps the sprite hit area.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* ```ts
|
|
732
|
+
* if (game.isPointerDownOverSprite(leftButton, 0.72)) {
|
|
733
|
+
* player.x -= 200 * dt;
|
|
734
|
+
* }
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
isPointerDownOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): boolean;
|
|
738
|
+
/**
|
|
739
|
+
* Returns `true` only on the frame any pointer first pressed over the target
|
|
740
|
+
* sprite. Works with both mouse input and multiple simultaneous touches.
|
|
741
|
+
*
|
|
742
|
+
* Pointer hit testing uses a circular area centered on the sprite. The radius
|
|
743
|
+
* is `sprite.size * radiusScale`. World-space sprites are tested against the
|
|
744
|
+
* current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
|
|
745
|
+
* screen space.
|
|
746
|
+
*
|
|
747
|
+
* Use this for one-shot virtual buttons such as menu taps.
|
|
748
|
+
*
|
|
749
|
+
* @param sprite - Target sprite to test. If `null` / `undefined`, returns `false`.
|
|
750
|
+
* @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
|
|
751
|
+
* Default: `0.5`.
|
|
752
|
+
* @returns `true` if any pointer began pressing this frame over the sprite hit area.
|
|
753
|
+
*
|
|
754
|
+
* @example
|
|
755
|
+
* ```ts
|
|
756
|
+
* if (game.isPointerPressedOverSprite(startButton, 0.8)) {
|
|
757
|
+
* startGame();
|
|
758
|
+
* }
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
761
|
+
isPointerPressedOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): boolean;
|
|
762
|
+
/**
|
|
763
|
+
* Returns a read-only snapshot of all currently active pointers.
|
|
764
|
+
*
|
|
765
|
+
* Mouse appears in the list only while the mouse button is held down. Touches
|
|
766
|
+
* appear while they remain active on the canvas. Coordinates are returned in
|
|
767
|
+
* canvas/screen space.
|
|
768
|
+
*
|
|
769
|
+
* Use this for advanced multitouch controls such as joysticks, drag handles,
|
|
770
|
+
* or gesture-like gameplay logic.
|
|
771
|
+
*
|
|
772
|
+
* @returns A read-only array of active pointer snapshots.
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```ts
|
|
776
|
+
* const pointers = game.getPointers();
|
|
777
|
+
* if (pointers.length > 0) {
|
|
778
|
+
* const first = pointers[0];
|
|
779
|
+
* game.text(`Pointer: ${first.x}, ${first.y}`, 10, 10);
|
|
780
|
+
* }
|
|
781
|
+
* ```
|
|
782
|
+
*/
|
|
783
|
+
getPointers(): readonly PointerInfo[];
|
|
784
|
+
/**
|
|
785
|
+
* Returns a read-only snapshot of all active pointers currently overlapping
|
|
786
|
+
* the target sprite.
|
|
787
|
+
*
|
|
788
|
+
* Pointer hit testing uses a circular area centered on the sprite. The radius
|
|
789
|
+
* is `sprite.size * radiusScale`. World-space sprites are tested against the
|
|
790
|
+
* current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
|
|
791
|
+
* screen space.
|
|
792
|
+
*
|
|
793
|
+
* Use this when you need more than a boolean result, such as reading the exact
|
|
794
|
+
* pointer position over a virtual joystick or draggable control.
|
|
795
|
+
*
|
|
796
|
+
* @param sprite - Target sprite to test. If `null` / `undefined`, returns an empty array.
|
|
797
|
+
* @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
|
|
798
|
+
* Default: `0.5`.
|
|
799
|
+
* @returns A read-only array of active pointer snapshots currently over the sprite.
|
|
800
|
+
*
|
|
801
|
+
* @example
|
|
802
|
+
* ```ts
|
|
803
|
+
* const touches = game.getPointersOverSprite(joystickBase, 1.0);
|
|
804
|
+
* if (touches.length > 0) {
|
|
805
|
+
* const p = touches[0];
|
|
806
|
+
* const dx = p.x - joystickBase.x;
|
|
807
|
+
* }
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
810
|
+
getPointersOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): readonly PointerInfo[];
|
|
688
811
|
/**
|
|
689
812
|
* Returns `true` when the current device appears to be mobile/touch-first.
|
|
690
813
|
*
|