暨上一篇將死亡與重生機制補齊之後,遊戲的基本循環已經成形。要繼續往 Rogue-lite 的隨機化與角色成長前進,第一步就是讓角色的數值可以成長。
過去的攻擊與防禦都寫死在常數中,玩家和敵人的數值都是寫好一個固定的值,沒有辦法延伸到裝備、藥水或升級系統。這篇的目標是把這些數值模組化,建立共用的運算流程,並做出一個可以即時調整的面板方便驗證。整體拆成三件事:
新的 Attack、Defense Component 放在 src/components/stats.rs,各自保留 base、bonus 和 multiplier 三個欄位,並提供 value() 來產出最終數值。額外準備 adjust_bonus()、adjust_multiplier()、reset_modifiers() 等方法,後續系統只要呼叫這些介面就能套用加成,而不用直接碰欄位。
// src/systems/setup.rs
commands.spawn((
    Player,
    Health::new(PLAYER_INITIAL_HEALTH),
    Attack::new(PLAYER_BASE_ATTACK),
    Defense::new(PLAYER_BASE_DEFENSE),
    Velocity::zero(),
    PlayerFacing::new(),
    InputVector(Vec2::ZERO),
));
玩家角色在產生時就擁有這兩個 Component,目前有的敵人包含史萊姆與獨眼巨人也在各自的 spawn_*_internal 裡加上 Attack::new(SLIME_BASE_ATTACK) 以及 Defense::new(SLIME_BASE_DEFENSE)。
常數也同步改名為 *_BASE_ATTACK、*_BASE_DEFENSE,與「最終傷害」做出區隔。之後如果要依照關卡或難度調整敵人,直接換掉基礎值即可。
所有傷害判定都改用 compute_damage(attack, defense)。流程很單純:先加上攻擊方的加成與倍率,若對方有 Defense 就扣掉防禦值,最後保底至少 1 點。玩家揮劍與史萊姆的碰撞攻擊都走同一條管線,確保之後加成不會出現「玩家有生效、敵人沒生效」的落差。
// src/systems/attack.rs
let total_attack = attack.value() * attack_count as i32;
let defense_value = defense.map(|value| value.value());
let damage = compute_damage(total_attack, defense_value);
health.current = (health.current - damage).max(0);
// src/systems/enemy.rs
let defense_value = defense.map(|value| value.value());
let damage = compute_damage(attack_stat.value(), defense_value);
health.current = (health.current - damage).max(0);
因為邏輯集中於 compute_damage,其他系統只需要關心「什麼時候送出攻擊事件」與「攻擊打得到誰」,維護起來更輕鬆。之後如果想加入暴擊或屬性傷害,只要在這裡擴充即可。
為了避免每次測試都得重新編譯,我做了一個 Debug 面板顯示當前屬性,放在血條下方,開場自動產生:
PLAYER STATS
ATK  xxx  (base xxx, bonus +xx, x1.x)
DEF  xxx  (base xxx, bonus +xx, x1.x)
Adjust: [1] ATK +5  [2] ATK -5  [3] DEF +3  [4] DEF -3
        [Q] ATK x-0.1  [W] ATK x+0.1  [A] DEF x-0.1  [S] DEF x+0.1  [R] Reset

player_attribute_debug_input_system 監聽鍵盤輸入,遇到指令就直接修改 Component:
1 / 2: 攻擊加減 5 點3 / 4: 防禦加減 3 點Q / W: 攻擊倍率 -0.1 / +0.1(下限 0)A / S: 防禦倍率 -0.1 / +0.1R: 重置加成與倍率每次修改後會同步更新 UI,也會在 log 印出最新數值,方便對照敵人的受傷情況或玩家被打的血量。面板的文字全部換成 ASCII,因為如果不換的話會有缺字問題。
完成後主要測了四種情境:

1 提升攻擊力,敵人死亡的刀數明顯減少,log 也同步印出更新後的值。
現在的攻擊力已經達到 40,相當輕鬆就秒殺史萊姆。

史萊姆的基礎攻擊力是 5,而玩家角色的原始防禦力是 4,在承受攻擊之後就造成傷害 1。

在防禦力已經升高到 22 之後,在承受史萊姆的 5 點攻擊力之後,即便防禦力比攻擊力還要高,一樣還是會被扣一點,這樣依然保有遊戲的樂趣。

把攻防變成 Component 是後續數值系統的基礎,現在只要某個效果想調整攻擊或防禦,就能直接操作 Attack / Defense 的 bonus 與 multiplier。不必再東挖一個常數、西改一個系統,整體維護成本低很多。
今天的程式碼分享在 repo