Use call_method! because the switch has one fixed target and asks that target
to perform behavior.
scene -> SwitchState.door = @ExitDoor
interaction -> switch.activate -> door.toggle -> bool(open)
The switch owns interaction and the dependency. The door owns open state and transition behavior. The scene chooses which door this switch controls.
#[State]
struct DoorState {
#[default = false]
open: bool,
}
lifecycle!({});
methods!({
// pub because the switch dispatches it via call_method!.
pub fn toggle(&self, ctx: &mut ScriptContext<'_, API>) -> bool {
with_state_mut!(ctx.run, DoorState, ctx.id, |state| {
state.open = !state.open;
state.open
}).unwrap_or(false)
}
});#[State]
struct SwitchState {
// pub because the scene injects it via script_vars.
#[expose]
#[node_ref(Node3D)]
pub door: Option<NodeID>,
}
lifecycle!({});
methods!({
fn activate(&self, ctx: &mut ScriptContext<'_, API>) -> bool {
let Some(door) = with_state!(ctx.run, SwitchState, ctx.id, |state| state.door).unwrap_or_default()
else {
return false;
};
call_method!(ctx.run, door, method!("toggle"), params![])
.as_bool()
.unwrap_or(false)
}
});script_vars = {
door = @ExitDoor
}
The switch owns the dependency. The door owns door state and behavior. The return value tells the switch whether the door ended open.
A method fits one known target, a command, and a reply. A signal would lose the request/reply relation and could reach many doors. Direct typed state mutation would let the switch bypass door animation, locks, and invariants.
Missing/nil door returns false; the switch does not panic. A missing method or
wrong return type also decodes to the neutral false used here. Extend the door
method with a key parameter, or emit door_opened after success for loose audio
and quest listeners.
ScriptPatterns uses the same fixed-ref + reply shape: scene wiring, controller caller, and player target.