use bevy::prelude::*;
const VIRTUAL_WIDTH: f32 = 1920.0;
const VIRTUAL_HEIGHT: f32 = 1080.0;
// 字体资源
struct FontAssets {
default: Handle<Font>,
}
// 图片资源
struct ImageAssets {
background: Handle<Image>,
main_menu: Handle<Image>,
}
// 加载状态
enum AppState {
Loading,
Ready,
}
// 按钮组件
struct StartGameButton;
struct AboutButton;
struct HelpButton;
struct ExitGameButton;
struct SceneEntity;
// 字体大小枚举
enum FontSize {
Small,
Normal,
Large,
XLarge,
XXLarge,
}
impl FontSize {
fn value(self) -> f32 {
match self {
FontSize::Small => 16.0,
FontSize::Normal => 24.0,
FontSize::Large => 32.0,
FontSize::XLarge => 48.0,
FontSize::XXLarge => 64.0,
}
}
}
// 字体样式
enum FontStyle {
Normal,
Bold,
Italic,
Chinese,
}
// 字体设置结构体
struct FontConfig {
style: FontStyle,
size: FontSize,
color: Color,
}
impl Default for FontConfig {
fn default() -> Self {
Self {
style: FontStyle::Normal,
size: FontSize::Normal,
color: Color::WHITE,
}
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<AppState>()
.add_systems(Startup, load_assets)
.add_systems(Update, (
check_assets_loaded.run_if(in_state(AppState::Loading)),
update_ui_scale.run_if(in_state(AppState::Ready)),
handle_button_clicks.run_if(in_state(AppState::Ready)),
))
.add_systems(OnEnter(AppState::Ready), setup)
.run();
}
// 加载资源
fn load_assets(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
// 加载字体
let font_assets = FontAssets {
default: asset_server.load("fonts/SarasaFixedHC-Light.ttf"),
};
// 加载图片
let image_assets = ImageAssets {
background: asset_server.load("gui/game3.png"),
main_menu: asset_server.load("gui/main_menu.png"),
};
commands.insert_resource(font_assets);
commands.insert_resource(image_assets);
// 显示加载画面
commands.spawn((Camera2d, Camera::default()));
// 简单的加载画面
commands
.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::BLACK),
))
.with_children(|parent| {
parent.spawn((
Text::new("Loading..."),
TextFont {
font_size: 48.0,
..default()
},
TextColor(Color::WHITE),
));
});
}
// 检查资源是否加载完成
fn check_assets_loaded(
asset_server: Res<AssetServer>,
font_assets: Res<FontAssets>,
image_assets: Res<ImageAssets>,
mut next_state: ResMut<NextState<AppState>>,
mut commands: Commands,
loading_ui: Query<Entity, With<Node>>,
) {
let font_loaded = matches!(
asset_server.load_state(font_assets.default.id()),
bevy::asset::LoadState::Loaded
);
let background_loaded = matches!(
asset_server.load_state(image_assets.background.id()),
bevy::asset::LoadState::Loaded
);
let main_menu_loaded = matches!(
asset_server.load_state(image_assets.main_menu.id()),
bevy::asset::LoadState::Loaded
);
if font_loaded && background_loaded && main_menu_loaded {
// 清除加载画面
for entity in loading_ui.iter() {
commands.entity(entity).despawn();
}
// 切换到就绪状态
next_state.set(AppState::Ready);
println!("All assets loaded successfully!");
}
}
fn setup(
mut commands: Commands,
_font_assets: Res<FontAssets>,
image_assets: Res<ImageAssets>,
asset_server: Res<AssetServer>,
) {
// Logo设置
let logo_text = "Raven engine";
let logo_font_size = 48.0;
let logo_text_color = Color::srgb(1.0, 0.8, 0.0); // 金色
// UI 根容器
commands
.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::BLACK),
))
.with_children(|parent| {
// 固定尺寸的内容区域
parent
.spawn((
Node {
width: Val::Px(VIRTUAL_WIDTH),
height: Val::Px(VIRTUAL_HEIGHT),
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
))
.with_children(|parent| {
// 背景图片 - game3.png (底层)
parent.spawn((
Node {
width: Val::Px(1920.0),
height: Val::Px(1080.0),
position_type: PositionType::Absolute,
left: Val::Px(0.0),
top: Val::Px(0.0),
..default()
},
ImageNode::new(image_assets.background.clone()),
));
// 主菜单图片 - main_menu.png (覆盖在背景图片上面)
parent.spawn((
Node {
width: Val::Px(1920.0),
height: Val::Px(1080.0),
position_type: PositionType::Absolute,
left: Val::Px(0.0),
top: Val::Px(0.0),
..default()
},
ImageNode::new(image_assets.main_menu.clone()),
));
// ===== UI菜单层 =====
parent.spawn((
SceneEntity,
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Row,
..default()
},
// 使用高Z-index确保UI在Sprite之上
GlobalZIndex(100),
))
.with_children(|parent| {
// 左侧菜单区域
parent.spawn((
Node {
position_type: PositionType::Absolute,
width: Val::Percent(50.0),
height: Val::Percent(100.0),
align_items: AlignItems::Start,
justify_content: JustifyContent::Center,
flex_direction: FlexDirection::Column,
padding: UiRect {
left: Val::Px(50.0),
right: Val::Px(30.0),
top: Val::Px(0.0),
bottom: Val::Px(70.0),
},
row_gap: Val::Px(20.0),
..default()
},
))
.with_children(|parent| {
// Logo文本
parent.spawn((
Text::new(logo_text),
TextFont {
font: asset_server.load("fonts/SarasaFixedHC-Regular.ttf"),
font_size: logo_font_size,
..default()
},
TextColor(logo_text_color),
Node {
margin: UiRect {
left: Val::Px(20.0),
right: Val::Px(0.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
},
..default()
},
GlobalZIndex(110),
));
// 开始游戏按钮
parent
.spawn((
StartGameButton,
Button,
Node {
width: Val::Px(200.0),
height: Val::Px(35.0),
margin: UiRect {
left: Val::Px(40.0),
right: Val::Px(0.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
},
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.0)),
GlobalZIndex(55),
))
.with_children(|parent| {
parent.spawn((
Text::new("开始游戏"),
TextFont {
font: asset_server.load("fonts/SarasaFixedHC-Light.ttf"),
font_size: 26.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
));
});
// 关于按钮
parent
.spawn((
AboutButton,
Button,
Node {
width: Val::Px(200.0),
height: Val::Px(35.0),
margin: UiRect {
left: Val::Px(40.0),
right: Val::Px(0.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
},
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.0)),
GlobalZIndex(55),
))
.with_children(|parent| {
parent.spawn((
Text::new("关于"),
TextFont {
font: asset_server.load("fonts/SarasaFixedHC-Light.ttf"),
font_size: 26.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
));
});
// 帮助按钮
parent
.spawn((
HelpButton,
Button,
Node {
width: Val::Px(200.0),
height: Val::Px(35.0),
margin: UiRect {
left: Val::Px(40.0),
right: Val::Px(0.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
},
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.0)),
GlobalZIndex(55),
))
.with_children(|parent| {
parent.spawn((
Text::new("帮助"),
TextFont {
font: asset_server.load("fonts/SarasaFixedHC-Light.ttf"),
font_size: 26.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
));
});
// 退出按钮
parent
.spawn((
ExitGameButton,
Button,
Node {
width: Val::Px(200.0),
height: Val::Px(35.0),
margin: UiRect {
left: Val::Px(40.0),
right: Val::Px(0.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
},
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.0)),
GlobalZIndex(55),
))
.with_children(|parent| {
parent.spawn((
Text::new("退出"),
TextFont {
font: asset_server.load("fonts/SarasaFixedHC-Light.ttf"),
font_size: 26.0,
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
));
});
});
// 右侧透明区域
parent.spawn((
Node {
width: Val::Percent(50.0),
height: Val::Percent(100.0),
..default()
},
));
});
});
});
}
// 处理按钮点击
fn handle_button_clicks(
mut interaction_query: Query<
(&Interaction, &mut BackgroundColor),
(Changed<Interaction>, With<Button>),
>,
start_button_query: Query<&Interaction, (Changed<Interaction>, With<StartGameButton>)>,
about_button_query: Query<&Interaction, (Changed<Interaction>, With<AboutButton>)>,
help_button_query: Query<&Interaction, (Changed<Interaction>, With<HelpButton>)>,
exit_button_query: Query<&Interaction, (Changed<Interaction>, With<ExitGameButton>)>,
) {
// 处理按钮悬停效果
for (interaction, mut color) in &mut interaction_query {
match *interaction {
Interaction::Hovered => {
*color = BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.1));
}
Interaction::None => {
*color = BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.0));
}
Interaction::Pressed => {
*color = BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.2));
}
}
}
// 处理具体按钮点击事件
for interaction in &start_button_query {
if *interaction == Interaction::Pressed {
println!("开始游戏按钮被点击!");
}
}
for interaction in &about_button_query {
if *interaction == Interaction::Pressed {
println!("关于按钮被点击!");
}
}
for interaction in &help_button_query {
if *interaction == Interaction::Pressed {
println!("帮助按钮被点击!");
}
}
for interaction in &exit_button_query {
if *interaction == Interaction::Pressed {
println!("退出按钮被点击!");
}
}
}
fn update_ui_scale(
windows: Query<&Window>,
mut ui_scale: ResMut<UiScale>,
) {
if let Some(window) = windows.iter().next() {
let window_width = window.width();
let window_height = window.height();
// 计算缩放比例,保持宽高比
let scale_x = window_width / VIRTUAL_WIDTH;
let scale_y = window_height / VIRTUAL_HEIGHT;
let scale = scale_x.min(scale_y);
ui_scale.0 = scale;
}
}