@maccesar/titools 4.15.0 → 4.16.1
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 +10 -1
- package/package.json +1 -1
- package/skills/ti-game/SKILL.md +90 -197
- package/skills/ti-game/references/api.md +52 -15
- package/skills/ti-game/references/debugging-performance.md +102 -0
- package/skills/ti-game/references/examples.md +53 -0
- package/skills/ti-game/references/patterns.md +699 -0
- package/skills/ti-game/references/project-setup.md +19 -6
- package/skills/ti-game/references/recipes-action.md +653 -0
- package/skills/ti-game/references/recipes-adventure-effects.md +340 -0
- package/skills/ti-game/references/roadmap.md +45 -65
- package/skills/ti-game/references/tilemaps.md +127 -0
- package/skills/ti-game/references/recipes.md +0 -1679
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,
|
|
180
|
+
| ti-game | 2D games with the `ti.game` module | Sprites, physics, tile layers, wall jumps, pathfinding, text/performance 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 |
|
|
@@ -436,6 +436,7 @@ When it activates:
|
|
|
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
438
|
- Collision groups, invisible trigger zones, enter/exit events, swept AABB for fast bullets, per-axis hitbox tuning with the `debug` overlays
|
|
439
|
+
- Native side-wall state, `wallhit` transitions and `wallSlideSpeed` for wall jumps/slides without a JS frame loop
|
|
439
440
|
- Raycasts for line of sight, ledge probes and hitscan shots
|
|
440
441
|
- A* pathfinding (`findPath`) around tagged obstacles, feeding native patrol paths (`followPath`) and animation chaining (`play(name, { then })`)
|
|
441
442
|
- Bitmap-font text sprites with native word wrap, and screen-fixed HUDs inside the scene
|
|
@@ -443,6 +444,8 @@ When it activates:
|
|
|
443
444
|
- Named anchors and percentage strings (`anchor: 'bottom'`, `hitboxScaleY: '55%'`) wherever the engine takes a ratio
|
|
444
445
|
- Particle emitters, Verlet ropes, camera follow/zoom/shake/effects, `scrollFactor` parallax, low-latency sound
|
|
445
446
|
- Game-clock timers that obey pause and slow motion, texture `unload()` for level streaming, and what LiveView reloads clean up on their own
|
|
447
|
+
- Native `TileLayer` maps with visible-cell rendering, Tiled GIDs, solid/one-way cells, live edits and A* integration
|
|
448
|
+
- Collision-shape overlays plus the expandable performance HUD and opt-in `performance` telemetry event
|
|
446
449
|
- Drag & drop, pinch, rotate, multitouch controls
|
|
447
450
|
|
|
448
451
|
Example prompts:
|
|
@@ -455,15 +458,21 @@ Example prompts:
|
|
|
455
458
|
"Add a score HUD that stays put while the camera follows the player."
|
|
456
459
|
"Make the guard walk a patrol route and turn around before the ledge."
|
|
457
460
|
"Tap to walk, but the player should go around the tree instead of through it."
|
|
461
|
+
"Render a 200x200 Tiled map without creating one sprite per tile."
|
|
462
|
+
"Show FPS, p95 frame time, draw calls and texture switches while I tune this scene."
|
|
463
|
+
"Add wall jumps and a slow wall slide without polling collisions."
|
|
458
464
|
```
|
|
459
465
|
|
|
460
466
|
Key rules:
|
|
461
467
|
- JS describes the scene and reacts to events; the engine runs every frame. Never move a sprite from a timer — use velocity, gravity, a tween, `carMode` or `thrust`
|
|
462
468
|
- Build the level inside a guarded `resize` handler, never from `Ti.Platform.displayCaps`
|
|
463
469
|
- `solidWith` blocks, `collidesWith` reports — they are independent
|
|
470
|
+
- Gate wall jumps on `onWallLeft` / `onWallRight`; reserve `wallhit` for transition effects
|
|
464
471
|
- A sprite with size but no sheet is an invisible trigger (scores, goals, walls)
|
|
465
472
|
- Add a whole level with one `gameView.add([...])` call
|
|
473
|
+
- Use `Game.createTileLayer()` for large/static grids; keep sprites for interactive cells and actors
|
|
466
474
|
- HUDs are `Game.createText` sprites with `screenFixed: true`, not `Ti.UI.Label` overlays
|
|
475
|
+
- `debug: true` means collision shapes; use `debug: { hud: true }` or `performance` for telemetry
|
|
467
476
|
- Timers that drive game logic belong on the game clock (`gameView.every`), so a paused game stops spawning
|
|
468
477
|
|
|
469
478
|
---
|
package/package.json
CHANGED
package/skills/ti-game/SKILL.md
CHANGED
|
@@ -1,56 +1,61 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ti-game
|
|
3
|
-
description: "Use when
|
|
4
|
-
argument-hint: "[genre or feature]"
|
|
3
|
+
description: "Use when a Titanium app declares ti.game in tiapp.xml, imports require('ti.game'), or needs a 2D game built or reviewed with that native engine: sprites, native movement, collisions, tile layers, camera, HUD, audio, particles, or touch. Covers Alloy and Classic on Android/iOS. Do not use for unrelated game engines or ordinary Titanium UI."
|
|
5
4
|
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(node *)
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
# ti.game — 2D
|
|
9
|
-
|
|
10
|
-
`ti.game` is
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
7
|
+
# ti.game — native 2D games for Titanium SDK
|
|
8
|
+
|
|
9
|
+
`ti.game` is Michael Gangolf's OpenGL ES 2.0 sprite engine for Titanium. JavaScript describes the scene and reacts to discrete events; Android and iOS run rendering, animation, physics, collision, gestures, audio, particles, ropes, tile layers, camera work, and telemetry natively.
|
|
10
|
+
|
|
11
|
+
This skill is verified against upstream `main` at **`c216e7f` (2026-08-28)**. Both manifests still say **`0.5.0`**, although important features landed after that version was introduced. Treat the commit or build date as the capability marker; feature-detect an installed build when the exact artifact is unknown.
|
|
12
|
+
|
|
13
|
+
The skill applies to **Classic and Alloy** projects. Nothing here requires PurgeTSS.
|
|
14
|
+
|
|
15
|
+
## Required workflow
|
|
16
|
+
|
|
17
|
+
Open the references relevant to the task before proposing code. Do not answer from familiarity with Phaser, Godot, Box2D, or an older `ti.game` build.
|
|
18
|
+
|
|
19
|
+
| Task | Read first |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Exact methods, properties, defaults, event payloads, percentages | [references/api.md](references/api.md) |
|
|
22
|
+
| Install, Classic/Alloy structure, assets, cleanup, lifecycle | [references/project-setup.md](references/project-setup.md) |
|
|
23
|
+
| Choose a runnable upstream example | [references/examples.md](references/examples.md) |
|
|
24
|
+
| Reusable level, pooling, HUD, collision, path, timer patterns | [references/patterns.md](references/patterns.md) |
|
|
25
|
+
| Platformer, racer, shooter, runner, rhythm, ball-game recipes | [references/recipes-action.md](references/recipes-action.md) |
|
|
26
|
+
| Cards, point-and-click, particles, ropes, camera, effects | [references/recipes-adventure-effects.md](references/recipes-adventure-effects.md) |
|
|
27
|
+
| Large maps, Tiled data, tile collision, live edits, pathfinding | [references/tilemaps.md](references/tilemaps.md) |
|
|
28
|
+
| Hitbox overlays, performance HUD, telemetry, tuning | [references/debugging-performance.md](references/debugging-performance.md) |
|
|
29
|
+
| Missing, partial, and deliberately out-of-scope features | [references/roadmap.md](references/roadmap.md) |
|
|
30
|
+
|
|
31
|
+
In the response, cite factual guidance with `[source: references/<file>.md]`. If a relevant reference cannot be consulted, prefix the claim with `FROM_MEMORY (unverified):` instead of silently filling the gap.
|
|
32
|
+
|
|
33
|
+
The module's demos are integration evidence, not production templates. Preserve the API combination they prove, but remove launcher plumbing, repeated navigation controls, and avoidable global state when adapting them.
|
|
34
|
+
|
|
35
|
+
## The engine rule
|
|
36
|
+
|
|
37
|
+
**JS describes and reacts. The engine runs every frame.**
|
|
38
|
+
|
|
39
|
+
Do not move sprites from `setInterval`, `requestAnimationFrame`, or a JS loop. Property writes cross the Kroll bridge; native properties and methods keep frame-by-frame work on the render thread. Coarse timers are appropriate for decisions such as enemy targeting or spawn cadence, preferably through `gameView.after()` / `every()` when they must obey pause and `timeScale`.
|
|
40
|
+
|
|
41
|
+
| Intent | Native mechanism |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Fall or move continuously | `gravity`, `gravityX`, `velocityX`, `velocityY` |
|
|
44
|
+
| Move to a point | `animate()` or `followPath()` |
|
|
45
|
+
| Drive or fly | `carMode` + `throttle`/`steering`, or `thrust` + `angularVelocity` |
|
|
46
|
+
| Drag, pinch, rotate | `draggable`, `pinchable`, `rotatable` |
|
|
47
|
+
| Follow the player | `gameView.follow()` |
|
|
48
|
+
| Walk around obstacles | `findPath()` → `followPath()` |
|
|
49
|
+
| Test line of sight or hitscan | `raycast()` |
|
|
50
|
+
| Fast projectile collision | `swept: true` on the mover |
|
|
51
|
+
| Detect a side wall or slide down it | `onWallLeft` / `onWallRight`, `wallhit`, `wallSlideSpeed` |
|
|
52
|
+
| Repeat a large map efficiently | `Game.createTileLayer()` |
|
|
53
|
+
| Attach a label, health bar, or turret | `attachTo()` |
|
|
54
|
+
| Score or in-game label | `Game.createText()`; usually `screenFixed: true` for a HUD |
|
|
55
|
+
| Smoke, sparks, explosions | `Game.createEmitter()` |
|
|
56
|
+
| Chain, tether, cape | `Game.createRope()` |
|
|
57
|
+
| Pause or slow the scene while still drawing | `gameView.timeScale` |
|
|
58
|
+
| Measure FPS, frame time, and draw calls | `debug: { hud: true }` or the `performance` event |
|
|
54
59
|
|
|
55
60
|
## Minimal scene
|
|
56
61
|
|
|
@@ -59,34 +64,31 @@ const Game = require('ti.game');
|
|
|
59
64
|
|
|
60
65
|
const win = Ti.UI.createWindow({
|
|
61
66
|
backgroundColor: '#000',
|
|
62
|
-
navBarHidden: true,
|
|
63
|
-
theme: 'Theme.Titanium.DayNight.NoTitleBar'
|
|
67
|
+
navBarHidden: true,
|
|
68
|
+
theme: 'Theme.Titanium.DayNight.NoTitleBar'
|
|
69
|
+
});
|
|
70
|
+
const gameView = Game.createGameView({
|
|
71
|
+
backgroundColor: '#8ed8f8',
|
|
72
|
+
maxFps: 60
|
|
64
73
|
});
|
|
65
|
-
const gameView = Game.createGameView({ backgroundColor: '#8ed8f8' });
|
|
66
|
-
|
|
67
74
|
const sheet = Game.createSpriteSheet({
|
|
68
75
|
image: 'assets/hero.png',
|
|
69
76
|
frameWidth: 32,
|
|
70
77
|
frameHeight: 48,
|
|
71
|
-
smoothing: false
|
|
78
|
+
smoothing: false
|
|
72
79
|
});
|
|
73
80
|
|
|
74
81
|
let initialized = false;
|
|
75
|
-
gameView.addEventListener('resize', (
|
|
82
|
+
gameView.addEventListener('resize', ({ width, height }) => {
|
|
76
83
|
if (initialized) {
|
|
77
84
|
return;
|
|
78
85
|
}
|
|
79
86
|
initialized = true;
|
|
80
|
-
buildLevel(e.width, e.height);
|
|
81
|
-
});
|
|
82
87
|
|
|
83
|
-
function buildLevel(W, H) {
|
|
84
88
|
const hero = Game.createSprite({
|
|
85
|
-
sheet
|
|
86
|
-
x:
|
|
87
|
-
y:
|
|
88
|
-
width: 96,
|
|
89
|
-
height: 144,
|
|
89
|
+
sheet,
|
|
90
|
+
x: width / 2,
|
|
91
|
+
y: height / 2,
|
|
90
92
|
animations: {
|
|
91
93
|
idle: { frames: [0], fps: 1, loop: true },
|
|
92
94
|
walk: { frames: [1, 2], fps: 6, loop: true }
|
|
@@ -94,150 +96,41 @@ function buildLevel(W, H) {
|
|
|
94
96
|
});
|
|
95
97
|
hero.play('idle');
|
|
96
98
|
gameView.add(hero);
|
|
97
|
-
}
|
|
99
|
+
});
|
|
98
100
|
|
|
99
101
|
win.add(gameView);
|
|
100
102
|
win.open();
|
|
101
103
|
```
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`Ti.Platform.displayCaps` includes the system bars and reports **points** on iOS, while the scene lives in surface **pixels**. Sizing a level from it puts bottom-anchored sprites below the visible area. The GameView fires `resize` with the real surface size — that is the coordinate space. The guard flag matters: `resize` fires again on rotation and on some lifecycle transitions, and rebuilding the level there would duplicate every sprite.
|
|
106
|
-
|
|
107
|
-
When you genuinely need dp→scene units (to keep a floor above 80dp on-screen buttons), measure the surface rather than trusting the density factor, because the iOS Simulator renders at 1x while still reporting the device scale:
|
|
108
|
-
|
|
109
|
-
```javascript
|
|
110
|
-
const density = Ti.Platform.osname === 'android'
|
|
111
|
-
? Ti.Platform.displayCaps.logicalDensityFactor
|
|
112
|
-
: H / Ti.Platform.displayCaps.platformHeight;
|
|
113
|
-
const buttonZone = Math.round(130 * density);
|
|
114
|
-
const groundTop = H - buttonZone;
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Coordinates
|
|
118
|
-
|
|
119
|
-
Top-left origin, y-down, surface pixels. Touch coordinates map 1:1. `(x, y)` is the sprite's **anchor** (default `0.5/0.5` = center, so `y` is the middle of the sprite, not its top). Rotation in degrees, positive clockwise. All JS durations are milliseconds.
|
|
120
|
-
|
|
121
|
-
## Pick a movement model
|
|
122
|
-
|
|
123
|
-
| Model | Properties | Fits |
|
|
124
|
-
| --- | --- | --- |
|
|
125
|
-
| Plain velocity | `velocityX/Y` (px/s), `gravity` (px/s²) | Flappy, projectiles, falling objects, notes |
|
|
126
|
-
| Platformer | velocity + `solidWith`, read-only `onGround`, `land` event | Mario-style run & jump |
|
|
127
|
-
| Bouncing body | `restitution` (0..1) on **either** side of the contact, on top of `solidWith` | Balls, pinball, breakout |
|
|
128
|
-
| Rolling body | `hitboxShape: 'circle'` + `linearDamping`, and `solidMode: 'push'` when they hit each other | Pool, plinko, a bingo drum |
|
|
129
|
-
| Car | `carMode: true`, `throttle`, `steering` (-1..1) | Top-down racer with emergent drift |
|
|
130
|
-
| Newtonian flight | `thrust` (px/s² along heading), `angularVelocity` (deg/s), `maxSpeed` | Asteroids, lunar lander |
|
|
131
|
-
| Tween | `animate({ x, y, duration, easing })` | Point-and-click walking, UI cards, patrols |
|
|
132
|
-
|
|
133
|
-
Gravity is per sprite, not global — set it on each body that should fall. A sprite with no `gravity` and no velocity simply stays put.
|
|
134
|
-
|
|
135
|
-
## Block versus react: `solidWith` and `collidesWith` are independent
|
|
136
|
-
|
|
137
|
-
- `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`.
|
|
138
|
-
- `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.
|
|
139
|
-
|
|
140
|
-
A sprite can use both — a player that stands on platforms (`solidWith`) and dies on spikes (`collidesWith`).
|
|
141
|
-
|
|
142
|
-
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.
|
|
143
|
-
|
|
144
|
-
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.
|
|
145
|
-
|
|
146
|
-
When the question is not "did we touch?" but "what is over there?", the answer is `gameView.raycast(x0, y0, x1, y1, ['group'])` — line of sight, a ledge probe ahead of a walker's feet, a hitscan shot. It returns `null` or the nearest hit (`{ x, y, distance, group, sprite, normal }`), needs nothing but a `collisionGroup` on the targets, and replaces the old trick of parking an invisible probe sprite where the ray would land. It is a discrete query: call it from a decision timer or a tap handler, never every frame.
|
|
147
|
-
|
|
148
|
-
And when the question is "how do I get over there?", `gameView.findPath(from, to, { cellSize, groups, clearance, bounds })` runs grid A* around the same tagged sprites and hands back waypoints for `followPath` — tap-to-walk that rounds a tree, a hound re-routing to the player on an 800 ms timer. Also discrete: the walk itself runs natively between queries, so pathfind on taps and AI ticks, never per frame. Pass `clearance` of about half the walker's width, since the route is a line for the sprite's **center**.
|
|
149
|
-
|
|
150
|
-
**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.
|
|
151
|
-
|
|
152
|
-
Tune fairness with `hitboxScale` (art rarely fills its frame; slightly small hitboxes feel better), `hitboxScaleX`/`hitboxScaleY` when the drawing fills its frame by a different fraction on each axis (a 20×44 hero in a 32×48 frame wants `0.62` wide and `0.92` tall — the two multiply `hitboxScale` rather than replace it), 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. Since 2026-08-27 the **solid's** shape counts too: a `'circle'` on the post makes the normal run centre to centre, and `'rotatedRect'` keeps a tilted platform's box turned with its art instead of re-boxing it square to the screen — which is what lets a crate slide down a ramp rather than stand on an invisible ledge. Two more properties live on the solid: `solidMode: 'contain'` holds matched circles *inside* a circular boundary with no ring of wall sprites, and `solidMode: 'push'` makes a pair of circles bodies that split the separation and trade momentum. `'push'` needs both sides to agree — both circles, both `'push'`, and each listing the other's `collisionGroup` — or it silently degrades to one ball shoving an immovable one. 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.
|
|
153
|
-
|
|
154
|
-
The hitbox shrinks **around the anchor**, which is why the anchor is half of that tuning and why both now have readable forms: `anchor: 'bottom'` with `hitboxScaleY: '55%'` is the same sprite as `anchorY: 1` with `hitboxScaleY: 0.55`, and says what it is doing. Every ratio the engine exposes takes a percentage string, and the anchors take names (`'left'`, `'bottom-right'`, `'center'`); numbers keep working untouched. [api.md](references/api.md) lists exactly which properties are ratios — coordinates, sizes, degrees and speeds are not, and neither is the car model's `grip`/`drag`.
|
|
155
|
-
|
|
156
|
-
## Performance rules that actually matter
|
|
157
|
-
|
|
158
|
-
- **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.
|
|
159
|
-
- **Know what else breaks the batch**, since none of it is visible on screen: every blend-mode transition in draw order flushes — there are four (`'normal'`, `'add'`, `'multiply'`, `'screen'`), so group same-blend sprites into their own `zIndex` band and the frame switches a handful of times instead of 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 non-normal content flushes too.
|
|
160
|
-
- **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.
|
|
161
|
-
- **Keep collision group lists targeted.** Cost is O(colliders × candidates) per frame. A bullet checking `['asteroid']` tests 5 sprites, not the whole scene.
|
|
162
|
-
- **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.
|
|
163
|
-
- **Read properties in handlers, not in loops.** `ball.x` is a synchronous snapshot of the live native value — perfect inside a `collision` handler or an 80 ms AI tick, wasteful in a tight loop.
|
|
164
|
-
- **`maxFps: 60`** on the GameView keeps 120 Hz ProMotion displays from doubling render work. `0` (default) follows the display refresh rate.
|
|
165
|
-
- **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.
|
|
166
|
-
|
|
167
|
-
## Text is a sprite
|
|
168
|
-
|
|
169
|
-
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.
|
|
170
|
-
|
|
171
|
-
```javascript
|
|
172
|
-
const score = Game.createText({ text: 'SCORE 0', screenFixed: true, x: W / 2, y: H * 0.08, scale: 3, zIndex: 100 });
|
|
173
|
-
gameView.add(score);
|
|
174
|
-
score.text = 'SCORE 10'; // re-lays out natively
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
One long string wraps itself: `maxWidth` breaks lines on word boundaries and re-wraps every time `text` changes, so a dialog box needs no hand-broken `\n`. It is measured in font-space px, *before* `scale` — divide the screen width you want by the scale you are drawing at.
|
|
178
|
-
|
|
179
|
-
`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).
|
|
180
|
-
|
|
181
|
-
A label that belongs *on* a moving sprite is neither: `tag.attachTo(hero, { offsetY: -40 })` pins it natively, resolved every frame after physics and before collisions, so it never lags by a frame and needs no JS. It works on any sprite, not just text — health bars, shadows, an invisible hitbox on a body, and with `rotate: true` a turret whose offset swings with the tank. Removing the owner removes what is attached to it, and fading the owner fades them with it: position, rotation and opacity are inherited. Scale, visibility and tint are not — set those per sprite.
|
|
182
|
-
|
|
183
|
-
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.
|
|
184
|
-
|
|
185
|
-
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`.
|
|
186
|
-
|
|
187
|
-
## Clean up what you created
|
|
105
|
+
## Non-negotiable invariants
|
|
188
106
|
|
|
189
|
-
|
|
107
|
+
- Build surface-dependent content from the GameView's guarded `resize` event. `Ti.Platform.displayCaps` is not the scene surface and can include system bars or different units.
|
|
108
|
+
- Coordinates use a top-left origin, y increases downward, and `(x, y)` is the sprite anchor. Durations crossing the JS boundary are milliseconds.
|
|
109
|
+
- `solidWith` blocks; `collidesWith` reports `collision`/`collisionend`. They are independent. A sized sprite without a sheet is a valid invisible solid or trigger.
|
|
110
|
+
- Put the hitbox shape on the object whose geometry matters. A round peg needs `hitboxShape: 'circle'`; a ramp needs `'rotatedRect'`.
|
|
111
|
+
- Gate wall jumps on `onWallLeft` / `onWallRight` inside the jump input handler. `wallhit` is the transition event for effects; `wallSlideSpeed` caps the downward speed natively.
|
|
112
|
+
- A `TileLayer` replaces one-sprite-per-tile maps when the map is large. Its visible-cell renderer, cell collision, live edits, and pathfinding integration are native.
|
|
113
|
+
- Collect initial sprites, emitters, ropes, and tile layers and add them in one `gameView.add([...])` call.
|
|
114
|
+
- Use `Game.createText()` for scene text. Use Titanium views only for UI the engine cannot draw, such as native input controls or system-font interfaces.
|
|
115
|
+
- Stop app-owned JS timers, looping sounds, and listeners on close/controller cleanup. `removeAllSprites()` clears Sprite/Text objects only; explicitly remove tracked emitters, ropes, and tile layers. `SpriteSheet.unload()` and `Font.unload()` are permanent level-streaming operations, not cache eviction.
|
|
116
|
+
- Check [references/roadmap.md](references/roadmap.md) before naming an API that merely exists in another engine.
|
|
190
117
|
|
|
191
|
-
|
|
118
|
+
## Source priority
|
|
192
119
|
|
|
193
|
-
|
|
120
|
+
When sources disagree, use this order:
|
|
194
121
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
music.stop(); // a looping track outlives the window otherwise
|
|
200
|
-
gameView.pause();
|
|
201
|
-
gameView.removeAllSprites();
|
|
202
|
-
});
|
|
203
|
-
```
|
|
122
|
+
1. Android and iOS native proxy/engine code for actual behavior and parity.
|
|
123
|
+
2. `example/*.js` for combinations exercised by the module, not code-quality conventions.
|
|
124
|
+
3. Upstream `README.md` and `TUTORIAL.md` for intended public usage.
|
|
125
|
+
4. Upstream `TODO.md` for what remains partial or absent.
|
|
204
126
|
|
|
205
|
-
|
|
127
|
+
Known upstream prose drift at the pinned commit: the README says 26 demos although there are 33; its camera row names the wrong HUD corner; and it says every `TileLayer` property is live although Android treats `legend`, `firstGid`, `cols`, and `rows` as creation-time configuration. The references already normalize those differences. No demo exercises the wall-contact API yet, so its contract comes from the README and both native implementations.
|
|
206
128
|
|
|
207
|
-
|
|
129
|
+
## Before returning code
|
|
208
130
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
When this skill runs out — a brand-new feature, a demo's exact code, a build question — read upstream itself at https://github.com/m1ga/ti.game. Each file there is authoritative for something different:
|
|
217
|
-
|
|
218
|
-
| Upstream file | What it is authoritative for |
|
|
219
|
-
| --- | --- |
|
|
220
|
-
| `README.md` | The module's own API documentation, and the demo index. Canonical, but it has lagged the code: verify a defaulted value against the source before relying on it |
|
|
221
|
-
| `example/` | 32 runnable demos, one per feature area — the reference implementations this skill's recipes are distilled from |
|
|
222
|
-
| `TODO.md` | What the maintainer has planned, with priorities. The source of [roadmap.md](references/roadmap.md) |
|
|
223
|
-
| `tutorial.md` | A step-by-step first scene (sprite, animation, tap-to-move). For a human learning the module, not for looking things up |
|
|
224
|
-
| `ios/README.md` | The iOS port's threading, coordinates and touch constants. Mostly for module developers; the app-facing facts are already here |
|
|
225
|
-
| `AGENTS.md` | Rules for changing the module itself — irrelevant to building a game with it |
|
|
226
|
-
| `android/src/ti/game/`, `ios/Classes/` | The truth. Defaults, clamping and edge cases in this skill were read here, not from prose |
|
|
227
|
-
|
|
228
|
-
## Before writing game code, check these
|
|
229
|
-
|
|
230
|
-
1. Is the level built inside a guarded `resize` handler, using `e.width`/`e.height`?
|
|
231
|
-
2. Does any timer move a sprite? Replace it with velocity, gravity, a tween, `carMode` or `thrust`.
|
|
232
|
-
3. Are all the level's objects added in one `gameView.add([...])` call?
|
|
233
|
-
4. Does every moving body have the right model — `solidWith` to be blocked, `collidesWith` to be notified?
|
|
234
|
-
5. Is `hitboxScale` set on sprites whose art does not fill the frame — with `hitboxScaleX`/`hitboxScaleY` when the fit differs per axis, and the `anchor` the box shrinks around named rather than left as a bare `1`?
|
|
235
|
-
6. Do triggers (scores, goals, kill zones) use sheet-less sprites instead of visible art?
|
|
236
|
-
7. Is every `createSound`, `setInterval` and `setTimeout` stopped when the window closes?
|
|
237
|
-
8. Is the HUD `Game.createText` with `screenFixed: true`, rather than a stack of overlaid labels?
|
|
238
|
-
9. Does anything travel far enough per frame to tunnel? Give it `swept: true`.
|
|
239
|
-
10. Does a timer drive game logic (spawn waves, AI ticks, respawns)? Move it to `gameView.every`/`after` so it obeys pause and slow motion.
|
|
240
|
-
11. Does anything ride on top of a moving sprite — a name tag, a health bar, a turret, a hitbox? `attachTo` pins it natively; a timer copying coordinates is the old workaround.
|
|
241
|
-
12. Is a solid's own **shape** doing the work, or is JS correcting for a box that is not what the art shows? A round post wants `hitboxShape: 'circle'` on itself; a tilted one wants `'rotatedRect'`.
|
|
242
|
-
13. Did anything get `restitution` on a *surface*? Every rider on it now bounces, and a hard landing on it stops setting `onGround` — check whatever gates jumps.
|
|
243
|
-
14. Are you using an API that actually exists? Anything involving tilemaps, full parent/child transforms, joysticks, gamepads, or the platformer *feel* on a slope (step-up, friction along the contact) — check [roadmap.md](references/roadmap.md) first.
|
|
131
|
+
- Confirm the installed module build exposes every API used; the manifest version alone is insufficient.
|
|
132
|
+
- Keep per-frame work native and JS event-driven.
|
|
133
|
+
- Prefer a demo-backed combination, then refactor it into focused, maintainable code.
|
|
134
|
+
- Preserve Android/iOS parity; do not rely on an Android-only permissive argument shape.
|
|
135
|
+
- State partial engine behavior honestly, especially slopes, tile triggers/raycast, parenting, audio polish, and input devices.
|
|
136
|
+
- Cite the references that support the result.
|