Transitional Mailbox Bridge (Godot -> Bevy Messages)
This pattern is for teams migrating existing Godot/GDScript gameplay into godot-bevy.
It provides a safe, typed bridge from temporary script-side "pending" fields to Bevy messages, so gameplay logic can stay ECS-native while legacy scripts are ported incrementally.
This is a transition aid, not the ideal long-term architecture.
When to use this
Use it when:
- Some gameplay producers are still in GDScript (for example, enemy contact damage or collectible callbacks)
- You want ECS systems to remain the source of truth
- You need a low-risk, iterative migration path
When not to use this
Avoid it for greenfield systems.
For new gameplay, prefer:
- direct Bevy messages/events from Rust systems
- typed signal routing via
GodotSignals - component/resource-driven state in ECS
API overview
godot-bevy provides:
GodotMailboxMessagetraitGodotMailboxPlugin<T, Marker>GodotMailboxSet::Drain
The plugin runs in PhysicsUpdate, queries entities with Marker + GodotNodeHandle,
calls T::drain_from_node(...), and writes T when present.
Example
use bevy::prelude::*;
use godot::builtin::{Variant, Vector3};
use godot::classes::Node;
use godot::prelude::ToGodot;
use godot_bevy::prelude::*;
const PENDING_DAMAGE: &str = "rust_pending_damage";
const PENDING_DAMAGE_FORCE: &str = "rust_pending_damage_force";
#[derive(Message, Debug, Clone, Copy)]
pub struct PlayerDamageRequest {
pub target: GodotNodeHandle,
pub force: Vector3,
}
impl GodotMailboxMessage for PlayerDamageRequest {
fn drain_from_node(node: &mut Node, source: GodotNodeHandle) -> Option<Self> {
let pending = node.get(PENDING_DAMAGE).try_to::<bool>().unwrap_or(false);
let force = node
.get(PENDING_DAMAGE_FORCE)
.try_to::<Vector3>()
.unwrap_or(Vector3::ZERO);
// Important: clear pending fields as part of draining.
node.set(PENDING_DAMAGE, &false.to_variant());
node.set(PENDING_DAMAGE_FORCE, &Vector3::ZERO.to_variant());
pending.then_some(Self {
target: source,
force,
})
}
}
#[derive(Component)]
struct PlayerMarker;
app.add_plugins(GodotMailboxPlugin::<PlayerDamageRequest, PlayerMarker>::default())
.add_systems(
PhysicsUpdate,
apply_damage_requests.after(GodotMailboxSet::Drain),
);
Migration guidance
- Keep mailbox fields small and explicit (for example,
pending_damage,pending_force). - Drain once per tick and clear immediately.
- Consume typed messages in ECS systems.
- Port producers from GDScript to Rust over time.
- Remove mailbox fields/plugin once all producers are Rust-native.
The end goal is to delete the mailbox layer entirely.