feat(engine): Tiled P3 — animated tiles (deterministic frame clock) + tile objects (#71)
- Animated tiles: a tileset <animation>'s {tileid, duration} frames advance as a
pure function of the fixed 60/s engine frame counter (the same clock SpriteAnim
rides), so an animated GID resolves at draw to the current frame's GID with its
flip flags preserved — deterministic, frame-identical across runs, ticks for
free. Tiled.frame_gid(map, gid, frame) / Tiled.animated(map, gid) expose it;
Tiled.draw_anim(map, camx, camy, frame) draws a map with animations advanced.
- Tile objects: object-layer entries with a `gid` draw the tile image (with their
own flip flags), bottom-anchored at the object position, as placeable sprites;
tmap_draw_full now renders object layers alongside tile layers.
Proven by library/tiled_p3.ludic (14 assertions): frame advance at authored
durations + wrap, flip flags riding an animation swap, tile-object parse + draw +
flip mirroring, and the design's named rpg/beach_tileset.tsx (33 <animation>
blocks / 131 frames). x test: 94 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0cb57774d7
commit
58e1105f2d
13 changed files with 6129 additions and 5312 deletions
16
assets/tiled-fixtures/anim_map.tmx
Normal file
16
assets/tiled-fixtures/anim_map.tmx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Hand-authored P3 fixture: animated tile + tile object (issue #71). CC0. -->
|
||||
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="4" height="4" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="2">
|
||||
<tileset firstgid="1" source="anim_tiles.tsx"/>
|
||||
<layer id="1" name="bg" width="4" height="4">
|
||||
<data encoding="csv">
|
||||
1,0,0,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="2" name="objects">
|
||||
<object id="1" gid="53" x="16" y="32" width="16" height="16"/>
|
||||
</objectgroup>
|
||||
</map>
|
||||
12
assets/tiled-fixtures/anim_tiles.tsx
Normal file
12
assets/tiled-fixtures/anim_tiles.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Hand-authored animated tileset over the Kenney atlas (issue #71). CC0. -->
|
||||
<tileset version="1.10" tiledversion="1.10.2" name="anim" tilewidth="16" tileheight="16" spacing="1" tilecount="132" columns="12">
|
||||
<image source="../kenney/tiny-dungeon/Tilemap/tilemap.png" width="203" height="186"/>
|
||||
<tile id="0">
|
||||
<animation>
|
||||
<frame tileid="0" duration="100"/>
|
||||
<frame tileid="40" duration="100"/>
|
||||
<frame tileid="80" duration="100"/>
|
||||
</animation>
|
||||
</tile>
|
||||
</tileset>
|
||||
267
assets/tiled-fixtures/beach_tileset.tsx
Normal file
267
assets/tiled-fixtures/beach_tileset.tsx
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.8" tiledversion="1.8.2" name="beach_tileset" tilewidth="16" tileheight="16" tilecount="936" columns="36">
|
||||
<image source="beach_tileset.png" width="576" height="416"/>
|
||||
<tile id="37">
|
||||
<animation>
|
||||
<frame tileid="37" duration="250"/>
|
||||
<frame tileid="46" duration="250"/>
|
||||
<frame tileid="55" duration="250"/>
|
||||
<frame tileid="64" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="38">
|
||||
<animation>
|
||||
<frame tileid="38" duration="250"/>
|
||||
<frame tileid="47" duration="250"/>
|
||||
<frame tileid="56" duration="250"/>
|
||||
<frame tileid="65" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="39">
|
||||
<animation>
|
||||
<frame tileid="39" duration="250"/>
|
||||
<frame tileid="48" duration="250"/>
|
||||
<frame tileid="57" duration="250"/>
|
||||
<frame tileid="66" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="41">
|
||||
<animation>
|
||||
<frame tileid="41" duration="250"/>
|
||||
<frame tileid="50" duration="250"/>
|
||||
<frame tileid="59" duration="250"/>
|
||||
<frame tileid="68" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="42">
|
||||
<animation>
|
||||
<frame tileid="42" duration="250"/>
|
||||
<frame tileid="51" duration="250"/>
|
||||
<frame tileid="60" duration="250"/>
|
||||
<frame tileid="69" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="43">
|
||||
<animation>
|
||||
<frame tileid="43" duration="250"/>
|
||||
<frame tileid="52" duration="250"/>
|
||||
<frame tileid="61" duration="250"/>
|
||||
<frame tileid="70" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="73">
|
||||
<animation>
|
||||
<frame tileid="73" duration="250"/>
|
||||
<frame tileid="82" duration="250"/>
|
||||
<frame tileid="91" duration="250"/>
|
||||
<frame tileid="100" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="75">
|
||||
<animation>
|
||||
<frame tileid="75" duration="250"/>
|
||||
<frame tileid="84" duration="250"/>
|
||||
<frame tileid="93" duration="250"/>
|
||||
<frame tileid="102" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="76">
|
||||
<animation>
|
||||
<frame tileid="76" duration="250"/>
|
||||
<frame tileid="85" duration="250"/>
|
||||
<frame tileid="94" duration="250"/>
|
||||
<frame tileid="103" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="77">
|
||||
<animation>
|
||||
<frame tileid="77" duration="250"/>
|
||||
<frame tileid="86" duration="250"/>
|
||||
<frame tileid="95" duration="250"/>
|
||||
<frame tileid="104" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="79">
|
||||
<animation>
|
||||
<frame tileid="79" duration="250"/>
|
||||
<frame tileid="88" duration="250"/>
|
||||
<frame tileid="97" duration="250"/>
|
||||
<frame tileid="106" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="109">
|
||||
<animation>
|
||||
<frame tileid="109" duration="250"/>
|
||||
<frame tileid="118" duration="250"/>
|
||||
<frame tileid="127" duration="250"/>
|
||||
<frame tileid="136" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="110">
|
||||
<animation>
|
||||
<frame tileid="110" duration="250"/>
|
||||
<frame tileid="119" duration="250"/>
|
||||
<frame tileid="128" duration="250"/>
|
||||
<frame tileid="137" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="114">
|
||||
<animation>
|
||||
<frame tileid="114" duration="250"/>
|
||||
<frame tileid="123" duration="250"/>
|
||||
<frame tileid="132" duration="250"/>
|
||||
<frame tileid="141" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="115">
|
||||
<animation>
|
||||
<frame tileid="115" duration="250"/>
|
||||
<frame tileid="124" duration="250"/>
|
||||
<frame tileid="133" duration="250"/>
|
||||
<frame tileid="142" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="146">
|
||||
<animation>
|
||||
<frame tileid="146" duration="250"/>
|
||||
<frame tileid="155" duration="250"/>
|
||||
<frame tileid="164" duration="250"/>
|
||||
<frame tileid="173" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="148">
|
||||
<animation>
|
||||
<frame tileid="148" duration="250"/>
|
||||
<frame tileid="157" duration="250"/>
|
||||
<frame tileid="166" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="150">
|
||||
<animation>
|
||||
<frame tileid="150" duration="250"/>
|
||||
<frame tileid="159" duration="250"/>
|
||||
<frame tileid="168" duration="250"/>
|
||||
<frame tileid="177" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="181">
|
||||
<animation>
|
||||
<frame tileid="181" duration="250"/>
|
||||
<frame tileid="190" duration="250"/>
|
||||
<frame tileid="199" duration="250"/>
|
||||
<frame tileid="208" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="182">
|
||||
<animation>
|
||||
<frame tileid="182" duration="250"/>
|
||||
<frame tileid="191" duration="250"/>
|
||||
<frame tileid="200" duration="250"/>
|
||||
<frame tileid="209" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="186">
|
||||
<animation>
|
||||
<frame tileid="186" duration="250"/>
|
||||
<frame tileid="195" duration="250"/>
|
||||
<frame tileid="204" duration="250"/>
|
||||
<frame tileid="213" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="187">
|
||||
<animation>
|
||||
<frame tileid="187" duration="250"/>
|
||||
<frame tileid="196" duration="250"/>
|
||||
<frame tileid="205" duration="250"/>
|
||||
<frame tileid="214" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="217">
|
||||
<animation>
|
||||
<frame tileid="217" duration="250"/>
|
||||
<frame tileid="226" duration="250"/>
|
||||
<frame tileid="235" duration="250"/>
|
||||
<frame tileid="244" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="219">
|
||||
<animation>
|
||||
<frame tileid="219" duration="250"/>
|
||||
<frame tileid="228" duration="250"/>
|
||||
<frame tileid="237" duration="250"/>
|
||||
<frame tileid="246" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="220">
|
||||
<animation>
|
||||
<frame tileid="220" duration="250"/>
|
||||
<frame tileid="229" duration="250"/>
|
||||
<frame tileid="238" duration="250"/>
|
||||
<frame tileid="247" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="221">
|
||||
<animation>
|
||||
<frame tileid="221" duration="250"/>
|
||||
<frame tileid="230" duration="250"/>
|
||||
<frame tileid="239" duration="250"/>
|
||||
<frame tileid="248" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="223">
|
||||
<animation>
|
||||
<frame tileid="223" duration="250"/>
|
||||
<frame tileid="232" duration="250"/>
|
||||
<frame tileid="241" duration="250"/>
|
||||
<frame tileid="250" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="253">
|
||||
<animation>
|
||||
<frame tileid="253" duration="250"/>
|
||||
<frame tileid="262" duration="250"/>
|
||||
<frame tileid="271" duration="250"/>
|
||||
<frame tileid="280" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="254">
|
||||
<animation>
|
||||
<frame tileid="254" duration="250"/>
|
||||
<frame tileid="263" duration="250"/>
|
||||
<frame tileid="272" duration="250"/>
|
||||
<frame tileid="281" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="255">
|
||||
<animation>
|
||||
<frame tileid="255" duration="250"/>
|
||||
<frame tileid="264" duration="250"/>
|
||||
<frame tileid="273" duration="250"/>
|
||||
<frame tileid="282" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="257">
|
||||
<animation>
|
||||
<frame tileid="257" duration="250"/>
|
||||
<frame tileid="266" duration="250"/>
|
||||
<frame tileid="275" duration="250"/>
|
||||
<frame tileid="284" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="258">
|
||||
<animation>
|
||||
<frame tileid="258" duration="250"/>
|
||||
<frame tileid="267" duration="250"/>
|
||||
<frame tileid="276" duration="250"/>
|
||||
<frame tileid="285" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
<tile id="259">
|
||||
<animation>
|
||||
<frame tileid="259" duration="250"/>
|
||||
<frame tileid="268" duration="250"/>
|
||||
<frame tileid="277" duration="250"/>
|
||||
<frame tileid="286" duration="250"/>
|
||||
</animation>
|
||||
</tile>
|
||||
</tileset>
|
||||
|
|
@ -7,3 +7,4 @@ type: feat
|
|||
- **P0.5 TMX/TSX reader (#68)** — `Tiled.read` / `Tiled.read_tsx` map the native XML formats onto the *same* intermediate the JSON path produces — a `Value` tree in Tiled's JSON schema, with every tile layer's data decoded to a dense GID list (CSV, base64, base64+zlib, base64+gzip). A CSV `.tmx` and a base64+zlib `.tmj` of the same map read structurally identically; the in-repo Kenney `sampleMap.tmx` + external `sampleSheet.tsx` load with no manual JSON re-export.
|
||||
- **P1 core load + render (#69)** — the runtime `rt_tmap` model (heap-allocated to `w·h`, lifting the old `96×64` cap), the GID resolver (`Tiled.resolve` → tileset / local id / H·V·D flips), image-backed rendering (`Tiled.draw`, flips applied at blit), and the legacy-tilemap compatibility projection so `Grid.*`/`Path.*`/`esys_move` keep working. `Tiled.load` reads either format, resolves external tilesets + images, and auto-projects a `collision` layer. The Kenney sample loads and renders pixel-identically from `.tmx` and `.tmj`; the `grid` and `physics_tiles` demos now run off a loaded map.
|
||||
- **P2 collision & grid (#70)** — normalise three collision sources into the byte tilemap `esys_move`/`Grid.*`/`Path.*` read, in the design's priority order: per-tile `<objectgroup>` hitboxes, the `solid`/`oneway`/`trigger` property convention (`Tiled.collision_kind`/`Tiled.tile_shapes`), and the designated collision layer (`Tiled.project`, any non-zero GID solid) — or drive collision from a visual layer's per-tile metadata alone (`Tiled.collide`). The property convention and the collision-layer fallback produce the same feed; `Path.a_star` over a loaded map matches the hand-authored baseline.
|
||||
- **P3 animated tiles + tile objects (#71)** — a tileset `<animation>` advances deterministically as a pure function of the fixed 60/s engine frame clock (`Tiled.frame_gid`/`Tiled.animated`), so an animated GID resolves at draw to the current frame's GID with its flip flags preserved and reproduces frame-for-frame across runs; `Tiled.draw_anim` draws a map with animations advanced. Object-layer entries with a `gid` render the tile image (with their own flips), bottom-anchored, as placeable sprites.
|
||||
|
|
|
|||
14
docs/language/tiled/tiled-animated.md
Normal file
14
docs/language/tiled/tiled-animated.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: tiled-animated
|
||||
name: Tiled.animated
|
||||
category: tiled
|
||||
kind: namespace-method
|
||||
tokens: Tiled.animated
|
||||
sig: Tiled.animated(map, gid: int) -> int
|
||||
tip: Whether a GID's tile is animated.
|
||||
order: 19
|
||||
ns: Tiled
|
||||
member: animated
|
||||
---
|
||||
|
||||
Returns <code>1</code> if the tile a GID resolves to carries a non-empty <code><animation></code>, else <code>0</code>.
|
||||
14
docs/language/tiled/tiled-draw_anim.md
Normal file
14
docs/language/tiled/tiled-draw_anim.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: tiled-draw_anim
|
||||
name: Tiled.draw_anim
|
||||
category: tiled
|
||||
kind: namespace-method
|
||||
tokens: Tiled.draw_anim
|
||||
sig: Tiled.draw_anim(map, camx: int, camy: int, frame: int) -> void
|
||||
tip: Draw with animated tiles advanced to a frame.
|
||||
order: 17
|
||||
ns: Tiled
|
||||
member: draw_anim
|
||||
---
|
||||
|
||||
Like <a href="tiled-draw"><code>Tiled.draw</code></a>, but animated tiles are advanced to the engine <code>frame</code> (pass <a href="time-frame"><code>Time.frame</code></a>) and every tile object on the object layers is drawn too. The animation frame is a pure function of the fixed 60/s clock, so it reproduces frame-for-frame across runs and lockstep replays.
|
||||
14
docs/language/tiled/tiled-frame_gid.md
Normal file
14
docs/language/tiled/tiled-frame_gid.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: tiled-frame_gid
|
||||
name: Tiled.frame_gid
|
||||
category: tiled
|
||||
kind: namespace-method
|
||||
tokens: Tiled.frame_gid
|
||||
sig: Tiled.frame_gid(map, gid: int, frame: int) -> int
|
||||
tip: The current GID of an animated tile at a frame.
|
||||
order: 18
|
||||
ns: Tiled
|
||||
member: frame_gid
|
||||
---
|
||||
|
||||
Returns the GID an (animated) <code>gid</code> resolves to at engine <code>frame</code>: elapsed ms = <code>frame * 1000 / 60</code>, walked across the tileset <code><animation></code>'s frame durations to the current frame's tile — its flip flags preserved. A GID with no animation passes through unchanged.
|
||||
105
examples/library/tiled_p3.ludic
Normal file
105
examples/library/tiled_p3.ludic
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# tiled_p3.ludic — Tiled P3 animated tiles + tile objects (issue #71). A tileset
|
||||
# <animation> maps to a deterministic frame = f(engine frame) (the fixed 60/s
|
||||
# clock SpriteAnim rides), so an animated GID resolves at draw to the current
|
||||
# frame's GID, its flip flags preserved. An object-layer entry with a `gid` draws
|
||||
# the tile image (with its own flips), bottom-anchored, as a placeable sprite.
|
||||
#
|
||||
# A full run prints: 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
program TiledP3 {
|
||||
property Tag { v: int = 0 }
|
||||
model Marker { Tag }
|
||||
|
||||
function fb_hash() -> int {
|
||||
var h = 0
|
||||
var y = 0
|
||||
while y < 64 { var x = 0
|
||||
while x < 64 { h = h * 31 + rt_get_px(x, y); x = x + 2 }
|
||||
y = y + 2 }
|
||||
return h
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
let m = Tiled.load("assets/tiled-fixtures/anim_map.tmx")
|
||||
|
||||
# --- animated tiles: current frame is f(engine frame), 100ms per frame ---
|
||||
if Tiled.animated(m, 1) == 1 { print(1) } # tile 0 carries <animation>
|
||||
if Tiled.animated(m, 2) == 0 { print(2) } # a frame tile, not itself animated
|
||||
let a0 = Tiled.resolve(m, Tiled.frame_gid(m, 1, 0)) # 0 ms -> frame 0 (local 0)
|
||||
if a0.local == 0 { print(3) }
|
||||
let a1 = Tiled.resolve(m, Tiled.frame_gid(m, 1, 6)) # frame 6 = 100 ms -> local 40
|
||||
if a1.local == 40 { print(4) }
|
||||
let a2 = Tiled.resolve(m, Tiled.frame_gid(m, 1, 12)) # 200 ms -> local 80
|
||||
if a2.local == 80 { print(5) }
|
||||
let a3 = Tiled.resolve(m, Tiled.frame_gid(m, 1, 18)) # 300 ms wraps -> local 0
|
||||
if a3.local == 0 { print(6) }
|
||||
# the flip flags ride through an animated frame swap
|
||||
let af = Tiled.resolve(m, Tiled.frame_gid(m, 1 | (1 << 31), 6))
|
||||
if af.local == 40 and af.fh == 1 { print(7) }
|
||||
|
||||
# --- tile object parsed from the object layer ---
|
||||
let tree = Tiled.tree(m)
|
||||
var oglayer = Value.null()
|
||||
let layers = Value.get(tree, "layers")
|
||||
var li = 0
|
||||
while li < Value.count(layers) {
|
||||
let L = Value.at(layers, li)
|
||||
if Value.as_str(Value.get(L, "type")) == "objectgroup" { oglayer = L }
|
||||
li = li + 1
|
||||
}
|
||||
let objs = Value.get(oglayer, "objects")
|
||||
if Value.count(objs) == 1 { print(8) }
|
||||
let ob = Value.at(objs, 0)
|
||||
if Value.as_int(Value.get(ob, "gid")) == 53 { print(9) }
|
||||
if Value.as_int(Value.get(ob, "x")) == 16 and Value.as_int(Value.get(ob, "y")) == 32 { print(10) }
|
||||
|
||||
# --- animated draw differs frame-to-frame (the tile actually advances) ---
|
||||
rt_clear(0)
|
||||
Tiled.draw_anim(m, 0, 0, 0)
|
||||
let h0 = fb_hash()
|
||||
rt_clear(0)
|
||||
Tiled.draw_anim(m, 0, 0, 6)
|
||||
if fb_hash() != h0 { print(11) }
|
||||
|
||||
# --- tile objects draw with correct GID flips (mirror test) ---
|
||||
# find an asymmetric tile, draw it normal vs H-flipped as a tile object
|
||||
var chosen = 0 - 1
|
||||
var t = 0
|
||||
while t < 132 {
|
||||
rt_clear(0)
|
||||
tmap_draw_gid(m, 1 + t, 0, 0)
|
||||
var asym = 0
|
||||
var yy = 0
|
||||
while yy < 16 { var xx = 0
|
||||
while xx < 8 { if rt_get_px(xx, yy) != rt_get_px(15 - xx, yy) { asym = 1 }; xx = xx + 1 }
|
||||
yy = yy + 1 }
|
||||
if asym == 1 and chosen < 0 { chosen = t }
|
||||
t = t + 1
|
||||
}
|
||||
rt_clear(0)
|
||||
tmap_draw_gid(m, 1 + chosen, 0, 0)
|
||||
tmap_draw_gid(m, (1 + chosen) | (1 << 31), 16, 0)
|
||||
var mirror = 1
|
||||
var yy2 = 0
|
||||
while yy2 < 16 { var xx2 = 0
|
||||
while xx2 < 16 { if rt_get_px(xx2, yy2) != rt_get_px(31 - xx2, yy2) { mirror = 0 }; xx2 = xx2 + 1 }
|
||||
yy2 = yy2 + 1 }
|
||||
if mirror == 1 and chosen >= 0 { print(12) }
|
||||
|
||||
# --- the design's named fixture: rpg/beach_tileset.tsx (33 animations) ---
|
||||
let beach = Tiled.read_tsx("assets/tiled-fixtures/beach_tileset.tsx")
|
||||
let tiles = Value.get(beach, "tiles")
|
||||
var nanim = 0
|
||||
var nframes = 0
|
||||
var ti = 0
|
||||
while ti < Value.count(tiles) {
|
||||
let tt = Value.at(tiles, ti)
|
||||
let an = Value.get(tt, "animation")
|
||||
if Value.count(an) > 0 { nanim = nanim + 1; nframes = nframes + Value.count(an) }
|
||||
ti = ti + 1
|
||||
}
|
||||
if nanim == 33 { print(13) }
|
||||
if nframes == 131 { print(14) }
|
||||
}
|
||||
|
||||
handler Run phase Update { quit() }
|
||||
}
|
||||
|
|
@ -700,28 +700,8 @@ function tmap_draw_gid(m: Tmap, gid: int, dx: int, dy: int) -> void {
|
|||
tmap_blit_tile(ts.imgid, sx, sy, ts.tilew, ts.tileh, dx, ddy, r.fh, r.fv, r.fd)
|
||||
}
|
||||
|
||||
# draw every visible tile layer in file order, offset by the camera (camx,camy).
|
||||
function tmap_draw(m: Tmap, camx: int, camy: int) -> void {
|
||||
var li = 0
|
||||
while li < len(m.layers) {
|
||||
let l = m.layers[li]
|
||||
if l.kind == 0 and l.visible != 0 {
|
||||
var y = 0
|
||||
while y < l.h {
|
||||
var x = 0
|
||||
while x < l.w {
|
||||
let gid = l.gids[y * l.w + x]
|
||||
if gid != 0 {
|
||||
tmap_draw_gid(m, gid, x * m.tilew - camx, y * m.tileh - camy)
|
||||
}
|
||||
x = x + 1
|
||||
}
|
||||
y = y + 1
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
}
|
||||
# (tmap_draw / tmap_draw_anim are defined in the P3 section below, over the shared
|
||||
# tmap_draw_full — a static draw plus animated tiles + tile objects.)
|
||||
|
||||
# ---- build the model from the intermediate Value tree ----------------------
|
||||
function tmap_gids_from_layer(lv: Val) -> []int {
|
||||
|
|
@ -831,3 +811,108 @@ function tiled_load(path: pointer) -> Tmap {
|
|||
if c >= 0 { tmap_project(m, c) }
|
||||
return m
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# P3 (#71) — animated tiles + tile objects.
|
||||
#
|
||||
# Animated tiles: a tileset's <animation> is a list of {tileid, duration(ms)}
|
||||
# frames. The current frame is a pure function of the engine frame counter
|
||||
# (fixed 60/s, deterministic — the same mechanism SpriteAnim rides), so it ticks
|
||||
# for free and reproduces frame-for-frame across runs. An animated GID resolves,
|
||||
# at draw, to the current frame's GID (its flip flags preserved).
|
||||
#
|
||||
# Tile objects: an object-layer entry with a `gid` draws the tile image (with its
|
||||
# own flip flags), bottom-anchored at the object position, as a placeable sprite.
|
||||
# ============================================================================
|
||||
|
||||
# the three flip-flag bits of a GID (0xE0000000), kept when swapping an animation
|
||||
# frame's tile id in.
|
||||
function tmap_flag_bits(gid: int) -> int { return gid & ((1 << 31) | (1 << 30) | (1 << 29)) }
|
||||
|
||||
# the GID an (animated) GID resolves to at engine `frame` — frames not carrying an
|
||||
# <animation> pass through unchanged. elapsed ms = frame * 1000 / 60 (the fixed
|
||||
# 60/s clock), then walk the frame durations to the current one.
|
||||
function tmap_frame_gid(m: Tmap, gid: int, frame: int) -> int {
|
||||
if gid == 0 { return 0 }
|
||||
let r = tmap_resolve(m, gid)
|
||||
if r.tileset < 0 { return gid }
|
||||
let t = tmap_tile_meta(m, gid)
|
||||
if value_kind(t) != 6 { return gid }
|
||||
let anim = value_get(t, "animation")
|
||||
if value_kind(anim) != 5 { return gid }
|
||||
let nfr = value_count(anim)
|
||||
if nfr == 0 { return gid }
|
||||
var total = 0
|
||||
var i = 0
|
||||
while i < nfr { total = total + value_as_int(value_get(value_at(anim, i), "duration")); i = i + 1 }
|
||||
if total <= 0 { return gid }
|
||||
let t_ms = (frame * 1000 / 60) % total
|
||||
var cur = value_as_int(value_get(value_at(anim, 0), "tileid"))
|
||||
var acc = 0
|
||||
i = 0
|
||||
while i < nfr {
|
||||
let fr = value_at(anim, i)
|
||||
acc = acc + value_as_int(value_get(fr, "duration"))
|
||||
if t_ms < acc { cur = value_as_int(value_get(fr, "tileid")); i = nfr }
|
||||
else { i = i + 1 }
|
||||
}
|
||||
let ts = m.tilesets[r.tileset]
|
||||
return (ts.firstgid + cur) | tmap_flag_bits(gid)
|
||||
}
|
||||
|
||||
# is a GID animated (its tile carries a non-empty <animation>)?
|
||||
function tmap_is_animated(m: Tmap, gid: int) -> int {
|
||||
let t = tmap_tile_meta(m, gid)
|
||||
if value_kind(t) != 6 { return 0 }
|
||||
let anim = value_get(t, "animation")
|
||||
if value_kind(anim) == 5 and value_count(anim) > 0 { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
# draw every visible tile layer (animated tiles resolved for `frame`) plus every
|
||||
# tile object on the object layers, in file order, offset by the camera.
|
||||
function tmap_draw_full(m: Tmap, camx: int, camy: int, frame: int, animate: int) -> void {
|
||||
var li = 0
|
||||
while li < len(m.layers) {
|
||||
let l = m.layers[li]
|
||||
if l.kind == 0 and l.visible != 0 { # tile layer
|
||||
var y = 0
|
||||
while y < l.h {
|
||||
var x = 0
|
||||
while x < l.w {
|
||||
var gid = l.gids[y * l.w + x]
|
||||
if gid != 0 {
|
||||
if animate != 0 { gid = tmap_frame_gid(m, gid, frame) }
|
||||
tmap_draw_gid(m, gid, x * m.tilew - camx, y * m.tileh - camy)
|
||||
}
|
||||
x = x + 1
|
||||
}
|
||||
y = y + 1
|
||||
}
|
||||
}
|
||||
if l.kind == 1 and l.visible != 0 { # object layer: draw tile objects
|
||||
let objs = value_get(l.data, "objects")
|
||||
var oi = 0
|
||||
while oi < value_count(objs) {
|
||||
let ob = value_at(objs, oi)
|
||||
if value_has(ob, "gid") == 1 {
|
||||
var g = value_as_int(value_get(ob, "gid"))
|
||||
if animate != 0 { g = tmap_frame_gid(m, g, frame) }
|
||||
let ox = value_as_int(value_get(ob, "x"))
|
||||
let oy = value_as_int(value_get(ob, "y"))
|
||||
# Tiled anchors a tile object by its bottom-left corner
|
||||
tmap_draw_gid(m, g, ox - camx, oy - m.tileh - camy)
|
||||
}
|
||||
oi = oi + 1
|
||||
}
|
||||
}
|
||||
li = li + 1
|
||||
}
|
||||
}
|
||||
|
||||
# static draw (no animation) — the P1 entry point.
|
||||
function tmap_draw(m: Tmap, camx: int, camy: int) -> void { tmap_draw_full(m, camx, camy, 0, 0) }
|
||||
|
||||
# animated draw at engine `frame` (pass Time.frame): animated tiles advance,
|
||||
# deterministically and frame-identically across runs.
|
||||
function tmap_draw_anim(m: Tmap, camx: int, camy: int, frame: int) -> void { tmap_draw_full(m, camx, camy, frame, 1) }
|
||||
|
|
|
|||
|
|
@ -621,6 +621,9 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "layer_count") { bare = "tmap_layer_count"; push(labels, "map") }
|
||||
if (meth == "layer_name") { bare = "tmap_layer_name"; push(labels, "map"); push(labels, "index") }
|
||||
if (meth == "draw") { bare = "tmap_draw"; push(labels, "map"); push(labels, "camx"); push(labels, "camy") }
|
||||
if (meth == "draw_anim") { bare = "tmap_draw_anim"; push(labels, "map"); push(labels, "camx"); push(labels, "camy"); push(labels, "frame") }
|
||||
if (meth == "frame_gid") { bare = "tmap_frame_gid"; push(labels, "map"); push(labels, "gid"); push(labels, "frame") }
|
||||
if (meth == "animated") { bare = "tmap_is_animated"; push(labels, "map"); push(labels, "gid") }
|
||||
if (meth == "project") { bare = "tmap_project"; push(labels, "map"); push(labels, "layer") }
|
||||
if (meth == "collide") { bare = "tmap_collide"; push(labels, "map"); push(labels, "layer") }
|
||||
if (meth == "collision_kind") { bare = "tmap_collision_kind"; push(labels, "map"); push(labels, "gid") }
|
||||
|
|
|
|||
10860
selfhost/ludicc.seed.ll
10860
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -698,6 +698,9 @@
|
|||
"tiled-tile_prop",
|
||||
"tiled-collide",
|
||||
"tiled-collision_kind",
|
||||
"tiled-tile_shapes"
|
||||
"tiled-tile_shapes",
|
||||
"tiled-draw_anim",
|
||||
"tiled-frame_gid",
|
||||
"tiled-animated"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ function cmd_test() -> int {
|
|||
feat_case("library/tiled_p05", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30", "tiled_p05.ludic (Tiled P0.5: TMX/TSX reader -> same Value-tree intermediate as TMJ; Kenney sampleMap.tmx; issue #68)")
|
||||
feat_case("library/tiled_p1", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p1.ludic (Tiled P1: rt_tmap model + GID resolver/flip decode + render; loads+draws Kenney map identically from .tmx and .tmj; issue #69)")
|
||||
feat_case("library/tiled_p2", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p2.ludic (Tiled P2: per-tile objectgroup / property-convention / collision-layer -> Solids feed; A* baseline; issue #70)")
|
||||
feat_case("library/tiled_p3", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p3.ludic (Tiled P3: animated tiles -> deterministic frame clock + tile objects w/ flips; beach_tileset 33 anims; issue #71)")
|
||||
feat_case("library/render", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "render.ludic (Screen pixel/oval/camera/clip/blend_mode/measure_text + Camera set/follow/shake, verified by pixel readback)")
|
||||
feat_case("library/lighting", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "lighting.ludic (Light ambient/point radial falloff + occluder hard shadows — 2D light accumulation, verified by pixel readback)")
|
||||
feat_case("library/light_tiers", "", "1 1 1 1 1 1 1 1 1", "light_tiers.ludic (Light spot/falloff/soft/gel/normal/time_of_day — render-quality tiers 3-4; issue #49)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue