Hausputz: UV-Doppel-Flip raus, tga::load → Result, hide_object entfernt
UVs laufen jetzt durchgehend in glTF-/tga-Konvention (der Flip-Hin-und- Zurück stammte vom gelöschten OBJ-Pfad). Kaputte Welt-Texturen fallen auf den Platzhalter statt zu panicken. Player::eye() statt vierfacher Augenpunkt-Rechnung. hide_object war ein irl3d-Demo-Relikt ohne Konsument. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,9 +60,9 @@ Start in der Konsole gemeldet (`[model]`, `[audio]`, `[tex]`, …).
|
|||||||
- Verdrahtet Signal → Aktionen. Signal-Namen: Objektname (Klick), Wert der
|
- Verdrahtet Signal → Aktionen. Signal-Namen: Objektname (Klick), Wert der
|
||||||
`signal`-Property (Zonen/Klick-Ziele), oder frei (aus Ink).
|
`signal`-Property (Zonen/Klick-Ziele), oder frei (aus Ink).
|
||||||
- Verben: `start_ink`, `set`, `inc`, `clear`, `debug_log`, `play_sound`,
|
- Verben: `start_ink`, `set`, `inc`, `clear`, `debug_log`, `play_sound`,
|
||||||
`hide_object`, `mode play|free|menu`.
|
`mode play|free|menu`.
|
||||||
- `$self` = auslösender Objektname (für generische Einträge wie
|
- `$self` = auslösender Objektname (für generische Einträge wie
|
||||||
`hide_object $self`).
|
`set $self true`).
|
||||||
- `[init]` feuert einmal beim Start — Ort für KV-Defaults.
|
- `[init]` feuert einmal beim Start — Ort für KV-Defaults.
|
||||||
|
|
||||||
## Testen
|
## Testen
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
# set <name> <value> KV setzen (true/false/i32/f32/string)
|
# set <name> <value> KV setzen (true/false/i32/f32/string)
|
||||||
# inc <name> [<delta>] KV-Integer +Delta (Default +1)
|
# inc <name> [<delta>] KV-Integer +Delta (Default +1)
|
||||||
# clear <name> KV-Eintrag entfernen
|
# clear <name> KV-Eintrag entfernen
|
||||||
# hide_object <name> deferred (Frontend): Instance ausblenden
|
|
||||||
# play_sound <name> deferred (Frontend): SFX aus assets/sounds/{name}.wav
|
# play_sound <name> deferred (Frontend): SFX aus assets/sounds/{name}.wav
|
||||||
#
|
#
|
||||||
# Reserviertes Signal `[init]` feuert einmal vor der Game-Loop —
|
# Reserviertes Signal `[init]` feuert einmal vor der Game-Loop —
|
||||||
@@ -26,7 +25,6 @@ actions = [
|
|||||||
actions = [
|
actions = [
|
||||||
"inc mushrooms_collected",
|
"inc mushrooms_collected",
|
||||||
"play_sound pickup",
|
"play_sound pickup",
|
||||||
"hide_object $self",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[Person]
|
[Person]
|
||||||
|
|||||||
+1
-3
@@ -26,9 +26,7 @@ pub type Signals = HashMap<String, Vec<String>>;
|
|||||||
/// geleert (konsumiert).
|
/// geleert (konsumiert).
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// Instance unsichtbar + kollisionslos + nicht mehr interagierbar machen.
|
/// `assets/sounds/{name}.wav` als Einmal-SFX abspielen.
|
||||||
HideObject(String),
|
|
||||||
/// WAV unter `assets/audio/<name>` als SFX abspielen.
|
|
||||||
PlaySound(String),
|
PlaySound(String),
|
||||||
/// Anzeige-/Eingabemodus wechseln (Spiel, Flycam, Menü). Vom Frontend
|
/// Anzeige-/Eingabemodus wechseln (Spiel, Flycam, Menü). Vom Frontend
|
||||||
/// auf seinen `Mode` gemappt; der Dialog-Modus ist hier bewusst nicht
|
/// auf seinen `Mode` gemappt; der Dialog-Modus ist hier bewusst nicht
|
||||||
|
|||||||
+3
-7
@@ -139,10 +139,6 @@ fn append_mesh(
|
|||||||
o.verts.push(transform(world, *p));
|
o.verts.push(transform(world, *p));
|
||||||
o.uvs.push(*uv);
|
o.uvs.push(*uv);
|
||||||
}
|
}
|
||||||
// glTF-UVs haben den Ursprung oben links — auf die Modell-Konvention
|
|
||||||
// (unten links, wie OBJ) spiegeln, damit der Render-Konsument beide
|
|
||||||
// Pfade gleich behandelt.
|
|
||||||
for uv in &mut o.uvs[base..] { uv[1] = 1.0 - uv[1]; }
|
|
||||||
|
|
||||||
let idxs: Vec<usize> = match prim["indices"].as_u64() {
|
let idxs: Vec<usize> = match prim["indices"].as_u64() {
|
||||||
Some(a) => read_indices(doc, bin, a as usize)?,
|
Some(a) => read_indices(doc, bin, a as usize)?,
|
||||||
@@ -410,10 +406,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uv_origin_is_flipped_to_model_convention() {
|
fn uvs_pass_through_unchanged() {
|
||||||
// glTF-UV (1,1) (oben-links-Ursprung) → Modell-Konvention (1,0).
|
// glTF-UVs (Ursprung oben links) sind die Modell-Konvention — kein Flip.
|
||||||
let m = parse_glb(&sample_glb()).unwrap();
|
let m = parse_glb(&sample_glb()).unwrap();
|
||||||
assert_eq!(m.objects[0].uvs[2], [1.0, 0.0]);
|
assert_eq!(m.objects[0].uvs[2], [1.0, 1.0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+2
-2
@@ -39,8 +39,8 @@ pub struct Object {
|
|||||||
/// Positionen in Engine-Koords (Y-up, Blick −Z), Welt-Raum (Transforms
|
/// Positionen in Engine-Koords (Y-up, Blick −Z), Welt-Raum (Transforms
|
||||||
/// vom Loader eingebacken), Maßstab 1:1 (Blender-Meter = Engine-Unit).
|
/// vom Loader eingebacken), Maßstab 1:1 (Blender-Meter = Engine-Unit).
|
||||||
pub verts: Vec<[f32; 3]>,
|
pub verts: Vec<[f32; 3]>,
|
||||||
/// UVs parallel zu `verts`, V-Ursprung unten links (OBJ-/glTF-Rohform;
|
/// UVs parallel zu `verts`, V-Ursprung oben links — glTF- und zugleich
|
||||||
/// den Flip auf Bild-Konvention macht der Render-Konsument).
|
/// `tga::Image`-Konvention, direkt sampelbar ohne Flip.
|
||||||
pub uvs: Vec<[f32; 2]>,
|
pub uvs: Vec<[f32; 2]>,
|
||||||
pub tris: Vec<[usize; 3]>,
|
pub tris: Vec<[usize; 3]>,
|
||||||
/// Material-Index je Dreieck, parallel zu `tris` (→ `Model::materials`).
|
/// Material-Index je Dreieck, parallel zu `tris` (→ `Model::materials`).
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ impl Player {
|
|||||||
self.pitch = (self.pitch - dy * sens).clamp(-PITCH_LIMIT, PITCH_LIMIT);
|
self.pitch = (self.pitch - dy * sens).clamp(-PITCH_LIMIT, PITCH_LIMIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Augenpunkt (Fußpunkt + [`EYE_HEIGHT`]) — Kamera- und Hörposition,
|
||||||
|
/// Startpunkt des Point-and-Click-Strahls.
|
||||||
|
pub fn eye(&self) -> [f32; 3] {
|
||||||
|
[self.pos[0], self.pos[1] + EYE_HEIGHT, self.pos[2]]
|
||||||
|
}
|
||||||
|
|
||||||
/// Blickrichtung inkl. Pitch — dieselbe Formel wie `camera::forward`/
|
/// Blickrichtung inkl. Pitch — dieselbe Formel wie `camera::forward`/
|
||||||
/// `math::view` (yaw=0 → −Z, positiver Pitch hebt). Für den
|
/// `math::view` (yaw=0 → −Z, positiver Pitch hebt). Für den
|
||||||
/// Point-and-Click-Strahl aus der Bildmitte.
|
/// Point-and-Click-Strahl aus der Bildmitte.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
//! `set <name> <value>` KV setzen (true/false/i32/f32/string)
|
//! `set <name> <value>` KV setzen (true/false/i32/f32/string)
|
||||||
//! `inc <name> [<delta>]` KV-Integer inkrementieren (Default +1)
|
//! `inc <name> [<delta>]` KV-Integer inkrementieren (Default +1)
|
||||||
//! `clear <name>` KV-Eintrag entfernen
|
//! `clear <name>` KV-Eintrag entfernen
|
||||||
//! `hide_object <name>` → Action::HideObject (deferred)
|
|
||||||
//! `play_sound <name>` → Action::PlaySound (deferred): SFX aus
|
//! `play_sound <name>` → Action::PlaySound (deferred): SFX aus
|
||||||
//! `assets/sounds/{name}.wav`
|
//! `assets/sounds/{name}.wav`
|
||||||
//! `mode <play|free|menu>` → Action::SetMode (deferred): Anzeige-/
|
//! `mode <play|free|menu>` → Action::SetMode (deferred): Anzeige-/
|
||||||
@@ -22,7 +21,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den
|
//! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den
|
||||||
//! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action
|
//! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action
|
||||||
//! wie `hide_object $self` für viele duplizierte Instances wirken, ohne pro
|
//! (z.B. `set $self true`) für viele duplizierte Instances wirken, ohne pro
|
||||||
//! Instance einen Eintrag in signals.toml zu brauchen.
|
//! Instance einen Eintrag in signals.toml zu brauchen.
|
||||||
//!
|
//!
|
||||||
//! Dispatch-Reihenfolge: erst Signal-Lookup; steht der Name in der Table,
|
//! Dispatch-Reihenfolge: erst Signal-Lookup; steht der Name in der Table,
|
||||||
@@ -79,7 +78,7 @@ pub fn dispatch(signal: &str, ctx: &mut ActionCtx) {
|
|||||||
/// entscheidet, ob das eine Meldung wert ist — siehe `dispatch`).
|
/// entscheidet, ob das eine Meldung wert ist — siehe `dispatch`).
|
||||||
fn execute(cmd: &str, ctx: &mut ActionCtx) -> bool {
|
fn execute(cmd: &str, ctx: &mut ActionCtx) -> bool {
|
||||||
// $self → ctx.instance_name. Substituieren bevor wir Verb/Args splitten,
|
// $self → ctx.instance_name. Substituieren bevor wir Verb/Args splitten,
|
||||||
// damit Tokens wie `hide_object $self` einheitlich funktionieren. Ohne
|
// damit Tokens wie `set $self true` einheitlich funktionieren. Ohne
|
||||||
// instance_name (z.B. `init`-Signal) bleibt `$self` stehen — die Action
|
// instance_name (z.B. `init`-Signal) bleibt `$self` stehen — die Action
|
||||||
// zielt dann ins Leere, was für deferred Actions ein No-Op beim
|
// zielt dann ins Leere, was für deferred Actions ein No-Op beim
|
||||||
// Konsumenten ist.
|
// Konsumenten ist.
|
||||||
@@ -98,7 +97,6 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) -> bool {
|
|||||||
"set" => kv::apply_set(args, ctx.kv),
|
"set" => kv::apply_set(args, ctx.kv),
|
||||||
"inc" => kv::apply_inc(args, ctx.kv),
|
"inc" => kv::apply_inc(args, ctx.kv),
|
||||||
"clear" => kv::apply_clear(args, ctx.kv),
|
"clear" => kv::apply_clear(args, ctx.kv),
|
||||||
"hide_object" => ctx.actions.push(Action::HideObject(args.to_string())),
|
|
||||||
"play_sound" => ctx.actions.push(Action::PlaySound(args.to_string())),
|
"play_sound" => ctx.actions.push(Action::PlaySound(args.to_string())),
|
||||||
"mode" => if let Some(t) = ModeTarget::parse(args) {
|
"mode" => if let Some(t) = ModeTarget::parse(args) {
|
||||||
ctx.actions.push(Action::SetMode(t));
|
ctx.actions.push(Action::SetMode(t));
|
||||||
@@ -183,8 +181,7 @@ mod tests {
|
|||||||
let mut signals = Signals::new();
|
let mut signals = Signals::new();
|
||||||
signals.insert("pickup".into(), vec![
|
signals.insert("pickup".into(), vec![
|
||||||
"inc items".into(),
|
"inc items".into(),
|
||||||
"play_sound pickup".into(),
|
"play_sound $self".into(), // $self-Substitution in deferred Action
|
||||||
"hide_object $self".into(),
|
|
||||||
]);
|
]);
|
||||||
let mut game = Game::new(signals);
|
let mut game = Game::new(signals);
|
||||||
|
|
||||||
@@ -193,8 +190,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(game.kv["items"].coerce_to_int().unwrap(), 1);
|
assert_eq!(game.kv["items"].coerce_to_int().unwrap(), 1);
|
||||||
assert_eq!(game.actions, vec![
|
assert_eq!(game.actions, vec![
|
||||||
Action::PlaySound("pickup".into()),
|
Action::PlaySound("Mushroom.005".into()),
|
||||||
Action::HideObject("Mushroom.005".into()),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -19,9 +19,9 @@ pub struct Image {
|
|||||||
pub rgba: Vec<u8>,
|
pub rgba: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(path: &str) -> Image {
|
pub fn load(path: &str) -> Result<Image, String> {
|
||||||
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("tga load {path}: {e}"));
|
let bytes = std::fs::read(path).map_err(|e| format!("tga load {path}: {e}"))?;
|
||||||
decode(&bytes).unwrap_or_else(|e| panic!("tga decode {path}: {e}"))
|
decode(&bytes).map_err(|e| format!("tga {path}: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode(d: &[u8]) -> Result<Image, String> {
|
pub fn decode(d: &[u8]) -> Result<Image, String> {
|
||||||
|
|||||||
+15
-8
@@ -101,7 +101,11 @@ pub fn run(mut session: Session) {
|
|||||||
// FONT_CGA, CURSORS, ORN). Decode (CPU) bleibt in run(); Fonts, Cursor und
|
// FONT_CGA, CURSORS, ORN). Decode (CPU) bleibt in run(); Fonts, Cursor und
|
||||||
// Ornament sind weiß-auf-schwarz und bekommen ihre Alpha-Maske aus der
|
// Ornament sind weiß-auf-schwarz und bekommen ihre Alpha-Maske aus der
|
||||||
// Luminanz.
|
// Luminanz.
|
||||||
let load_keyed = |p: &str| ui::key_luminance(&tga::load(&assets::path(p)));
|
// UI-Texturen sind Projekt-Assets (kein Autorinnen-Content) — fehlen sie,
|
||||||
|
// ist die Installation kaputt, da darf es scheitern.
|
||||||
|
let load_keyed = |p: &str| {
|
||||||
|
ui::key_luminance(&tga::load(&assets::path(p)).expect("UI-Textur (Projekt-Asset)"))
|
||||||
|
};
|
||||||
// Font-Atlanten zuerst keyen, daraus die Glyph-Breiten messen (Font),
|
// Font-Atlanten zuerst keyen, daraus die Glyph-Breiten messen (Font),
|
||||||
// dann dieselben Bilder in die GPU-Texturliste übernehmen.
|
// dann dieselben Bilder in die GPU-Texturliste übernehmen.
|
||||||
let ega_img = load_keyed("assets/textures/fonts/oldschool-ega-8x14.tga");
|
let ega_img = load_keyed("assets/textures/fonts/oldschool-ega-8x14.tga");
|
||||||
@@ -196,13 +200,17 @@ fn load_sound(name: &str) -> Option<audio::Sound> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Welt-Textur laden. Autorinnen-Content: fehlt oder klemmt die Datei, wird
|
||||||
|
/// gemeldet und der Platzhalter genommen — der selbst ist Projekt-Asset und
|
||||||
|
/// darf hart scheitern.
|
||||||
fn load_texture(name: &str) -> Image {
|
fn load_texture(name: &str) -> Image {
|
||||||
let path = assets::path(&format!("assets/textures/{name}.tga"));
|
match tga::load(&assets::path(&format!("assets/textures/{name}.tga"))) {
|
||||||
if std::path::Path::new(&path).exists() {
|
Ok(img) => img,
|
||||||
tga::load(&path)
|
Err(e) => {
|
||||||
} else {
|
eprintln!("[tex] {e} — Platzhalter");
|
||||||
eprintln!("[tex] {name}: keine Datei unter {path} — Platzhalter");
|
|
||||||
tga::load(&assets::path(&format!("assets/textures/{}.tga", props::FALLBACK_TEXTURE)))
|
tga::load(&assets::path(&format!("assets/textures/{}.tga", props::FALLBACK_TEXTURE)))
|
||||||
|
.expect("Platzhalter-Textur (Projekt-Asset)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,8 +495,7 @@ impl App {
|
|||||||
// Klick-Ziele (`signal`-Property). Steuert den Interakt-Cursor und
|
// Klick-Ziele (`signal`-Property). Steuert den Interakt-Cursor und
|
||||||
// löst beim Klick unten das `use` aus.
|
// löst beim Klick unten das `use` aus.
|
||||||
let aim: Option<String> = if play {
|
let aim: Option<String> = if play {
|
||||||
let p = self.session.player.pos;
|
let eye = self.session.player.eye();
|
||||||
let eye = [p[0], p[1] + player::EYE_HEIGHT, p[2]];
|
|
||||||
let dir = self.session.player.look_dir();
|
let dir = self.session.player.look_dir();
|
||||||
self.session.pick(eye, dir, crate::session::INTERACT_REACH).map(str::to_string)
|
self.session.pick(eye, dir, crate::session::INTERACT_REACH).map(str::to_string)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+8
-9
@@ -8,11 +8,10 @@
|
|||||||
//! `assets/textures/{name}.tga` (macht der Aufrufer). Faces ohne Material
|
//! `assets/textures/{name}.tga` (macht der Aufrufer). Faces ohne Material
|
||||||
//! (`""`) laufen unter [`FALLBACK_TEXTURE`].
|
//! (`""`) laufen unter [`FALLBACK_TEXTURE`].
|
||||||
//!
|
//!
|
||||||
//! UVs: das Modell trägt den V-Ursprung unten links, unsere `tga::Image`s
|
//! UVs kommen unverändert durch (Modell und `tga::Image` teilen den
|
||||||
//! oben links — V wird hier gespiegelt (Achsen-Konvention ist Sache des
|
//! Oben-links-Ursprung). Nur `visible`-Objekte werden ausgegeben;
|
||||||
//! Konsumenten, siehe engine::tga). Nur `visible`-Objekte werden
|
//! Collider-/Zonen-Volumen sind Sache von engine::collision und
|
||||||
//! ausgegeben; Collider-/Zonen-Volumen sind Sache von engine::collision
|
//! engine::trigger.
|
||||||
//! und engine::trigger.
|
|
||||||
|
|
||||||
use crate::engine::model::Model;
|
use crate::engine::model::Model;
|
||||||
use crate::render::scene::{Batch, Mesh, Vertex};
|
use crate::render::scene::{Batch, Mesh, Vertex};
|
||||||
@@ -46,7 +45,7 @@ pub fn build(model: &Model, tex_names: &[String]) -> Mesh {
|
|||||||
if !o.visible { continue; }
|
if !o.visible { continue; }
|
||||||
let base = verts.len() as u32;
|
let base = verts.len() as u32;
|
||||||
for (p, uv) in o.verts.iter().zip(&o.uvs) {
|
for (p, uv) in o.verts.iter().zip(&o.uvs) {
|
||||||
verts.push(Vertex { pos: *p, uv: [uv[0], 1.0 - uv[1]] }); // V-Flip
|
verts.push(Vertex { pos: *p, uv: *uv });
|
||||||
}
|
}
|
||||||
for (t, &m) in o.tris.iter().zip(&o.tri_mats) {
|
for (t, &m) in o.tris.iter().zip(&o.tri_mats) {
|
||||||
let name = resolve(&model.materials[m]);
|
let name = resolve(&model.materials[m]);
|
||||||
@@ -93,7 +92,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builds_only_visible_objects_with_v_flip() {
|
fn builds_only_visible_objects() {
|
||||||
let m = sample();
|
let m = sample();
|
||||||
let names = texture_names(&m);
|
let names = texture_names(&m);
|
||||||
assert_eq!(names, vec!["carpet".to_string()]);
|
assert_eq!(names, vec!["carpet".to_string()]);
|
||||||
@@ -103,8 +102,8 @@ mod tests {
|
|||||||
assert_eq!(mesh.verts.len(), 3);
|
assert_eq!(mesh.verts.len(), 3);
|
||||||
assert_eq!(mesh.indices, vec![0, 1, 2]);
|
assert_eq!(mesh.indices, vec![0, 1, 2]);
|
||||||
assert_eq!(mesh.batches.len(), 1);
|
assert_eq!(mesh.batches.len(), 1);
|
||||||
// uv (1,1) → V-Flip → (1,0).
|
// UVs unverändert (geteilte Oben-links-Konvention).
|
||||||
assert_eq!(mesh.verts[2].uv, [1.0, 0.0]);
|
assert_eq!(mesh.verts[2].uv, [1.0, 1.0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+1
-3
@@ -130,8 +130,7 @@ impl Session {
|
|||||||
/// Aktuelle Emitter-Gains (parallel zu [`Session::emitters`]) für die
|
/// Aktuelle Emitter-Gains (parallel zu [`Session::emitters`]) für die
|
||||||
/// Ohrposition des Spielers — jeden Frame vom Audio-Treiber gezogen.
|
/// Ohrposition des Spielers — jeden Frame vom Audio-Treiber gezogen.
|
||||||
pub fn emitter_gains(&self) -> Vec<f32> {
|
pub fn emitter_gains(&self) -> Vec<f32> {
|
||||||
let p = self.player.pos;
|
self.emitters.gains(self.player.eye())
|
||||||
self.emitters.gains([p[0], p[1] + player::EYE_HEIGHT, p[2]])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aufgelaufene Einmal-SFX (`play_sound`-Actions) abholen — jeden Frame
|
/// Aufgelaufene Einmal-SFX (`play_sound`-Actions) abholen — jeden Frame
|
||||||
@@ -357,7 +356,6 @@ impl Session {
|
|||||||
let mut out = Vec::with_capacity(actions.len());
|
let mut out = Vec::with_capacity(actions.len());
|
||||||
for a in actions {
|
for a in actions {
|
||||||
match a {
|
match a {
|
||||||
Action::HideObject(n) => out.push(format!("[action] hide_object {n}")),
|
|
||||||
Action::PlaySound(n) => {
|
Action::PlaySound(n) => {
|
||||||
out.push(format!("[action] play_sound {n}"));
|
out.push(format!("[action] play_sound {n}"));
|
||||||
self.pending_sounds.push(n);
|
self.pending_sounds.push(n);
|
||||||
|
|||||||
Reference in New Issue
Block a user