star-canvas 0.1.8 → 0.1.9
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/PROMPT.md +55 -135
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
package/PROMPT.md
CHANGED
|
@@ -124,6 +124,7 @@ A safe manager for your HTML overlay, stacked on top of the canvas.
|
|
|
124
124
|
- `ui.render(html: string)`: **Use this** to set your UI. It's safe and won't destroy the canvas.
|
|
125
125
|
- Automatically skips updates if HTML is unchanged (safe to call in loop for static content)
|
|
126
126
|
- For best performance with dynamic content (score), only call when values actually change
|
|
127
|
+
- **Positioning:** The UI overlay covers the full viewport, not just the letterboxed game area. Use percentage-based positioning (e.g., `top: 50%; left: 50%; transform: translate(-50%, -50%)`) to stay centered within the visible area. Fixed offsets like `bottom: 18px` may land in the letterbox bars on some screen sizes.
|
|
127
128
|
- `ui.el(selector)`: Scoped `querySelector` for the UI root.
|
|
128
129
|
- `ui.all(selector)`: Scoped `querySelectorAll` for the UI root.
|
|
129
130
|
|
|
@@ -180,6 +181,15 @@ g.loop((dt) => {
|
|
|
180
181
|
});
|
|
181
182
|
```
|
|
182
183
|
|
|
184
|
+
**Hover effects for canvas buttons:**
|
|
185
|
+
```ts
|
|
186
|
+
g.loop((dt) => {
|
|
187
|
+
const hoverLb = state === 'gameover' && inRect(g.pointer, lbBtn);
|
|
188
|
+
drawButton('VIEW LEADERBOARD', lbBtn, hoverLb ? '#9061f9' : '#7c3aed');
|
|
189
|
+
g.canvas.style.cursor = hoverLb ? 'pointer' : 'default';
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
183
193
|
Taps on interactive UI elements (from `on()` or native `<button>`) are automatically suppressed from `g.tap`.
|
|
184
194
|
|
|
185
195
|
### Cursor Management
|
|
@@ -202,39 +212,34 @@ if (state === 'menu' || state === 'gameover') canvas.style.cursor = 'auto'; //
|
|
|
202
212
|
|
|
203
213
|
**Decision:** Does player click on game objects? → `'pointer'` | Aim precisely? → `'crosshair'` | WASD/touch only? → `'none'` during play, `'auto'` for menus
|
|
204
214
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
Converts `MouseEvent` or `PointerEvent` client coordinates to the stage's logical coordinates.
|
|
215
|
+
### Drag and Drop (Polling Pattern)
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
### `createDrag()`
|
|
212
|
-
|
|
213
|
-
Creates a drag state helper that handles coordinate conversion and offset tracking automatically.
|
|
217
|
+
Use `g.tap`, `g.pointer`, and `g.released` for drag-and-drop — coordinates are automatic.
|
|
214
218
|
|
|
215
219
|
```ts
|
|
216
|
-
|
|
220
|
+
let dragging = null;
|
|
221
|
+
let offsetX = 0, offsetY = 0;
|
|
217
222
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
g.loop((dt) => {
|
|
224
|
+
if (g.tap) {
|
|
225
|
+
const hit = pieces.find(p => inRect(g.tap, p));
|
|
226
|
+
if (hit) {
|
|
227
|
+
dragging = hit;
|
|
228
|
+
offsetX = hit.x - g.tap.x; // Offset so piece doesn't jump to cursor
|
|
229
|
+
offsetY = hit.y - g.tap.y;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (dragging && g.pointer.down) {
|
|
233
|
+
dragging.x = g.pointer.x + offsetX;
|
|
234
|
+
dragging.y = g.pointer.y + offsetY;
|
|
235
|
+
}
|
|
236
|
+
if (g.released && dragging) {
|
|
237
|
+
handleDrop(dragging);
|
|
238
|
+
dragging = null;
|
|
239
|
+
}
|
|
228
240
|
});
|
|
229
241
|
```
|
|
230
242
|
|
|
231
|
-
**API:**
|
|
232
|
-
- `point(e)` - Pure coordinate conversion, no side effects
|
|
233
|
-
- `grab(e, obj)` - Start dragging an object, computing offset from cursor
|
|
234
|
-
- `move(e)` - Update dragged object's position
|
|
235
|
-
- `release()` - End drag, returns dropped object or null
|
|
236
|
-
- `dragging` - The currently dragged object (or null)
|
|
237
|
-
|
|
238
243
|
### `GameOptions` (optional)
|
|
239
244
|
|
|
240
245
|
Pass an options object as the second argument to `game()`:
|
|
@@ -508,15 +513,15 @@ game(({ ctx, scoped, loop }) => {
|
|
|
508
513
|
|
|
509
514
|
**Why use `scoped()`:** Prevents transform stack corruption from early returns, exceptions, or forgetting `ctx.restore()`. The context is always restored, even if the function exits early.
|
|
510
515
|
|
|
511
|
-
### Recipe 7: Drag and Drop
|
|
516
|
+
### Recipe 7: Drag and Drop
|
|
512
517
|
|
|
513
|
-
Use
|
|
518
|
+
Use `g.tap`, `g.pointer`, and `g.released` for drag-and-drop. Coordinates are canvas-space automatically.
|
|
514
519
|
|
|
515
520
|
```ts
|
|
516
521
|
import { game } from 'star-canvas';
|
|
517
522
|
|
|
518
|
-
game((
|
|
519
|
-
|
|
523
|
+
game((g) => {
|
|
524
|
+
const { ctx, width, height } = g;
|
|
520
525
|
const pieceSize = height * 0.15;
|
|
521
526
|
|
|
522
527
|
const pieces = [
|
|
@@ -525,77 +530,8 @@ game(({ ctx, width, height, loop, canvas, createDrag }) => {
|
|
|
525
530
|
{ x: width * 0.6, y: height * 0.3, color: '#3b82f6' },
|
|
526
531
|
];
|
|
527
532
|
|
|
528
|
-
// Create drag helper - handles coordinate conversion automatically
|
|
529
|
-
const drag = createDrag();
|
|
530
|
-
|
|
531
|
-
function hitTest(x, y) {
|
|
532
|
-
for (let i = pieces.length - 1; i >= 0; i--) {
|
|
533
|
-
const p = pieces[i];
|
|
534
|
-
if (x >= p.x && x < p.x + pieceSize && y >= p.y && y < p.y + pieceSize) {
|
|
535
|
-
return p;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
canvas.addEventListener('pointerdown', (e) => {
|
|
542
|
-
canvas.setPointerCapture(e.pointerId); // IMPORTANT: Ensures drag works outside canvas
|
|
543
|
-
const { x, y } = drag.point(e); // Convert coordinates
|
|
544
|
-
const hit = hitTest(x, y);
|
|
545
|
-
if (hit) {
|
|
546
|
-
drag.grab(e, hit); // Start drag with offset from cursor
|
|
547
|
-
canvas.style.cursor = 'grabbing';
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
canvas.addEventListener('pointermove', (e) => {
|
|
552
|
-
drag.move(e); // Updates grabbed object position
|
|
553
|
-
if (!drag.dragging) {
|
|
554
|
-
const { x, y } = drag.point(e);
|
|
555
|
-
canvas.style.cursor = hitTest(x, y) ? 'grab' : 'default';
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
canvas.addEventListener('pointerup', () => {
|
|
560
|
-
const dropped = drag.release(); // Returns dropped object (or null)
|
|
561
|
-
if (dropped) {
|
|
562
|
-
console.log('Dropped:', dropped);
|
|
563
|
-
}
|
|
564
|
-
canvas.style.cursor = 'default';
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
loop(() => {
|
|
568
|
-
ctx.fillStyle = '#1f2937';
|
|
569
|
-
ctx.fillRect(0, 0, width, height);
|
|
570
|
-
|
|
571
|
-
for (const p of pieces) {
|
|
572
|
-
ctx.fillStyle = drag.dragging === p ? '#f59e0b' : p.color;
|
|
573
|
-
ctx.fillRect(p.x, p.y, pieceSize, pieceSize);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
**CRITICAL: Always use `setPointerCapture()`** - This ensures drags work even when the pointer moves outside the canvas. Without it, fast drags can leave objects stuck mid-drag.
|
|
580
|
-
|
|
581
|
-
### Recipe 8: Drag and Drop (Manual Pattern)
|
|
582
|
-
|
|
583
|
-
If you need more control, here's the manual approach with `toStagePoint()`.
|
|
584
|
-
|
|
585
|
-
```ts
|
|
586
|
-
import { game } from 'star-canvas';
|
|
587
|
-
|
|
588
|
-
game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
|
|
589
|
-
const pieceSize = height * 0.15;
|
|
590
|
-
const pieces = [
|
|
591
|
-
{ x: width * 0.2, y: height * 0.3, color: '#ef4444' },
|
|
592
|
-
{ x: width * 0.4, y: height * 0.4, color: '#10b981' },
|
|
593
|
-
];
|
|
594
|
-
|
|
595
|
-
// Manual drag state
|
|
596
533
|
let dragging = null;
|
|
597
|
-
let
|
|
598
|
-
let dragOffsetY = 0;
|
|
534
|
+
let offsetX = 0, offsetY = 0;
|
|
599
535
|
|
|
600
536
|
function hitTest(px, py) {
|
|
601
537
|
for (let i = pieces.length - 1; i >= 0; i--) {
|
|
@@ -607,34 +543,28 @@ game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
|
|
|
607
543
|
return null;
|
|
608
544
|
}
|
|
609
545
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
546
|
+
g.loop(() => {
|
|
547
|
+
// Grab
|
|
548
|
+
if (g.tap) {
|
|
549
|
+
const hit = hitTest(g.tap.x, g.tap.y);
|
|
550
|
+
if (hit) {
|
|
551
|
+
dragging = hit;
|
|
552
|
+
offsetX = hit.x - g.tap.x; // Offset so piece doesn't jump to cursor
|
|
553
|
+
offsetY = hit.y - g.tap.y;
|
|
554
|
+
}
|
|
619
555
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
556
|
+
// Move
|
|
557
|
+
if (dragging && g.pointer.down) {
|
|
558
|
+
dragging.x = g.pointer.x + offsetX;
|
|
559
|
+
dragging.y = g.pointer.y + offsetY;
|
|
560
|
+
}
|
|
561
|
+
// Drop
|
|
562
|
+
if (g.released && dragging) {
|
|
563
|
+
console.log('Dropped:', dragging);
|
|
564
|
+
dragging = null;
|
|
629
565
|
}
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
canvas.addEventListener('pointerup', () => {
|
|
633
|
-
dragging = null;
|
|
634
|
-
canvas.style.cursor = 'default';
|
|
635
|
-
});
|
|
636
566
|
|
|
637
|
-
|
|
567
|
+
// Draw
|
|
638
568
|
ctx.fillStyle = '#1f2937';
|
|
639
569
|
ctx.fillRect(0, 0, width, height);
|
|
640
570
|
|
|
@@ -646,16 +576,6 @@ game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
|
|
|
646
576
|
});
|
|
647
577
|
```
|
|
648
578
|
|
|
649
|
-
**Common Drag-Drop Mistakes:**
|
|
650
|
-
|
|
651
|
-
1. ❌ Forgetting `toStagePoint()` in pointermove → `createDrag()` fixes this
|
|
652
|
-
2. ❌ No drag offset (piece "jumps" to cursor) → `createDrag()` fixes this
|
|
653
|
-
3. ❌ Using `e.clientX/clientY` directly → `createDrag()` fixes this
|
|
654
|
-
4. ❌ Not clearing state on pointerup → `createDrag()` fixes this
|
|
655
|
-
5. ❌ Missing `setPointerCapture()` (drags break outside canvas) → **You must add this!**
|
|
656
|
-
|
|
657
|
-
**Recommendation:** Use `createDrag()` + `setPointerCapture()` for bulletproof drag-and-drop.
|
|
658
|
-
|
|
659
579
|
### Recipe 9: Image Backgrounds
|
|
660
580
|
|
|
661
581
|
Two patterns for backgrounds: **full-canvas** (unique scenes) or **tileable patterns** (repeating textures).
|
package/dist/index.d.cts
CHANGED
|
@@ -72,7 +72,7 @@ interface GameContext {
|
|
|
72
72
|
x: number;
|
|
73
73
|
y: number;
|
|
74
74
|
};
|
|
75
|
-
/**
|
|
75
|
+
/** @deprecated Use g.tap/g.pointer/g.released polling instead. */
|
|
76
76
|
createDrag: <T extends {
|
|
77
77
|
x: number;
|
|
78
78
|
y: number;
|
|
@@ -192,7 +192,7 @@ interface DragState<T extends {
|
|
|
192
192
|
/** The currently grabbed object (or null) */
|
|
193
193
|
readonly dragging: T | null;
|
|
194
194
|
}
|
|
195
|
-
/**
|
|
195
|
+
/** @deprecated Use g.tap/g.pointer/g.released polling for drag-and-drop instead. */
|
|
196
196
|
declare function createDragState<T extends {
|
|
197
197
|
x: number;
|
|
198
198
|
y: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ interface GameContext {
|
|
|
72
72
|
x: number;
|
|
73
73
|
y: number;
|
|
74
74
|
};
|
|
75
|
-
/**
|
|
75
|
+
/** @deprecated Use g.tap/g.pointer/g.released polling instead. */
|
|
76
76
|
createDrag: <T extends {
|
|
77
77
|
x: number;
|
|
78
78
|
y: number;
|
|
@@ -192,7 +192,7 @@ interface DragState<T extends {
|
|
|
192
192
|
/** The currently grabbed object (or null) */
|
|
193
193
|
readonly dragging: T | null;
|
|
194
194
|
}
|
|
195
|
-
/**
|
|
195
|
+
/** @deprecated Use g.tap/g.pointer/g.released polling for drag-and-drop instead. */
|
|
196
196
|
declare function createDragState<T extends {
|
|
197
197
|
x: number;
|
|
198
198
|
y: number;
|