Replace placeholder rendering with real sprites and audio
Terrain, actors, boss and bullets are sprites now, and four sounds play off server events. 548KB total across seven files, out of 3.9GB of source packs. - src/view/art.gd holds every atlas rect and sound path in one table, copied from the 0x72 pack's own tile_list. Scattering coordinates through draw calls would make re-cutting the atlas a hunt, and a wrong rect invisible. - Bullets get one MultiMesh per kind rather than one overall: a MultiMeshInstance2D carries a single texture and each kind needs a different region of the sheet. Four draw calls, no custom shader passing UVs through per-instance data. - src/view/sfx.gd is a 16-voice round-robin pool. Bullet-hell fire rates mean sounds overlap constantly, and identical sounds landing within two frames are collapsed so a ring hitting eight bullets is a bang rather than clipping. Every sound is triggered by a server event, never a local guess, so what you hear matches what happened. Audio was converted, not copied: the pack ships 24-bit/96kHz masters averaging 2MB. Downsampled to 16-bit/44.1kHz mono, silence-trimmed, and the shoot sound hard-capped to 0.30s -- it came out at 2.03s, against a fire cooldown of 0.12s, which would have smeared held fire into noise. tests/unit/test_art.gd checks what cannot be eyeballed here: every atlas rect lands inside its texture, every animation frame of a strip fits (the last frame is what runs off the sheet, not the first), there is a sprite per bullet kind and per enemy visual, and no sound is long enough to stack badly. A wrong atlas coordinate does not error -- it silently draws the wrong pixels. Licence sources recorded in docs/ASSETS.md now that they are known. All four are free versions and four rows still say "confirm at source": free itch packs vary on credit and commercial use, and there is no credits screen yet. 155 tests (was 146). check.sh, test.sh and smoke.sh pass.
This commit is contained in:
@@ -10,6 +10,11 @@ extends Node
|
||||
signal instance_changed
|
||||
signal hud_dirty
|
||||
signal local_hit(damage: int)
|
||||
## Cosmetic signals for the view. Every one is raised from a server event, not
|
||||
## from a local guess, so what the player hears matches what actually happened.
|
||||
signal shot_fired
|
||||
signal enemy_died
|
||||
signal boss_died
|
||||
|
||||
var my_peer: int = 0
|
||||
var instance_id: int = 0
|
||||
@@ -200,6 +205,12 @@ func _resync_input_tick(server_tick: int, why: String) -> void:
|
||||
GameLog.warn("client", "input re-sync: %s" % why)
|
||||
|
||||
|
||||
## Whether the local player is moving, for choosing a run vs idle animation.
|
||||
## View-only; nothing in the simulation asks.
|
||||
func is_moving() -> bool:
|
||||
return not pending.is_empty() and pending[pending.size() - 1].move.length_squared() > 0.04
|
||||
|
||||
|
||||
func on_map_chunks(from_instance: int, data: PackedByteArray) -> void:
|
||||
# A chunk still in flight when we changed instances describes the wrong map.
|
||||
if from_instance != instance_id or world.map == null:
|
||||
@@ -329,6 +340,8 @@ func on_events(data: PackedByteArray) -> void:
|
||||
ev["team"], ev["kind"], ev["accel"], ev["turn"], ev["uid"])
|
||||
if slot >= 0 and catchup > 0:
|
||||
world.pool.advance_slot(slot, catchup)
|
||||
if int(ev["team"]) == SimConfig.TEAM_PLAYER:
|
||||
shot_fired.emit()
|
||||
SimEvent.Type.BULLET_DESPAWN:
|
||||
world.apply_event(ev)
|
||||
SimEvent.Type.PLAYER_HIT:
|
||||
@@ -346,6 +359,10 @@ func on_events(data: PackedByteArray) -> void:
|
||||
predicted_pos = ev["pos"]
|
||||
pending.clear()
|
||||
hud_dirty.emit()
|
||||
SimEvent.Type.ENEMY_DIED:
|
||||
enemy_died.emit()
|
||||
SimEvent.Type.BOSS_DIED:
|
||||
boss_died.emit()
|
||||
_:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user