Skip to content

Latest commit

Β 

History

History
142 lines (116 loc) Β· 5.61 KB

File metadata and controls

142 lines (116 loc) Β· 5.61 KB

Script Authoring Guide

Use this guide as the default design standard for Perro gameplay scripts. It explains ownership and communication choices before listing macros. The goal is code whose data source, target, lifetime, and failure behavior are visible.

Mental Model

One script instance belongs to one node. ctx.id identifies that owner. #[State] holds values that survive callbacks. A scene wires known dependencies and per-instance assets through script_vars before on_init. Runtime calls cross an ownership boundary only when the target owns the behavior or data.

scene construction -> script_vars -> on_init -> on_all_init -> update callbacks
owned node <-> owned typed state -> fixed refs / relations / queries -> other owners

Canonical File Shape

Keep attachable gameplay files in this order:

  1. imports and optional helper types
  2. one #[State] root struct
  3. lifecycle! engine callbacks
  4. methods! behavior methods
use perro_api::prelude::*;

#[State]
struct GameState {
    #[default = 100]
    pub health: i32,
    velocity: Vector2,
}

lifecycle!({
    fn on_update(&self, ctx: &mut ScriptContext<'_, API>) {
        self.internal_method(ctx);
    }
});

methods!({
    fn internal_method(&self, ctx: &mut ScriptContext<'_, API>) {
        with_state_mut!(ctx.run, GameState, ctx.id, |state| {
            state.velocity.x = 0.0;
        });
    }

    pub fn externally_callable_method(
        &self,
        ctx: &mut ScriptContext<'_, API>,
        amount: i32,
    ) {
        with_state_mut!(ctx.run, GameState, ctx.id, |state| {
            state.health += amount;
        });
    }
});

The state type name may match the behavior (PlayerState, DoorState, and so on). Group cohesive large data in nested helper structs; derive Variant when the group crosses scene or dynamic script boundaries. Keep private methods for same-script calls. Use pub fn only for external dispatch, signal handlers, animation events, or call_method!. Do not hand-write an impl for script behavior.

Author fixed node trees and reusable composition in .scn files. Use the scene templates for node blocks and runtime spawning for authored .scn prefabs plus small generated/transient leaf data.

See state for persistent/nested state and ownership rules, lifecycle for callback/helper boundaries, and methods for engine-facing vs pure helpers.

Guide Map

Need Use
choose state fields, node refs, or asset IDs State And References
edit self, another node, or another script Node And State Access
choose typed state, methods, signals, or dynamic vars Script Communication
use timers and avoid nested runtime borrows Timers And Borrows
choose a callback and understand init order Lifecycle
wire scenes and assign ownership Ownership And Scene Wiring
choose fixed refs, relations, or queries References And Queries
inject typed assets and understand lifetime Typed Assets
spawn nodes or attach scripts at runtime Spawn And Runtime Attach
split scripts, debug, test, and check perf Boundaries And Quality
see several scripts form one feature Examples
write or review docs examples Documentation Standard

Core Rules

  • use ctx.id for the node that owns the current script
  • store fixed dependencies as scene-injected NodeID fields
  • use parent/child relations for structural dependencies
  • use queries for dynamic sets, not fixed refs
  • use with_state! / with_state_mut! when the Rust state type is known
  • use call_method! for a targeted dynamic command with params or a return value
  • use signals for events, fan-out, and loose or cross-scene flow
  • use get_var! / set_var! only when the member name or type is dynamic
  • declare dynamically dispatched members pub: call_method! and signal handlers need a pub fn, get_var! / set_var! a pub state field
  • use named timers for delays and cooldown completion
  • copy values out of runtime closures before the next ctx.run call
  • split scripts by behavior ownership, not a fixed size rule

Communication Choice

known Rust state type? -> with_state! / with_state_mut!
targeted behavior?     -> call_method!
event or many listeners? -> signal_emit!
runtime member name?   -> get_var! / set_var!

Full Examples

The runnable ScriptPatterns demo combines fixed refs, typed asset injection, methods, signal fan-out, dynamic vars, a named timer, typed node access, and borrow-safe flow.

API References