暨上一篇把多個關卡流程設定完,這篇要把傳送門的門前放上一個 boss。然後也一直想做遠程攻擊,所以就放了一個巫師站在門前施放魔法,這樣可以維持距離又能造成玩家的壓力。
這回的重點:
new_demo/src/constants.rs
先引入 Color
,新的一組常數定義巫師的遠程設定:
pub const WIZARD_BOSS_ATTACK_RADIUS: f32 = 260.0;
pub const WIZARD_BOSS_CAST_MIN_DISTANCE: f32 = 140.0;
pub const WIZARD_BOSS_PROJECTILE_SPEED: f32 = 360.0;
pub const WIZARD_BOSS_PROJECTILE_LIFETIME: f32 = 2.0;
pub const WIZARD_BOSS_PROJECTILE_SIZE: f32 = 18.0;
pub const WIZARD_BOSS_PROJECTILE_HIT_RADIUS: f32 = 24.0;
pub const WIZARD_BOSS_PROJECTILE_COLOR: Color = Color::srgb(0.72, 0.28, 0.92);
ATTACK_RADIUS
:施法上限(傳送門前站著也會被打)。CAST_MIN_DISTANCE
:過近時會後退,防止玩家卡在腳邊無限揮空。PROJECTILE_*
:速度/壽命/碰撞半徑與視覺尺寸,魔法球顏色直接用 Color::srgb
設成紫色。素材方面使用新增的 assets/characters/enemies/wizard.png
、assets/weapons/enemy/wizard_staff.png
,法杖縮放改成 1.0,並把 offset 調到 8.5, -2.0
讓武器貼在右手。
為了讓巫師的魔法球能在 ECS 裡統一管理,new_demo/src/components/enemy.rs
增加兩個 component:
#[derive(Component)]
pub struct BossWizardProjectile {
pub velocity: Vec2,
pub damage: i32,
}
#[derive(Component)]
pub struct BossWizardProjectileLifetime {
pub timer: Timer,
}
魔法球會帶著速度 / 傷害值,搭配 Timer
自動過期。產生時同時加上 LevelEntity
,換關即會被清掉。
new_demo/src/systems/enemy.rs
的 boss_wizard_ai_system
原本只是共用近戰巡邏邏輯,現在改成專屬流程:
to_player
和 distance
是整個策略的基礎。距離小於 CAST_MIN_DISTANCE
就以 retreat_dir
往反方向滑動;距離過大則微幅前進,始終保持在 140~260 之間。因為巫師只需左右移動,所以 clamp 只作用在 translation.x
。EnemyPatrol
的 bounds()
。這樣做的好處是,萬一場景很狹長,巫師還是會遵守房間左右邊界,不會退到牆外。EnemyAttack.cooldown.tick(delta)
後判斷 finished()
,若是第一次施法會立即重置冷卻,確保巫師能在看到玩家的瞬間開火。transform.translation
與 direction
傳入,函式內部會根據 WIZARD_BOSS_STAFF_OFFSET_X
和 WIZARD_BOSS_CAST_HEIGHT_OFFSET
把產生點往前、往上偏移,呈現出從法杖頂端發射的視覺效果。patrol.direction = dx.signum()
維持左右 facing 的正負號,後續如果要切換 sprite 甚至決定施法音效,都能沿用這個欄位。這段仍然保留原始巡邏 fallback,若玩家離開觸發/牽制距離,EnemyBehaviorState::Patrolling
會接手,巫師就像一般敵人一樣在房間內來回,避免卡在單一點位等待玩家回頭。
巫師的魔法會在 boss_wizard_projectile_system
中統一更新,相關程式如下:
pub fn boss_wizard_projectile_system(
mut commands: Commands,
time: Res<Time>,
mut projectile_query: Query<(
Entity,
&mut Transform,
&BossWizardProjectile,
&mut BossWizardProjectileLifetime,
)>,
player_target_query: Query<(
&Transform,
Option<&Defense>,
), (With<Player>, Without<PlayerDead>, Without<BossWizardProjectile>)>,
mut player_health_query: Query<
&mut Health,
(With<Player>, Without<PlayerDead>, Without<BossWizardProjectile>),
>,
mut damage_events: EventWriter<PlayerDamagedEvent>,
mut enemy_attack_events: EventWriter<EnemyAttackHitEvent>,
) { /* ... */ }
velocity
持續向前,BossWizardProjectileLifetime
的 Timer
到期就 despawn,確保不會永久漂浮或在離線畫面耗效能。Without<BossWizardProjectile>
,避免和 projectile_query
同時借用 Transform
造成 B0001 錯誤,不然可能會遇到 panic,並且透過條件約束避免重複抓取同一批 component。distance <= WIZARD_BOSS_PROJECTILE_HIT_RADIUS
做圓形範圍判斷。若未來要做更複雜的彈道(例如穿透或爆炸),可以在這邊擴充碰撞邏輯。hits
,統一處理 despawn 與扣血,並發出 PlayerDamagedEvent
、EnemyAttackHitEvent
。這樣 UI、音效、震動等下游行為完全不需修改,只要監聽既有事件即可。同一幀可能有多顆魔法球命中,程式會先把結果放進 hits
陣列,再統一 despawn,確保血量只在成功命中時扣一次,且事件順序穩定。若玩家已死亡,player_health_query.single_mut()
會失敗並落到 else 分支,只把子彈清掉不再寫事件,避免額外 panic。
src/plugins/enemy.rs
:把 boss_wizard_projectile_system
加進 Update
,放在近戰傷害前面,魔法球命中仍會觸發相同的傷害事件。src/resources/level.rs
:第一關 boss_wizards: 1
,進遊戲就能立即觀察巫師招式,,目前還在思考 boss 會在哪幾關出現。src/systems/level.rs
:產生巫師時把 Name
、EnemyAttack
初始值調整好,保持 Fallback 跟巡邏邏輯一樣。配合上一篇的系統,boss 固定站在傳送門前,只要玩家到一定的距離內,就會開始釋放魔法攻擊玩家。
加上一個遠程攻擊的法師 boss 後,傳送門的威脅感立刻提升許多。不過我預想的會是:
今日程式碼同步至 repo