iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Rust

Bevy Rogue-lite 勇者冒險篇 × Rust 遊戲開發筆記系列 第 14

道具效果與自動拾取系統

  • 分享至 

  • xImage
  •  

接下來要讓戰鬥增加一些難度,主要是對角色有一些限制,還有在玩的時候可能會遇到一些異常狀態。所以想到的是角色不能無止盡地揮劍,在異常狀態下會有持續的壓力。不過也加入了一下東西在地圖上,放些資源讓玩家去權衡取捨。

這個章節預計導入三個核心要素:

  1. Stamina 耐力值 — 攻擊會耗費資源,停下來才能喘息回復。
  2. Poisoned 中毒狀態 — 會持續扣血,死亡或解毒藥水才能解除。
  3. 地圖補給品 — 地圖會隨機散落藥水,經過就可以自動使用。

Stamina - 用資源控制輸出節奏

Component 與常數

src/components/stats.rs 新增 Stamina

#[derive(Component, Debug, Clone)]
pub struct Stamina {
    pub current: f32,
    pub max: f32,
    pub regen_per_second: f32,
}

提供 spend()regen()refill() 等方法,呼叫端完全不用碰欄位。相關數值集中在 src/constants.rs

pub const PLAYER_MAX_STAMINA: f32 = 100.0;
pub const PLAYER_STAMINA_REGEN_PER_SECOND: f32 = 25.0;
pub const PLAYER_ATTACK_STAMINA_COST: f32 = 35.0;

玩家在 spawn_playersrc/systems/setup.rs)時就帶著滿耐力,保證開場即可揮第一刀。

攻擊扣耐力

src/systems/attack.rsattack_input_system 現在會先檢查耐力:

if !stamina.spend(PLAYER_ATTACK_STAMINA_COST) {
    info!("耐力不足,攻擊動作取消。");
    return;
}

也就是說,只要耐力不足,攻擊事件就不會送出去,武器也不會被重置計時,徹底避免空白鍵連發刷怪這個無腦攻擊。

停手就回復

player_stamina_regen_system 放在 src/systems/player_status.rs,只要鬆開空白鍵(沒有持續攻擊),每秒回復 PLAYER_STAMINA_REGEN_PER_SECOND。這讓戰鬥節奏變成「找到破綻 → 爆發 → 拉開距離喘氣」,比起無腦輸出更貼近魂系節奏,也讓地圖上的補給品有存在價值。

Poisoned - 持續壓力與復活清理

Component 與扣血流程

同一個檔案(stats.rs)也加入 Poisoned

#[derive(Component, Debug)]
pub struct Poisoned {
    pub tick_timer: Timer,
    pub damage_per_tick: i32,
}

常數定義在 constants.rs

pub const PLAYER_POISON_TICK_SECONDS: f32 = 1.25;
pub const PLAYER_POISON_TICK_DAMAGE: i32 = 3;

player_poison_tick_system 每 1.25 秒觸發一次,處理邏輯集中在 src/systems/player_status.rs

if !poisoned.tick_timer.tick(time.delta()).just_finished() {
    return;
}

let damage = poisoned.damage_per_tick;
health.current = (health.current - damage).max(0);
info!("中毒造成 {} 點傷害,玩家剩餘 HP: {}", damage, health.current);

這段程式碼是用上一篇的 PlayerDamagedEvent,因此受傷動畫和 UI 已經自動支援。

死亡與解毒處理

player_respawn_systemsrc/systems/health.rs)中,復活時會補滿血、補滿耐力並移除 Poisoned,避免帶著負面狀態重生。

另一方面,如果玩家手上有解毒藥水或使用 Debug 快捷鍵,也可以即時移除 Component,詳細在後面的拾取系統介紹。

在 UI 增加看得見的資源與狀態

系統邏輯更新後,資訊也要跟上。所以在 src/systems/ui.rs 大幅調整:

  1. 新增 Stamina BarPlayerStaminaUiRoot 放在血條下方,綠色長條即時顯示耐力占比。
  2. 狀態提示文字PlayerStatusText 顯示 ASCII 訊息:
    • 中毒時顯示 POISON: HP -3 per tick | Use [G] antidote to cleanse
    • 否則顯示 Space drains stamina, release to recover | [T] refill [Y] apply poison
  3. Debug 面板擴充update_player_stats_panel 追加 stamina line 以及 STATUS 欄位,讓測試人員可以直接看到目前是否中毒、攻擊一次耗掉多少耐力。

整個 UI 需要把提示換成 ASCII,否則會有字型缺字的情況。

UI 更新

Debug 快捷鍵:快速重現狀態

player_status_debug_shortcuts_systemsrc/systems/player_status.rs)提供三個常用指令:

  • Tstamina.refill(),模擬喝耐力藥水。
  • G:移除 Poisoned,當成解毒藥水。
  • Y:如果目前沒有中毒,就加上 Poisoned::new(...)。當玩家已經中毒時再按一次會提示「請先使用解毒藥水」。

這組指令能搭配 UI 立刻驗證各種狀態,方便在遊戲中測試,但最後這個要記得拿掉。

模擬中毒以及移除中毒狀態

模擬中毒以及移除中毒狀態。

在地圖隨機產生補給跟自動使用系統

有了耐力與中毒,接下來就是補給系統。把三種藥水的圖片放進 assets/items/potions/

藥水圖片資源

  • health.png:紅藥水補 40 HP。
  • stamina.png:綠藥水補 60 Stamina。
  • toxic.png:灰藥水解除 Poisoned。

(藍色回魔留給未來的 MP 系統。)

Component 與 Plugin

src/components/items.rs 定義 PickupPickupEffectsrc/plugins/items.rs 插入新的 ItemPlugin,在 PostStartup 階段呼叫 spawn_random_pickups,並在 Update 階段執行 player_pickup_detection_system

隨機產生流程

spawn_random_pickupssrc/systems/items.rs)會:

  1. 掃描所有 RoomTile,挑出室內與戶外地板。
  2. 將座標打亂後取出 ITEM_RANDOM_PICKUP_COUNT(現在是 6 個)。
  3. 依序在地圖上丟紅、綠、灰藥水,Sprite 由 AssetServer 載入。
  4. 位置會加上 ITEM_PICKUP_Z_OFFSET 讓道具懸浮一點點,看起來比較像在地上。

常數全部寫在 constants.rs,修改起來很直觀。

自動使用與效果

player_pickup_detection_system 每禎檢查玩家與所有 Pickup 的距離,小於 ITEM_PICKUP_DISTANCE 就觸發效果並 despawn:

match &pickup.effect {
    PickupEffect::Heal(amount) => {
        let before = health.current;
        health.current = (health.current + amount).min(health.max);
        info!("拾取紅色藥水:HP {} -> {}", before, health.current);
    }
    PickupEffect::RestoreStamina(amount) => {
        if let Some(stamina_ref) = stamina.as_mut() {
            let before = stamina_ref.current;
            stamina_ref.current = (stamina_ref.current + amount).min(stamina_ref.max);
            info!("拾取綠色藥水:耐力 {:.1} -> {:.1}", before, stamina_ref.current);
        }
    }
    PickupEffect::CurePoison => {
        if poison_state.is_some() {
            commands.entity(player_entity).remove::<Poisoned>();
            info!("拾取解毒藥水:中毒狀態已解除");
        } else {
            info!("拾取解毒藥水:目前沒有中毒狀態");
        }
    }
}

使用者完全不用按鍵,「走過去就撿」;同時 log 會顯示前後差值,方便調整補量。之後如果要導入背包,也可以把 despawn 改成發送事件。

中毒狀態下撿到藥水解除狀態

中毒狀態下撿到藥水解除狀態

小結

有了耐力、異常狀態與補給,戰鬥步調和資源管理算是建立起來。接下來的重點是讓遊戲進入真正的「探索 → 戰鬥 → 恢復 → 前進」迴圈,玩家現在必須注意資源、規劃路線,也有更多可視化回饋。接下來只要把掉落、寶箱、裝備串起來,就能構成完整的 Rogue-lite 框架。

今日程式碼同步至 repo


上一篇
增加角色屬性數值
下一篇
打怪掉落道具
系列文
Bevy Rogue-lite 勇者冒險篇 × Rust 遊戲開發筆記18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言