@maccesar/titools 4.7.0 → 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
CHANGED
|
@@ -177,7 +177,7 @@ The knowledge index is based on the latest Titanium SDK documentation. If your p
|
|
|
177
177
|
| ti-expert | Architecture and implementation | Starting point for most tasks |
|
|
178
178
|
| purgetss | Utility-first styling | UI styling and animations |
|
|
179
179
|
| ti-ui | UI/UX patterns | Complex layouts, ListViews, platform UI |
|
|
180
|
-
| ti-game | 2D games with the `ti.game` module | Sprites, physics, collisions, particles, camera |
|
|
180
|
+
| ti-game | 2D games with the `ti.game` module | Sprites, physics, collisions, text HUDs, particles, camera |
|
|
181
181
|
| ti-api | Complete Titanium API reference | Looking up properties, methods, events |
|
|
182
182
|
| ti-guides | SDK fundamentals | tiapp.xml, Hyperloop, distribution, JDK/Xcode compatibility, release notes |
|
|
183
183
|
| ti-howtos | Native feature integration | Location, push, media, platform APIs |
|
|
@@ -186,7 +186,7 @@ The knowledge index is based on the latest Titanium SDK documentation. If your p
|
|
|
186
186
|
|
|
187
187
|
Notes:
|
|
188
188
|
- The first four (`ti-expert`, `purgetss`, `ti-ui`, `ti-game`) are opinionated workflow skills reflecting TiTools' Titanium conventions.
|
|
189
|
-
- `ti-game` documents the `ti.game` native module (2D sprite engine on OpenGL ES 2.0, Android and iOS). Its API reference was verified against the module source; it applies to Alloy and Classic projects alike and does not depend on PurgeTSS.
|
|
189
|
+
- `ti-game` documents the `ti.game` native module (2D sprite engine on OpenGL ES 2.0, Android and iOS). Its API reference was verified against the module source and tracks upstream `main` (the module ships features ahead of its manifest version); it applies to Alloy and Classic projects alike and does not depend on PurgeTSS.
|
|
190
190
|
- The latter five are documentation-mirror skills sourced from the official Titanium SDK docs (`tidev/titanium-docs`) and audited against them via the internal `titools-skill-auditor`.
|
|
191
191
|
- `purgetss` reference files are audited against the official PurgeTSS documentation, but its workflow conventions are opinionated.
|
|
192
192
|
|
|
@@ -435,7 +435,8 @@ When it activates:
|
|
|
435
435
|
- `tiapp.xml` declares `ti.game`, or any file calls `require('ti.game')`
|
|
436
436
|
- Sprites, sprite sheets, TexturePacker atlases, frame animations, native tweens
|
|
437
437
|
- Physics: gravity and velocity, solid platforms, one-way floors, bouncing, the arcade car model, Newtonian thrust
|
|
438
|
-
- Collision groups, invisible trigger zones, hitbox tuning
|
|
438
|
+
- Collision groups, invisible trigger zones, enter/exit events, swept AABB for fast bullets, hitbox tuning
|
|
439
|
+
- Bitmap-font text sprites and screen-fixed HUDs inside the scene
|
|
439
440
|
- Particle emitters, Verlet ropes, camera follow/zoom/shake/effects, low-latency sound
|
|
440
441
|
- Drag & drop, pinch, rotate, multitouch controls
|
|
441
442
|
|
|
@@ -446,6 +447,7 @@ Example prompts:
|
|
|
446
447
|
"Add a camera that follows the player with a dead zone."
|
|
447
448
|
"How do I make a top-down racer that drifts?"
|
|
448
449
|
"Why does my game stutter when I move sprites from setInterval?"
|
|
450
|
+
"Add a score HUD that stays put while the camera follows the player."
|
|
449
451
|
```
|
|
450
452
|
|
|
451
453
|
Key rules:
|
|
@@ -454,7 +456,7 @@ Key rules:
|
|
|
454
456
|
- `solidWith` blocks, `collidesWith` reports — they are independent
|
|
455
457
|
- A sprite with size but no sheet is an invisible trigger (scores, goals, walls)
|
|
456
458
|
- Add a whole level with one `gameView.add([...])` call
|
|
457
|
-
-
|
|
459
|
+
- HUDs are `Game.createText` sprites with `screenFixed: true`, not `Ti.UI.Label` overlays
|
|
458
460
|
|
|
459
461
|
---
|
|
460
462
|
|
package/package.json
CHANGED
package/skills/ti-game/SKILL.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ti-game
|
|
3
|
-
description: "Use when building or reviewing 2D games with the ti.game module for Titanium SDK (Android and iOS) — sprites, sprite sheets, frame animations, tweens, physics (gravity, solid platforms, carMode drifting, thrust), collision groups, particles, Verlet ropes, camera follow/zoom/shake, low-latency sound, drag & drop, multitouch. AUTO-DETECT: if tiapp.xml declares ti.game or any file calls require('ti.game'), invoke BEFORE writing ANY game code. Also trigger on: game, juego, sprite, spritesheet, platformer, endless runner, top-down, shooter, racer, flappy, breakout, rhythm game, card game, puzzle, collision, hitbox, parallax, particles, game loop, 60 fps, GameView. ti.game is NOT Phaser, Godot or Box2D: you never write a game loop, never move a sprite from setInterval, and several familiar features (
|
|
3
|
+
description: "Use when building or reviewing 2D games with the ti.game module for Titanium SDK (Android and iOS) — sprites, sprite sheets, frame animations, tweens, physics (gravity, solid platforms, carMode drifting, thrust), collision groups, trigger zones, swept AABB, bitmap-font text and HUDs, particles, Verlet ropes, camera follow/zoom/shake, low-latency sound, drag & drop, multitouch. AUTO-DETECT: if tiapp.xml declares ti.game or any file calls require('ti.game'), invoke BEFORE writing ANY game code. Also trigger on: game, juego, sprite, spritesheet, platformer, endless runner, top-down, shooter, racer, flappy, breakout, rhythm game, card game, puzzle, collision, hitbox, parallax, particles, HUD, score, game loop, 60 fps, GameView. ti.game is NOT Phaser, Godot or Box2D: you never write a game loop, never move a sprite from setInterval, and several familiar features (tilemap layer, sprite parenting, raycast, joystick) do not exist yet — check references/roadmap.md before inventing an API."
|
|
4
4
|
argument-hint: "[genre or feature]"
|
|
5
5
|
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(node *)
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# ti.game — 2D game engine for Titanium SDK
|
|
9
9
|
|
|
10
|
-
`ti.game` is a native OpenGL ES 2.0 sprite engine exposed to JavaScript, written and maintained by **Michael Gangolf (m1ga)** — upstream is https://github.com/m1ga/ti.game. This skill
|
|
10
|
+
`ti.game` is a native OpenGL ES 2.0 sprite engine exposed to JavaScript, written and maintained by **Michael Gangolf (m1ga)** — upstream is https://github.com/m1ga/ti.game. This skill tracks upstream `main` as of **2026-08-18** — the manifest still reads `0.3.0`, but the text engine, `screenFixed`, `swept` collisions and `collisionend` landed after it, so a version number alone will not tell you what a checkout has. When upstream and any fork disagree, upstream wins. Same JS API on Android and iOS — the iOS side is a class-per-class Obj-C twin of the Android engine.
|
|
11
11
|
|
|
12
12
|
Works in **both Classic and Alloy** projects. Nothing here depends on PurgeTSS.
|
|
13
13
|
|
|
@@ -31,6 +31,9 @@ The practical consequence, and the single biggest mistake to avoid:
|
|
|
31
31
|
| Drag a sprite with the finger | `touchmove` handler | `draggable: true` |
|
|
32
32
|
| Scrolling background | timer repositioning copies | `wrapX` / `wrapShift` + `velocityX` |
|
|
33
33
|
| Camera that follows the player | timer writing `cameraX` | `gameView.follow(sprite, options)` |
|
|
34
|
+
| A score, a title, a label | `Ti.UI.Label` overlays | `Game.createText({ text, screenFixed: true })` |
|
|
35
|
+
| A bullet fast enough to skip past a thin wall | shrinking the time step | `swept: true` on the bullet |
|
|
36
|
+
| To know the player *left* a zone | polling on a timer | the `collisionend` event |
|
|
34
37
|
| Smoke, sparks, explosions | pooled sprites in JS | `Game.createEmitter(...)` |
|
|
35
38
|
| A swinging chain or cape | verlet solver in JS | `Game.createRope(...)` |
|
|
36
39
|
| Slow motion or a pause that still draws | pausing your own timers | `gameView.timeScale = 0.5` / `0` |
|
|
@@ -117,10 +120,14 @@ Gravity is per sprite, not global — set it on each body that should fall. A sp
|
|
|
117
120
|
## Block versus react: `solidWith` and `collidesWith` are independent
|
|
118
121
|
|
|
119
122
|
- `solidWith: ['ground']` **blocks** movement against sprites tagged `collisionGroup: 'ground'`. The engine pushes the sprite out along the axis of least penetration, sets read-only `onGround` and fires `land`.
|
|
120
|
-
- `collidesWith: ['coin', 'enemy']` **reports** overlap. Fires `collision` once per overlap-enter (
|
|
123
|
+
- `collidesWith: ['coin', 'enemy']` **reports** overlap. Fires `collision` once per overlap-enter and `collisionend` once on separation (both carry `group`, `other`, `x`, `y`). Nothing is blocked.
|
|
121
124
|
|
|
122
125
|
A sprite can use both — a player that stands on platforms (`solidWith`) and dies on spikes (`collidesWith`).
|
|
123
126
|
|
|
127
|
+
The enter/exit pair is the trigger lifecycle other engines call `OnTriggerEnter`/`Exit`: water that tints the hero while he is in it, a pressure plate holding a door open exactly while something rests on it. Removing or hiding the partner mid-contact also counts as separation, so a despawned object still closes its door. There is deliberately **no** per-frame "stay" event — keep that state in JS.
|
|
128
|
+
|
|
129
|
+
A fast mover can slip through a thin target between frames: it never overlaps on any single frame, so the discrete test misses. `swept: true` on the *mover* tests its movement as a path — `collision` fires for whatever the path crossed, and `solidWith` walls stop it at the impact point instead of letting it teleport through.
|
|
130
|
+
|
|
124
131
|
**A sprite with `width`/`height` but no `sheet` renders nothing and works as an invisible trigger.** Score zones, goals, checkpoints, ceilings, kill floors and walls are all built this way in the official demos. This is the idiomatic way to add logic to a scene without art.
|
|
125
132
|
|
|
126
133
|
Tune fairness with `hitboxScale` (art rarely fills its frame; slightly small hitboxes feel better) and `hitboxShape: 'circle'` for balls and rocks — circles also resolve against solids along the contact normal, so they bounce off corners diagonally instead of like a box. Turn on `debug: true` per sprite, or on the GameView for everything, to see green collision AABBs, blue touch bounds and the orange anchor dot.
|
|
@@ -128,6 +135,7 @@ Tune fairness with `hitboxScale` (art rarely fills its frame; slightly small hit
|
|
|
128
135
|
## Performance rules that actually matter
|
|
129
136
|
|
|
130
137
|
- **One texture = one draw call.** The batcher accumulates up to 1000 quads and flushes on texture switch. A scene whose sprites all share one sheet renders in a single draw call regardless of sprite count. Pack art into as few sheets as possible; reuse one white particle texture and tint it at runtime.
|
|
138
|
+
- **Know what else breaks the batch**, since none of it is visible on screen: every `'normal'` ↔ `'add'` transition in draw order flushes (group additive sprites into their own `zIndex` band so it happens twice a frame, not constantly); each glowing sprite costs 2 extra draw calls per frame, plus 2 more while a `flash()` runs; ropes, skid marks and debug overlays always draw with normal blending, so interleaving them with additive content flushes too.
|
|
131
139
|
- **Bulk-add a level.** `gameView.add([spriteA, spriteB, emitter, rope])` crosses the bridge once and commits under one scene lock. Collect a level's objects in an array and add them in one call; `add(object)` still works for single objects.
|
|
132
140
|
- **Keep collision group lists targeted.** Cost is O(colliders × candidates) per frame. A bullet checking `['asteroid']` tests 5 sprites, not the whole scene.
|
|
133
141
|
- **Pool, don't create.** Bullets, notes, obstacles and enemies are created once (usually 3–10 of them), then recycled by setting `visible = false` and zeroing velocity. Creating sprites mid-game costs a bridge hop and a scene lock.
|
|
@@ -135,9 +143,19 @@ Tune fairness with `hitboxScale` (art rarely fills its frame; slightly small hit
|
|
|
135
143
|
- **`maxFps: 60`** on the GameView keeps 120 Hz ProMotion displays from doubling render work. `0` (default) follows the display refresh rate.
|
|
136
144
|
- **Judge performance on device.** The iOS Simulator's OpenGL translation layer is disproportionately slow and renders at a 1x drawable; real devices keep native screen scale and run far smoother.
|
|
137
145
|
|
|
138
|
-
##
|
|
146
|
+
## Text is a sprite
|
|
147
|
+
|
|
148
|
+
Text draws **inside** the scene: `Game.createText({ text: 'SCORE 0' })` returns a sprite made of glyph quads, so it z-sorts, tweens, tints, flashes, glows, wobbles on `idleAnimation` and takes `tap` events like any other sprite — a text button needs no overlay view. With no `font` it uses a pixel font embedded in the module (nothing to ship); `Game.createFont({...})` loads a BMFont/AngelCode atlas or a monospace grid image. There is no font size — scale the sprite, with integer values for crisp pixels.
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
const score = Game.createText({ text: 'SCORE 0', screenFixed: true, x: W / 2, y: H * 0.08, scale: 3, zIndex: 100 });
|
|
152
|
+
gameView.add(score);
|
|
153
|
+
score.text = 'SCORE 10'; // re-lays out natively
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`screenFixed: true` works on **any** sprite: `x`/`y` become surface coordinates and camera position, zoom and shake are ignored, while touch still maps back. That is the HUD pattern — the score stays put while the camera follows the player. Without it, text is world-space and scrolls past like scenery (signposts, floating damage numbers).
|
|
139
157
|
|
|
140
|
-
|
|
158
|
+
Overlaid `Ti.UI.Label` views still work and are still right for anything the engine cannot draw: native inputs, system fonts, accented text the built-in font lacks, scrollable menus. Just do not reach for them for a score any more.
|
|
141
159
|
|
|
142
160
|
Standard Titanium touch events do not reach the GameView, but ordinary Titanium buttons and views layered on top work normally, and sibling views receive simultaneous pointers — which is how the demos build multitouch d-pads (hold ▶ and press jump at the same time). One caveat from the demos: Android's `touchFeedback` ripple cannot animate over this canvas and spams `RippleDrawable` errors, so on-screen buttons set their own `backgroundColor` on `touchstart`/`touchend`.
|
|
143
161
|
|
|
@@ -175,5 +193,6 @@ Read the one that matches the task instead of guessing — the API surface below
|
|
|
175
193
|
5. Is `hitboxScale` set on sprites whose art does not fill the frame?
|
|
176
194
|
6. Do triggers (scores, goals, kill zones) use sheet-less sprites instead of visible art?
|
|
177
195
|
7. Is every `createSound`, `setInterval` and `setTimeout` stopped when the window closes?
|
|
178
|
-
8. Is the HUD
|
|
179
|
-
9.
|
|
196
|
+
8. Is the HUD `Game.createText` with `screenFixed: true`, rather than a stack of overlaid labels?
|
|
197
|
+
9. Does anything travel far enough per frame to tunnel? Give it `swept: true`.
|
|
198
|
+
10. Are you using an API that actually exists? Anything involving tilemaps, sprite parenting, raycasting, joysticks or `blend: 'multiply'` — check [roadmap.md](references/roadmap.md) first.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ti.game API reference
|
|
2
2
|
|
|
3
|
-
Complete JS surface of `ti.game`
|
|
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
4
|
|
|
5
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
6
|
|
|
@@ -10,6 +10,8 @@ Every property listed is **live**: reading returns the current native value, eve
|
|
|
10
10
|
- [GameView](#gameview)
|
|
11
11
|
- [SpriteSheet](#spritesheet)
|
|
12
12
|
- [Sprite](#sprite)
|
|
13
|
+
- [Font](#font)
|
|
14
|
+
- [Text](#text)
|
|
13
15
|
- [Sound](#sound)
|
|
14
16
|
- [Emitter](#emitter)
|
|
15
17
|
- [Rope](#rope)
|
|
@@ -27,6 +29,8 @@ const Game = require('ti.game');
|
|
|
27
29
|
| `Game.createGameView(options)` | GameView — the canvas, a Titanium view |
|
|
28
30
|
| `Game.createSpriteSheet(options)` | SpriteSheet — a texture cut into frames |
|
|
29
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 |
|
|
30
34
|
| `Game.createSound(options)` | Sound |
|
|
31
35
|
| `Game.createEmitter(options)` | Emitter — particle system |
|
|
32
36
|
| `Game.createRope(options)` | Rope — Verlet chain |
|
|
@@ -112,6 +116,7 @@ Textures upload to the GPU lazily on first use, from the render thread, and are
|
|
|
112
116
|
| `zIndex` | `0` | Draw order |
|
|
113
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) |
|
|
114
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 |
|
|
115
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 |
|
|
116
121
|
|
|
117
122
|
### Sheet and animation
|
|
@@ -145,7 +150,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
|
|
|
145
150
|
| --- | --- | --- |
|
|
146
151
|
| `velocityX`, `velocityY` | `0` | px/s |
|
|
147
152
|
| `gravity` | `0` | px/s², added to `velocityY`. Per sprite — there is no global gravity |
|
|
148
|
-
| `maxSpeed` | `500` | px/s cap |
|
|
153
|
+
| `maxSpeed` | `500` | px/s cap applied **only** to `thrust` acceleration and the `carMode` model. Writing `velocityX`/`velocityY` directly is never clamped |
|
|
149
154
|
| `angularVelocity` | `0` | deg/s |
|
|
150
155
|
| `thrust` | `0` | px/s² along the current heading (Newtonian flight) |
|
|
151
156
|
| `wrapAround` | `false` | Re-enter from the opposite screen edge (Asteroids) |
|
|
@@ -169,6 +174,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
|
|
|
169
174
|
| `collidesWith` | — | Array of groups this sprite reports overlaps with |
|
|
170
175
|
| `hitboxScale` | `1` | Shrinks the collision box around the anchor |
|
|
171
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 |
|
|
172
178
|
| `debug` | `false` | Draw this sprite's shapes: green = collision AABB, blue = touch bounds, orange dot = anchor |
|
|
173
179
|
|
|
174
180
|
### Car (`carMode`)
|
|
@@ -206,7 +212,7 @@ Every sprite gets its own phase, and the wobble unwinds exactly when disabled. T
|
|
|
206
212
|
| --- | --- | --- |
|
|
207
213
|
| `tintColor` | — | Multiplies the frame's colors (team colors, day/night, damage states). `null` or `'#fff'` = unchanged. Multiplicative, so it can only darken |
|
|
208
214
|
| `glowColor` | — | Tinted, blurred silhouette drawn behind the sprite by a shader pass |
|
|
209
|
-
| `glowBlur` | `0` | Blur radius in px; `0` = off |
|
|
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 |
|
|
210
216
|
| `glowOpacity` | `1` | Halo strength 0..1 — tweenable via `animate`, so a glow can fade in without touching the blur |
|
|
211
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` |
|
|
212
218
|
|
|
@@ -237,8 +243,69 @@ Every sprite gets its own phase, and the wobble unwinds exactly when disabled. T
|
|
|
237
243
|
| `animationcomplete` | `animation` | A non-looping sheet animation finished |
|
|
238
244
|
| `complete` | final transform values | A tween finished |
|
|
239
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 |
|
|
240
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) |
|
|
241
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
|
+
|
|
242
309
|
## Sound
|
|
243
310
|
|
|
244
311
|
```javascript
|
|
@@ -315,12 +382,14 @@ The tether yields at the end no finger owns: with a fixed head anchor the tail s
|
|
|
315
382
|
Only these events exist. Nothing fires per frame, and events are only fired if a listener is registered.
|
|
316
383
|
|
|
317
384
|
- **GameView**: `press`, `tap`, `release`, `resize`
|
|
318
|
-
- **Sprite
|
|
319
|
-
- **SpriteSheet, Sound, Emitter, Rope**: none
|
|
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.
|
|
320
389
|
|
|
321
390
|
## Gotchas the property tables do not show
|
|
322
391
|
|
|
323
|
-
- **`maxSpeed`
|
|
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.
|
|
324
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.
|
|
325
394
|
- **`follow()` resets its options.** See the GameView section.
|
|
326
395
|
- **`animate()` ignores unknown keys.** Tweening `width`, `height` or `tintColor` silently does nothing — those are instant writes only.
|
|
@@ -328,6 +397,12 @@ Only these events exist. Nothing fires per frame, and events are only fired if a
|
|
|
328
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.
|
|
329
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`.
|
|
330
399
|
- **`music` is chosen at creation.** Creating an effect sound and flipping `music` later does not switch backends.
|
|
331
|
-
- **`collision` fires once per overlap-enter**, re-arming only after the shapes separate. A sprite resting inside a trigger does not re-fire.
|
|
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`.
|
|
332
406
|
- **`hitboxShape: 'circle'` also changes the touch area**, not just collisions.
|
|
333
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.
|
|
@@ -9,6 +9,7 @@ Installing the module, wiring it into Classic and Alloy projects, organizing art
|
|
|
9
9
|
- [Alloy projects](#alloy-projects)
|
|
10
10
|
- [Where art and sound live](#where-art-and-sound-live)
|
|
11
11
|
- [Sprite sheets and atlases](#sprite-sheets-and-atlases)
|
|
12
|
+
- [Fonts](#fonts)
|
|
12
13
|
- [Lifecycle and cleanup](#lifecycle-and-cleanup)
|
|
13
14
|
- [Debugging and tuning](#debugging-and-tuning)
|
|
14
15
|
- [Platform differences](#platform-differences)
|
|
@@ -40,6 +41,8 @@ Then declare it in `tiapp.xml`. The iOS platform key is `iphone`, not `ios`:
|
|
|
40
41
|
|
|
41
42
|
Pinning `version` is worth the extra attribute: with two module versions installed side by side, an unpinned entry silently picks the highest one.
|
|
42
43
|
|
|
44
|
+
The version number lies right now: the manifest has read `0.3.0` through several feature landings. A zip built before 2026-08-18 has no `createText`, no `createFont`, no `screenFixed`, no `swept` and no `collisionend` while still calling itself 0.3.0. If a call that this skill documents is `undefined` at runtime, rebuild the module from upstream `main` rather than hunting for a typo.
|
|
45
|
+
|
|
43
46
|
Building the module from source:
|
|
44
47
|
|
|
45
48
|
```bash
|
|
@@ -50,7 +53,7 @@ ti build -p ios --build-only # macOS only → ios/dist/ti.game-iphone-<ve
|
|
|
50
53
|
|
|
51
54
|
## Classic projects
|
|
52
55
|
|
|
53
|
-
`require('ti.game')` anywhere and add the GameView to a window. The
|
|
56
|
+
`require('ti.game')` anywhere and add the GameView to a window. The 21 demos in the module's `example/` folder are Classic, each one a CommonJS module exporting a start function:
|
|
54
57
|
|
|
55
58
|
```javascript
|
|
56
59
|
// Resources/flappy.js
|
|
@@ -85,7 +88,6 @@ The standard Alloy element for a native module view. `method` names the factory
|
|
|
85
88
|
<Alloy>
|
|
86
89
|
<Window id="index" navBarHidden="true" exitOnClose="true" onOpen="onOpen" onClose="onClose">
|
|
87
90
|
<Module id="gameView" module="ti.game" method="createGameView" backgroundColor="#8ed8f8" />
|
|
88
|
-
<Label id="scoreLabel" />
|
|
89
91
|
<Button id="jumpButton" title="JUMP" onTouchstart="jump" />
|
|
90
92
|
</Window>
|
|
91
93
|
</Alloy>
|
|
@@ -112,6 +114,8 @@ function onResize(e) {
|
|
|
112
114
|
}
|
|
113
115
|
```
|
|
114
116
|
|
|
117
|
+
Only views that must be native — buttons, menus, dialogs — belong in the XML. The HUD does not: scores and labels are `Game.createText` sprites created in the controller and added to the game view, so they batch with the scene and can tween and flash.
|
|
118
|
+
|
|
115
119
|
Style the view in TSS like any other component (`'#gameView': { width: Ti.UI.FILL, height: Ti.UI.FILL }`). The `<Module>` element itself is documented Alloy behavior; the pattern below is the one verified in a shipped ti.game app.
|
|
116
120
|
|
|
117
121
|
### B: an empty container in XML, the GameView created in JS
|
|
@@ -219,6 +223,28 @@ Practical rules:
|
|
|
219
223
|
|
|
220
224
|
`frameCount` is `0` until a grid sheet's texture has loaded, so do not compute animation ranges from it at creation time.
|
|
221
225
|
|
|
226
|
+
## Fonts
|
|
227
|
+
|
|
228
|
+
Nothing to ship for the default look: `Game.createText({ text: 'SCORE 0' })` uses a 9×15 pixel font embedded in the module, on both platforms.
|
|
229
|
+
|
|
230
|
+
A custom face is an asset like any other, created once outside `resize`:
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
// BMFont / AngelCode descriptor + its page image, side by side in assets/
|
|
234
|
+
const hud = Game.createFont({ font: 'fonts/hud.fnt' }); // hud.png resolved next to it
|
|
235
|
+
|
|
236
|
+
// or a monospace grid image
|
|
237
|
+
const mono = Game.createFont({ image: 'fonts/mono.png', charWidth: 9, charHeight: 15 });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
| File | Referenced as (Alloy) |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `app/assets/fonts/hud.fnt` + `app/assets/fonts/hud.png` | `'fonts/hud.fnt'` |
|
|
243
|
+
|
|
244
|
+
These are **bitmap** fonts — do not put them in `app/assets/fonts/` expecting Titanium's native font handling, and do not use a `.ttf` here. To go from a TTF to either format, the module ships `tools/genfont.py`, which rasterizes it into a grid or BMFont atlas.
|
|
245
|
+
|
|
246
|
+
The built-in font covers ASCII 32..126 only. Any game whose text includes `ñ`, accents or `¿` needs a generated font — or a `Ti.UI.Label` overlay for that specific string.
|
|
247
|
+
|
|
222
248
|
## Lifecycle and cleanup
|
|
223
249
|
|
|
224
250
|
The engine handles what it owns: the render loop pauses and resumes with the activity or app, music pauses and resumes with it, and textures survive an EGL context loss. **Everything you created in JS is yours to stop.**
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# ti.game recipes by genre
|
|
2
2
|
|
|
3
|
-
Working patterns distilled from the
|
|
3
|
+
Working patterns distilled from the 21 official demos in `example/` and from a shipped Alloy game (Titanium Lander). Each recipe shows the part that is specific to the genre; the shared scaffolding is in [The scaffolding](#the-scaffolding) and is not repeated.
|
|
4
4
|
|
|
5
|
-
Every snippet uses only API that exists in
|
|
5
|
+
Every snippet uses only API that exists in upstream `main` as of 2026-08-18. Check [roadmap.md](roadmap.md) before reaching for anything not shown here.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
9
|
- [The scaffolding](#the-scaffolding)
|
|
10
|
-
- [Cross-cutting patterns](#cross-cutting-patterns) — pooling, parallax, multitouch controls, HUD, invisible triggers, hit-stop
|
|
10
|
+
- [Cross-cutting patterns](#cross-cutting-patterns) — pooling, parallax, multitouch controls, HUD text, invisible triggers, enter/exit zones, hit-stop
|
|
11
11
|
- [Tap-to-flap](#tap-to-flap) (`flappy.js`)
|
|
12
12
|
- [Platformer](#platformer) (`platformer.js`)
|
|
13
13
|
- [Top-down / Zelda](#top-down--zelda) (`topdown.js`)
|
|
@@ -91,7 +91,7 @@ for (let i = 0; i < 10; i++) {
|
|
|
91
91
|
height: 16,
|
|
92
92
|
visible: false,
|
|
93
93
|
wrapAround: true,
|
|
94
|
-
|
|
94
|
+
swept: true, // path-tested: a fast bolt cannot skip past a rock
|
|
95
95
|
collidesWith: ['asteroid']
|
|
96
96
|
});
|
|
97
97
|
const state = { sprite: sprite, active: false, generation: 0 };
|
|
@@ -135,6 +135,10 @@ function fire(x, y, vx, vy) {
|
|
|
135
135
|
|
|
136
136
|
The `generation` counter matters: without it, a timeout fired for an old shot deactivates the *new* bullet occupying that slot.
|
|
137
137
|
|
|
138
|
+
`swept: true` (compared side by side in `swept.js`) is what keeps a fast bolt from crossing a rock between two frames without ever overlapping it. The asteroids demo predates it and inflates the hitbox instead (`hitboxScale: 1.4`) — that works, at the price of counting near misses as hits. Prefer `swept`, and reserve `hitboxScale` for fairness tuning.
|
|
139
|
+
|
|
140
|
+
Recycling a sprite with `visible = false` fires `collisionend` on anything it was touching. Handlers that treat separation as "the player walked away" need to tolerate a despawn.
|
|
141
|
+
|
|
138
142
|
### Seamless parallax
|
|
139
143
|
|
|
140
144
|
Two screen-wide copies per layer, scrolled by native velocity. `wrapX`/`wrapShift` teleport a copy that left the screen back behind its sibling. No JS runs in the loop.
|
|
@@ -220,24 +224,37 @@ bindHold(leftButton, () => {
|
|
|
220
224
|
|
|
221
225
|
The guard in the release handler is what keeps two opposite buttons from fighting: releasing left only zeroes steering if left is still the active direction.
|
|
222
226
|
|
|
223
|
-
### HUD
|
|
227
|
+
### HUD — `text.js`, `flappy.js`
|
|
224
228
|
|
|
225
|
-
|
|
229
|
+
Text sprites with `screenFixed: true`. They live in the scene — one batch, no overlay views — but ignore the camera, so a score stays in the corner while the world scrolls under it.
|
|
226
230
|
|
|
227
231
|
```javascript
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
// Created outside resize (no size known yet), positioned once it is
|
|
233
|
+
const scoreLabel = Game.createText({ text: '0', screenFixed: true, zIndex: 100 });
|
|
234
|
+
const statusLabel = Game.createText({ text: 'Tap to start!', screenFixed: true, zIndex: 100 });
|
|
235
|
+
|
|
236
|
+
function buildLevel(W, H) {
|
|
237
|
+
const TEXT_SCALE = Math.max(1, Math.round(W / 240)); // integer: crisp pixels
|
|
238
|
+
Object.assign(scoreLabel, { scale: TEXT_SCALE, x: W / 2, y: H * 0.08 });
|
|
239
|
+
Object.assign(statusLabel, { scale: TEXT_SCALE, x: W / 2, y: H * 0.45 });
|
|
240
|
+
gameView.add([scoreLabel, statusLabel]);
|
|
241
|
+
// ...
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function score() {
|
|
245
|
+
points += 1;
|
|
246
|
+
scoreLabel.text = points + ' / ' + GATE_COUNT;
|
|
247
|
+
scoreLabel.flash('#4dff88', 250); // text flashes like any sprite
|
|
248
|
+
}
|
|
238
249
|
```
|
|
239
250
|
|
|
240
|
-
|
|
251
|
+
Scaling from `W` is what replaces `fontSize`: a bitmap font has one native size, and an integer multiplier is the only way to keep texels square. `Math.max(1, ...)` guards the smallest screens.
|
|
252
|
+
|
|
253
|
+
Because a label is a sprite, the HUD gets the whole toolbox for free — `animate()` to pop a score on change, `tintColor` per state, `glowColor` on a title, `idleAnimation` for a gentle wobble, and `tap` handlers so a `[ RESET ]` label works as a button with no Titanium view behind it.
|
|
254
|
+
|
|
255
|
+
Drop `screenFixed` and the same label becomes world furniture: signposts, floating damage numbers, a name over an NPC — it scrolls with everything else.
|
|
256
|
+
|
|
257
|
+
Overlaid `Ti.UI.Label` views remain the answer for text the engine's font cannot draw (accents, system fonts, right-to-left) and for real UI: menus, dialogs, text inputs.
|
|
241
258
|
|
|
242
259
|
### Invisible triggers
|
|
243
260
|
|
|
@@ -269,6 +286,37 @@ car.addEventListener('collision', (e) => {
|
|
|
269
286
|
});
|
|
270
287
|
```
|
|
271
288
|
|
|
289
|
+
### Zones you can be inside of (enter / exit) — `zones.js`
|
|
290
|
+
|
|
291
|
+
`collision` and `collisionend` are the two halves of a trigger: entering water, standing on a pressure plate, holding a capture point. Nothing fires while you stay inside — the state lives in JS between the two events, and that is deliberate, since a per-frame "stay" would be bridge traffic every frame.
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
const pool = Game.createSprite({
|
|
295
|
+
x: W * 0.32, y: H * 0.3, width: W * 0.44, height: H * 0.18,
|
|
296
|
+
sheet: waterSheet, tintColor: '#3d6fd4', opacity: 0.35,
|
|
297
|
+
touchEnabled: false, // drags pass through to whatever is inside
|
|
298
|
+
collisionGroup: 'water'
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
hero.collidesWith = ['water'];
|
|
302
|
+
hero.addEventListener('collision', (e) => {
|
|
303
|
+
if (e.group === 'water') {
|
|
304
|
+
hero.tintColor = '#7fb2ff';
|
|
305
|
+
swimming = true;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
hero.addEventListener('collisionend', (e) => {
|
|
309
|
+
if (e.group === 'water') {
|
|
310
|
+
hero.tintColor = null;
|
|
311
|
+
swimming = false;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The listener goes on the sprite that declares `collidesWith`. For a pressure plate holding a door open exactly while a crate rests on it, that is the **plate** (`collidesWith: ['crate']`), not the crate — then `collision` opens the door and `collisionend` closes it, with no polling anywhere.
|
|
317
|
+
|
|
318
|
+
Removing the partner from the scene, hiding it (`visible = false`) or changing its `collisionGroup` mid-contact all count as separation, so the exit still fires and the door cannot stick open when an object despawns.
|
|
319
|
+
|
|
272
320
|
### Hit-stop on impact
|
|
273
321
|
|
|
274
322
|
Freezing the scene for a few frames makes an impact read before the explosion starts. `timeScale = 0` stops the engine from advancing while it keeps drawing and accepting touches — no render loop to restart, no state to save. (Pattern from Titanium Lander: 140 ms on a crash, 90 ms when a shield absorbs a hit.)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# What does not exist yet — and what to do instead
|
|
2
2
|
|
|
3
|
-
The most expensive mistake with `ti.game` is writing code against a feature that sounds like it should be there because Phaser, Godot or Cocos has it. This file is the guard rail: everything below is **absent from
|
|
3
|
+
The most expensive mistake with `ti.game` is writing code against a feature that sounds like it should be there because Phaser, Godot or Cocos has it. This file is the guard rail: everything below is **absent from upstream `main` as of 2026-08-18**, with the workaround that is idiomatic today.
|
|
4
4
|
|
|
5
5
|
Planned items come from the module's `TODO.md`. Planned is not shipped — when a game needs one of these, use the workaround and keep the code isolated enough to swap later.
|
|
6
6
|
|
|
@@ -8,14 +8,12 @@ Planned items come from the module's `TODO.md`. Planned is not shipped — when
|
|
|
8
8
|
|
|
9
9
|
| You might reach for | Status | What to do now |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
-
| `Game.createText()`, bitmap fonts, text sprites | Planned (priority 5) | Overlay `Ti.UI.Label` views on the window, above the GameView. Costs: labels are screen-fixed, so they do not scroll with the camera, do not z-sort against sprites and cannot be tweened by the engine |
|
|
12
11
|
| A tilemap layer, Tiled JSON import, collision layer | Planned (priority 4) | One sprite per tile. Fine at 12×15, dead at 200×200. For large ground planes use a single `tileRepeat` sprite instead of a grid, and add sparse invisible solids for collision |
|
|
13
12
|
| `sprite.parent`, attaching a turret to a tank, a hat to a hero, multi-part bosses | Planned (priority 7), the biggest structural item | Move the parts together from JS on the same coarse tick, or bake the combination into sheet frames. There is no transform inheritance |
|
|
14
13
|
| `gameView.raycast(...)`, line-of-sight, ground probes | Planned (priority 3) | Compare positions in JS on a decision timer, or place an invisible trigger sprite where the probe would land and listen for `collision` |
|
|
15
14
|
| Native virtual joystick or d-pad bound to a sprite | Planned (priority 6) | Overlaid Titanium views, one per button, writing `velocityX`/`steering`/`throttle` on `touchstart`/`touchend`. Sibling views get simultaneous pointers, so multitouch works |
|
|
16
15
|
| Gamepad support | Planned (priority 6) | Nothing native. Touch controls only |
|
|
17
16
|
| `blend: 'multiply'` or `'screen'` | Planned (priority 2) | Only `'normal'` and `'add'` exist. For darkening, use `tintColor` (multiplicative) on the sprite itself |
|
|
18
|
-
| Swept AABB so fast bullets stop tunneling | Planned (priority 3) | Cap bullet speed, or enlarge the hitbox along the travel axis with `hitboxScale` — the asteroids demo uses `hitboxScale: 1.4` on its bolts for exactly this |
|
|
19
17
|
| Slopes in platformer terrain | Planned (priority 3), conditional | Build slopes as stepped rectangles, or keep terrain flat |
|
|
20
18
|
| Per-frame animation events (footstep on frame 3) | Planned (priority 9) | Use `animationcomplete` on a short non-looping animation, or a timer aligned to the animation's fps |
|
|
21
19
|
| `play('attack', { then: 'idle' })` chaining | Planned (priority 9) | Handle `animationcomplete` and call `play()` from it |
|
|
@@ -49,7 +47,11 @@ Recent additions that are easy to miss and easy to reimplement badly in JS:
|
|
|
49
47
|
- `createSound` with separate low-latency effect and streaming music backends.
|
|
50
48
|
- `timeScale`, `maxFps`, `flipX`/`flipY`, `pixelSnap`, `idleAnimation`, `ySort`, `glowColor`/`glowBlur`/`glowOpacity`.
|
|
51
49
|
- `gameView.add([array])` committing a whole level in one bridge crossing.
|
|
50
|
+
- **Text is in the engine now** (2026-08-18): `createText` glyph sprites with an embedded pixel font, `createFont` for BMFont/AngelCode or monospace grid atlases, `align`/`letterSpacing`/`lineSpacing`, and `tools/genfont.py` to rasterize a TTF. Text objects are sprites — they tween, tint, flash, glow, z-sort and take taps. Do **not** fall back to `Ti.UI.Label` overlays for scores.
|
|
51
|
+
- **`screenFixed`** on any sprite: surface coordinates, camera position/zoom/shake ignored, touch mapped back. HUDs no longer need overlay views.
|
|
52
|
+
- **`swept: true`** per sprite: the frame's movement is path-tested (Minkowski + slab) for both collision events and solid blocking. Fast bullets stop tunneling; `hitboxScale` tricks along the travel axis are obsolete.
|
|
53
|
+
- **`collisionend`**: the exit half of the trigger lifecycle, including when the partner is removed, hidden or re-tagged mid-contact.
|
|
52
54
|
|
|
53
55
|
## Checking the current state
|
|
54
56
|
|
|
55
|
-
This file describes 0.3.0. Before relying on any "planned" item being still absent — or on a workaround still being necessary — read `TODO.md` and `README.md` at https://github.com/m1ga/ti.game, the module's upstream repo. `README.md` there is the canonical API documentation; the `documentation/` folder only points at it. The priorities quoted above are the maintainer's, not a roadmap this skill decides.
|
|
57
|
+
This file describes upstream `main` on 2026-08-18 — a moving target: four items on this list shipped within two days of the skill being written, and the manifest version (`0.3.0`) did not change with them. Before relying on any "planned" item being still absent — or on a workaround still being necessary — read `TODO.md` and `README.md` at https://github.com/m1ga/ti.game, the module's upstream repo. `README.md` there is the canonical API documentation; the `documentation/` folder only points at it. The priorities quoted above are the maintainer's, not a roadmap this skill decides.
|