@maccesar/titools 4.6.1 → 4.8.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 +46 -8
- package/commands/ti-audit.md +1 -0
- package/commands/ti-check.md +3 -2
- package/lib/config.js +1 -0
- package/package.json +1 -1
- package/skills/ti-game/SKILL.md +198 -0
- package/skills/ti-game/references/api.md +408 -0
- package/skills/ti-game/references/project-setup.md +306 -0
- package/skills/ti-game/references/recipes.md +1268 -0
- package/skills/ti-game/references/roadmap.md +57 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# ti.game API reference
|
|
2
|
+
|
|
3
|
+
Complete JS surface of `ti.game` as of upstream `main` on 2026-08-18, verified against the module source (`android/src/ti/game/*.java` and `engine/*.java`, mirrored by `ios/Classes/TG*`). Defaults come from the engine fields, not from prose. The manifest still reads `0.3.0`; the text engine (`createFont`/`createText`), `screenFixed`, `swept` and `collisionend` landed after it and are not in any numbered release yet.
|
|
4
|
+
|
|
5
|
+
Every property listed is **live**: reading returns the current native value, even mid-drag or mid-tween. Every property can also be passed to its `create*` factory. All durations crossing the JS boundary are **milliseconds** (the engine converts to seconds internally).
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Module](#module)
|
|
10
|
+
- [GameView](#gameview)
|
|
11
|
+
- [SpriteSheet](#spritesheet)
|
|
12
|
+
- [Sprite](#sprite)
|
|
13
|
+
- [Font](#font)
|
|
14
|
+
- [Text](#text)
|
|
15
|
+
- [Sound](#sound)
|
|
16
|
+
- [Emitter](#emitter)
|
|
17
|
+
- [Rope](#rope)
|
|
18
|
+
- [Events at a glance](#events-at-a-glance)
|
|
19
|
+
- [Gotchas the property tables do not show](#gotchas-the-property-tables-do-not-show)
|
|
20
|
+
|
|
21
|
+
## Module
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
const Game = require('ti.game');
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Factory | Returns |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `Game.createGameView(options)` | GameView — the canvas, a Titanium view |
|
|
30
|
+
| `Game.createSpriteSheet(options)` | SpriteSheet — a texture cut into frames |
|
|
31
|
+
| `Game.createSprite(options)` | Sprite |
|
|
32
|
+
| `Game.createFont(options)` | Font — a bitmap font for text sprites |
|
|
33
|
+
| `Game.createText(options)` | Text — a sprite that draws a string |
|
|
34
|
+
| `Game.createSound(options)` | Sound |
|
|
35
|
+
| `Game.createEmitter(options)` | Emitter — particle system |
|
|
36
|
+
| `Game.createRope(options)` | Rope — Verlet chain |
|
|
37
|
+
|
|
38
|
+
Easing constants (for `sprite.animate({ easing })`): `Game.EASE_LINEAR`, `Game.EASE_IN`, `Game.EASE_OUT`, `Game.EASE_IN_OUT`, `Game.EASE_BOUNCE`, `Game.EASE_ELASTIC`.
|
|
39
|
+
|
|
40
|
+
## GameView
|
|
41
|
+
|
|
42
|
+
A normal Titanium view — add it to a window or any container, size it with the usual Titanium properties.
|
|
43
|
+
|
|
44
|
+
| Member | Type | Default | Notes |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| `add(object)` / `add([objects])` | method | — | Accepts Sprite, Emitter or Rope. An array crosses the bridge once and is committed under a single native scene lock — build a level into an array and add it in one call |
|
|
47
|
+
| `remove(object)` | method | — | Remove one object from the scene |
|
|
48
|
+
| `removeAllSprites()` | method | — | Clear the whole scene |
|
|
49
|
+
| `pause()` / `resume()` | method | — | Render loop control. The activity/app lifecycle already pauses and resumes automatically |
|
|
50
|
+
| `backgroundColor` | string | — | GL clear color |
|
|
51
|
+
| `maxFps` | int | `0` | Frame cap. `60` stops 120 Hz ProMotion displays from doubling render work; `0` = display refresh rate |
|
|
52
|
+
| `timeScale` | float | `1` | Global multiplier for everything the engine ticks: physics, sheet animations, tweens, particles, camera. `0.5` = slow motion, `0` freezes the scene while rendering and touch keep running (pause menus, hit-stop) |
|
|
53
|
+
| `surfaceWidth` / `surfaceHeight` | int | — | Read-only surface size in pixels |
|
|
54
|
+
| `cameraX` / `cameraY` | float | `0` | World-space offset of the view |
|
|
55
|
+
| `cameraScale` | float | `1` | Zoom, anchored on the view center |
|
|
56
|
+
| `cameraBounds` | dict / null | `null` | `{ minX, minY, maxX, maxY }` world rect the visible area is clamped into |
|
|
57
|
+
| `follow(sprite, options)` | method | — | Native dead-zone follow, see below |
|
|
58
|
+
| `stopFollow()` | method | — | Stop following; the camera stays where it is |
|
|
59
|
+
| `shake({ strength, duration })` | method | `12` px, `400` ms | Detuned-sine rumble on the projection only — follow, bounds and touch mapping are unaffected |
|
|
60
|
+
| `cameraEffect` | string | `'none'` | Fullscreen shader pass: `'none'`, `'tint'`, `'glitch'`. With `'none'` the extra pass is skipped entirely |
|
|
61
|
+
| `cameraTint` | string | — | Color for the `'tint'` effect, e.g. `'#4f8'` (night vision, poison, flashback) |
|
|
62
|
+
| `cameraEffectIntensity` | float | `1` | 0..1 — tint mix or glitch amount |
|
|
63
|
+
| `debug` | bool | `false` | Draw collision shapes for every sprite in the scene |
|
|
64
|
+
|
|
65
|
+
Events: `press`, `tap`, `release` — fired for **every** touch anywhere on the view, with payload `x`, `y` in scene coordinates (tap-anywhere controls, flappy-style). And `resize` with payload `width`, `height` — the real surface size in pixels.
|
|
66
|
+
|
|
67
|
+
### `follow(sprite, options)`
|
|
68
|
+
|
|
69
|
+
| Option | Default | Effect |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `topMargin` | `0.33` | Vertical dead-zone top edge, as a fraction of visible height |
|
|
72
|
+
| `bottomMargin` | `0.7` | Vertical dead-zone bottom edge |
|
|
73
|
+
| `leftMargin` | `0.35` when horizontal follow is enabled | Horizontal dead-zone left edge |
|
|
74
|
+
| `rightMargin` | `0.65` | Horizontal dead-zone right edge |
|
|
75
|
+
| `smoothing` | `0` | 0..1. `0` snaps; otherwise the camera eases by that fraction of the remaining distance per 1/60 s |
|
|
76
|
+
| `maxY` | `0` | The camera's `cameraY` never goes past this. Default `0` suits a platformer whose start view is the bottom of the level — for a free-roaming world pass the world height |
|
|
77
|
+
|
|
78
|
+
Vertical follow is always on. **Horizontal follow is off until you pass `leftMargin` or `rightMargin`.**
|
|
79
|
+
|
|
80
|
+
Each call **resets every option to its default** before applying what you passed. Calling `follow(sprite)` with no options after configuring it wipes the configuration.
|
|
81
|
+
|
|
82
|
+
## SpriteSheet
|
|
83
|
+
|
|
84
|
+
Create one sheet per texture and share it across every sprite that uses it — sprites sharing a sheet batch into a single draw call.
|
|
85
|
+
|
|
86
|
+
| Option | Default | Notes |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `image` | — | Path to the texture, relative to the resources root (`'assets/hero.png'`) |
|
|
89
|
+
| `frameWidth` / `frameHeight` | — | Grid mode: cell size in source pixels. Frames are numbered left-to-right, top-to-bottom, starting at 0 |
|
|
90
|
+
| `atlas` | — | TexturePacker JSON (hash or array format) instead of a grid |
|
|
91
|
+
| `smoothing` | `true` | `false` = GL_NEAREST, the pixel-art setting |
|
|
92
|
+
| `repeat` | `false` | GL_REPEAT wrap, required by sprites using `tileRepeat`. Needs power-of-two texture dimensions |
|
|
93
|
+
|
|
94
|
+
| Member | Notes |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `frameCount` | Number of frames. `0` until the texture has loaded, for grid sheets |
|
|
97
|
+
| `frameNames` | Sorted frame names — atlas sheets only |
|
|
98
|
+
| `frameIndex(name)` | Index for an atlas frame name, `-1` if unknown |
|
|
99
|
+
|
|
100
|
+
Textures upload to the GPU lazily on first use, from the render thread, and are re-created automatically after an EGL context loss.
|
|
101
|
+
|
|
102
|
+
## Sprite
|
|
103
|
+
|
|
104
|
+
### Transform
|
|
105
|
+
|
|
106
|
+
| Property | Default | Notes |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `x`, `y` | `0` | Position of the **anchor**, not the top-left corner |
|
|
109
|
+
| `width`, `height` | frame size | `0` means "use the sheet frame size" |
|
|
110
|
+
| `scale` | `1` | Write-only convenience that sets both axes |
|
|
111
|
+
| `scaleX`, `scaleY` | `1` | Negative values flip *and* affect physics/hit testing — prefer `flipX`/`flipY` for pure mirroring |
|
|
112
|
+
| `rotation` | `0` | Degrees, clockwise |
|
|
113
|
+
| `anchorX`, `anchorY` | `0.5` | `0/0` = top-left, `0.5/0.5` = center, `0.5/1` = bottom-center (useful for feet) |
|
|
114
|
+
| `opacity` | `1` | |
|
|
115
|
+
| `visible` | `true` | `false` = no render **and no collision** — the pooling idiom |
|
|
116
|
+
| `zIndex` | `0` | Draw order |
|
|
117
|
+
| `ySort` | `false` | Within the same `zIndex`, sort by the sprite's **bottom edge** — top-down depth (walk behind a tree, in front of it below) |
|
|
118
|
+
| `flipX`, `flipY` | `false` | Mirror the drawn frame only. Position, anchor, physics and hit testing are untouched |
|
|
119
|
+
| `screenFixed` | `false` | `x`/`y` become **surface** coordinates and the sprite ignores camera position, zoom and shake — HUDs, on-screen buttons, overlays. Touch is mapped back automatically, so `tap` still works. Works on any sprite, not only text |
|
|
120
|
+
| `pixelSnap` | `false` | Rounds only the rendered anchor to a framebuffer pixel after camera position and zoom. Physics and live `x`/`y` stay subpixel floats. Combine with `smoothing: false` when a moving pixel-art sprite must keep a stable texel phase |
|
|
121
|
+
|
|
122
|
+
### Sheet and animation
|
|
123
|
+
|
|
124
|
+
| Property | Default | Notes |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| `sheet` | — | The SpriteSheet |
|
|
127
|
+
| `frame` | `0` | Current frame index; writing it stops nothing, it just shows that frame |
|
|
128
|
+
| `animations` | — | Named definitions: `{ walk: { frames: [1, 2], fps: 6, loop: true } }` |
|
|
129
|
+
| `animation` | — | Read-only name of the current animation. **Keeps its value after `stop()`** — track "is walking" yourself if you need it |
|
|
130
|
+
| `tileRepeat` | `false` | `true` / `'x'` / `'y'` — tile the frame at native size instead of stretching. Needs `repeat: true` on the sheet and a frame spanning the whole texture |
|
|
131
|
+
|
|
132
|
+
An animation definition takes `frames` (array of indices), `fps`, `loop`, and an optional `frame` — the sheet frame to show once a non-looping animation finishes (default: hold the last animation frame).
|
|
133
|
+
|
|
134
|
+
Methods: `play(name)` starts an animation, `stop()` halts it on the current frame.
|
|
135
|
+
|
|
136
|
+
### Touch
|
|
137
|
+
|
|
138
|
+
| Property | Default | Notes |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `draggable` | `false` | Native drag & drop — the sprite moves on the UI thread, JS only hears milestones |
|
|
141
|
+
| `pinchable` | `false` | Two-finger scale |
|
|
142
|
+
| `rotatable` | `false` | Two-finger rotate |
|
|
143
|
+
| `touchEnabled` | `true` | `false` = touches pass through to sprites underneath |
|
|
144
|
+
|
|
145
|
+
Hit-testing runs against the transformed shape (rotation and scale included), topmost first, and is multi-touch: each finger runs its own gesture and a sprite belongs to at most one finger. A second finger landing on empty space — or on the sprite already held — pinches/rotates that sprite instead, per its flags.
|
|
146
|
+
|
|
147
|
+
### Physics
|
|
148
|
+
|
|
149
|
+
| Property | Default | Notes |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `velocityX`, `velocityY` | `0` | px/s |
|
|
152
|
+
| `gravity` | `0` | px/s², added to `velocityY`. Per sprite — there is no global gravity |
|
|
153
|
+
| `maxSpeed` | `500` | px/s cap applied **only** to `thrust` acceleration and the `carMode` model. Writing `velocityX`/`velocityY` directly is never clamped |
|
|
154
|
+
| `angularVelocity` | `0` | deg/s |
|
|
155
|
+
| `thrust` | `0` | px/s² along the current heading (Newtonian flight) |
|
|
156
|
+
| `wrapAround` | `false` | Re-enter from the opposite screen edge (Asteroids) |
|
|
157
|
+
| `wrapX`, `wrapShift` | `0` | Scroll looping: at `x < wrapX`, add `wrapShift`. Two screen-wide copies with `{ wrapX: -W/2, wrapShift: 2*W }` and a negative `velocityX` make a seamless parallax layer with no JS in the loop |
|
|
158
|
+
|
|
159
|
+
### Solids
|
|
160
|
+
|
|
161
|
+
| Property | Default | Notes |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `solidWith` | — | Array of group names that block this sprite. The engine pushes it out along the axis of least penetration |
|
|
164
|
+
| `onGround` | `false` | Read-only. Gate jumps on it |
|
|
165
|
+
| `restitution` | `0` | 0..1 bounce factor against solids |
|
|
166
|
+
| `oneWay` | `false` | On the **solid**: pass-through except for landings on its top edge — classic platformer floors |
|
|
167
|
+
| `carryRiders` | `true` | On the **solid**: a moving solid carries whoever stands on it (velocity, tweens, idle wobble — wrap teleports excluded). Set `false` for world-scroll terrain that moves while the player is meant to stay put |
|
|
168
|
+
|
|
169
|
+
### Collision
|
|
170
|
+
|
|
171
|
+
| Property | Default | Notes |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| `collisionGroup` | — | The tag *this* sprite carries |
|
|
174
|
+
| `collidesWith` | — | Array of groups this sprite reports overlaps with |
|
|
175
|
+
| `hitboxScale` | `1` | Shrinks the collision box around the anchor |
|
|
176
|
+
| `hitboxShape` | `'rect'` | `'circle'` — radius = half the smaller drawn side × `hitboxScale`. Circles resolve against solids along the contact normal (corner bounces) and get a round touch area |
|
|
177
|
+
| `swept` | `false` | Test this sprite's movement as a **path** (swept AABB), not just at the end position, so a fast mover cannot tunnel between frames. Applies to both `collidesWith` events and `solidWith` blocking (the sprite is clamped to the impact point, then resolved by the normal static pass). Circle hitboxes sweep as their bounding box. Set it on the *mover* — the bullet, not the wall |
|
|
178
|
+
| `debug` | `false` | Draw this sprite's shapes: green = collision AABB, blue = touch bounds, orange dot = anchor |
|
|
179
|
+
|
|
180
|
+
### Car (`carMode`)
|
|
181
|
+
|
|
182
|
+
| Property | Default | Notes |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `carMode` | `false` | Enables the arcade car model |
|
|
185
|
+
| `throttle` | `0` | `-1` (brake/reverse) .. `1` (gas) |
|
|
186
|
+
| `steering` | `0` | `-1` (left) .. `1` (right) |
|
|
187
|
+
| `enginePower` | `600` | Forward acceleration, px/s² |
|
|
188
|
+
| `maxSpeed` | `500` | px/s; reverse caps at 40% |
|
|
189
|
+
| `turnRate` | `200` | deg/s at full steering and speed |
|
|
190
|
+
| `grip` | `4` | Lateral friction, 1/s. **Lower = more drift** |
|
|
191
|
+
| `drag` | `0.6` | Longitudinal friction, 1/s |
|
|
192
|
+
| `skidMarks` | `false` | Fading rubber trails while drifting |
|
|
193
|
+
| `skidThreshold` | `0` | Lateral px/s that counts as drifting |
|
|
194
|
+
| `drifting` | `false` | Read-only — handy for triggering tire sounds |
|
|
195
|
+
|
|
196
|
+
Drift is emergent, not a mode: lateral grip is finite, so hard cornering at speed keeps sideways momentum.
|
|
197
|
+
|
|
198
|
+
### Idle wobble
|
|
199
|
+
|
|
200
|
+
| Property | Default | Notes |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `idleAnimation` | `false` | Gentle organic sway around the base transform |
|
|
203
|
+
| `idleRotation` | `3` | Degrees |
|
|
204
|
+
| `idleMovement` | `4` | px |
|
|
205
|
+
| `idleSpeed` | `1` | Frequency multiplier |
|
|
206
|
+
|
|
207
|
+
Every sprite gets its own phase, and the wobble unwinds exactly when disabled. Turn it **off before tweening a sprite to a position where alignment matters** — tweens write absolute values, so a wobbling sprite lands with a leftover offset.
|
|
208
|
+
|
|
209
|
+
### Color
|
|
210
|
+
|
|
211
|
+
| Property | Default | Notes |
|
|
212
|
+
| --- | --- | --- |
|
|
213
|
+
| `tintColor` | — | Multiplies the frame's colors (team colors, day/night, damage states). `null` or `'#fff'` = unchanged. Multiplicative, so it can only darken |
|
|
214
|
+
| `glowColor` | — | Tinted, blurred silhouette drawn behind the sprite by a shader pass |
|
|
215
|
+
| `glowBlur` | `0` | Blur radius in px; `0` = off. An active glow switches to the silhouette shader and back: **2 extra draw calls per glowing sprite per frame** (2 more while a `flash()` runs), even on a shared texture. Fine for a few highlights, not for every coin in the level |
|
|
216
|
+
| `glowOpacity` | `1` | Halo strength 0..1 — tweenable via `animate`, so a glow can fade in without touching the blur |
|
|
217
|
+
| `blend` | `'normal'` | `'add'` brightens the backdrop instead of covering it (glows, fire, lasers). Costs one batch flush per mode change — group additive sprites by `zIndex` |
|
|
218
|
+
|
|
219
|
+
### Methods
|
|
220
|
+
|
|
221
|
+
| Method | Notes |
|
|
222
|
+
| --- | --- |
|
|
223
|
+
| `play(name)` | Start a named animation |
|
|
224
|
+
| `stop()` | Stop the animation on the current frame |
|
|
225
|
+
| `animate(options)` | Native tween, fires `complete` |
|
|
226
|
+
| `clearTweens()` | Cancel tweens in progress — call before starting a replacement tween |
|
|
227
|
+
| `flash(color, duration)` | Fills the sprite's silhouette with `color` (default white) and fades it out over `duration` ms (default 150). The classic damage/invincibility flash that a multiplicative `tintColor` cannot do |
|
|
228
|
+
|
|
229
|
+
`animate()` accepts exactly these keys — anything else is silently ignored: `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `opacity`, `glowOpacity`, `duration` (ms), `delay` (ms), `easing`, `frame` (the sheet frame to set once the tween finishes). Chain moves by re-calling `animate` from the `complete` handler — that is how patrols and ping-pong motions are built.
|
|
230
|
+
|
|
231
|
+
### Sprite events
|
|
232
|
+
|
|
233
|
+
| Event | Payload | Fires |
|
|
234
|
+
| --- | --- | --- |
|
|
235
|
+
| `press` | `x`, `y`, `touchX`, `touchY` | Finger down on the sprite |
|
|
236
|
+
| `release` | `x`, `y` | Finger up or cancel after a press (after `tap`/`dragend`) |
|
|
237
|
+
| `tap` | `x`, `y`, `touchX`, `touchY` | Quick touch without movement |
|
|
238
|
+
| `dragstart` | `x`, `y` | Drag exceeded touch slop |
|
|
239
|
+
| `drag` | `x`, `y` | Throttled to ~10 Hz while dragging |
|
|
240
|
+
| `dragend` | `x`, `y` | Finger lifted; the sprite has already moved natively |
|
|
241
|
+
| `pinch` | `scaleX`, `scaleY` | While two-finger scaling |
|
|
242
|
+
| `rotate` | `rotation` | While two-finger rotating |
|
|
243
|
+
| `animationcomplete` | `animation` | A non-looping sheet animation finished |
|
|
244
|
+
| `complete` | final transform values | A tween finished |
|
|
245
|
+
| `collision` | `group`, `other`, `x`, `y` | Overlap with a `collidesWith` group began |
|
|
246
|
+
| `collisionend` | `group`, `other`, `x`, `y` | That overlap ended: the shapes separated, **or** the partner was removed from the scene, hidden (`visible = false`) or stopped matching the group filter |
|
|
247
|
+
| `land` | `x`, `y`, `other`, `group` | Landed on top of a `solidWith` solid. `other` is the solid — read it to react to what you landed on (trampolines, hazards) |
|
|
248
|
+
|
|
249
|
+
## Font
|
|
250
|
+
|
|
251
|
+
A glyph atlas for text sprites. Create it once, outside `resize`, like a SpriteSheet — it holds a GL texture.
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
// 1. Built-in: nothing to ship. A 9x15 pixel font embedded in the module.
|
|
255
|
+
// You do not even need a Font object — createText without `font` uses it.
|
|
256
|
+
const builtin = Game.createFont({});
|
|
257
|
+
|
|
258
|
+
// 2. BMFont / AngelCode: .fnt text format or its JSON export, kerning included.
|
|
259
|
+
// Produced by BMFont, Hiero, fontbm or the module's tools/genfont.py.
|
|
260
|
+
const hud = Game.createFont({ font: 'assets/hud.fnt' });
|
|
261
|
+
|
|
262
|
+
// 3. Monospace grid image: cells row-major, ASCII 32..126 by default.
|
|
263
|
+
const mono = Game.createFont({ image: 'assets/mono.png', charWidth: 9, charHeight: 15 });
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| Option | Default | Notes |
|
|
267
|
+
| --- | --- | --- |
|
|
268
|
+
| `font` | — | Path to a `.fnt` descriptor (AngelCode text or JSON). The page image is loaded from **next to the descriptor** unless `image` overrides it |
|
|
269
|
+
| `image` | — | Grid mode: the glyph sheet. Also overrides a BMFont page path |
|
|
270
|
+
| `charWidth`, `charHeight` | — | **Required** in grid mode. Missing or ≤ 0 silently falls back to the built-in font |
|
|
271
|
+
| `characters` | ASCII 32..126 | Grid mode: which characters the cells map to, row-major |
|
|
272
|
+
| `smoothing` | `true` | Filters the glyph texture like a sheet. The built-in font is always crisp |
|
|
273
|
+
| `lineHeight` | — | Read-only: the font's natural line height in px |
|
|
274
|
+
|
|
275
|
+
A descriptor that fails to parse falls back to the built-in font and logs the error — the text still renders, in the wrong face, which is the symptom to recognize.
|
|
276
|
+
|
|
277
|
+
## Text
|
|
278
|
+
|
|
279
|
+
`Game.createText(options)` returns a **Sprite**. Everything in the Sprite section applies — `zIndex`, `ySort`, `animate()`, `flash()`, `tintColor`, glow, `idleAnimation`, `screenFixed`, touch events, `collidesWith`. Add it to the view like any sprite: `gameView.add(label)`.
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
const score = Game.createText({
|
|
283
|
+
text: 'SCORE 0',
|
|
284
|
+
x: 16,
|
|
285
|
+
y: 40,
|
|
286
|
+
anchorX: 0, // left-aligned to a margin
|
|
287
|
+
anchorY: 0,
|
|
288
|
+
scale: 3, // bitmap fonts size by scale, not by fontSize
|
|
289
|
+
screenFixed: true, // ignore the camera
|
|
290
|
+
zIndex: 100
|
|
291
|
+
});
|
|
292
|
+
gameView.add(score);
|
|
293
|
+
score.text = 'SCORE 10'; // re-lays out natively on the next frame
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
| Property | Default | Notes |
|
|
297
|
+
| --- | --- | --- |
|
|
298
|
+
| `text` | `''` | The string. `\n` breaks lines. Writing it re-lays out natively |
|
|
299
|
+
| `font` | built-in | A Font object. Omitted or `null` = the embedded pixel font, which the scene assigns per GameView |
|
|
300
|
+
| `align` | `'left'` | `'center'` / `'right'` — how multiple lines align against each other. Unknown values fall back to `'left'` |
|
|
301
|
+
| `letterSpacing` | `0` | Extra px between glyphs; negative tightens |
|
|
302
|
+
| `lineSpacing` | `1` | Multiplier on the font's line height |
|
|
303
|
+
| `width`, `height` | laid-out size | **Derived, not settable** — the glyph layout drives the size, and with it the anchor, hit test, AABB and `ySort` bottom edge |
|
|
304
|
+
|
|
305
|
+
There is no font size: scale the sprite. With the built-in pixel font, use **integer** `scale` values (`Math.max(1, Math.round(W / 240))` is the demos' idiom) so texels stay square.
|
|
306
|
+
|
|
307
|
+
Every glyph is a quad in the same batch, so one label costs one draw call — a screen of labels sharing a font costs one too.
|
|
308
|
+
|
|
309
|
+
## Sound
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
const jump = Game.createSound({ url: 'assets/jump.wav', volume: 0.8 });
|
|
313
|
+
jump.play(); // fire-and-forget; rapid plays overlap
|
|
314
|
+
|
|
315
|
+
const music = Game.createSound({ url: 'assets/theme.mp3', music: true, loop: true });
|
|
316
|
+
music.play();
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
| Option | Default | Notes |
|
|
320
|
+
| --- | --- | --- |
|
|
321
|
+
| `url` | required | App resource or file path |
|
|
322
|
+
| `volume` | `1` | 0..1, live |
|
|
323
|
+
| `loop` | `false` | Live |
|
|
324
|
+
| `music` | `false` | Chooses the backend — **set at creation, not changeable later** |
|
|
325
|
+
|
|
326
|
+
Effect mode (the default) is built for low latency: a shared `SoundPool` on Android, a small pool of preloaded players on iOS. Call `play()` from any handler and repeated plays overlap instead of cutting each other off. `music: true` picks the streaming backend (`MediaPlayer` / `AVAudioPlayer`) for longer tracks; music pauses when the app backgrounds and resumes with it.
|
|
327
|
+
|
|
328
|
+
Methods: `play()`, `pause()` (resumes where it stopped), `stop()` (rewinds to the beginning). Formats: WAV, MP3, OGG on Android; WAV, MP3, M4A on iOS.
|
|
329
|
+
|
|
330
|
+
## Emitter
|
|
331
|
+
|
|
332
|
+
Add and remove like a sprite: `gameView.add(emitter)` / `remove(emitter)`. Spawning, integration, fading and drawing all run in the native loop — JS only writes configuration and calls `emit()`.
|
|
333
|
+
|
|
334
|
+
| Property | Default | Notes |
|
|
335
|
+
| --- | --- | --- |
|
|
336
|
+
| `sheet`, `frame` | — | All particles share one frame, so an emitter renders as a single batch run |
|
|
337
|
+
| `x`, `y` | `0` | Ignored while `target` is set |
|
|
338
|
+
| `target` | — | Sprite to follow. Set to `null` to detach |
|
|
339
|
+
| `offsetX`, `offsetY` | `0` | Offset from the target (rear wheels, exhaust) |
|
|
340
|
+
| `zIndex` | `0` | Emitters draw above sprites of the same z |
|
|
341
|
+
| `rate` | `0` | Particles per second while `emitting`. Leave at `0` for burst-only emitters |
|
|
342
|
+
| `emitting` | `true` | Toggle the continuous stream |
|
|
343
|
+
| `lifetime` | `800` | ms |
|
|
344
|
+
| `speed` | `100` | px/s, randomized between 50% and 100% so bursts do not form perfect rings |
|
|
345
|
+
| `angle` | `0` | Base direction: `0` = up, clockwise degrees |
|
|
346
|
+
| `spread` | `360` | Cone width in degrees |
|
|
347
|
+
| `gravity` | `0` | px/s² applied to particle velocity |
|
|
348
|
+
| `size` | `0` | Base particle width in px; `0` = frame size |
|
|
349
|
+
| `startScale` / `endScale` | `1` / `1` | Interpolated over `lifetime` |
|
|
350
|
+
| `startOpacity` / `endOpacity` | `1` / `0` | Interpolated over `lifetime` |
|
|
351
|
+
| `tint` | white | Tint white particle art at runtime — one tiny texture covers every color in the game |
|
|
352
|
+
| `blend` | `'normal'` | `'add'` for fire, sparks and magic |
|
|
353
|
+
| `maxParticles` | `200` | Pool size, hard cap 1000 |
|
|
354
|
+
|
|
355
|
+
Methods: `emit(n)` fires a one-shot burst on top of `rate`; `clear()` kills all live particles.
|
|
356
|
+
|
|
357
|
+
## Rope
|
|
358
|
+
|
|
359
|
+
A native Verlet chain: integration and distance constraints run in the game loop, segments render as quads oriented along the rope (one sheet frame, one batch run).
|
|
360
|
+
|
|
361
|
+
| Property | Default | Notes |
|
|
362
|
+
| --- | --- | --- |
|
|
363
|
+
| `sheet`, `frame` | — | One frame, textured along each link |
|
|
364
|
+
| `segments` | `10` | Number of links |
|
|
365
|
+
| `segmentLength` | `30` | px |
|
|
366
|
+
| `thickness` | `10` | Drawn width in px |
|
|
367
|
+
| `gravity` | `1500` | px/s² |
|
|
368
|
+
| `damping` | `0.98` | Velocity kept per step |
|
|
369
|
+
| `iterations` | `3` | Constraint passes per frame — higher is stiffer and costlier |
|
|
370
|
+
| `head` | — | Sprite the head is pinned to. Without it, the head anchors at `x`/`y` |
|
|
371
|
+
| `tail` | — | Sprite pinned to the other end (hanging weights, bridges) |
|
|
372
|
+
| `x`, `y` | `0` | Fixed head anchor when no `head` sprite is set |
|
|
373
|
+
| `maxLength` | `0` | `0` = off. With a `tail`, turns the rope into a tether: when head→tail distance exceeds it, the sprites are pulled back onto the limit each frame and their outward velocity is cancelled — a falling weight snaps taut and swings like a pendulum instead of stretching |
|
|
374
|
+
| `zIndex` | `0` | |
|
|
375
|
+
| `visible` | `true` | |
|
|
376
|
+
| `endX`, `endY` | — | Read-only live position of the loose end (grappling-hook tips) |
|
|
377
|
+
|
|
378
|
+
The tether yields at the end no finger owns: with a fixed head anchor the tail sprite is simply leashed, but with sprites on both ends you can drag either one and the other is towed once the rope goes taut.
|
|
379
|
+
|
|
380
|
+
## Events at a glance
|
|
381
|
+
|
|
382
|
+
Only these events exist. Nothing fires per frame, and events are only fired if a listener is registered.
|
|
383
|
+
|
|
384
|
+
- **GameView**: `press`, `tap`, `release`, `resize`
|
|
385
|
+
- **Sprite** (and therefore **Text**): `press`, `release`, `tap`, `dragstart`, `drag`, `dragend`, `pinch`, `rotate`, `animationcomplete`, `complete`, `collision`, `collisionend`, `land`
|
|
386
|
+
- **SpriteSheet, Font, Sound, Emitter, Rope**: none
|
|
387
|
+
|
|
388
|
+
There is `collision` (enter) and `collisionend` (exit), but deliberately **no** per-frame "stay" event — that would be bridge traffic every frame. Hold the in-between state in JS: you heard the enter, you will hear the end.
|
|
389
|
+
|
|
390
|
+
## Gotchas the property tables do not show
|
|
391
|
+
|
|
392
|
+
- **`maxSpeed` caps `thrust` and `carMode` only — not plain velocity.** The clamp runs inside the thrust integration and inside the car model, so a sprite given `velocityX = 3000` travels at 3000 no matter what `maxSpeed` says. A ship accelerating on `thrust`, on the other hand, stops gaining speed at 500 px/s unless you raise its own `maxSpeed`, which is the cap people actually hit.
|
|
393
|
+
- **`sprite.animation` survives `stop()`.** Comparing it to decide whether to restart an animation gives a false negative. The top-down demo tracks a `walking` boolean itself.
|
|
394
|
+
- **`follow()` resets its options.** See the GameView section.
|
|
395
|
+
- **`animate()` ignores unknown keys.** Tweening `width`, `height` or `tintColor` silently does nothing — those are instant writes only.
|
|
396
|
+
- **`visible: false` removes a sprite from collision too.** That is why pooled bullets and rocks use it instead of removing them from the scene.
|
|
397
|
+
- **`scaleX: -1` versus `flipX: true`.** Negative scale flips the physics and hit-test shape with the art; `flipX` mirrors only the drawing. Use `flipX` unless you specifically want the former.
|
|
398
|
+
- **Tween positions are absolute.** A sprite with `idleAnimation` on will land off-target; disable the wobble before the tween and re-enable it on `complete`.
|
|
399
|
+
- **`music` is chosen at creation.** Creating an effect sound and flipping `music` later does not switch backends.
|
|
400
|
+
- **`collision` fires once per overlap-enter**, re-arming only after the shapes separate. A sprite resting inside a trigger does not re-fire — `collisionend` tells you when it left.
|
|
401
|
+
- **Pooling a sprite with `visible = false` fires `collisionend`** on whoever was touching it. That is usually what you want (a plate whose ball was despawned closes its door), but a handler that assumes separation means "the player walked away" will misfire.
|
|
402
|
+
- **The built-in pixel font only covers ASCII 32..126.** Em dashes, accents and `ñ` render as blanks or garbage — the module's own demos rewrote `—` as `-` for this reason. Ship a BMFont atlas (`tools/genfont.py` rasterizes any TTF) when the game needs accented Spanish text.
|
|
403
|
+
- **`swept` belongs on the fast mover, not on the target.** A stationary thin wall with `swept: true` changes nothing; the bullet is the sprite whose path needs testing.
|
|
404
|
+
- **A swept bullet can enter and leave in consecutive frames.** If it crosses a thin target entirely, you get `collision` and then `collisionend` almost immediately — react on the enter.
|
|
405
|
+
- **Text `width`/`height` are read-only in practice.** They report the laid-out block; to make text bigger use `scale`.
|
|
406
|
+
- **`hitboxShape: 'circle'` also changes the touch area**, not just collisions.
|
|
407
|
+
- **`blend: 'add'` costs a batch flush per mode change.** Group additive sprites on their own `zIndex` band.
|
|
408
|
+
- **`glowColor` alone draws nothing.** The glow pass only runs when `glowBlur > 0` *and* `glowOpacity > 0`; both default in a way that makes `glowColor` on its own a no-op (`glowBlur` is `0`). Always set the blur radius with the color.
|