暨上一篇將死亡與重生機制補齊之後,遊戲的基本循環已經成形。要繼續往 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