interaction bridge pattern
Guide for interaction bridge pattern
Interaction Bridge Pattern
Purpose
Decouple UI "use item" actions from specific game logic (equip, consume, etc.) using an event translation layer.
Implementation
1. Create Interaction Plugin
File:
src/systems/player/interactions.rsuse bevy::prelude::*;
use crate::systems::data::GameData;
use crate::systems::data::SkillsData; // [NEW] Required for food item lookup
use crate::systems::player::inventory::{UseItemEvent, ItemId};
use crate::systems::player::equipment::EquipItemEvent;
use crate::systems::skills::cooking::EatFoodEvent;
pub struct InteractionPlugin;
impl Plugin for InteractionPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, handle_use_item_events);
}
}
pub fn handle_use_item_events(
mut use_events: EventReader<UseItemEvent>,
mut equip_events: EventWriter<EquipItemEvent>,
mut eat_events: EventWriter<EatFoodEvent>,
game_data: Res<GameData>,
skills_data: Res<SkillsData>, // [NEW] Required for food item validation
) {
for event in use_events.read() {
if let Some(item_def) = game_data.items.get(&event.item.0) {
// Check if equippable
if item_def.equip_slot.is_some() {
equip_events.send(EquipItemEvent {
entity: event.entity,
item_id: event.item,
});
info!("Requesting equip for {}", item_def.name);
}
// Check if edible (using SkillsData for verification)
else if skills_data.food_items.contains_key(&event.item.0) {
eat_events.send(EatFoodEvent {
player: event.entity, // [UPDATED] field renamed from 'entity'
food_item: event.item, // [UPDATED] field renamed from 'item_id'
});
info!("Requesting consumption for {}", item_def.name);
}
else {
info!("Used {} (No action defined)", item_def.name);
}
}
}
}2. Register Plugin
File:
src/systems/player/mod.rspub mod interactions; // Add module declaration
pub struct PlayerPlugin;
impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
inventory::InventoryPlugin,
equipment::EquipmentPlugin,
interactions::InteractionPlugin, // Register plugin
// ... other plugins
));
}
}Key Benefits
- Extensibility: Easy to add new item types (e.g., readable scrolls, drinkable potions)
- Testability: Can test interaction logic independently from UI
- Maintainability: Single source of truth for "what happens when you use X"
- Data-Driven: Behavior determined by item definition, not hardcoded
Event Flow
┌─────────────┐
│ Inventory │
│ UI │ User right-clicks item → "Use / Equip"
└──────┬──────┘
│
│ UseItemEvent { entity, item }
▼
┌─────────────────┐
│ Interaction │
│ Plugin │ Reads item_def.equip_slot / consumable
└──────┬──────────┘
│
├─── EquipItemEvent ───► Equipment System
│
└─── EatFoodEvent ──────► Cooking SystemExtension Example
To add a new item type (e.g., readable books):
// In handle_use_item_events:
else if item_def.readable.is_some() {
read_events.send(ReadItemEvent {
entity: event.entity,
item_id: event.item,
});
info!("Reading {}", item_def.name);
}No UI changes needed - the context menu "Use" button works for all item types!