上一篇完成了盾牌的裝備,想到武器應該也可以比照辦理,所以決定擴充武器系統。預設目標是當玩家打敗 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