延續上一篇的進度,現在敵人攻擊玩家角色,玩家的血量會扣除,但是當玩家攻擊敵人時,敵人的血量不會有影響,另外門的開啟應該也要調整,否則距離太遠也能開關門這件事也是有點奇怪。
讓玩家能夠對敵人造成傷害,同時也整理了門的互動判定,避免揮劍攻擊時誤觸開門。這次的進度主要分成兩件事:
想法是把揮劍拆成兩個步驟:
AttackInputEvent
一樣由輸入系統觸發,但當武器真的開始擺動時才會廣播 PlayerMeleeAttackEvent
;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