上一篇完成了盾牌的裝備,想到武器應該也可以比照辦理,所以決定擴充武器系統。預設目標是當玩家打敗 boss 之後,會出現並打開特定寶箱時,可以獲得更高等級的武器,可以立即感受到攻擊力的成長。也順便整理武器的圖片素材與資料結構,讓未來要新增武器時更容易維護。
設定目標:
new_demo/src/components/equipment.rs 新增 WeaponKind 與 EquippedWeapon:
#[derive(Clone, Copy, Debug)]
pub enum WeaponKind {
    Level1,
    Level2,
    Level3,
    Level4,
    Level5,
}
impl WeaponKind {
    pub fn attack_bonus(&self) -> i32 {
        match self {
            WeaponKind::Level1 => 0,
            WeaponKind::Level2 => 6,
            WeaponKind::Level3 => 12,
            WeaponKind::Level4 => 20,
            WeaponKind::Level5 => 30,
        }
    }
    pub fn right_sprite_path(&self) -> &'static str {
        match self {
            WeaponKind::Level1 => "weapons/lv1.png",
            WeaponKind::Level2 => "weapons/lv2.png",
            WeaponKind::Level3 => "weapons/lv3.png",
            WeaponKind::Level4 => "weapons/lv4.png",
            WeaponKind::Level5 => "weapons/lv5.png",
        }
    }
}
所有武器數值與圖片素材都集中在這個 enum 中。EquippedWeapon 則儲存目前等級與攻擊加成,讓系統在任何地方都能讀取現況。武器素材同樣以 lv1.png ~ lv5.png 命名,放在 new_demo/assets/weapons/,方便後續擴充。

玩家角色誕生時,直接掛上 EquippedWeapon::new(WeaponKind::Level1),並把手中的 Sprite 改為對應貼圖:
commands
    .spawn((
        Player,
        Sprite::from_image(asset_server.load("characters/players/knight_lv1.png")),
        Attack::new(PLAYER_BASE_ATTACK),
        EquippedWeapon::new(WeaponKind::Level1),
        // ...
    ));
commands
    .spawn((
        Weapon,
        Sprite::from_image(asset_server.load(WeaponKind::Level1.right_sprite_path())),
        WeaponSprites {
            right_sprite: asset_server.load(WeaponKind::Level1.right_sprite_path()),
            left_sprite: asset_server.load(WeaponKind::Level1.left_sprite_path()),
        },
        Name::new(format!("Equipped{}", WeaponKind::Level1.display_name())),
    ));
如此一來,角色在出現時就擁有等級資訊,升級時也只要更新這個 component。
參考上一篇的盾牌流程,補上 WeaponEquipEvent。new_demo/src/systems/equipment.rs 讀取事件後會:
Attack::adjust_bonus 即時計算總攻擊力。EquippedWeapon,記住目前的武器等級及加成。for event in events.read() {
    let new_bonus = event.kind.attack_bonus();
    attack.adjust_bonus(new_bonus - current_bonus);
    *equipped_weapon = EquippedWeapon::new(event.kind);
    if let Some(children) = children {
        for child in children.iter() {
            if let Ok((entity, mut weapon_sprites, mut sprite)) = weapon_query.get_mut(*child) {
                let right = asset_server.load(event.kind.right_sprite_path());
                weapon_sprites.right_sprite = right.clone();
                weapon_sprites.left_sprite = asset_server.load(event.kind.left_sprite_path());
                sprite.image = right;
                commands
                    .entity(entity)
                    .insert(Name::new(format!("Equipped{}", event.kind.display_name())));
                break;
            }
        }
    }
}
事件來源包含兩條:
chest_item_reveal_system 送出。player_pickup_detection_system 送出。EquipmentPlugin 將 handle_weapon_equip_events 排在 Update 迴圈中,緊跟著寶箱與拾取的系統,確保畫面與數值同步更新。
為了方便測試,新版的 spawn_weapon_demo_chests 將房間中心附近的地板挑出來,依序會有六個寶箱:兩個盾牌、四個武器。
let chest_loadout = [
    (
        ChestContents::Item(PickupEffect::EquipShield(ShieldKind::Level1)),
        ShieldKind::Level1.display_name(),
    ),
    (
        ChestContents::Item(PickupEffect::EquipShield(ShieldKind::Level2)),
        ShieldKind::Level2.display_name(),
    ),
    (
        ChestContents::Item(PickupEffect::EquipWeapon(WeaponKind::Level2)),
        WeaponKind::Level2.display_name(),
    ),
    // ... Lv3~Lv5
];
for (index, (contents, label)) in chest_loadout.iter().enumerate() {
    let Some(base_position) = positions.get(index) else { break };
    commands.spawn((
        Chest::new(contents.clone()),
        Sprite::from_image(asset_server.load("items/chests/chest_closed.png")),
        Transform::from_translation(Vec3::new(base_position.x, base_position.y, CHEST_Z))
            .with_scale(Vec3::splat(CHEST_SCALE)),
        Name::new(format!("{}Chest", label)),
    ));
}
靠近寶箱按空白鍵之後,展示武器素材並在計時器結束時替玩家升級。Log 會顯示攻擊總值,例如「武器裝備完成,當前攻擊:51 (SwordLv4)」,方便確認數值是否如預期增加。
lv1.png 武器,造型與攻擊力對應 WeaponKind::Level1。lv2.png ~ lv5.png,攻擊加成最多可提升 +30。ChestContents 換成對應項目即可。
打完怪或開箱後,武器外觀與攻擊數值同步升級。
目前把武器升級的資料與事件管線串起來,未來如果要新增其它武器類型(例如弓或法杖)時,只要擴充 WeaponKind 與對應的事件處理即可。寶箱與地上掉落都走同一條流程,也方便在 boss 戰後直接丟出高等級武器作為獎勵。
今日程式碼同步至 repo