Closes the two open issues and lands the pending unreleased batch: - #90: `Sprite { atlas: 1 }` routes esys_sprite through atlas_draw_ex (scale/flip/tint), so cell / cell_span / strip ids of any size draw through the engine sprite-render system. examples/library/sprite_atlas is the pixel-readback regression. - #91: `become` from an @On(Event) listener / global handler / plain function no longer segfaults the compiler; it emits @L_scene_leave() (a dispatch on the live scene id) so the leaving scene's on-exit runs. UI_* handles are readable from any code (widget table built on first use). examples/library/scene_menus covers it. - fix: a windowed `ludicc -o` build that reaches the audio runtime only through the atlas/Assets preload import now links audio.ll + AVFoundation (the audio backend link was gated on a game-level Audio.* call, so any windowed game declaring Sprite failed to link). - the hand-written "Unreleased" CHANGELOG section is converted to changesets under changes/ so `x release` generates it. - plus the batch: engine-driven retained UI + UiClicked event, Overlay phase, TileSkin tilemap-render system, Key.* constants, Font/Ui/File namespaces, Sprite.strip, prefabs, managers, countdown fields, enum-typed machines, layer @Queries, ludic.prefs / ludic.dungeon packages, Ai.seek pathing, Solids.solid2, cursor confine (mode 3) fix, shooter centre-aim fix, reserved-word function diagnostic. Verified: x test (124/124), x test-tools, check-impl, check-vocabulary, check-docs, docs-gen + docs-check, bootstrap-cfree (seed is a fixpoint). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e9c2c51bc3
commit
ad548840c7
139 changed files with 58981 additions and 43671 deletions
|
|
@ -47,6 +47,8 @@ var at_nname: int = 0
|
|||
# bounded number per frame (Assets.pump) so a loading scene stays responsive and
|
||||
# a game only enters play once Assets.ready(). Deterministic: the same enqueue +
|
||||
# pump order loads the same assets in the same order on every run.
|
||||
import "audio.ludic" # the preload queue feeds .wav/.mp3 into the sound bank
|
||||
|
||||
const ATLAS_MAX_QUEUE: int = 512
|
||||
var at_q_name: pointers = null # name to register the loaded sprite under
|
||||
var at_q_path: pointers = null # file path to load
|
||||
|
|
@ -228,10 +230,94 @@ function atlas_draw_scaled(id: int, dx: int, dy: int, sc: int) -> void {
|
|||
}
|
||||
}
|
||||
|
||||
# blit an atlas sprite with the full Sprite-component treatment (#90): integer scale,
|
||||
# horizontal flip, and a solid tint (non-zero = every opaque pixel in that colour).
|
||||
# This is what the engine sprite-render system calls for `Sprite { atlas: 1 }`.
|
||||
function atlas_draw_ex(id: int, dx: int, dy: int, sc0: int, flip: int, tint: int) -> void {
|
||||
atlas_init()
|
||||
if (id < 0) or (id >= at_nspr) { return }
|
||||
var sc = sc0
|
||||
if sc < 1 { sc = 1 }
|
||||
let sheet = at_spr_sheet[id]
|
||||
let img = at_sheet_img[sheet]
|
||||
let iw = img_w[img]
|
||||
let ih = img_h[img]
|
||||
let s: words = img_px[img]
|
||||
let sx = at_spr_sx[id]
|
||||
let sy = at_spr_sy[id]
|
||||
let w = at_spr_w[id]
|
||||
let h = at_spr_h[id]
|
||||
var y = 0
|
||||
while y < h {
|
||||
let srcy = sy + y
|
||||
if (srcy >= 0) and (srcy < ih) {
|
||||
var x = 0
|
||||
while x < w {
|
||||
var rx = x
|
||||
if flip != 0 { rx = w - 1 - x }
|
||||
let srcx = sx + rx
|
||||
if (srcx >= 0) and (srcx < iw) {
|
||||
let px = s[srcy * iw + srcx]
|
||||
if ((px >> 24) & 255) >= 128 {
|
||||
var col = px & 16777215
|
||||
if tint != 0 { col = tint & 16777215 }
|
||||
if sc == 1 { rt_put_px(dx + x, dy + y, col) }
|
||||
else { rt_fill_rect(dx + x * sc, dy + y * sc, sc, sc, col) }
|
||||
}
|
||||
}
|
||||
x = x + 1
|
||||
}
|
||||
}
|
||||
y = y + 1
|
||||
}
|
||||
}
|
||||
|
||||
# a strip of `count` frames starting at (col,row), each `rows` cells tall, registered
|
||||
# as consecutive ids so `first + SpriteAnim.frame` addresses the current frame.
|
||||
function atlas_strip(sheet: int, col: int, row: int, count: int, rows: int) -> int {
|
||||
var first = 0 - 1
|
||||
var i = 0
|
||||
while i < count {
|
||||
let id = atlas_cell_span(sheet, col + i, row, 1, rows)
|
||||
if first < 0 { first = id }
|
||||
i = i + 1
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
# an atlas sprite's pixel size — handy for centering / layout.
|
||||
function atlas_width(id: int) -> int { atlas_init(); if (id < 0) or (id >= at_nspr) { return 0 }; return at_spr_w[id] }
|
||||
function atlas_height(id: int) -> int { atlas_init(); if (id < 0) or (id >= at_nspr) { return 0 }; return at_spr_h[id] }
|
||||
|
||||
# Sprite.draw_meter: `value` of `max` as a row of icons — full, half or empty per
|
||||
# `per_icon` points (hearts, stars, ammo pips), `spacing` px apart
|
||||
function atlas_draw_meter(x: int, y: int, value: int, max: int, per_icon: int, spacing: int, full: int, half: int, empty: int) -> void {
|
||||
if per_icon <= 0 { return }
|
||||
var i = 0
|
||||
while i < max / per_icon {
|
||||
var id = empty
|
||||
if value >= i * per_icon + per_icon / 2 { id = half }
|
||||
if value >= (i + 1) * per_icon { id = full }
|
||||
atlas_draw(id, x + i * spacing, y)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
# Assets.enqueue_dir: every file of a directory, named by its file name without the
|
||||
# extension (assets/audio/hit.wav -> "hit"); the queue sorts by extension as usual
|
||||
function assets_enqueue_dir(dir: pointer) -> void {
|
||||
let names = Fs.list(dir)
|
||||
if names == null { return }
|
||||
var i = 0
|
||||
while i < len(names) {
|
||||
let file = names[i]
|
||||
var dot = len(file) - 1
|
||||
while (dot > 0) and (file[dot] != 46) { dot = dot - 1 }
|
||||
if dot > 0 { assets_enqueue(file[0..dot], dir + ("/") + file) }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- incremental preload (#82) --------------------------------------------
|
||||
# Enqueue a named image file to load later (does not load it now).
|
||||
function assets_enqueue(name: pointer, path: pointer) -> void {
|
||||
|
|
@ -245,17 +331,70 @@ function assets_enqueue(name: pointer, path: pointer) -> void {
|
|||
# Load up to `max` queued assets this frame, registering each under its name, and
|
||||
# return how many were loaded by this call. Call it each frame in a loading scene
|
||||
# (a small `max` keeps the frame short); Assets.ready() flips true when done.
|
||||
event AssetsReady { } # fired once, the frame the queue finishes
|
||||
var at_q_announced: bool = false
|
||||
function assets_pump(max: int) -> int {
|
||||
atlas_init()
|
||||
var done = 0
|
||||
while (done < max) and (at_q_pos < at_q_n) {
|
||||
let id = atlas_image(at_q_path[at_q_pos])
|
||||
atlas_register_name(at_q_name[at_q_pos], id)
|
||||
assets_load_one(at_q_name[at_q_pos], at_q_path[at_q_pos])
|
||||
at_q_pos = at_q_pos + 1
|
||||
done = done + 1
|
||||
}
|
||||
if (at_q_pos >= at_q_n) and (not at_q_announced) { at_q_announced = true; emit AssetsReady() }
|
||||
return done
|
||||
}
|
||||
# the default loading bar of a `scene X loads then Y`: centred, a fifth of the screen wide
|
||||
function assets_draw_progress() -> void {
|
||||
let w = rt_fbw / 2
|
||||
let x = rt_fbw / 4
|
||||
let y = rt_fbh / 2 - 4
|
||||
rt_fill_rect(x, y, w, 8, 0x303030)
|
||||
rt_fill_rect(x, y, w * assets_progress() / 100, 8, 0xffcc44)
|
||||
}
|
||||
|
||||
# what kind of asset a path is, by its extension: sounds and fonts go to their own
|
||||
# managers (Audio.play(name:), Assets.font(name)); everything else is an image
|
||||
function path_has_suffix(path: pointer, suffix: pointer) -> bool {
|
||||
let n = len(path)
|
||||
let m = len(suffix)
|
||||
if m > n { return false }
|
||||
var i = 0
|
||||
while i < m {
|
||||
if path[n - m + i] != suffix[i] { return false }
|
||||
i = i + 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
const ATLAS_MAX_FONT: int = 16
|
||||
var at_font_name: pointers = null
|
||||
var at_font_id: words = null
|
||||
var at_font_n: int = 0
|
||||
function assets_load_one(name: pointer, path: pointer) -> void {
|
||||
if path_has_suffix(path, ".wav") or path_has_suffix(path, ".mp3") or path_has_suffix(path, ".ogg") {
|
||||
audio_define(name, path)
|
||||
return
|
||||
}
|
||||
if path_has_suffix(path, ".ttf") or path_has_suffix(path, ".ttc") {
|
||||
if at_font_name == null { at_font_name = bytes(ATLAS_MAX_FONT * 8); at_font_id = words(ATLAS_MAX_FONT) }
|
||||
if at_font_n < ATLAS_MAX_FONT {
|
||||
at_font_name[at_font_n] = name
|
||||
at_font_id[at_font_n] = rt_font_load(path)
|
||||
at_font_n = at_font_n + 1
|
||||
}
|
||||
return
|
||||
}
|
||||
atlas_register_name(name, atlas_image(path))
|
||||
}
|
||||
# the font handle registered under `name` (Assets.font), or 0
|
||||
function assets_font(name: pointer) -> int {
|
||||
var i = 0
|
||||
while i < at_font_n {
|
||||
if at_font_name[i] == name { return at_font_id[i] }
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function assets_total() -> int { atlas_init(); return at_q_n }
|
||||
function assets_loaded() -> int { atlas_init(); return at_q_pos }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue