玩家的負面狀態有中毒,但目前並沒有一個敵人角色可以造成這種狀態,所以需要新增「蜘蛛」這個新敵人。它的攻擊模式不太一樣,並且會吐出造成中毒效果的細絲。
同時也需要整理關卡獎勵流程,讓每一層打完 Boss 的報酬與武器成長有明確節奏,並重新調整 Boss 的能力,會依照玩家目前能力放大 10%~30%。
這回的重點:
蜘蛛的 AI 會在距離合適時,射出一條帶碰撞檢查的蛛絲專案。spider_ranged_attack_system
讀取玩家位置並控制冷卻 (new_demo/src/systems/enemy.rs:486-531
),衝出的一瞬間會呼叫 spawn_spider_web_projectile
建立射出物。核心片段如下:
if distance > attack.radius || distance < SPIDER_MIN_ATTACK_DISTANCE {
continue;
}
let direction = to_player.normalize_or_zero();
if direction == Vec2::ZERO {
continue;
}
spawn_spider_web_projectile(
&mut commands,
transform.translation,
direction,
attack_stat.value(),
);
專案本體在 spider_web_projectile_system
內追蹤碰撞與傷害 (new_demo/src/systems/enemy.rs:900-1035
)。這段程式碼我只顯示命中處理的部分:
if along.abs() <= half_length {
let perpendicular = (to_player - projectile.direction * along).length();
if perpendicular <= SPIDER_WEB_PROJECTILE_HIT_RADIUS {
let damage = compute_damage(projectile.damage, defense_value);
if damage > 0 {
health.current = (health.current - damage).max(0);
damage_events.write(PlayerDamagedEvent { damage, remaining_health: health.current });
}
if let Some(poison) = poison_component.as_mut() {
poison.reset_timer();
} else if !poison_active {
commands.entity(player_entity).insert(Poisoned::new(
PLAYER_POISON_TICK_SECONDS,
PLAYER_POISON_TICK_DAMAGE,
));
}
enemy_attack_events.write(EnemyAttackHitEvent);
commands.entity(entity).despawn();
continue;
}
}
可以看到命中後會先計算傷害,再檢查玩家是否已經中毒;若沒有,就立即插入新的 Poisoned
組件。毒傷本身透過 PLAYER_POISON_TICK_SECONDS
與 PLAYER_POISON_TICK_DAMAGE
控制,這樣就能和既有的 update_player_health_ui
、音效系統整合。
場景啟動時會挑出三個地板擺放寶箱,順序固定為寶箱怪 + 兩個補給箱 (new_demo/src/systems/level.rs:1123-1204
)。這段程式會先從候選地板中挑出三個位置,再交給 Chest::new
建立寶箱。
let mut chest_payload: Vec<(Vec3, ChestContents, &'static str)> = Vec::new();
if let Some(pos) = slots.get(0) {
chest_payload.push((*pos, ChestContents::Mimic, "RewardChestMimic"));
}
if let Some(pos) = slots.get(1) {
let effect = match rng.gen_range(0..3) {
0 => PickupEffect::Heal(ITEM_HEALTH_POTION_HEAL_AMOUNT),
1 => PickupEffect::RestoreStamina(ITEM_STAMINA_POTION_AMOUNT),
_ => PickupEffect::CurePoison,
};
chest_payload.push((*pos, ChestContents::Item(effect), "RewardChestElixir"));
}
這樣每一個房間都保證有寶箱怪製造緊張感,剩下的兩格則是補給抽籤。prop_position_valid
會確保寶箱距離門口、出生點與出口都有安全距離,不會把走道堵死。
打倒 Boss 之後的掉落改成依照關卡索引直接指定裝備 (new_demo/src/systems/level.rs:1258-1291
):
let loot_items = match rewards.level_index {
0 => vec![
PickupEffect::EquipShield(ShieldKind::Level1),
PickupEffect::EquipWeapon(WeaponKind::Level2),
],
1 => vec![PickupEffect::EquipWeapon(WeaponKind::Level3)],
2 => vec![PickupEffect::EquipWeapon(WeaponKind::Level4)],
_ => vec![
PickupEffect::EquipWeapon(WeaponKind::Level5),
PickupEffect::EquipShield(ShieldKind::Level2),
],
};
這個 switch 直接定義四層的掉落順序,不會再隨機跳階。後續只是把效果裝進 Chest::new
,在出口門附近一字排開。透過這個表單,玩家會依序拿到 Lv2~Lv5 武器與升級的盾牌,數值成長非常明確。
現在打完 Boss 後,會出現獎勵寶箱,以及傳送門
wizard_boss_stats
會讀取玩家目前的攻擊、防禦與最大血量,再乘上 1.1(一般關卡)或 1.3(最終戰)當作 Boss 的新數值 (new_demo/src/systems/level.rs:1060-1083
)。具體寫法如下:
let multiplier = if final_boss { 1.3 } else { 1.1 };
if let Some(stats) = player_stats {
let attack = ((stats.attack as f32) * multiplier).ceil() as i32;
let defense = ((stats.defense as f32) * multiplier).ceil() as i32;
let health = ((stats.max_health as f32) * multiplier).ceil() as i32;
return (health.max(1), attack.max(1), defense.max(0));
}
也就是說 Boss 的基礎能力直接跟玩家綁定,即使玩家撿到 Lv5 武器或升級,也不會把 Boss 秒掉;反過來說如果升級不足,Boss 也只會高出 10%,仍然能靠走位與補品打過。
現在的戰鬥節奏比過去多了中遠距離威脅,蜘蛛若沒及時閃開就會吃到毒。打倒 Boss 後也能明確看到下一把武器、下一面盾牌的升級。
今日程式碼同步至 repo