Signal Handling
Godot signals are a core communication mechanism in the Godot engine. godot-bevy bridges those signals into Bevy events so your ECS systems can react to UI, gameplay, and scene-tree events in a type-safe way.
This page focuses on the typed signals API (recommended). A legacy API remains available but is deprecated; see the Legacy section below.
Outline
- Quick Start
- Multiple Typed Events
- Passing Context (Node, Entity, Arguments)
- Deferred Connections
- Attaching signals to Godot scenes
- Untyped Legacy API (Deprecated)
Quick Start
- Define a Bevy message for your case:
#![allow(unused)] fn main() { use bevy::prelude::*; use godot_bevy::prelude::*; #[derive(Message, Debug, Clone)] struct StartGameRequested; }
- Register the typed plugin for your message type:
#![allow(unused)] fn main() { fn build_app(app: &mut App) { app.add_plugins(GodotTypedSignalsPlugin::<StartGameRequested>::default()); } }
- Connect a Godot signal and map it to your message:
#![allow(unused)] fn main() { fn connect_button( mut buttons: Query<&mut GodotNodeHandle, With<Button>>, typed: TypedGodotSignals<StartGameRequested>, ) { for mut handle in &mut buttons { typed.connect_map(&mut handle, "pressed", None, |_args, _node, _ent| Some(StartGameRequested)); } } }
- Listen for the message anywhere:
#![allow(unused)] fn main() { fn on_start(mut ev: MessageReader<StartGameRequested>) { for _ in ev.read() { // Start the game! } } }
Multiple Typed Events
Use one plugin per event type. You can map the same Godot signal to multiple typed events if you like:
#![allow(unused)] fn main() { #[derive(Message, Debug, Clone)] struct ToggleFullscreen; #[derive(Message, Debug, Clone)] struct QuitRequested { source: GodotNodeHandle } fn setup(app: &mut App) { app.add_plugins(GodotTypedSignalsPlugin::<ToggleFullscreen>::default()) .add_plugins(GodotTypedSignalsPlugin::<QuitRequested>::default()); } fn connect_menu( mut menu: Query<(&mut GodotNodeHandle, &MenuTag)>, toggle: TypedGodotSignals<ToggleFullscreen>, quit: TypedGodotSignals<QuitRequested>, ) { for (mut button, tag) in &mut menu { match tag { MenuTag::Fullscreen => { toggle.connect_map(&mut button, "pressed", None, |_a, _n, _e| Some(ToggleFullscreen)); } MenuTag::Quit => { quit.connect_map(&mut button, "pressed", None, |_a, n, _e| Some(QuitRequested { source: n.clone() })); } } } } }
Passing Context (Node, Entity, Arguments)
The mapper closure receives:
args: &[Variant]: raw Godot arguments (clone if you need detailed parsing)node: &GodotNodeHandle: emitting node; clone into your event if usefulentity: Option<Entity>: Bevy entity if you passedSome(entity)toconnect_map
Example adding the entity:
#![allow(unused)] fn main() { #[derive(Message, Debug, Clone, Copy)] struct AreaExited(Entity); fn connect_area( mut q: Query<(Entity, &mut GodotNodeHandle), With<Area2D>>, typed: TypedGodotSignals<AreaExited>, ) { for (entity, mut area) in &mut q { typed.connect_map(&mut area, "body_exited", Some(entity), |_a, _n, e| Some(AreaExited(e.unwrap()))); } } }
Deferred Connections
When spawning entities before their GodotNodeHandle is ready, you can defer connections. Add TypedDeferredSignalConnections<T> with a signal-to-event mapper; the GodotTypedSignalsPlugin<T> wires it once the handle appears.
#![allow(unused)] fn main() { #[derive(Component)] struct MyArea; #[derive(Message, Debug, Clone, Copy)] struct BodyEntered(Entity); fn setup(app: &mut App) { app.add_plugins(GodotTypedSignalsPlugin::<BodyEntered>::default()); } fn spawn_area(mut commands: Commands) { commands.spawn(( MyArea, // Defer until GodotNodeHandle is available on this entity TypedDeferredSignalConnections::<BodyEntered>::with_connection("body_entered", |_a, _n, e| Some(BodyEntered(e.unwrap()))), )); } }
Attaching signals to Godot scenes
When spawning an entity associated with a Godot scene, you can schedule
signals to be connected to children of the scene once the scene is spawned.
When inserting a GodotScene resource, use the with_signal_connection builder method to schedule connections.
The method arguments are similar to other typed signal constructors such as connect_map:
node_path- Path relative to the scene root (e.g., "VBox/MyButton" or "." for root node). Argument supports the same syntax as Node.get_node.signal_name- Name of the Godot signal to connect (e.g., "pressed").mapper- Closure that maps signal arguments to your typed message.- The closure receives three arguments:
args,node_handle, andentity:args: &[Variant]: raw Godot arguments (clone if you need detailed parsing).node_handle: &GodotNodeHandle: emitting node; clone into your event if useful.entity: Option<Entity>: Bevy entity the GodotScene component is attached to (Always Some).
- The closure returns an optional Bevy Message, or None to not send the message.
- The closure receives three arguments:
impl Command for SpawnPickup {
fn apply(self, world: &mut World) -> () {
let assets = world.get_resource::<PickupAssets>().cloned();
let mut pickup = world.spawn_empty();
pickup
.insert(Name::new("Pickup"))
.insert(Transform::from_xyz(200.0, 200.0, 0.0));
// Only insert GodotScene if Godot engine is running; useful when running tests without Godot.
if let Some(assets) = assets {
pickup.insert(
GodotScene::from_handle(assets.scene.clone())
// Schedule the "area_entered" signal on the Area2D child
// to be connected to PickupAreaEntered message
.with_signal_connection(
"Area2D",
"area_entered",
|_args, _handle, _entity| {
// Pickup "area_entered" signal mapped
Some(PickupAreaEntered)
},
),
);
}
}
}
Untyped Legacy API (Deprecated)
The legacy API (GodotSignals, GodotSignal, connect_godot_signal) remains available but is deprecated. Prefer the typed API above. Minimal usage for migration:
#![allow(unused)] fn main() { fn connect_legacy(mut q: Query<&mut GodotNodeHandle, With<Button>>, legacy: GodotSignals) { for mut handle in &mut q { legacy.connect(&mut handle, "pressed"); } } fn read_legacy(mut ev: MessageReader<GodotSignal>) { for s in ev.read() { if s.name == "pressed" { /* ... */ } } } }
For physics signals (collisions), use the collisions plugin/events instead of raw signals when possible.