1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
//! # image_overlay.rs
//!
//! # image_overlay.rs 文件
//!
//! ## Module Overview
//!
//! ## 模块概述
//!
//! Provides a debug tool to overlay an image on the screen (toggled via F4), useful for comparing in-game visuals with reference assets.
//!
//! 提供一个在屏幕上覆盖图像的调试工具(通过 F4 切换),用于将游戏内视觉效果与参考资产进行对比。
#[cfg(feature = "debug")]
pub mod debug_image_overlay {
use bevy::prelude::*;
use std::fs;
use std::path::Path;
use std::time::SystemTime;
/// Resource to control image overlay visibility.
///
/// 控制图像覆盖可见性的调试资源。
#[derive(Resource, Default)]
pub struct ImageOverlaySettings {
pub show_overlay: bool,
}
/// Component for the overlay image entity.
///
/// 覆盖图像实体的组件。
#[derive(Component)]
pub struct DebugImageOverlay;
/// Set up the image overlay debug systems.
///
/// 设置图像覆盖调试系统。
pub fn setup_image_overlay_debug(app: &mut App) {
app.init_resource::<ImageOverlaySettings>().add_systems(
Update,
(toggle_image_overlay_system, maintain_overlay_system),
);
}
/// Toggle the image overlay with the F4 key (debug only).
///
/// F4 键切换图像覆盖的系统(仅调试模式)。
fn toggle_image_overlay_system(
keyboard: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<ImageOverlaySettings>,
mut commands: Commands,
asset_server: Res<AssetServer>,
overlay_query: Query<Entity, With<DebugImageOverlay>>,
window_query: Query<&Window>,
) {
if keyboard.just_pressed(KeyCode::F4) {
settings.show_overlay = !settings.show_overlay;
if settings.show_overlay {
// Remove any existing overlay entity before spawning a new one.
//
// 若已存在覆盖层实体,则在生成新实体前先移除。
for entity in overlay_query.iter() {
commands.entity(entity).despawn();
}
// Look up the most recently modified image in the debug folder.
//
// 在 debug 文件夹中查找最近修改的图像。
if let Some(latest_image_path) = find_latest_debug_image() {
info!("Loading debug overlay image: {}", latest_image_path);
// Load the selected image asset.
//
// 加载所选的图像资源。
let image_handle: Handle<Image> = asset_server.load(&latest_image_path);
// Query the current window size for correct scaling.
//
// 查询当前窗口尺寸以便正确缩放。
if let Ok(window) = window_query.single() {
let window_width = window.width();
let window_height = window.height();
// Spawn the overlay node with a semi-transparent background.
//
// 创建带半透明背景的覆盖层节点。
commands
.spawn((
Name::new("DebugImageOverlay"),
DebugImageOverlay,
Node {
position_type: PositionType::Absolute,
width: Val::Percent(100.0),
height: Val::Percent(100.0),
top: Val::Px(0.0),
left: Val::Px(0.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.3)),
ZIndex(1000),
))
.with_children(|parent| {
parent.spawn((
ImageNode {
image: image_handle,
// Render the image as semi-transparent.
//
// 以半透明方式渲染图像。
color: Color::srgba(1.0, 1.0, 1.0, 0.7),
..default()
},
Node {
// Scale the image to fit the window while preserving aspect ratio.
//
// 缩放图像以适配窗口并保持纵横比。
width: Val::Percent(100.0),
height: Val::Percent(100.0),
max_width: Val::Px(window_width),
max_height: Val::Px(window_height),
..default()
},
));
});
info!("Debug image overlay: ON");
}
}
} else {
// Remove the overlay entity.
//
// 移除覆盖层实体。
for entity in overlay_query.iter() {
commands.entity(entity).despawn();
}
info!("Debug image overlay: OFF");
}
}
}
/// Maintain the overlay entity and remove it when needed.
///
/// 维护覆盖层的系统(如需要则移除)。
fn maintain_overlay_system(
settings: Res<ImageOverlaySettings>,
mut commands: Commands,
overlay_query: Query<Entity, With<DebugImageOverlay>>,
) {
if !settings.show_overlay {
for entity in overlay_query.iter() {
commands.entity(entity).despawn();
}
}
}
/// Find the most recently modified image in the debug folder.
///
/// 查找 debug 文件夹中最近修改的图像。
fn find_latest_debug_image() -> Option<String> {
// Check multiple potential debug folder locations.
//
// 检查多个可能的 debug 文件夹位置。
let possible_paths = ["crates/souprune/assets/debug", "assets/debug"];
let extensions = ["png", "jpg", "jpeg", "gif", "bmp", "tiff"];
let mut latest_file: Option<(String, SystemTime)> = None;
let mut found_debug_folder = false;
for debug_path in &possible_paths {
if !Path::new(debug_path).exists() {
continue;
}
found_debug_folder = true;
if let Ok(entries) = fs::read_dir(debug_path) {
for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type()
&& file_type.is_file()
&& let Some(file_name) = entry.file_name().to_str()
{
// Check whether the file uses a supported image extension.
//
// 判断文件是否使用受支持的图像扩展名。
if let Some(extension) = file_name.split('.').next_back()
&& extensions.contains(&extension.to_lowercase().as_str())
&& let Ok(metadata) = entry.metadata()
&& let Ok(modified) = metadata.modified()
{
let relative_path = format!("debug/{}", file_name);
if latest_file.is_none() || latest_file.as_ref().unwrap().1 < modified {
latest_file = Some((relative_path, modified));
}
}
}
}
// Once files are found in this path we can stop probing others.
//
// 在该路径找到文件后即可停止继续检查其他路径。
if latest_file.is_some() {
break;
}
}
}
if !found_debug_folder {
warn!("Debug folder not found in any of the expected locations");
return None;
}
if let Some((path, _)) = latest_file {
info!("Selected latest debug image: {}", path);
Some(path)
} else {
warn!("No image files found in debug folder");
None
}
}
}