iT邦幫忙

2025 iThome 鐵人賽

DAY 11
1
Rust

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

玩家近戰攻擊命中與門的互動優化

  • 分享至 

  • xImage
  •  

延續上一篇的進度,現在敵人攻擊玩家角色,玩家的血量會扣除,但是當玩家攻擊敵人時,敵人的血量不會有影響,另外門的開啟應該也要調整,否則距離太遠也能開關門這件事也是有點奇怪。

讓玩家能夠對敵人造成傷害,同時也整理了門的互動判定,避免揮劍攻擊時誤觸開門。這次的進度主要分成兩件事:

  • 建立玩家攻擊事件與判定範圍
  • 重新定義門的互動判斷條件

玩家攻擊 - 事件驅動的命中判定

想法是把揮劍拆成兩個步驟:

  1. AttackInputEvent 一樣由輸入系統觸發,但當武器真的開始擺動時才會廣播 PlayerMeleeAttackEvent
  2. player_melee_attack_system 接手事件,根據玩家面向、距離與攻擊扇形範圍來扣除敵人的血量。

集中在 constants.rs 的幾個新常數讓整體調整更簡單:

// src/constants.rs
pub const PLAYER_ATTACK_DAMAGE: i32 = 15;
pub const PLAYER_ATTACK_RADIUS: f32 = 48.0;
pub const PLAYER_ATTACK_REACH: f32 = 24.0;
pub const PLAYER_ATTACK_FACING_COS_THRESHOLD: f32 = 0.25;

攻擊發出後,系統會以玩家前方的圓形區域為中心,檢查是否有敵人在有效距離內,並使用 dot product 確保只有面前的敵人會中招:

// src/systems/attack.rs
if facing_direction.dot(direction_to_enemy) < PLAYER_ATTACK_FACING_COS_THRESHOLD {
    continue; // 不在揮劍方向內就忽略
}

let new_health = (health.current - total_damage).max(0);
if new_health != health.current {
    health.current = new_health;
    info!("玩家攻擊造成 {} 傷害,敵人剩餘 HP: {}", total_damage, health.current);
}

AttackPlugin 則在 Update 排程中註冊了這些事件與系統,確保攻擊事件在同一幀內完成武器動畫與傷害判定。

門的互動 - 距離與面向雙重檢查

遠距離也能開門

只要按空白鍵就會嘗試與門互動,這個是一開始的設定。原因是想讓使用者可以很直覺的控制,並且判定的距離寬鬆一點範圍又大,但是問題是會導致戰鬥時常常誤開門。

所以把門的互動條件換成距離 + 面向兩個條件:

  • 距離限制:DOOR_INTERACTION_RADIUS 設成約 1.5 個角色高度,只要貼近門才會觸發;
  • 面向限制:透過玩家 PlayerFacing,如果 dot product 小於 0.5,就判定玩家不是在看門,也就不開門。
// src/systems/input.rs
if distance == 0.0 || distance > DOOR_INTERACTION_RADIUS {
    continue;
}

if facing_direction != Vec2::ZERO {
    let direction = to_door / distance;
    if facing_direction.dot(direction) < DOOR_INTERACTION_FACING_COS_THRESHOLD {
        continue; // 沒有正面朝門,當成純攻擊
    }
}

這樣一來,空白鍵會先嘗試攻擊,只有在同時符合條件的情況下才改為觸發 DoorInteractionEvent,戰鬥與互動終於不會互相干擾。

敵人死亡流程 - 立即清理戰場

敵人血量歸零沒有消失

目前敵人被擊倒後沒有消失,所以需要解決這個問題。新的 despawn_dead_enemies_system 會掃描所有標記為 Enemy 的實體,只要偵測到 Health 降到 0 就掛上一個 EnemyDeathEffect 計時器:

// src/systems/enemy.rs
if health.current <= 0 && death_effect.is_none() {
    sprite.color.set_alpha(1.0);
    commands.entity(entity).insert(EnemyDeathEffect {
        timer: Timer::from_seconds(ENEMY_DEATH_FADE_SECONDS, TimerMode::Once),
    });
}

EnemyPlugin 也把這個系統加入 Update 排程,並放在 player_melee_attack_system 之後,確保同一幀內完成扣血與清除:

// src/plugins/enemy.rs
app.add_systems(
    Update,
    (
        slime_ai_system,
        cyclops_ai_system,
        enemy_contact_attack_system,
        despawn_dead_enemies_system.after(player_melee_attack_system),
        enemy_death_effect_system.after(despawn_dead_enemies_system),
    ),
);

接著 enemy_death_effect_system 會在數幀內遞減 Sprite 的透明度,計時器走完才真正 despawn,讓敵人的離場帶著簡單的煙霧式淡出效果。這樣玩家的每一次命中都會留下乾淨的戰場,也替之後的死亡動畫或掉落物流程清出了空間。

敵人被擊倒後會淡出

小結

目前已經可以讓玩家攻擊敵人,並且把敵人消滅。同時如果沒有謹慎行動的話,就會讓自己的血量扣除。但這裡也發現一個問題,就是玩家被攻擊會被扣血,但是當被扣到歸零時,並不會受到任何影響。

所以下一步要做的是,玩家死亡的呈現,這樣才會讓玩家更認真地對待這個遊戲。

今天的程式碼分享在 repo


上一篇
顯示出血量條 UI
下一篇
死亡與重生迴圈
系列文
Bevy Rogue-lite 勇者冒險篇 × Rust 遊戲開發筆記18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言