Skip to content

Commit 812fcbf

Browse files
authored
Merge pull request #12 from NOVIIC/fix/chores
fix: 优化边界条件错误处理 && 修复穿墙bug && 地图保存相关bug
2 parents dfc3311 + 3006874 commit 812fcbf

7 files changed

Lines changed: 416 additions & 68 deletions

File tree

crates/client/src/camera.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use glam::{Mat4, Vec3};
77
/// 相机模式:决定物理子系统跑哪条分支。
88
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
99
pub enum CameraMode {
10-
/// 飞行:WASD + 空格/Shift,无重力、无碰撞
10+
/// 飞行:WASD + 空格/Shift,无重力、有碰撞(分轴扫动,与 Walk 一致)
1111
Fly,
1212
/// 步行:受重力、跳跃、AABB 分轴碰撞
1313
Walk,

crates/client/src/lib.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ struct App {
145145

146146
/// Phase 7:上一帧的 CPU pass / 网格化统计,用于 HUD。
147147
perf: FramePerfStats,
148+
149+
/// 游戏内通知队列(timestamp_ms, 消息)。用于在 InGame 状态下显示信令错误等浮窗提示。
150+
notifications: Vec<(f64, String)>,
148151
}
149152

150153
#[wasm_bindgen(start)]
@@ -211,6 +214,7 @@ pub async fn start() -> Result<(), JsValue> {
211214
preload_state: None,
212215
relayed_peers: HashSet::new(),
213216
perf: FramePerfStats::default(),
217+
notifications: Vec::new(),
214218
}));
215219

216220
install_event_listeners(&canvas, &document, input.clone(), egui_events, app.clone())?;
@@ -677,20 +681,21 @@ fn render_lobby_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result<(), St
677681
// —— 异步加载存档列表(仅首次进入 Lobby 时触发)——
678682
{
679683
let mut a = app.borrow_mut();
680-
if a.lobby_state.saved_worlds.is_empty() && !a.lobby_state.saves_loading {
684+
if !a.lobby_state.saves_loaded && !a.lobby_state.saves_loading {
681685
a.lobby_state.saves_loading = true;
682686
let app_ref = app.clone();
683687
wasm_bindgen_futures::spawn_local(async move {
684688
let result = crate::storage::list_saved_worlds().await;
685689
let mut a = app_ref.borrow_mut();
686690
a.lobby_state.saves_loading = false;
691+
a.lobby_state.saves_loaded = true;
687692
match result {
688693
Ok(worlds) => {
689694
a.lobby_state.saved_worlds = worlds;
690695
}
691696
Err(e) => {
692697
log::warn!("[lobby] 加载存档列表失败: {e:?}");
693-
a.lobby_state.error_message = Some(format!("加载存档失败: {e:?}"));
698+
a.lobby_state.error_message = Some(format!("Failed to load saves: {e:?}"));
694699
}
695700
}
696701
});
@@ -788,6 +793,9 @@ fn render_lobby_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result<(), St
788793
wasm_bindgen_futures::spawn_local(async move {
789794
if let Err(e) = crate::storage::delete_world_by_key(&key).await {
790795
log::warn!("[lobby] 删除存档失败: {e:?}");
796+
let mut a = app_ref.borrow_mut();
797+
a.lobby_state.error_message = Some(format!("Failed to delete save: {e:?}"));
798+
return;
791799
}
792800
// 刷新列表
793801
let result = crate::storage::list_saved_worlds().await;
@@ -799,6 +807,8 @@ fn render_lobby_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result<(), St
799807
}
800808
Err(e) => {
801809
log::warn!("[lobby] 刷新存档列表失败: {e:?}");
810+
a.lobby_state.error_message =
811+
Some(format!("Failed to refresh saves: {e:?}"));
802812
}
803813
}
804814
});
@@ -814,6 +824,8 @@ fn render_lobby_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result<(), St
814824
}
815825
Err(e) => {
816826
log::warn!("[lobby] 刷新存档列表失败: {e:?}");
827+
a.lobby_state.error_message =
828+
Some(format!("Failed to refresh saves: {e:?}"));
817829
}
818830
}
819831
});
@@ -1770,6 +1782,15 @@ fn apply_room_event(app: &Rc<RefCell<App>>, ev: RoomEvent) {
17701782
}
17711783
RoomEvent::SignalingError(msg) => {
17721784
log::warn!("[net] signaling error: {msg}");
1785+
// InGame 状态下将错误推入通知队列,让玩家在游戏内看到浮窗提示
1786+
if matches!(a.state, AppState::InGame { .. }) {
1787+
let now = now_ms();
1788+
a.notifications.push((now, msg.clone()));
1789+
// 最多保留 8 条通知,超出时移除最旧的
1790+
if a.notifications.len() > 8 {
1791+
a.notifications.remove(0);
1792+
}
1793+
}
17731794
a.connecting_error = Some(msg);
17741795
}
17751796
RoomEvent::PeerCount(n) => {
@@ -2155,6 +2176,16 @@ fn render_game_frame(
21552176
}
21562177
}
21572178

2179+
// 提取游戏内通知(5 秒内有效),供 egui 闭包渲染浮窗
2180+
let active_notifications: Vec<String> = a
2181+
.notifications
2182+
.iter()
2183+
.filter(|(ts, _)| now_local - ts < 5000.0)
2184+
.map(|(_, msg)| msg.clone())
2185+
.collect();
2186+
// 清理过期通知
2187+
a.notifications.retain(|(ts, _)| now_local - ts < 5000.0);
2188+
21582189
// —— 跑 egui:在同一 ctx.run 内绘制 HUD + 玩家列表 + 名牌 + 聊天浮窗 + 聊天框 + 暂停菜单 ——
21592190
let App {
21602191
ref egui_ctx,
@@ -2170,6 +2201,10 @@ fn render_game_frame(
21702201
if let Some(hud) = hud_data.as_ref() {
21712202
draw_hud(ctx, hud.clone());
21722203
}
2204+
// 1b) 游戏内通知浮窗(信令错误等,5 秒自动消失)
2205+
if !active_notifications.is_empty() {
2206+
draw_toast_notifications(ctx, &active_notifications);
2207+
}
21732208
// 2) 玩家列表
21742209
ui::players::draw_player_list(ctx, &player_list_entries);
21752210
// 3) 远端玩家名牌
@@ -3068,6 +3103,33 @@ fn draw_hud(ctx: &egui::Context, data: HudData) {
30683103
});
30693104
}
30703105

3106+
/// 在屏幕顶部居中绘制通知浮窗(信令错误等),5 秒自动消失。
3107+
/// 多条通知从上到下堆叠,半透明深色背景 + 橙红色文字。
3108+
fn draw_toast_notifications(ctx: &egui::Context, messages: &[String]) {
3109+
egui::Area::new(egui::Id::new("toast_notifications"))
3110+
.anchor(egui::Align2::CENTER_TOP, egui::vec2(0.0, 60.0))
3111+
.order(egui::Order::Foreground)
3112+
.show(ctx, |ui| {
3113+
ui.vertical_centered(|ui| {
3114+
for msg in messages {
3115+
egui::Frame::default()
3116+
.fill(egui::Color32::from_rgba_unmultiplied(40, 20, 20, 200))
3117+
.corner_radius(egui::CornerRadius::same(6))
3118+
.inner_margin(egui::Margin::symmetric(16, 8))
3119+
.show(ui, |ui| {
3120+
ui.set_max_width(420.0);
3121+
ui.label(
3122+
egui::RichText::new(msg)
3123+
.color(egui::Color32::from_rgb(240, 140, 120))
3124+
.size(14.0),
3125+
);
3126+
});
3127+
ui.add_space(4.0);
3128+
}
3129+
});
3130+
});
3131+
}
3132+
30713133
// ============================================================
30723134
// 工具
30733135
// ============================================================

0 commit comments

Comments
 (0)