js-game-zero 0.0.0-stage → 0.3.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 +22 -0
- package/README.md +138 -2
- package/dist/active-service.d.ts +21 -0
- package/dist/actor.d.ts +182 -0
- package/dist/animation.d.ts +61 -0
- package/dist/assets.d.ts +148 -0
- package/dist/audio.d.ts +150 -0
- package/dist/browser-asset-backends.d.ts +8 -0
- package/dist/browser.d.ts +10 -0
- package/dist/chunk-brk0rjcy.js +7846 -0
- package/dist/clock.d.ts +29 -0
- package/dist/color.d.ts +15 -0
- package/dist/events.d.ts +145 -0
- package/dist/friendly-names.d.ts +43 -0
- package/dist/geometry.d.ts +125 -0
- package/dist/helpers.d.ts +28 -0
- package/dist/host.d.ts +58 -0
- package/dist/host.js +435 -0
- package/dist/index.d.ts +340 -0
- package/dist/index.js +258 -0
- package/dist/input.d.ts +200 -0
- package/dist/inspection.d.ts +58 -0
- package/dist/jgz.js +8419 -0
- package/dist/node.d.ts +14 -0
- package/dist/node.js +145 -0
- package/dist/pixel-density.d.ts +23 -0
- package/dist/random.d.ts +31 -0
- package/dist/reference/actor.md +197 -0
- package/dist/reference/audio.md +128 -0
- package/dist/reference/clock.md +150 -0
- package/dist/reference/helpers.md +46 -0
- package/dist/reference/hooks.md +379 -0
- package/dist/reference/index.d.ts +2 -0
- package/dist/reference/index.js +39 -0
- package/dist/reference/loaders.md +164 -0
- package/dist/reference/rect.md +129 -0
- package/dist/reference/runtime.md +478 -0
- package/dist/reference/screen.md +313 -0
- package/dist/reference/storage.md +90 -0
- package/dist/rendering.d.ts +188 -0
- package/dist/runtime-contracts.d.ts +117 -0
- package/dist/runtime-diagnostics.d.ts +37 -0
- package/dist/runtime-persistence.d.ts +32 -0
- package/dist/runtime-source.d.ts +82 -0
- package/dist/runtime.d.ts +81 -0
- package/dist/source-scan.d.ts +49 -0
- package/dist/storage-path.d.ts +3 -0
- package/dist/storage.d.ts +88 -0
- package/dist/tone.d.ts +81 -0
- package/dist/touch-controls.d.ts +24 -0
- package/dist/touch-pointer.d.ts +11 -0
- package/dist/util.d.ts +62 -0
- package/package.json +69 -4
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Screen And Drawing
|
|
2
|
+
|
|
3
|
+
`screen` is the global canvas surface. Everything you draw happens inside
|
|
4
|
+
`draw()`, in screen pixels: `[0, 0]` is the top-left corner, x grows right,
|
|
5
|
+
y grows down, and `[WIDTH, HEIGHT]` is the bottom-right corner.
|
|
6
|
+
|
|
7
|
+
## Short forms
|
|
8
|
+
|
|
9
|
+
Five short calls draw the most common shapes and text, with each position
|
|
10
|
+
given as separate numbers rather than an `[x, y]` list:
|
|
11
|
+
|
|
12
|
+
| Call | Draws |
|
|
13
|
+
| ------------------------------------------------ | -------------------------------------------------- |
|
|
14
|
+
| `screen.circle(x, y, radius, color)` | a filled circle centered on `x, y` |
|
|
15
|
+
| `screen.rect(x, y, width, height, color)` | a filled rectangle whose top-left corner is `x, y` |
|
|
16
|
+
| `screen.triangle(x1, y1, x2, y2, x3, y3, color)` | a filled triangle with those three corners |
|
|
17
|
+
| `screen.line(x1, y1, x2, y2, color, width = 1)` | a line from `x1, y1` to `x2, y2`, `width` px thick |
|
|
18
|
+
| `screen.text(text, x, y, color, size = 24)` | text whose top-left corner is `x, y`, `size` tall |
|
|
19
|
+
|
|
20
|
+
`color` is optional and defaults to `"white"`; it takes any of the
|
|
21
|
+
[colors](#colors) below.
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
function draw() {
|
|
25
|
+
screen.clear();
|
|
26
|
+
screen.rect(0, 220, 400, 80, "green");
|
|
27
|
+
screen.circle(200, 150, 25, "gold");
|
|
28
|
+
screen.triangle(160, 40, 120, 120, 200, 120);
|
|
29
|
+
screen.line(20, 20, 200, 120, "white", 3);
|
|
30
|
+
screen.text("Score: " + score, 20, 30);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`screen.text` takes words in quotes or a number. To place text by its
|
|
35
|
+
center, wrap it or give it an outline, use
|
|
36
|
+
[`screen.draw.text`](#screendrawtexttext-pos-options--screendrawtexttext-options).
|
|
37
|
+
|
|
38
|
+
The short forms fill their shapes. The outline versions, and the versions
|
|
39
|
+
that take `[x, y]` lists, stay under `screen.draw` below. Mind the names:
|
|
40
|
+
**`screen.circle` fills, but `screen.draw.circle` draws only an outline**,
|
|
41
|
+
and the same goes for `screen.rect` and `screen.draw.rect`.
|
|
42
|
+
|
|
43
|
+
A wrong call throws a `TypeError` that says what to change. A list, as in
|
|
44
|
+
`screen.circle([200, 150], 25)`, points to the `screen.draw` method that
|
|
45
|
+
takes lists (here `screen.draw.filledCircle`). A missing or non-number
|
|
46
|
+
argument is named, and an extra one, such as an outline width, points to
|
|
47
|
+
the outline method.
|
|
48
|
+
|
|
49
|
+
## Colors
|
|
50
|
+
|
|
51
|
+
Every drawing method accepts a `Color`, which is either a CSS color string
|
|
52
|
+
(`"white"`, `"#0f172a"`, `"rgb(20, 30, 40)"`) or an RGB/RGBA tuple of
|
|
53
|
+
integers `0`–`255`: `[249, 115, 22]` or `[249, 115, 22, 128]` (the fourth
|
|
54
|
+
value is alpha out of 255, not a `0`–`1` fraction). Passing anything else
|
|
55
|
+
throws a `TypeError` that says what was wrong:
|
|
56
|
+
|
|
57
|
+
- A misspelled color name suggests the right one: `"lightblu"` fails with
|
|
58
|
+
`Did you mean "lightblue"?`. (A canvas would otherwise ignore the color
|
|
59
|
+
and keep drawing in the previous one.)
|
|
60
|
+
- A hex code needs 3 or 6 hex digits (or 4 or 8, with alpha).
|
|
61
|
+
- A fractional alpha such as `[255, 0, 0, 0.5]` fails with the 0–255 value
|
|
62
|
+
to use instead (`128`).
|
|
63
|
+
|
|
64
|
+
Drawing options are checked the same way: an option a method doesn't have,
|
|
65
|
+
such as `{ colour: "red" }`, throws and names the closest real option.
|
|
66
|
+
|
|
67
|
+
### `rgb(red, green, blue, alpha?)`
|
|
68
|
+
|
|
69
|
+
Mixes a color from red, green and blue light, each `0`–`255`, with an
|
|
70
|
+
optional alpha out of 255. It returns the matching list, so
|
|
71
|
+
`rgb(255, 140, 105)` is `[255, 140, 105]` and works anywhere a color does.
|
|
72
|
+
It's ready to use in a game file; in import mode, import it from
|
|
73
|
+
`js-game-zero`.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
let coral = rgb(255, 140, 105);
|
|
77
|
+
let glass = rgb(120, 200, 255, 128); // half see-through
|
|
78
|
+
screen.circle(200, 150, 25, coral);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Its numbers are checked like a list's, with the same hints (a fractional
|
|
82
|
+
alpha such as `0.5` names `128` instead), and a missing one is named:
|
|
83
|
+
`rgb(255, 140)` fails with `rgb is missing blue`.
|
|
84
|
+
|
|
85
|
+
## `screen.clear()`
|
|
86
|
+
|
|
87
|
+
Resets the whole canvas to opaque black.
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
function draw() {
|
|
91
|
+
screen.clear();
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## `screen.fill(color, bottomColor?)`
|
|
96
|
+
|
|
97
|
+
Fills the canvas with a solid color, or a top-to-bottom gradient when a
|
|
98
|
+
second color is given: `color` at the top fading to `bottomColor` at the
|
|
99
|
+
bottom.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
screen.fill("#0f172a"); // solid
|
|
103
|
+
screen.fill("#0f172a", "#164e63"); // gradient, dark at top to teal at bottom
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## `screen.draw.image(image, pos)` / `screen.blit(image, pos)`
|
|
107
|
+
|
|
108
|
+
`screen.draw.image` and `screen.blit` (Pygame Zero's name) do the same
|
|
109
|
+
thing. Draws an already-loaded image (by asset name, or a `SurfaceLike` you already
|
|
110
|
+
hold) at a position. `pos` is a `[x, y]` point for the image's top-left
|
|
111
|
+
corner, or a rect-like value (its top-left corner is used; blit does not
|
|
112
|
+
scale the image to the rect's size).
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
screen.draw.image("player.svg", [40, 40]);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
If the named image hasn't finished loading yet, nothing is drawn: it returns
|
|
119
|
+
a `Promise` that resolves once the image has loaded, and the runtime holds
|
|
120
|
+
the frames that follow until then, so the image shows from the next one.
|
|
121
|
+
If it can't be loaded, the promise rejects and the run reports the missing
|
|
122
|
+
image by name, so `draw()` needn't handle the promise itself.
|
|
123
|
+
Images touched while the script starts up load before the first frame (see
|
|
124
|
+
[`docs/reference/runtime.md`](runtime.md#preload-gate)).
|
|
125
|
+
Prefer `Actor` for anything that moves or needs collision — drawing an image
|
|
126
|
+
directly is for background art and tiles.
|
|
127
|
+
|
|
128
|
+
## `screen.bounds()`
|
|
129
|
+
|
|
130
|
+
Returns a `ZRect` covering the whole canvas (`[0, 0, WIDTH, HEIGHT]`), handy
|
|
131
|
+
for clamping or collision against the screen edge.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
if (!screen.bounds().contains(player.rect()))
|
|
135
|
+
player.center = [WIDTH / 2, HEIGHT / 2];
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## `PIXEL_DENSITY` and `screen.density`
|
|
139
|
+
|
|
140
|
+
On a phone, one screen pixel covers several of the device's own pixels.
|
|
141
|
+
The canvas has a pixel for each of them, so text, outlines and SVG pictures
|
|
142
|
+
stay sharp. `screen.density` says how many canvas pixels make up one screen
|
|
143
|
+
pixel along each side: between 2 and 3 on most phones and 1 on a computer
|
|
144
|
+
monitor. Your game's numbers don't change with it: a game 360 pixels wide
|
|
145
|
+
is 360 wide at any density.
|
|
146
|
+
|
|
147
|
+
Set `PIXEL_DENSITY` next to `WIDTH` and `HEIGHT` to choose the density
|
|
148
|
+
yourself. Without it, the game uses the device's own, up to 3.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
WIDTH = 360;
|
|
152
|
+
HEIGHT = 640;
|
|
153
|
+
PIXEL_DENSITY = 1; // one canvas pixel per screen pixel, even on a phone
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
See [pixel density](runtime.md#pixel-density) for how hosts size and read
|
|
157
|
+
the canvas.
|
|
158
|
+
|
|
159
|
+
## `screen.draw.line(start, end, color, width = 1)`
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
screen.draw.line([20, 20], [200, 120], "white", 3);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## `screen.draw.circle(pos, radius, color, width = 1)` / `filledCircle(pos, radius, color)`
|
|
166
|
+
|
|
167
|
+
`screen.draw.circle` draws an outline; `filledCircle` and the short form
|
|
168
|
+
[`screen.circle`](#short-forms) fill.
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
screen.draw.circle([160, 90], 40, "#38bdf8", 2); // outline
|
|
172
|
+
screen.draw.filledCircle([160, 90], 40, "#38bdf8"); // filled
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## `screen.draw.rect(rect, color, width = 1)` / `filledRect(rect, color)`
|
|
176
|
+
|
|
177
|
+
`rect` accepts anything geometry accepts: a `Rect`, an `Actor` (via its
|
|
178
|
+
`.rect()` duck type — see [`docs/reference/rect.md`](rect.md)), or a plain
|
|
179
|
+
`[left, top, width, height]` tuple.
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
screen.draw.filledRect([40, 40, 120, 60], "#f97316");
|
|
183
|
+
screen.draw.rect(player.rect(), "yellow", 2); // highlight a hitbox
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## `screen.draw.polygon(points, color, width = 1)` / `filledPolygon(points, color)`
|
|
187
|
+
|
|
188
|
+
Needs at least three points.
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
screen.draw.filledPolygon(
|
|
192
|
+
[
|
|
193
|
+
[238, 40],
|
|
194
|
+
[292, 64],
|
|
195
|
+
[270, 126],
|
|
196
|
+
[214, 118],
|
|
197
|
+
],
|
|
198
|
+
"#f8fafc",
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## `screen.draw.text(text, pos?, options?)` / `screen.draw.text(text, options)`
|
|
203
|
+
|
|
204
|
+
Draws text with a large set of beginner-friendly options — this is a
|
|
205
|
+
JavaScript-native reimplementation of pgzero's `ptext` module, including its
|
|
206
|
+
word-wrapping algorithm and gradient/outline/shadow effects.
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
screen.draw.text("Score: 12", [16, 16], {
|
|
210
|
+
size: 24,
|
|
211
|
+
color: "white",
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
When the options say where the text goes (`center`, `topRight`, ...), pass
|
|
216
|
+
them second, in place of the position:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
screen.draw.text("Game Over", { center: [WIDTH / 2, HEIGHT / 2], size: 40 });
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Several options have two names: a readable one, and the shorter one Pygame
|
|
223
|
+
Zero uses. Either works, but not both at once:
|
|
224
|
+
|
|
225
|
+
| Readable name | Pygame Zero name |
|
|
226
|
+
| --------------- | ---------------- |
|
|
227
|
+
| `size` | `fontsize` |
|
|
228
|
+
| `outline` | `owidth` |
|
|
229
|
+
| `outlineColor` | `ocolor` |
|
|
230
|
+
| `shadowColor` | `scolor` |
|
|
231
|
+
| `gradientColor` | `gcolor` |
|
|
232
|
+
|
|
233
|
+
### Position options
|
|
234
|
+
|
|
235
|
+
Give **exactly one** of the following ways to position the text (mixing
|
|
236
|
+
them throws a `TypeError`):
|
|
237
|
+
|
|
238
|
+
- `pos` (or the second positional argument) — a `[x, y]` point. Combined
|
|
239
|
+
with `anchor` (default `"topLeft"`) to decide which part of the text box
|
|
240
|
+
sits at that point.
|
|
241
|
+
- A named anchor point directly: `topLeft`, `topRight`, `bottomLeft`,
|
|
242
|
+
`bottomRight`, `midTop`, `midLeft`, `midRight`, `midBottom`, `center` —
|
|
243
|
+
each takes a `[x, y]` point for that corner/edge/center of the text.
|
|
244
|
+
- Scalar axis keywords, combined per-axis: `left`/`centerX`/`right` for the
|
|
245
|
+
horizontal position, `top`/`centerY`/`bottom` for the vertical position.
|
|
246
|
+
You can mix one horizontal and one vertical scalar (`{ right: 300, top: 10
|
|
247
|
+
}`), or use just one axis and fall back to `pos` for the other.
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
screen.draw.text("Game Over", {
|
|
251
|
+
center: [WIDTH / 2, HEIGHT / 2],
|
|
252
|
+
size: 40,
|
|
253
|
+
});
|
|
254
|
+
screen.draw.text("HUD", { right: WIDTH - 10, top: 10 }); // top-right corner, 10px margin
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Font options
|
|
258
|
+
|
|
259
|
+
- `size` / `fontsize` (default `24`)
|
|
260
|
+
- `fontname` — the stem of a font asset under `fonts/` (e.g. `"pixel"` for
|
|
261
|
+
`fonts/pixel.ttf`); loads asynchronously the first time it's used.
|
|
262
|
+
- `sysfontname` — a literal CSS font family name instead (e.g. `"Georgia"`).
|
|
263
|
+
Mutually exclusive with `fontname`.
|
|
264
|
+
- `bold`, `italic`, `underline` — booleans.
|
|
265
|
+
- `color` (default `"white"`), `background` (fills a box behind the text).
|
|
266
|
+
- `gradientColor` / `gcolor` — a second color for a top-to-bottom gradient
|
|
267
|
+
across the text.
|
|
268
|
+
|
|
269
|
+
### Layout options
|
|
270
|
+
|
|
271
|
+
- `width` — wraps text to fit this many pixels; without it, text is only
|
|
272
|
+
broken at explicit newlines (`\n`). Wrapping is greedy word-wrap: a single
|
|
273
|
+
word longer than `width` is placed on its own (overflowing) line rather
|
|
274
|
+
than being cut.
|
|
275
|
+
- `widthem` — like `width`, but expressed as a multiple of `size`.
|
|
276
|
+
- `lineheight` — pixels between lines (default `size * 1.2`).
|
|
277
|
+
- `align` — `"left"` (default), `"center"`, or `"right"`, for multi-line text.
|
|
278
|
+
A [`textbox`](#screendrawtextboxtext-rect-options) follows its `anchor`
|
|
279
|
+
instead, so it centers by default.
|
|
280
|
+
- `strip` — trims trailing spaces from each wrapped line (default `true`).
|
|
281
|
+
- `anchor` — overrides which part of the text's bounding box sits at the
|
|
282
|
+
resolved position; either one of the named anchor strings above, or a
|
|
283
|
+
`[horizontal, vertical]` pair where each side is `"left"`/`"center"`/`"right"`,
|
|
284
|
+
`"top"`/`"center"`/`"bottom"`, or a literal pixel-offset number.
|
|
285
|
+
- `angle` — rotates the text counterclockwise around the anchor point, in
|
|
286
|
+
degrees.
|
|
287
|
+
|
|
288
|
+
### Effects
|
|
289
|
+
|
|
290
|
+
- `outline` / `owidth` + `outlineColor` / `ocolor` — an outline stroke
|
|
291
|
+
around the glyphs, `outline` pixels thick.
|
|
292
|
+
- `shadow` (a number or `[x, y]` offset) + `shadowColor` / `scolor`
|
|
293
|
+
(default `"black"`) — a drop shadow.
|
|
294
|
+
- `alpha` — overall opacity, `0`–`1` (default `1`).
|
|
295
|
+
|
|
296
|
+
## `screen.draw.textbox(text, rect, options?)`
|
|
297
|
+
|
|
298
|
+
Like `draw.text`, but instead of a fixed `size` you give a rectangle and
|
|
299
|
+
`textbox` picks the **largest font size that fits inside it** (shrinking
|
|
300
|
+
down to `minSize`, default `4`, if necessary; `minFontsize` is another name
|
|
301
|
+
for it, but not both at once). Text is centered in the rect by default.
|
|
302
|
+
Pass `anchor` (e.g. `"topLeft"`) to place it elsewhere in the rect; its
|
|
303
|
+
lines line up the same way (on the left for `"topLeft"`) unless you give
|
|
304
|
+
`align`. The rect places the text, so `textbox` takes none of
|
|
305
|
+
`draw.text`'s position options (`pos`, `center`, `topLeft`, `left`, ...)
|
|
306
|
+
and no `width`; giving one throws a `TypeError` that says so.
|
|
307
|
+
|
|
308
|
+
```js
|
|
309
|
+
screen.draw.textbox("You win!", [40, 60, 240, 80], {
|
|
310
|
+
color: "white",
|
|
311
|
+
size: 48, // upper bound; shrinks to fit if "You win!" would overflow
|
|
312
|
+
});
|
|
313
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Storage
|
|
2
|
+
|
|
3
|
+
`storage` is a persistent, dictionary-like object for saving small amounts
|
|
4
|
+
of data between play sessions — high scores, unlocked levels, settings.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
let highScore = storage.get("highScore") ?? 0;
|
|
8
|
+
|
|
9
|
+
function update() {
|
|
10
|
+
if (score > highScore) {
|
|
11
|
+
highScore = score;
|
|
12
|
+
storage.set("highScore", highScore);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Auto-load and auto-save
|
|
18
|
+
|
|
19
|
+
You don't need to call `save()` or `load()` yourself in normal use:
|
|
20
|
+
|
|
21
|
+
- **Auto-load**: the default browser adapter has a synchronous `loadSync()`
|
|
22
|
+
path, so a top-level read like `let score = storage.get("score") ?? 0`
|
|
23
|
+
sees previously-saved data immediately. So does a custom adapter with a
|
|
24
|
+
`loadSync()`, or whose `load()` returns its text rather than a Promise. A
|
|
25
|
+
custom asynchronous adapter is loaded once during startup and delays
|
|
26
|
+
frames/`handle.ready`, but cannot retroactively change values already
|
|
27
|
+
copied into top-level variables, and what it loads replaces anything the
|
|
28
|
+
top level stored before it arrived.
|
|
29
|
+
- **Auto-save**: any mutation (`storage.set(...)`, `storage.score = 5`,
|
|
30
|
+
`storage.delete(...)`, `storage.clear()`) schedules a save a fraction of a
|
|
31
|
+
second later, and within a second even while the game keeps changing it
|
|
32
|
+
every frame; rapid changes coalesce into one write instead of saving on
|
|
33
|
+
every single mutation. Storage is also flushed immediately when the tab is
|
|
34
|
+
hidden or closed, and when the game stops, so nothing is lost. A change
|
|
35
|
+
inside a stored object or list, such as `storage.highScores.push(score)`,
|
|
36
|
+
is saved too, with the next of those saves; set the key again
|
|
37
|
+
(`storage.highScores = storage.highScores`) to save it sooner.
|
|
38
|
+
|
|
39
|
+
## Reading and writing
|
|
40
|
+
|
|
41
|
+
Two equivalent styles work — plain method calls, or attribute-style access
|
|
42
|
+
through a proxy (closer to pgzero's `storage` object):
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
storage.set("score", 10);
|
|
46
|
+
storage.get("score"); // 10
|
|
47
|
+
|
|
48
|
+
storage.score = 10; // same as storage.set("score", 10)
|
|
49
|
+
storage.score; // 10
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Method | Behavior |
|
|
53
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `storage.get(key, defaultValue?)` | Returns the stored value, or `defaultValue` (`undefined` if omitted) if `key` isn't set. |
|
|
55
|
+
| `storage.set(key, value)` | Stores `value` under `key`. |
|
|
56
|
+
| `storage.setDefault(key, defaultValue)` | Sets `key` to `defaultValue` only if it isn't already set; returns the resulting value either way. |
|
|
57
|
+
| `storage.has(key)` | `boolean`. |
|
|
58
|
+
| `storage.delete(key)` | Removes `key`; also works as `delete storage.key`. Returns `true` if something was removed. |
|
|
59
|
+
| `storage.clear()` | Removes everything. |
|
|
60
|
+
| `storage.keys()` | Every stored key, alphabetically sorted. |
|
|
61
|
+
| `storage.path` | The backing location: a scoped `localStorage` key in the browser, or a file path in the sandbox. |
|
|
62
|
+
|
|
63
|
+
Values must be JSON-serializable — plain objects, arrays, strings, numbers,
|
|
64
|
+
booleans, and `null`. Storing anything else (a class instance, a function,
|
|
65
|
+
`undefined`, `NaN`, `Infinity`, a circular reference), anywhere inside the
|
|
66
|
+
value, throws a `JsonStorageError` naming the offending path, e.g.
|
|
67
|
+
`NaN is not a JSON value at storage.highScores[2].score`, and stores nothing.
|
|
68
|
+
A value changed inside a stored object or list after it was stored is only
|
|
69
|
+
checked when storage saves: until it's a JSON value again, saving fails,
|
|
70
|
+
with a warning naming its path, and nothing in storage is saved.
|
|
71
|
+
|
|
72
|
+
## Gotchas
|
|
73
|
+
|
|
74
|
+
- **Don't shadow method names.** `storage.save`, `storage.load`, `storage.path`
|
|
75
|
+
and the other method names above are real properties on `storage` itself,
|
|
76
|
+
not just proxy-forwarded keys — writing `storage.save = 5` overwrites the
|
|
77
|
+
actual `save` method instead of storing a value, and another spelling of
|
|
78
|
+
a method, such as `storage.setdefault`, throws naming the method. Pick key
|
|
79
|
+
names that don't collide with the method list.
|
|
80
|
+
- Storage is scoped per project: in the browser it's namespaced by project
|
|
81
|
+
id (so two different games on the same site don't share data, as long as
|
|
82
|
+
each has its own `projectId`; games run without one all share the id
|
|
83
|
+
`default`); in the sandbox it's a JSON file inside that project's own
|
|
84
|
+
directory.
|
|
85
|
+
|
|
86
|
+
## Manual save/load
|
|
87
|
+
|
|
88
|
+
For advanced cases — forcing an immediate write instead of waiting for the
|
|
89
|
+
debounce, or working outside of `run()` — `storage.save()` and
|
|
90
|
+
`storage.load()` are both available directly and return `Promise<void>`.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { type AssetLoader, type SurfaceLike } from "./assets.js";
|
|
2
|
+
import { type Color } from "./color.js";
|
|
3
|
+
import { ZRect, type Point, type RectLike } from "./geometry.js";
|
|
4
|
+
export { normalizeColor } from "./color.js";
|
|
5
|
+
export type { Color } from "./color.js";
|
|
6
|
+
export type CanvasLike = {
|
|
7
|
+
readonly width: number;
|
|
8
|
+
readonly height: number;
|
|
9
|
+
getContext?: (contextId: "2d") => CanvasRenderingContext2D | null;
|
|
10
|
+
};
|
|
11
|
+
export type ScreenOptions = {
|
|
12
|
+
readonly imageLoader?: Pick<AssetLoader<SurfaceLike>, "load" | "get">;
|
|
13
|
+
};
|
|
14
|
+
export type TextAlignOption = "left" | "center" | "right";
|
|
15
|
+
export type TextAnchor = "topLeft" | "topRight" | "bottomLeft" | "bottomRight" | "midTop" | "midLeft" | "midRight" | "midBottom" | "center" | readonly [number | HorizontalAnchor, number | VerticalAnchor];
|
|
16
|
+
export type HorizontalAnchor = "left" | "center" | "middle" | "right";
|
|
17
|
+
export type VerticalAnchor = "top" | "center" | "middle" | "bottom";
|
|
18
|
+
export type TextOptions = {
|
|
19
|
+
readonly fontname?: string;
|
|
20
|
+
readonly sysfontname?: string;
|
|
21
|
+
readonly fontsize?: number;
|
|
22
|
+
/** Same as `fontsize`. */
|
|
23
|
+
readonly size?: number;
|
|
24
|
+
readonly bold?: boolean;
|
|
25
|
+
readonly italic?: boolean;
|
|
26
|
+
readonly underline?: boolean;
|
|
27
|
+
readonly color?: Color;
|
|
28
|
+
readonly background?: Color;
|
|
29
|
+
readonly pos?: Point;
|
|
30
|
+
readonly topLeft?: Point;
|
|
31
|
+
readonly topRight?: Point;
|
|
32
|
+
readonly bottomLeft?: Point;
|
|
33
|
+
readonly bottomRight?: Point;
|
|
34
|
+
readonly midTop?: Point;
|
|
35
|
+
readonly midLeft?: Point;
|
|
36
|
+
readonly midRight?: Point;
|
|
37
|
+
readonly midBottom?: Point;
|
|
38
|
+
readonly center?: Point;
|
|
39
|
+
readonly top?: number;
|
|
40
|
+
readonly left?: number;
|
|
41
|
+
readonly bottom?: number;
|
|
42
|
+
readonly right?: number;
|
|
43
|
+
readonly centerX?: number;
|
|
44
|
+
readonly centerY?: number;
|
|
45
|
+
readonly width?: number;
|
|
46
|
+
readonly widthem?: number;
|
|
47
|
+
readonly lineheight?: number;
|
|
48
|
+
readonly align?: TextAlignOption;
|
|
49
|
+
readonly strip?: boolean;
|
|
50
|
+
readonly owidth?: number;
|
|
51
|
+
/** Same as `owidth`. */
|
|
52
|
+
readonly outline?: number;
|
|
53
|
+
readonly ocolor?: Color;
|
|
54
|
+
/** Same as `ocolor`. */
|
|
55
|
+
readonly outlineColor?: Color;
|
|
56
|
+
readonly shadow?: number | Point;
|
|
57
|
+
readonly scolor?: Color;
|
|
58
|
+
/** Same as `scolor`. */
|
|
59
|
+
readonly shadowColor?: Color;
|
|
60
|
+
readonly gcolor?: Color;
|
|
61
|
+
/** Same as `gcolor`. */
|
|
62
|
+
readonly gradientColor?: Color;
|
|
63
|
+
readonly alpha?: number;
|
|
64
|
+
readonly anchor?: TextAnchor;
|
|
65
|
+
readonly angle?: number;
|
|
66
|
+
};
|
|
67
|
+
export type TextboxOptions = Omit<TextOptions, TextPositionKey | "width" | "widthem" | "top" | "left" | "bottom" | "right" | "centerX" | "centerY"> & {
|
|
68
|
+
readonly minFontsize?: number;
|
|
69
|
+
/** Same as `minFontsize`. */
|
|
70
|
+
readonly minSize?: number;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* `screen.draw.text` takes its options either third, after a position, or
|
|
74
|
+
* second, in place of one: `text("Hi", [10, 10], { size: 20 })` and
|
|
75
|
+
* `text("Hi", { center: [160, 90], size: 20 })` both work.
|
|
76
|
+
*/
|
|
77
|
+
export type DrawText = {
|
|
78
|
+
(text: string, options?: TextOptions): void;
|
|
79
|
+
(text: string, pos: Point | undefined, options?: TextOptions): void;
|
|
80
|
+
};
|
|
81
|
+
export type TextLayout = {
|
|
82
|
+
readonly lines: readonly string[];
|
|
83
|
+
readonly font: string;
|
|
84
|
+
readonly fontSize: number;
|
|
85
|
+
readonly lineHeight: number;
|
|
86
|
+
readonly boxWidth: number;
|
|
87
|
+
readonly boxHeight: number;
|
|
88
|
+
readonly textWidth: number;
|
|
89
|
+
readonly align: TextAlignOption;
|
|
90
|
+
readonly topLeft: Point;
|
|
91
|
+
readonly anchorOffset: Point;
|
|
92
|
+
readonly alpha: number;
|
|
93
|
+
};
|
|
94
|
+
type TextPositionKey = "pos" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight" | "midTop" | "midLeft" | "midRight" | "midBottom" | "center";
|
|
95
|
+
/**
|
|
96
|
+
* Sizes the screen to `width` × `height` logical pixels with `density`
|
|
97
|
+
* canvas pixels each, and scales the context so every drawing call keeps
|
|
98
|
+
* working in logical pixels. A density change keeps what's on the canvas,
|
|
99
|
+
* rescaled; a new size starts blank, as setting a canvas's size does.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resizeScreen(screen: Screen, width: number, height: number, density?: number): void;
|
|
102
|
+
/**
|
|
103
|
+
* Restores the density scale if something reset the canvas since the last
|
|
104
|
+
* frame. Code outside the runtime that sets `canvas.width`/`height` means
|
|
105
|
+
* a logical size, so the screen adopts it at the same density.
|
|
106
|
+
*/
|
|
107
|
+
export declare function syncScreen(screen: Screen): void;
|
|
108
|
+
/** A value a drawn thing reports, as JSON. */
|
|
109
|
+
type DrawnValue = string | number | boolean | readonly number[];
|
|
110
|
+
/**
|
|
111
|
+
* One thing a frame drew, as a host reads it through the inspector's
|
|
112
|
+
* `$drawn`: its kind (`fill`, `image`, `actor`, `circle`, `rect`, `polygon`,
|
|
113
|
+
* `line`, `text`, `clear`), and what it is and where, as the game gave them.
|
|
114
|
+
*/
|
|
115
|
+
export type DrawnThing = {
|
|
116
|
+
readonly kind: string;
|
|
117
|
+
readonly [key: string]: DrawnValue;
|
|
118
|
+
};
|
|
119
|
+
/** Starts recording what `screen` draws, afresh: the runtime calls it before each `draw()`. */
|
|
120
|
+
export declare function beginDrawn(screen: Screen): void;
|
|
121
|
+
/** What `screen`'s last `draw()` drew, in order; empty before its first. */
|
|
122
|
+
export declare function drawnOn(screen: Screen): readonly DrawnThing[];
|
|
123
|
+
/** Records one thing drawn, when the screen is recording. */
|
|
124
|
+
export declare function recordDrawn(screen: Screen, thing: DrawnThing): void;
|
|
125
|
+
export declare class Screen {
|
|
126
|
+
readonly canvas: CanvasLike;
|
|
127
|
+
readonly context: CanvasRenderingContext2D;
|
|
128
|
+
readonly imageLoader: Pick<AssetLoader<SurfaceLike>, "load" | "get">;
|
|
129
|
+
readonly draw: {
|
|
130
|
+
line: (start: Point, end: Point, color: Color, width?: number) => void;
|
|
131
|
+
circle: (pos: Point, radius: number, color: Color, width?: number) => void;
|
|
132
|
+
filledCircle: (pos: Point, radius: number, color: Color) => void;
|
|
133
|
+
polygon: (points: Point[], color: Color, width?: number) => void;
|
|
134
|
+
filledPolygon: (points: Point[], color: Color) => void;
|
|
135
|
+
rect: (rect: RectLike, color: Color, width?: number) => void;
|
|
136
|
+
filledRect: (rect: RectLike, color: Color) => void;
|
|
137
|
+
text: DrawText;
|
|
138
|
+
textbox: (text: string, rect: RectLike, options?: TextboxOptions) => void;
|
|
139
|
+
/** Same as `screen.blit`: draws an image with its top-left at `pos`. */
|
|
140
|
+
image: (image: string | SurfaceLike, pos: Point | RectLike) => Promise<void> | void;
|
|
141
|
+
};
|
|
142
|
+
constructor(canvas: CanvasLike, context?: CanvasRenderingContext2D | null, options?: ScreenOptions);
|
|
143
|
+
/** The screen's width in logical pixels: the game's `WIDTH`. */
|
|
144
|
+
get width(): number;
|
|
145
|
+
/** The screen's height in logical pixels: the game's `HEIGHT`. */
|
|
146
|
+
get height(): number;
|
|
147
|
+
/** Canvas pixels per logical pixel along each side. */
|
|
148
|
+
get density(): number;
|
|
149
|
+
bounds(): ZRect;
|
|
150
|
+
clear(): void;
|
|
151
|
+
fill(color: Color, gcolor?: Color): void;
|
|
152
|
+
blit(image: string | SurfaceLike, pos: Point | RectLike): Promise<void> | void;
|
|
153
|
+
/**
|
|
154
|
+
* The short forms take positions as separate numbers rather than `[x, y]`
|
|
155
|
+
* lists, fill their shapes, and default to white, so
|
|
156
|
+
* `screen.circle(200, 150, 25, "gold")` fits a phone-width line where
|
|
157
|
+
* `screen.draw.filledCircle([200, 150], 25, "gold")` doesn't. The
|
|
158
|
+
* outline versions stay under `screen.draw`.
|
|
159
|
+
*/
|
|
160
|
+
circle(x: number, y: number, radius: number, color?: Color): void;
|
|
161
|
+
/** A filled rectangle whose top-left corner is at `x, y`. */
|
|
162
|
+
rect(x: number, y: number, width: number, height: number, color?: Color): void;
|
|
163
|
+
triangle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, color?: Color): void;
|
|
164
|
+
line(x1: number, y1: number, x2: number, y2: number, color?: Color, width?: number): void;
|
|
165
|
+
/**
|
|
166
|
+
* Text whose top-left corner is at `x, y`, `size` pixels tall, so a score
|
|
167
|
+
* fits one short line: `screen.text("Score: " + score, 20, 30)`. A number
|
|
168
|
+
* is drawn as it reads. Placing by its center or any other option stays
|
|
169
|
+
* under `screen.draw.text`.
|
|
170
|
+
*/
|
|
171
|
+
text(text: string | number, x: number, y: number, color?: Color, size?: number): void;
|
|
172
|
+
private drawSurface;
|
|
173
|
+
private drawLine;
|
|
174
|
+
private drawCircle;
|
|
175
|
+
private drawPolygon;
|
|
176
|
+
private drawRect;
|
|
177
|
+
private drawText;
|
|
178
|
+
private drawTextbox;
|
|
179
|
+
}
|
|
180
|
+
export declare function setScreen(screen: Screen | null): Screen | null;
|
|
181
|
+
export declare function getScreen(): Screen;
|
|
182
|
+
export declare const screen: Screen;
|
|
183
|
+
export declare function layoutText(context: CanvasRenderingContext2D, text: string, pos: Point | undefined, options?: TextOptions): TextLayout;
|
|
184
|
+
export declare function fitTextboxFontSize(context: CanvasRenderingContext2D, text: string, rectLike: RectLike, options?: TextboxOptions): {
|
|
185
|
+
fontSize: number;
|
|
186
|
+
lineHeight: number;
|
|
187
|
+
lines: readonly string[];
|
|
188
|
+
};
|