現在玩家血量歸零只會在 log 看到死亡訊息,畫面不會有任何反饋,也沒有真正回到起點。所以這篇預計要把整個死亡 — 重生的流程補齊,順便讓敵人也會重置,體驗終於像個 Rogue-lite 了。
這次的改動分成三個部分:
health_system
現在會將血量歸零的玩家標記成 PlayerDead
並送出 PlayerDiedEvent
,接著 start_player_death_sequence_system
會抽掉傷害殘影、鎖定輸入,並啟動 PlayerDeathState
內的計時器:
// src/systems/health.rs
if health.current <= 0 && dead_marker.is_none() {
commands.entity(entity).insert(PlayerDead);
death_events.write(PlayerDiedEvent);
}
// 啟動死亡計時
death_state.start(PLAYER_DEATH_DISPLAY_SECONDS);
所有控制輸入、移動與攻擊的系統都改成查詢 Without<PlayerDead>
,只要角色處於死亡狀態就完全不能動作,讓死亡演出期間保持一致的體驗。
我在 assets/fonts
補了一份 NotoSans-Regular.ttf
,並在 constants.rs
放入相關常數。新系統 spawn_player_death_screen
會監聽 PlayerDiedEvent
,生成全螢幕遮罩與紅色文字,直到 PlayerRespawnedEvent
才移除:
// src/systems/ui.rs
commands
.spawn((DeathScreenRoot, Node { /*...*/ }, BackgroundColor(overlay_color)))
.with_children(|parent| {
parent.spawn((
DeathScreenText,
Text::new(PLAYER_DEATH_MESSAGE),
TextFont { font: font_handle.clone(), font_size: PLAYER_DEATH_FONT_SIZE, ..Default::default() },
TextColor(text_color),
));
});
這段疊圖被掛進 UiPlugin
的 Update
排程,確保死亡當幀就出現提示,並於 PostUpdate
監聽復活事件清理畫面。
計時器跑完後,player_respawn_system
會把玩家傳回房間入口(沿用 EntranceLocation
資源),恢復滿血並移除 PlayerDead
標記。這時也廣播 PlayerRespawnedEvent
,讓敵人可以同步重置:
// src/systems/health.rs
transform.translation = spawn_position;
health.current = health.max;
commands.entity(entity).remove::<PlayerDead>();
respawn_events.write(PlayerRespawnedEvent);
reset_enemies_on_player_respawn
會掃描所有敵人,還原血量、AI 狀態、冷卻,並移除淡出效果;如果場上已經沒有史萊姆或獨眼巨人,還會透過共用的 spawn_*_internal
重新生成。為了避免與敵人查詢衝突,地板查詢都改成 Query<(&Transform, &RoomTile), Without<Enemy>>
。
// src/systems/enemy.rs
if slime_entities.iter().next().is_none() {
spawn_slime_internal(&mut commands, asset_server.as_ref(), &floor_query);
}
if cyclops_entities.iter().next().is_none() {
spawn_cyclops_internal(&mut commands, asset_server.as_ref(), &floor_query);
}
這樣玩家復活後戰鬥場景就會重新整理,避免敵人殘留在死亡位置或整個房間空掉。
目前的遊戲進度算是可以讓玩家有一個處罰的機制,必須要小心的操作,才能避免生命值被扣光。這裡可以再做的更好的地方有加上音效這類加深體驗的功能,預計之後會有幫這個遊戲加上音效的功能。
今天的程式碼分享在 repo