Migration Guide: v0.9 to v0.10

This guide covers breaking changes and new features when upgrading from godot-bevy 0.9.x to 0.10.0.

Table of Contents

Breaking changes:

Breaking: Upgrade to Bevy 0.17

✨ Godot-Bevy is now up to date with Bevy 0.17! ✨

Here is the Bevy 0.16 to 0.17 migration guide for reference: https://bevy.org/learn/migration-guides/0-16-to-0-17/

The biggest difference is that Godot-Bevy now uses Message instead of Event.

Migration Path

Cargo.toml

🗑️ Before:

bevy = { version = "0.16", default-features = false, features = ["bevy_state"] }
bevy_asset_loader = "0.23.0"

🟢 After:

bevy = { version = "0.17", default-features = false, features = ["bevy_state"] }
bevy_asset_loader = "0.24.0-rc.1"

Event to Message

🗑️ Before:

#[derive(Event, Debug)]
pub enum SceneOperationEvent {

🟢 After:

#[derive(Message, Debug)]
pub enum SceneOperationMessage {

🗑️ Before:

app.add_event::<SceneOperationEvent>()

🟢 After:

app.add_message::<SceneOperationMessage>()

🗑️ Before:

mut operation_events: EventReader<SceneOperationEvent>,

🟢 After:

mut operation_events: MessageReader<SceneOperationMessage>,

Migration Checklist

  • Update Bevy to 0.17.
  • Update Bevy Asset Loader to 0.24.0-rc.1.
  • Replace usages of Event with Message.

Breaking: Custom Scene Tree Relationships

Godot's scene tree is now represented by a custom ECS relationship: GodotChildOf / GodotChildren. The built-in Bevy ChildOf / Children relationship is no longer used for scene tree mirroring.

This avoids conflicts with other plugins that use Bevy's hierarchy for their own purposes (physics, AI, scene graphs, etc.).

Migration Path

Queries and Traversal

🗑️ Before:

fn parent_of(entity: Entity, query: Query<&ChildOf>) -> Option<Entity> {
    query.get(entity).ok().map(|parent| parent.parent())
}

fn children_of(entity: Entity, query: Query<&Children>) -> Vec<Entity> {
    query
        .get(entity)
        .map(|children| children.iter().copied().collect())
        .unwrap_or_default()
}

🟢 After:

fn parent_of(entity: Entity, query: Query<&GodotChildOf>) -> Option<Entity> {
    query.get(entity).ok().map(|parent| parent.get())
}

fn children_of(entity: Entity, query: Query<&GodotChildren>) -> Vec<Entity> {
    query
        .get(entity)
        .map(|children| children.iter().copied().collect())
        .unwrap_or_default()
}

Configuration

The scene_tree_add_child_relationship attribute and GodotSceneTreePlugin { add_child_relationship: ... } have been removed.

If you want children to outlive their parents, use the new scene_tree_auto_despawn_children attribute (or the plugin config).

🗑️ Before:

#[bevy_app(scene_tree_add_child_relationship = false)]
fn build_app(app: &mut App) {}

app.add_plugins(GodotSceneTreePlugin {
    add_child_relationship: true,
});

🟢 After:

#[bevy_app(scene_tree_auto_despawn_children = false)]
fn build_app(app: &mut App) {}

app.add_plugins(GodotSceneTreePlugin {
    auto_despawn_children: true,
});

Breaking changes

  • Bevy ChildOf / Children are no longer used for Godot scene tree hierarchy.
  • scene_tree_add_child_relationship was removed.
  • New scene_tree_auto_despawn_children configuration option.

Migration Checklist

  • Replace ChildOf / Children queries with GodotChildOf / GodotChildren.
  • Remove scene_tree_add_child_relationship usage.
  • Use scene_tree_auto_despawn_children if you need to keep children alive on parent despawn.

Breaking: NodeTreeView::from_node now returns a Result type

NodeTreeView::from_node derive macro now returns a Result<Self, NodeTreeViewError> type to avoid surprise panic in user code if a godot node is not found.

Migration Path

Use match, if let or unwrap to handle the Result type returned by NodeTreeView::from_node.

Example 1:

🗑️ Before:

let mut mob_nodes = MobNodes::from_node(mob);

🟢 After:

let mut mob_nodes = MobNodes::from_node(mob).unwrap();

Example 2:

🗑️ Before:

match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| MenuUi::from_node(root))) {
    Ok(menu_ui) => {
        info!("MainMenu: Successfully found menu nodes");
    }
    Err(_) => {
        debug!("MainMenu: Menu nodes not ready yet, will retry next frame");
    }
}

🟢 After:

match MenuUi::from_node(root) {
    Ok(menu_ui) => {
        info!("MainMenu: Successfully found menu nodes");
    }
    Err(_) => {
        debug!("MainMenu: Menu nodes not ready yet, will retry next frame");
    }
}

Breaking changes

Return type of NodeTreeView::from_node derive macro changed from Self to Result<Self, NodeTreeViewError>.

Migration Checklist

  • Use match, if let or unwrap to handle the Result type returned by NodeTreeView::from_node.

Breaking: connect_map Callback Now Returns Option<T>

The callback provided to TypedGodotSignals::connect_map (and related APIs) must now return Option<T> instead of T. When an event is emitted, return Some(event) or return None to suppress event emission. This applies to both immediate (connect_map) and deferred (TypedDeferredSignalConnections) signal connections.

This enables advanced uses like filtering certain signal events, but requires code update for all users of connect_map and deferred connection mappers.

Migration Path

🗑️ Before (old API: return event value directly):

typed.connect_map(&mut node, "signal", None, |_args, _node_id, _ent| MyEvent {});
// or for deferred:
TypedDeferredSignalConnections::<MyEvent>::with_connection("signal", |_a, _node_id, _e| MyEvent {});

🟢 After (new API: return event value inside Some, or None to suppress):

typed.connect_map(&mut node, "signal", None, |_args, _node_id, _ent| Some(MyEvent {}));
// or for deferred:
TypedDeferredSignalConnections::<MyEvent>::with_connection("signal", |_a, _node_id, _e| Some(MyEvent {}));

Migration Checklist

  • Update all usages of TypedGodotSignals<...>::connect_map to return Some(event) (or None to suppress event).
  • Update all TypedDeferredSignalConnections::<...>::with_connection mappers for typed deferred signals to return Option<T>.