Attachable Components

AttachableComponent turns an editor-authored child node into a component on its parent's Bevy entity. The child is a one-shot carrier: successful attachment queues it for deletion. Its node and path disappear. It never gets a Bevy entity.

Use this derive for configuration authored as child nodes. Use BevyComponents when the authored node should survive and receive components on its own entity. Neither derive provides live Inspector synchronization.

Define a carrier

GodotCorePlugins registers carriers automatically. Derive GodotClass and AttachableComponent, name the target component, and implement the conversion. The derive is available through godot_bevy::prelude::*.

#![allow(unused)]
fn main() {
use bevy::prelude::Component;
use godot::prelude::*;
use godot_bevy::prelude::*;

#[derive(Component)]
struct Movement {
    max_speed: f32,
}

#[derive(AttachableComponent, GodotClass)]
#[class(init, base=Node)]
#[gdbevy(target = Movement)]
struct MovementComponent {
    #[export]
    max_speed: f32,
}

impl From<&MovementComponent> for Movement {
    fn from(value: &MovementComponent) -> Self {
        Self {
            max_speed: value.max_speed,
        }
    }
}
}

In Godot, add MovementComponent as a child of the node to configure and set max_speed in the Inspector. Keep the carrier empty. Ordinary and internal children both cause rejection before the conversion runs. Nested carriers are also rejected. Rejection preserves the authored subtree; ordinary descendants may mirror without a Bevy relationship to the unmirrored carrier.

Placement and lifetime

Attachment runs after ordinary scene-tree messages, so a parent added in the same batch is available. The carrier's live parent is the destination, even if it was reparented before the messages were processed. Duplicate adds within a drain convert once. A carrier already queued for deletion does not convert again.

The parent must be a live, mirrored node. It cannot be the root viewport (/root), another carrier, excluded, or queued for deletion. A carrier directly under the mirrored current scene root is valid. Exclusion applies to the whole subtree and normally prevents add messages from reaching the mirror.

An invalid placement leaves the carrier alive and unmirrored. The warning names its class, path, parent, and the rejection reason. A missing parent mapping is also rejected. Fix the placement and remove and re-add the carrier to trigger a new attempt. Later empty drains do not retry. Freed, detached, or queued carriers do not convert.

Startup scanning and GodotScene instantiation use the same rules. Detaching and re-adding a surviving scene node cannot restore consumed carriers or replay their configuration onto its new entity. Instantiate the saved PackedScene again for fresh carriers.

Conversion references

From may copy values and retain owned resources. Handles may refer to nodes that survive independently, such as the parent or a sibling outside the carrier. Do not capture the carrier, its descendants, other carriers, or paths that pass through consumed nodes. Do not mutate the tree during conversion.

Captured nodes can still be freed later. Resolve their handles with GodotAccess::try_get when their lifetime is uncertain.