Skip to content

Latest commit

 

History

History
74 lines (60 loc) · 1.86 KB

File metadata and controls

74 lines (60 loc) · 1.86 KB

Example: Player Health Signal Updates HUD

Use a signal because health change is an event and more listeners may appear later. The player does not need a HUD reference.

Owners And Data Flow

damage method -> PlayerState.health -> player_health_changed(health)
-> HUD UiLabel
-> optional audio/achievement/analytics listeners

Player Script

#[State]
struct PlayerState {
    #[default = 100]
    health: i32,
}

lifecycle!({});

methods!({
    // pub so attackers can dispatch it via call_method!.
    pub fn take_damage(&self, ctx: &mut ScriptContext<'_, API>, amount: i32) -> bool {
        let health = with_state_mut!(ctx.run, PlayerState, ctx.id, |state| {
            state.health = (state.health - amount.max(0)).max(0);
            state.health
        }).unwrap_or(0);

        signal_emit!(
            ctx.run,
            signal!("player_health_changed"),
            params![health]
        );
        health > 0
    }
});

HUD Script

lifecycle!({
    fn on_all_init(&self, ctx: &mut ScriptContext<'_, API>) {
        signal_connect!(
            ctx.run,
            ctx.id,
            signal!("player_health_changed"),
            func!("on_health_changed")
        );
    }
});

methods!({
    // pub because signal dispatch uses the same glue as call_method!.
    pub fn on_health_changed(&self, ctx: &mut ScriptContext<'_, API>, health: i32) {
        with_node_mut!(ctx.run, UiLabel, ctx.id, |label| {
            label.text = format!("Health: {health}").into();
        });
    }
});

Audio, achievements, or analytics scripts may connect to the same signal without changing the player script.

No listeners is valid. A receiver removed later simply stops reacting. Use a method instead if the player must target one receiver and consume its reply. Do not inject every possible listener into player state.

Back To Examples