feat(render): #85 engine sprite-render system + deprecate bare draw_sprite
The engine already auto-ticks SpriteAnim/Motion; it now auto-DRAWS too. Declare a Sprite component (id/offx/offy/scale/flip/tint/hidden, shipped from ludic.core) on an entity with a Position and esys_sprite draws it each Render frame — no hand-written Render handler, no hand animation (adds the SpriteAnim frame when present). Registered on the engine-system registry (Render) and spliced only when the game declares Sprite, so a game that never declares it compiles byte-identically; opt out by omitting Sprite or `disable system esys_sprite`. Deprecates the bare draw_sprite/draw_sprite_scaled globals in favour of Screen.sprite/Screen.sprite_scaled: a direct bare call emits a one-time compile-time deprecation note (the bare form still lowers, since Screen.sprite uses it); migrates the chronorift demo to the namespaced calls (golden render byte-identical). Example sprite_render (pixel-readback: engine draws the sprite, respects hidden). Full suite 109/0, goldens byte-identical, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d9287d6b1d
commit
57b8747c31
11 changed files with 18366 additions and 18002 deletions
3
changes/sprite-render.md
Normal file
3
changes/sprite-render.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
Engine sprite-render system (#85) — the engine already auto-*ticks* SpriteAnim and Motion; it now auto-*draws* too. Declare a `Sprite` component (id + optional offx/offy/scale/flip/tint/hidden, shipped from **ludic.core**) on an entity with a `Position` and the engine draws it each Render frame — no hand-written Render handler querying positions and calling `draw_sprite` per entity, and no hand animation (when the entity also carries `SpriteAnim`, the current frame is added to the base id). Registered on the compile-time engine-system registry for the Render phase and spliced only when a game declares `Sprite`, so a game that never declares it compiles byte-identically; a game wanting a custom draw omits `Sprite` (or `disable system esys_sprite`). Also **deprecates the bare `draw_sprite` / `draw_sprite_scaled` globals** in favour of the namespaced `Screen.sprite` / `Screen.sprite_scaled`: a direct bare call now emits a one-time compile-time deprecation note (the bare form still lowers, since `Screen.sprite` uses it), and the in-repo `chronorift` demo is migrated to the namespaced calls. Example: `examples/library/sprite_render.ludic`.
|
||||||
|
|
@ -53,7 +53,7 @@ controller.
|
||||||
|
|
||||||
| Package | Issue | What it gives you |
|
| Package | Issue | What it gives you |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **ludic.core** | #77 | The canonical engine-ABI components the movement system reads by name — `Position`, `Body`, `Collider`, `Solids` — so a game imports them instead of hand-declaring the bundles. Extend by composition (attach your own components alongside). |
|
| **ludic.core** | #77, #85 | The canonical engine-ABI components the engine systems read by name — `Position`, `Body`, `Collider`, `Solids`, and `Sprite` — so a game imports them instead of hand-declaring the bundles. Declaring `Sprite` (id/offset/scale/flip/tint/hidden) makes the engine sprite-render system auto-draw the entity each Render frame (adding the SpriteAnim frame when present). Extend by composition (attach your own components alongside). |
|
||||||
| **ludic.gameplay** | #57 | The shared foundation: `Cooldown` timer, `Stats` + a timed modifier stack, a `Faction` friend/enemy/neutral table, and a `Combat` damage pipeline (`cancellable DamageAboutToApply` with a mutable amount, `Damaged`/`Died`/`Healed`). |
|
| **ludic.gameplay** | #57 | The shared foundation: `Cooldown` timer, `Stats` + a timed modifier stack, a `Faction` friend/enemy/neutral table, and a `Combat` damage pipeline (`cancellable DamageAboutToApply` with a mutable amount, `Damaged`/`Died`/`Healed`). |
|
||||||
| **ludic.platformer** | #58 | Jump-feel `Platformer` (apex/coyote/buffer/variable-height/multi-jump), decomposed input/move/gravity/jump/anim sub-systems, `JumpRequested`/`Landed`/`StateChanged` events, a gravity policy enum, and opt-in scaffolding (moving/crumble platforms with rider carry, pickups+`Score`, springs, hazards+`Life`, checkpoints/goal). |
|
| **ludic.platformer** | #58 | Jump-feel `Platformer` (apex/coyote/buffer/variable-height/multi-jump), decomposed input/move/gravity/jump/anim sub-systems, `JumpRequested`/`Landed`/`StateChanged` events, a gravity policy enum, and opt-in scaffolding (moving/crumble platforms with rider carry, pickups+`Score`, springs, hazards+`Life`, checkpoints/goal). |
|
||||||
| **ludic.shooter** | #60 | `TopDown` decoupled move + aim (mouse / stick / move-dir / nearest-enemy auto-aim), a name-keyed `Weapon` registry (fire-rate/spread/pellets/pattern + data-driven pierce/homing), a deterministic `Projectile` pool (faction-filtered hits through `Combat`, pierce, ring/spiral patterns, homing), and a budgeted wave `Spawner`. |
|
| **ludic.shooter** | #60 | `TopDown` decoupled move + aim (mouse / stick / move-dir / nearest-enemy auto-aim), a name-keyed `Weapon` registry (fire-rate/spread/pellets/pattern + data-driven pierce/homing), a deterministic `Projectile` pool (faction-filtered hits through `Combat`, pierce, ring/spiral patterns, homing), and a budgeted wave `Spawner`. |
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,19 @@ handler Draw phase Render {
|
||||||
',', 'S', 'B' => base = SPR_FLOOR
|
',', 'S', 'B' => base = SPR_FLOOR
|
||||||
'=' => base = SPR_WALL
|
'=' => base = SPR_WALL
|
||||||
}
|
}
|
||||||
draw_sprite(base, sx, sy)
|
Screen.sprite(base, sx, sy)
|
||||||
if c == 'T' { draw_sprite(SPR_TREE, sx, sy) }
|
if c == 'T' { Screen.sprite(SPR_TREE, sx, sy) }
|
||||||
if c == '#' { draw_sprite(SPR_TREE, sx, sy) }
|
if c == '#' { Screen.sprite(SPR_TREE, sx, sy) }
|
||||||
if c == 'H' { draw_sprite(SPR_HOUSE, sx, sy) }
|
if c == 'H' { Screen.sprite(SPR_HOUSE, sx, sy) }
|
||||||
if c == 'D' { draw_sprite(SPR_DOOR, sx, sy) }
|
if c == 'D' { Screen.sprite(SPR_DOOR, sx, sy) }
|
||||||
if c == 'S' { draw_sprite(SPR_DOOR, sx, sy) }
|
if c == 'S' { Screen.sprite(SPR_DOOR, sx, sy) }
|
||||||
if c == 'B' {
|
if c == 'B' {
|
||||||
if boss_down == 0 { draw_sprite(SPR_BOSS, sx, sy) }
|
if boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (p, a) in query [Pos, Actor] {
|
for (p, a) in query [Pos, Actor] {
|
||||||
draw_sprite(a.kind, p.x * 16 - cx, p.y * 16 - cy)
|
Screen.sprite(a.kind, p.x * 16 - cx, p.y * 16 - cy)
|
||||||
}
|
}
|
||||||
for (h, pp) in query [Stats, Party] {
|
for (h, pp) in query [Stats, Party] {
|
||||||
if pp.slot == 0 {
|
if pp.slot == 0 {
|
||||||
|
|
@ -59,7 +59,7 @@ handler Draw phase Render {
|
||||||
for (e, a) in query [Stats, Actor, {Enemy}] {
|
for (e, a) in query [Stats, Actor, {Enemy}] {
|
||||||
var bs = 5
|
var bs = 5
|
||||||
if enemy_type == 2 { bs = 7 }
|
if enemy_type == 2 { bs = 7 }
|
||||||
draw_sprite_scaled(a.kind, 160 - bs * 8, 44, bs)
|
Screen.sprite_scaled(a.kind, 160 - bs * 8, 44, bs)
|
||||||
Screen.draw_text(100, 20, enemy_name(enemy_type), 0xffffff, 1)
|
Screen.draw_text(100, 20, enemy_name(enemy_type), 0xffffff, 1)
|
||||||
Screen.fill_rectangle(100, 30, 120, 8, 0x401018)
|
Screen.fill_rectangle(100, 30, 120, 8, 0x401018)
|
||||||
Screen.fill_rectangle(100, 30, e.hp * 120 / e.maxhp, 8, 0xe04040)
|
Screen.fill_rectangle(100, 30, e.hp * 120 / e.maxhp, 8, 0xe04040)
|
||||||
|
|
@ -75,7 +75,7 @@ handler Draw phase Render {
|
||||||
var spr = 5
|
var spr = 5
|
||||||
if pt.slot == 1 { spr = 9 }
|
if pt.slot == 1 { spr = 9 }
|
||||||
if pt.slot == act { Screen.draw_rectangle(hx - 2, 180, 88, 44, 0xffff00) }
|
if pt.slot == act { Screen.draw_rectangle(hx - 2, 180, 88, 44, 0xffff00) }
|
||||||
draw_sprite_scaled(spr, hx, 184, 2)
|
Screen.sprite_scaled(spr, hx, 184, 2)
|
||||||
if pt.slot == 0 { Screen.draw_text(hx + 36, 182, "KNIGHT", 0xffe08a, 1) }
|
if pt.slot == 0 { Screen.draw_text(hx + 36, 182, "KNIGHT", 0xffe08a, 1) }
|
||||||
if pt.slot == 1 { Screen.draw_text(hx + 36, 182, "MAGE", 0xc9a0ff, 1) }
|
if pt.slot == 1 { Screen.draw_text(hx + 36, 182, "MAGE", 0xc9a0ff, 1) }
|
||||||
Screen.draw_text(hx + 36, 195, "HP", 0x88ff88, 1)
|
Screen.draw_text(hx + 36, 195, "HP", 0x88ff88, 1)
|
||||||
|
|
|
||||||
32
examples/library/sprite_render.ludic
Normal file
32
examples/library/sprite_render.ludic
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# sprite_render.ludic — the engine-owned sprite-render system (#85). Declare a
|
||||||
|
# Sprite component (from ludic.core) on an entity with a Position and the engine
|
||||||
|
# draws it each Render frame — no hand-written Render handler querying positions
|
||||||
|
# and calling draw_sprite per entity. A `hidden` sprite is skipped. Verified by
|
||||||
|
# pixel readback after one frame.
|
||||||
|
#
|
||||||
|
# Driven headless by one stdin key ('q' quits after the frame renders).
|
||||||
|
# printf 'q' | LUDIC_MODULES=packages bin/ludic examples/library/sprite_render.ludic -> 1 0
|
||||||
|
program SpriteRender {
|
||||||
|
import "ludic.core/components.ludic"
|
||||||
|
|
||||||
|
var HERO: int = 0
|
||||||
|
|
||||||
|
@OnStart handler Boot {
|
||||||
|
HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png")
|
||||||
|
spawn Shown { Position { x: 40, y: 40 }, Sprite { id: HERO } }
|
||||||
|
spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: HERO, hidden: 1 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
# An empty Render handler: the engine sprite system draws the Sprite entities.
|
||||||
|
handler Draw phase Render { }
|
||||||
|
|
||||||
|
@OnQuit handler Report {
|
||||||
|
var drew = 0
|
||||||
|
for yy in 40 .. 56 { for xx in 40 .. 56 { if Screen.pixel(xx, yy) != 0 { drew = 1 } } }
|
||||||
|
print(drew) # 1 — the engine drew the shown sprite
|
||||||
|
|
||||||
|
var hid = 0
|
||||||
|
for yy in 40 .. 56 { for xx in 100 .. 116 { if Screen.pixel(xx, yy) != 0 { hid = 1 } } }
|
||||||
|
print(hid) # 0 — the hidden sprite was skipped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,15 @@ property Collider {
|
||||||
hit: int = 0, entered: int = 0, exited: int = 0
|
hit: int = 0, entered: int = 0, exited: int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A drawable sprite (#85): declare it and the engine-owned sprite-render system
|
||||||
|
# draws the entity each Render frame from its Position — no hand-written Render
|
||||||
|
# handler. `id` is a sprite id (png_load / atlas cell); when the entity also carries
|
||||||
|
# SpriteAnim, the current animation frame is added to it. offx/offy offset from the
|
||||||
|
# Position anchor, scale is an integer zoom (0/1 = 1:1), flip mirrors horizontally,
|
||||||
|
# tint (non-zero) draws every opaque pixel in one colour (hit flash), hidden = 1
|
||||||
|
# skips it. Draw order is spawn order; a game wanting custom draw omits Sprite.
|
||||||
|
property Sprite { id: int = 0, offx: int = 0, offy: int = 0, scale: int = 1, flip: int = 0, tint: int = 0, hidden: int = 0 }
|
||||||
|
|
||||||
# Optional single config entity that turns the tile-grid broadphase on: solids are
|
# Optional single config entity that turns the tile-grid broadphase on: solids are
|
||||||
# read from the Map.* tilemap. tile > 0 sets the tile size in px; wall is the solid
|
# read from the Map.* tilemap. tile > 0 sets the tile size in px; wall is the solid
|
||||||
# glyph; oneway is an optional one-way-platform glyph (0 = none).
|
# glyph; oneway is an optional one-way-platform glyph (0 = none).
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,4 @@ provides "Position"
|
||||||
provides "Body"
|
provides "Body"
|
||||||
provides "Collider"
|
provides "Collider"
|
||||||
provides "Solids"
|
provides "Solids"
|
||||||
|
provides "Sprite"
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,32 @@ function rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The full sprite blit the engine sprite-render system (#85) uses: an integer
|
||||||
|
# scale (>= 1), a horizontal flip, and an optional solid tint (0 = keep the
|
||||||
|
# sprite's own colours; non-zero = draw every opaque pixel in the tint colour, for
|
||||||
|
# a hit-flash / team-colour silhouette). It funnels through the same put_px /
|
||||||
|
# fill_rect chokepoints, so camera / zoom / clip / blend all apply.
|
||||||
|
function rt_draw_sprite_ex(id: int, px: int, py: int, sc: int, flip: int, tint: int) -> void {
|
||||||
|
if id < 0 { return }
|
||||||
|
if id >= spr_n { return }
|
||||||
|
var s = sc
|
||||||
|
if s < 1 { s = 1 }
|
||||||
|
let base = id * SPR_SZ * SPR_SZ
|
||||||
|
for y in 0 .. SPR_SZ {
|
||||||
|
for x in 0 .. SPR_SZ {
|
||||||
|
var rx = x
|
||||||
|
if flip != 0 { rx = SPR_SZ - 1 - x } # sample mirrored, draw upright
|
||||||
|
let p = spr_px[base + y * SPR_SZ + rx]
|
||||||
|
if ((p >> 24) & 255) != 0 {
|
||||||
|
var col = (p & 16777215)
|
||||||
|
if tint != 0 { col = (tint & 16777215) }
|
||||||
|
if s == 1 { rt_put_px(px + x, py + y, col) }
|
||||||
|
else { rt_fill_rect(px + x * s, py + y * s, s, s, col) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# ---- .lspr sprite sheets (palette + ASCII rows) ---------------------------
|
# ---- .lspr sprite sheets (palette + ASCII rows) ---------------------------
|
||||||
function rt_hexval(c: int) -> int {
|
function rt_hexval(c: int) -> int {
|
||||||
if c >= 48 { if c <= 57 { return c - 48 } }
|
if c >= 48 { if c <= 57 { return c - 48 } }
|
||||||
|
|
|
||||||
84
runtime/native/systems_sprite.ludic
Normal file
84
runtime/native/systems_sprite.ludic
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# ============================================================================
|
||||||
|
# systems_sprite.ludic — the engine-owned sprite-render system (#85).
|
||||||
|
#
|
||||||
|
# The engine already auto-*ticks* SpriteAnim (advance the frame) and Motion; this
|
||||||
|
# adds the missing half: it auto-*draws* an entity's sprite. Declare a `Sprite`
|
||||||
|
# component (id + optional offset / scale / flip / tint / hidden) and the engine
|
||||||
|
# draws it each Render frame from the entity's `Position` — no hand-written Render
|
||||||
|
# handler querying positions and calling draw_sprite per entity, and no hand
|
||||||
|
# animation: when the entity also carries SpriteAnim, the current animation frame
|
||||||
|
# is added to the base id automatically.
|
||||||
|
#
|
||||||
|
# It is registered on the compile-time engine-system registry (parse_program) for
|
||||||
|
# the Render phase and spliced only when a game declares `Sprite`, so a game that
|
||||||
|
# never declares it compiles byte-identically. A game that wants a custom draw
|
||||||
|
# simply doesn't declare Sprite (or `disable system esys_sprite`) and draws itself.
|
||||||
|
#
|
||||||
|
# Component contract (POD, every field defaulted; ship it from ludic.core):
|
||||||
|
# property Sprite {
|
||||||
|
# id : int # sprite id (png_load / atlas cell); base frame for SpriteAnim
|
||||||
|
# offx : int = 0 # draw offset from the Position anchor, px
|
||||||
|
# offy : int = 0
|
||||||
|
# scale : int = 1 # integer scale (0/1 = 1:1)
|
||||||
|
# flip : int = 0 # 1 = mirror horizontally
|
||||||
|
# tint : int = 0 # 0 = sprite's own colours; non-zero = solid tint (hit flash)
|
||||||
|
# hidden : int = 0 # 1 = skip drawing this entity
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Draw order is entity/query order (spawn order). Everything integer + through the
|
||||||
|
# camera/zoom/clip chokepoints, so it stays deterministic and headless-identical.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# read entity `e`'s world coordinate from a Position component (x when axis==0,
|
||||||
|
# y when axis==1), or 0 if it has none.
|
||||||
|
function spr_pos(e: int, axis: int) -> int {
|
||||||
|
let pp = World.prop_id("Position")
|
||||||
|
if pp < 0 { return 0 }
|
||||||
|
if World.has(e, pp) == 0 { return 0 }
|
||||||
|
var f = World.field_id(pp, "x")
|
||||||
|
if axis == 1 { f = World.field_id(pp, "y") }
|
||||||
|
if f < 0 { return 0 }
|
||||||
|
return World.get(e, pp, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
function esys_sprite() -> void {
|
||||||
|
let P = World.prop_id("Sprite")
|
||||||
|
if P < 0 { return }
|
||||||
|
let f_id = World.field_id(P, "id")
|
||||||
|
if f_id < 0 { return }
|
||||||
|
let f_offx = World.field_id(P, "offx")
|
||||||
|
let f_offy = World.field_id(P, "offy")
|
||||||
|
let f_scale = World.field_id(P, "scale")
|
||||||
|
let f_flip = World.field_id(P, "flip")
|
||||||
|
let f_tint = World.field_id(P, "tint")
|
||||||
|
let f_hidden = World.field_id(P, "hidden")
|
||||||
|
|
||||||
|
let SA = World.prop_id("SpriteAnim")
|
||||||
|
var f_frame = 0 - 1
|
||||||
|
if SA >= 0 { f_frame = World.field_id(SA, "frame") }
|
||||||
|
|
||||||
|
var e = World.query_next(P, 0)
|
||||||
|
while e >= 0 {
|
||||||
|
var hidden = 0
|
||||||
|
if f_hidden >= 0 { hidden = World.get(e, P, f_hidden) }
|
||||||
|
if hidden == 0 {
|
||||||
|
var id = World.get(e, P, f_id)
|
||||||
|
# add the current animation frame when the entity also carries SpriteAnim.
|
||||||
|
if (SA >= 0) and (f_frame >= 0) {
|
||||||
|
if World.has(e, SA) != 0 { id = id + World.get(e, SA, f_frame) }
|
||||||
|
}
|
||||||
|
var x = spr_pos(e, 0)
|
||||||
|
var y = spr_pos(e, 1)
|
||||||
|
if f_offx >= 0 { x = x + World.get(e, P, f_offx) }
|
||||||
|
if f_offy >= 0 { y = y + World.get(e, P, f_offy) }
|
||||||
|
var sc = 1
|
||||||
|
if f_scale >= 0 { let sv = World.get(e, P, f_scale); if sv > 1 { sc = sv } }
|
||||||
|
var flip = 0
|
||||||
|
if f_flip >= 0 { flip = World.get(e, P, f_flip) }
|
||||||
|
var tint = 0
|
||||||
|
if f_tint >= 0 { tint = World.get(e, P, f_tint) }
|
||||||
|
rt_draw_sprite_ex(id, x, y, sc, flip, tint)
|
||||||
|
}
|
||||||
|
e = World.query_next(P, e + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -210,7 +210,9 @@ function p_postfix() -> Node {
|
||||||
else { if is_op("[") { pi = pi + 1; let lo = expr()
|
else { if is_op("[") { pi = pi + 1; let lo = expr()
|
||||||
if is_op("..") { pi = pi + 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
|
if is_op("..") { pi = pi + 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
|
||||||
else { let ix = node(E_INDEX); ix.a = e; ix.b = lo; eat_op("]"); e = ix } }
|
else { let ix = node(E_INDEX); ix.a = e; ix.b = lo; eat_op("]"); e = ix } }
|
||||||
else { if is_op("(") { let c = node(E_CALL); c.a = e; c.line = toks[pi].line; args_call(c); e = c } else { break } } }
|
else { if is_op("(") { let c = node(E_CALL); c.a = e; c.line = toks[pi].line; args_call(c); e = c
|
||||||
|
if (c.a.kind == E_ID) and ((c.a.s == "draw_sprite") or (c.a.s == "draw_sprite_scaled")) { warn_draw_sprite(c.a.s) } # #85 deprecate the bare aliases
|
||||||
|
} else { break } } }
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
@ -445,6 +447,17 @@ var g_uses_esys: bool = false # an engine-owned system component (SpriteAnim
|
||||||
var g_uses_input: bool = false # a program used Input.bind/down/poll/… (action maps + record/replay) -> splice input.ludic
|
var g_uses_input: bool = false # a program used Input.bind/down/poll/… (action maps + record/replay) -> splice input.ludic
|
||||||
var g_has_clear_color: bool = false # @ClearColor(N) declared -> the Render phase auto-clears + auto-presents (#86)
|
var g_has_clear_color: bool = false # @ClearColor(N) declared -> the Render phase auto-clears + auto-presents (#86)
|
||||||
var g_clear_color: int = 0 # the declared clear colour (0x00RRGGBB)
|
var g_clear_color: int = 0 # the declared clear colour (0x00RRGGBB)
|
||||||
|
var g_warned_draw_sprite: bool = false # emit the bare-draw_sprite deprecation note once (#85)
|
||||||
|
|
||||||
|
# #85 — steer a direct bare draw_sprite / draw_sprite_scaled call to the namespaced
|
||||||
|
# Screen.sprite (or the engine sprite-render system). Warns once, non-fatally — the
|
||||||
|
# bare form still compiles (Screen.sprite lowers to it), it is just deprecated.
|
||||||
|
function warn_draw_sprite(name: pointer) -> void {
|
||||||
|
if g_warned_draw_sprite { return }
|
||||||
|
g_warned_draw_sprite = true
|
||||||
|
let m = `ludicc: warning: bare {name}(...) is deprecated (#85) — use Screen.sprite / Screen.sprite_scaled, or declare a Sprite component for the engine sprite-render system\n`
|
||||||
|
file_write(file_stderr(), m, len(m))
|
||||||
|
}
|
||||||
var g_uses_anim_rt: bool = false # Anim.play/clip/on_frame/fired or Motion.to (#48) -> splice systems.ludic + force the reflection ABI
|
var g_uses_anim_rt: bool = false # Anim.play/clip/on_frame/fired or Motion.to (#48) -> splice systems.ludic + force the reflection ABI
|
||||||
var g_uses_tween_rt: bool = false # Tween.to/chain/delay/… (#48) -> splice tween.ludic + run esys_tween each Update
|
var g_uses_tween_rt: bool = false # Tween.to/chain/delay/… (#48) -> splice tween.ludic + run esys_tween each Update
|
||||||
var g_uses_audio: bool = false # Audio.* (#22) -> splice audio.ludic; a windowed build also links audio.ll + AVFoundation
|
var g_uses_audio: bool = false # Audio.* (#22) -> splice audio.ludic; a windowed build also links audio.ll + AVFoundation
|
||||||
|
|
@ -844,6 +857,13 @@ function maybe_splice_runtime() -> void {
|
||||||
do_import("runtime/native/core.ludic")
|
do_import("runtime/native/core.ludic")
|
||||||
do_import("runtime/native/systems_move.ludic")
|
do_import("runtime/native/systems_move.ludic")
|
||||||
}
|
}
|
||||||
|
# Sprite -> the engine sprite-render system (#85). It draws through
|
||||||
|
# rt_draw_sprite_ex (image.ludic, part of core), so it links against core.ludic
|
||||||
|
# (do_import dedupes when a game already pulled core in).
|
||||||
|
if find_comp("Sprite") != null {
|
||||||
|
do_import("runtime/native/core.ludic")
|
||||||
|
do_import("runtime/native/systems_sprite.ludic")
|
||||||
|
}
|
||||||
# Light2D / Occluder -> the lighting render system, which links against the
|
# Light2D / Occluder -> the lighting render system, which links against the
|
||||||
# 2D light pass (light.ludic). do_import dedupes, so this is a no-op when the
|
# 2D light pass (light.ludic). do_import dedupes, so this is a no-op when the
|
||||||
# game also uses Light.* directly (g_uses_light already pulled it in).
|
# game also uses Light.* directly (g_uses_light already pulled it in).
|
||||||
|
|
@ -882,6 +902,7 @@ function parse_program() -> void {
|
||||||
g_uses_input = false
|
g_uses_input = false
|
||||||
g_has_clear_color = false
|
g_has_clear_color = false
|
||||||
g_clear_color = 0
|
g_clear_color = 0
|
||||||
|
g_warned_draw_sprite = false
|
||||||
g_uses_light = false
|
g_uses_light = false
|
||||||
g_uses_value = false
|
g_uses_value = false
|
||||||
g_uses_xml = false
|
g_uses_xml = false
|
||||||
|
|
@ -899,6 +920,7 @@ function parse_program() -> void {
|
||||||
push(g_esys_comp, "SpriteAnim"); push(g_esys_fn, "esys_spriteanim"); push(g_esys_phase, "Update")
|
push(g_esys_comp, "SpriteAnim"); push(g_esys_fn, "esys_spriteanim"); push(g_esys_phase, "Update")
|
||||||
push(g_esys_comp, "Motion"); push(g_esys_fn, "esys_motion"); push(g_esys_phase, "Update")
|
push(g_esys_comp, "Motion"); push(g_esys_fn, "esys_motion"); push(g_esys_phase, "Update")
|
||||||
push(g_esys_comp, "Body"); push(g_esys_fn, "esys_move"); push(g_esys_phase, "Update")
|
push(g_esys_comp, "Body"); push(g_esys_fn, "esys_move"); push(g_esys_phase, "Update")
|
||||||
|
push(g_esys_comp, "Sprite"); push(g_esys_fn, "esys_sprite"); push(g_esys_phase, "Render")
|
||||||
push(g_esys_comp, "Light2D"); push(g_esys_fn, "esys_light2d"); push(g_esys_phase, "Render")
|
push(g_esys_comp, "Light2D"); push(g_esys_fn, "esys_light2d"); push(g_esys_phase, "Render")
|
||||||
g_namespaces = new []pointer
|
g_namespaces = new []pointer
|
||||||
loaded_paths = new []pointer
|
loaded_paths = new []pointer
|
||||||
|
|
|
||||||
36166
selfhost/ludicc.seed.ll
36166
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -243,6 +243,7 @@ function cmd_test() -> int {
|
||||||
feat_case("library/physics_tiles", "", "112 1 80 1 48 1 60", "physics_tiles.ludic (esys_move tile-grid broadphase: tile floor/wall + one-way tile land-from-above / rise-through; issue #65)")
|
feat_case("library/physics_tiles", "", "112 1 80 1 48 1 60", "physics_tiles.ludic (esys_move tile-grid broadphase: tile floor/wall + one-way tile land-from-above / rise-through; issue #65)")
|
||||||
# builtin gameplay controllers, consumed as in-repo packages (issues #57-#61)
|
# builtin gameplay controllers, consumed as in-repo packages (issues #57-#61)
|
||||||
controller_case("library/core_components", "", "110 30 184 1", "core_components.ludic (ludic.core #77: canonical Position/Body/Collider imported from a package, driving esys_move, composed with a game Health component)")
|
controller_case("library/core_components", "", "110 30 184 1", "core_components.ludic (ludic.core #77: canonical Position/Body/Collider imported from a package, driving esys_move, composed with a game Health component)")
|
||||||
|
controller_case("library/sprite_render", "q", "1 0", "sprite_render.ludic (ludic.core #85: engine sprite-render system auto-draws a Sprite entity from its Position; respects hidden)")
|
||||||
controller_case("library/gameplay_foundation", "", "2 0 1 5 24 0 2 1 15 85 7 1 1", "gameplay_foundation.ludic (ludic.gameplay #57: Cooldown timer + Stats/modifier stack + Faction table + Combat cancel/mutable pipeline)")
|
controller_case("library/gameplay_foundation", "", "2 0 1 5 24 0 2 1 15 85 7 1 1", "gameplay_foundation.ludic (ludic.gameplay #57: Cooldown timer + Stats/modifier stack + Faction table + Combat cancel/mutable pipeline)")
|
||||||
controller_case("games/platformer_demo", "", "1 1 1 1 1 1 1 1", "platformer_demo.ludic (ludic.platformer #58: gravity/land, jump apex, coyote, veto-gated double jump, wall collision + disable-system lever)")
|
controller_case("games/platformer_demo", "", "1 1 1 1 1 1 1 1", "platformer_demo.ludic (ludic.platformer #58: gravity/land, jump apex, coyote, veto-gated double jump, wall collision + disable-system lever)")
|
||||||
controller_case("games/platformer_scaffolding", "", "1 1 1 1 1 1", "platformer_scaffolding.ludic (ludic.platformer #58 layers 4-5: moving-platform rider carry, pickup->score, spring, hazard+Life i-frames)")
|
controller_case("games/platformer_scaffolding", "", "1 1 1 1 1 1", "platformer_scaffolding.ludic (ludic.platformer #58 layers 4-5: moving-platform rider carry, pickup->score, spring, hazard+Life i-frames)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue