README.md
bevy = {git = "https://github.com/bevyengine/bevy.git"}
翻译自 bevyengine/bevy - Examples
Latest commit b724a0f
use bevy::prelude::*; /// 运行一个输出 “Hello World” 的最小示例 fn main() { App::new().add_system(你好世界).run(); } fn 你好世界() { println!("hello world"); }
Latest commit 01aedc8
用四元数旋转 2D 实体。
use bevy::math::Vec3Swizzles; use bevy::prelude::*; use bevy::time::FixedTimestep; const TIME_STEP: f32 = 1.0 / 60.0; const BOUNDS: Vec2 = Vec2::new(1200.0, 640.0); fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_startup_system(初始设置); app.add_system_set( SystemSet::new() .with_run_criteria(FixedTimestep::step(TIME_STEP as f64)) .with_system(玩家运动) .with_system(snap_to_player_system) .with_system(rotate_to_player_system) ); app.run(); } #[derive(Component)] struct Player { movement_speed: f32, rotation_speed: f32, } // 使敌人飞船即时面向玩家所在的位置 #[derive(Component)] struct SnapToPlayer; // 使敌人飞船根据旋转速度慢慢地面向玩家所在的位置 #[derive(Component)] struct RotateToPlayer { rotation_speed: f32, } /// 添加实体、创建摄像机 /// X 轴指向右,Y 轴指向上,Z 轴指向观察者,原点位于屏幕中心点 fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { // 摄像机 commands.spawn(Camera2dBundle::default()); let ship_handle = asset_server.load("ship.png"); let enemy_a_handle = asset_server.load("enemy.png"); let enemy_b_handle = asset_server.load("enemy.png"); let horizontal_margin = BOUNDS.x / 4.0; let vertical_margin = BOUNDS.y / 4.0; // 玩家控制的飞船 commands.spawn(( SpriteBundle { texture: ship_handle, // todo: 不用 into() 吗? ..default() }, Player { movement_speed: 500.0, rotation_speed: f32::to_radians(360.0), }, )); // 在左边和下边生成两个敌人 commands.spawn(( SpriteBundle { texture: enemy_a_handle.clone(), transform: Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0), ..default() }, SnapToPlayer, )); commands.spawn(( SpriteBundle { texture: enemy_a_handle, transform: Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0), ..default() }, SnapToPlayer, )); // 在右边和上边生成两个敌人 commands.spawn(( SpriteBundle { texture: enemy_b_handle.clone(), transform: Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0), ..default() }, RotateToPlayer { rotation_speed: f32::to_radians(45.0), }, )); commands.spawn(( SpriteBundle { texture: enemy_b_handle, transform: Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0), ..default() }, RotateToPlayer { rotation_speed: f32::to_radians(90.0), }, )); } fn 玩家运动(mut query: Query<(&Player, &mut Transform)>, keyboard_input: Res<Input<KeyCode>>) { let (player, mut transform) = query.single_mut(); let mut rotation_factor = 0.0; let mut movement_factor = 0.0; if keyboard_input.pressed(KeyCode::Left) { rotation_factor += 1.0; } if keyboard_input.pressed(KeyCode::Right) { rotation_factor += -1.0; } if keyboard_input.pressed(KeyCode::Up) { movement_factor += 1.0; } // 使飞船绕 Z 轴旋转(也就是在 2D 平面上旋转) // 按下左键获得正值,逆时针旋转;按下右键获得负值,顺时针旋转 transform.rotate_z(rotation_factor * player.rotation_speed * TIME_STEP); // 用飞船当前的旋转(四元数)乘 Y 轴正方向,得到一个新的向量,作为前进方向 let movement_direction = transform.rotation * Vec3::Y; // 移动距离 let movement_distance = movement_factor * player.movement_speed * TIME_STEP; // 下一帧的位置 = 方向(Vec3) * 距离(f32) let translation_delta = movement_direction * movement_distance; transform.translation += translation_delta; // 超出屏幕时,位置设置在屏幕内 // let extents = Vec3::from((BOUNDS / 2.0, 0.0)); let extents = Vec3::new(BOUNDS.x / 2.0, BOUNDS.y / 2.0, 0.0); transform.translation = transform.translation.min(extents).max(-extents); } // 敌人立即转向玩家所在位置 fn snap_to_player_system( mut query: Query<&mut Transform, (With<SnapToPlayer>, Without<Player>)>, player_query: Query<&Transform, With<Player>>, ) { let player_transform = player_query.single(); // 用 Vec2 来进行相关计算 let player_translation = player_transform.translation.xy(); for mut enemy_transform in &mut query { // 获取从敌人面向玩家的方向 let to_player = (player_translation - enemy_transform.translation.xy()).normalize(); // 获取从“敌人的的朝向”直接旋转到“面向玩家的方向”的四元数 // 素材的飞船面向正上方,因此用 Vec::Y let rotate_to_player = Quat::from_rotation_arc(Vec3::Y, to_player.extend(0.0)); enemy_transform.rotation = rotate_to_player; } } /// 敌人以固定的旋转速度转向玩家所在位置,根据点积的值来选择旋转方式 /// /// 值为 1.0,两向量方向相同,夹角为 0 度; /// 值为 0.0,两向量垂直,夹角为 90 度; /// 值为 -1.0,两向量反向平行,夹角为 180 度; /// 略... fn rotate_to_player_system( mut query: Query<(&RotateToPlayer, &mut Transform), Without<Player>>, player_query: Query<&Transform, With<Player>>, ) { let player_transform = player_query.single(); // 用 Vec2 来进行相关计算 let player_translation = player_transform.translation.xy(); for (config, mut enemy_transform) in &mut query { // 获取敌人的朝向、从敌人到玩家的方向,计算点积, // 如果点积约等于 1.0,说明敌人已经面向玩家,不再继续旋转 let enemy_forward = (enemy_transform.rotation * Vec3::Y).xy(); let to_player = (player_translation - enemy_transform.translation.xy()).normalize(); let forward_dot_player = enemy_forward.dot(to_player); if (forward_dot_player - 1.0).abs() < f32::EPSILON { continue; } // 获取敌人默认方向、从敌人到玩家的方向,计算点积, // 如果点积是负数,需要逆时针旋转,如果是正数,需要顺时针旋转 let enemy_right = (enemy_transform.rotation * Vec3::X).xy(); let right_dot_player = enemy_right.dot(to_player); // 确定旋转方向,要取反方向 let rotation_sign = -f32::copysign(1.0, right_dot_player); // 限制旋转 let max_angle = forward_dot_player.clamp(-1.0, 1.0).acos(); let rotation_angle = rotation_sign * (config.rotation_speed * TIME_STEP).min(max_angle); // 旋转 enemy_transform.rotate_z(rotation_angle); } }
Latest commit ffecb05
use bevy::prelude::*; /// 渲染精灵 fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(初始设置) .run(); } fn 初始设置( mut commands: Commands, asset_server: Res<AssetServer>, ) { // 生成 2D 相机 commands.spawn_bundle(OrthographicCameraBundle::new_2d()); // bevy::ecs::Res,对资源的引用 // bevy::asset::AssetServer,从文件系统加载资源 let image_handle: Handle<Image> = asset_server.load("icon.png"); commands.spawn_bundle(SpriteBundle { texture: image_handle, ..Default::default() }); }
Latest commit 07ed1d0
use bevy::prelude::*; /// 生成2D文本 fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(初始设置) .add_system(动画_平移) .add_system(动画_旋转) .add_system(动画_缩放) .run(); } #[derive(Component)] struct AnimateTranslation; #[derive(Component)] struct AnimateRotation; #[derive(Component)] struct AnimateScale; fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { let font = asset_server.load("fonts/LXGWWenKai.ttf"); let text_style = TextStyle { font, font_size: 60.0, color: Color::WHITE, }; let text_alignment = TextAlignment { vertical: VerticalAlign::Center, horizontal: HorizontalAlign::Center, }; // 2D摄像机 commands.spawn_bundle(OrthographicCameraBundle::new_2d()); commands .spawn_bundle(Text2dBundle { text: Text::with_section("平移", text_style.clone(), text_alignment), ..Default::default() }) // 用 AnimateTranslation 组件来标记这段 Text .insert(AnimateTranslation); commands .spawn_bundle(Text2dBundle { text: Text::with_section("旋转", text_style.clone(), text_alignment), ..Default::default() }) // 用 AnimateRotation 组件来标记这段 Text .insert(AnimateRotation); commands .spawn_bundle(Text2dBundle { text: Text::with_section("缩放", text_style.clone(), text_alignment), ..Default::default() }) // 用 AnimateScale 组件来标记这段 Text .insert(AnimateScale); } fn 动画_平移( mut query: Query<&mut Transform, (With<Text>, With<AnimateTranslation>)>, time: Res<Time>, ) { for mut transform in query.iter_mut() { transform.translation.x = 100.0 * time.seconds_since_startup().sin() as f32 - 400.0; transform.translation.y = 100.0 * time.seconds_since_startup().cos() as f32; } } fn 动画_旋转( mut query: Query<&mut Transform, (With<Text>, With<AnimateRotation>)>, time: Res<Time>, ) { for mut transform in query.iter_mut() { transform.rotation = Quat::from_rotation_z(time.seconds_since_startup().cos() as f32); } } fn 动画_缩放( mut query: Query<&mut Transform, (With<Text>, With<AnimateScale>)>, time: Res<Time>, ) { for mut transform in query.iter_mut() { transform.translation = Vec3::new(400.0, 0.0, 0.0); transform.scale = Vec3::splat((time.seconds_since_startup().sin() as f32 + 1.1) * 2.0); } }
Latest commit b724a0f
use bevy::prelude::*; /// 演示如何在应用程序中处理拖放 fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(拖放文件) .run(); } fn 拖放文件(mut events: EventReader<FileDragAndDrop>) { for event in events.iter() { println!("{:?}", event); } }
Latest commit 6d6bc2a
use bevy::prelude::*; /// 一个空的程序,不做任何事 fn main() { App::new().run(); }
Latest commit 6d6bc2a
use bevy::prelude::*; /// 一个空的但带有默认插件的程序 fn main() { App::new().add_plugins(DefaultPlugins).run(); }
Latest commit b724a0f
use bevy::{app::ScheduleRunnerSettings, prelude::*, utils::Duration}; // 这个例子只启用了 bevy 运行所需的一组最低限度的插件 // 也可以从 bevy 中完全删除渲染/窗口插件,只需在 Cargo.toml 中设置成这样: // // [dependencies] // bevy = { version = "*", default-features = false } /// 不添加默认插件的应用程序 fn main() { // 这个 App 只运行一次 App::new() .insert_resource(ScheduleRunnerSettings::run_once()) .add_plugins(MinimalPlugins) .add_system(你好世界) .run(); // 这个App每秒运行 60 次(60 fps) App::new() .insert_resource(ScheduleRunnerSettings::run_loop(Duration::from_scs_f64( 1.0 / 60.0, ))) .add_plugins(MinimalPlugins) .add_system(计数器) .run() } fn 你好世界() { println!("hello world"); } #[derive(Default)] struct CounterState { count: u32, } fn 计数器(mut state: Local<CounterState>) { if state.count % 60 == 0 { println!("{}", state.count); } state.count += 1; }
Latest commit b724a0f
use bevy::prelude::*; /// 这个例子说明了如何在 bevy 中使用日志 fn main() { App::new() // 取消注释即可用新的日志设置来覆盖默认的日志设置: // .insert_resource(bevy::log::LogSettings { // level: bevy::log::Level::TRACE, // filter: "wgpu=warn,bevy_ecs=info".to_string(), // }) .add_plugins(DefaultPlugins) .add_system(日志) .run(); } fn 日志() { // 下面是在每个“日志级别”(按从“最不重要”到“最重要”的顺序)编写新日志的方式: trace!("very noisy"); debug!("helpful for debugging"); info!("helpful information that is worth printing by default"); warn!("something bad happened that is worth printing by default"); error!("something failed"); // 默认情况下,跟踪和调试日志会被忽略,因为它们“嘈杂”, // 可以通过添加LogSettings资源来控制记录的级别,也可以通过RUST_LOG = LEVEL环境变量来设置日志级别, // 例如:RUST_LOG = trace,RUST_LOG = info ,bevy_ecs =warn >此处使用的格式非常灵活。 // 查看:https://docs.rs/tracing-subscriber/*/tracing_subscriber/filter/struct.EnvFilter.html }
Latest commit b724a0f
use bevy::{prelude::*, utils::Duration}; /// 插件是 Bevy 的基础 /// 插件是组件、资源和系统的范围集,提供了特定功能(范围越小越好) /// 这个示例用来演示自定义插件如何被创建和注册 fn main() { App::new() .add_plugins(DefaultPlugins) // 注册插件是“构建程序(app building)”过程的一部分 .add_plugin(PrintMessagePlugin { wait_duration: Duration::from_secs(1), message: "这是一个插件示例。".to_string(), }) .run(); } // 这个“打印消息插件”每隔一段时间打印一次消息 pub struct PrintMessagePlugin { // 在这里放置插件配置 wait_duration: Duration, message: String, } impl Plugin for PrintMessagePlugin { // 在这里设置插件 fn build(&self, app: &mut App) { let state = PrintMessageState { message: self.message.clone(), timer: Timer::new(self.wait_duration, true), }; app.insert_resource(state).add_system(打印消息); } } struct PrintMessageState { message: String, timer: Timer, } fn 打印消息(mut state: ResMut<PrintMessageState>, time: Res<Time>) { if state.timer.tick(time.delta_seconds()).finished() { info!("{}", state.message); } }
Latest commit e6bce74
use bevy::{app::PluginGroupBuilder, prelude::*}; /// 这个示例用来演示自定义插件组如何被创建和注册 /// 需要同时注册多个插件时,用插件组(PluginGroups) fn main() { App::new() // bevy中有两个内置插件组:DefaultPlugins 和 MinimalPlugins .add_plugins(DefaultPlugins) // 添加插件组时,默认添加组内所有的插件 .add_plugins(HelloWorldPlugins) // 还可以像这样修改插件组(例如禁用插件): // .add_plugins_with(HelloWorldPlugins, |group| { // group // .disable::<PrintWorldPlugin>() // .add_before::<PrintHelloPlugin, // _>(bevy::diagnostic::LogDiagnosticsPlugin::default()) }) .run(); } /// 插件组,生成“hello world” struct HelloWorldPlugins; impl PluginGroup for HelloWorldPlugins { fn build(&mut self, group: &mut PluginGroupBuilder) { group.add(PrintHelloPlugin).add(PrintWorldPlugin); } } pub struct PrintHelloPlugin; impl Plugin for PrintHelloPlugin { fn build(&self, app: &mut App) { app.add_system(print_hello_system); } } fn print_hello_system() { info!("hello"); } pub struct PrintWorldPlugin; impl Plugin for PrintWorldPlugin { fn build(&self, app: &mut App) { app.add_system(print_world_system); } } fn print_world_system() { info!("world"); }
Latest commit ffecb05
use bevy::{prelude::*, winit::WinitConfig}; /// 演示如何在退出 Bevy 应用程序后返回 main fn main() { println!("正在运行第一个 App。"); App::new() .insert_resource(WinitConfig { return_from_run: true, }) .insert_resource(ClearColor(Color::rgb(0.2, 0.2, 0.8))) .add_plugins(DefaultPlugins) .add_system(system_1) .run(); println!("正在运行另一个 App。"); App::new() .insert_resource(WinitConfig { return_from_run: true, }) .insert_resource(ClearColor(Color::rgb(0.2, 0.8, 0.2))) .add_plugins_with(DefaultPlugins, |group| { group.disable::<bevy::log::LogPlugin>() }) .add_system(system_2) .run(); println!("完成。"); } fn system_1() { info!("logging from first app"); } fn system_2() { info!("logging from second app"); }
Latest commit b724a0f
use bevy::prelude::*; /// 这个示例演示了几种加载资源的方法 fn main() { App::new() .insert_resource(Msaa { samples: 4 }) .add_plugins(DefaultPlugins) .add_startup_system(初始设置) .run(); } fn 初始设置( mut commands: Commands, asset_server: Res<AssetServer>, meshes: Res<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>, ) { // 默认情况下,AssetServer 会从根目录的 assets 文件夹内加载资产 // 修改 CARGO_MANIFEST_DIR 环境变量来指定资产文件夹 // 从 Cargo 运行 App 时,CARGO_MANIFEST_DIR 自动设置成当前工作空间的根目录 // 查看 https://doc.rust-lang.org/cargo/reference/environment-variables.html let cube_handle = asset_server.load("modles/cube/cube.gltf#Mesh0/Primitive0"); let sphere_handle = asset_server.load("modles/sphere/sphere.gltf#Mesh0/Primitive0"); // 所有的资产在完成加载后,最终都会进入其 Assets<T> 集合 if let Some(sphere) = meshes.get(&sphere_handle) { // 可能会注意到它没有运行! 这是因为资产并行加载而没有阻塞 // 加载资产后,会显示在相关的 Assets<T> 集合中 info!("{:?}", sphere.primitive_topology()); } else { info!("还没有加载球体"); } // 可以将所有资产加载到这样的文件夹中。 它们将并行加载而不会阻塞 let _scenes: Vec<HandleUntyped> = asset_server.load_folder("modles/monkey").unwrap(); // 然后可以像这样访问文件夹中的任何资产: let monkey_handle = asset_server.get_handle("models/monkey/Monkey.gltf#Mesh0/Primitive0"); // 还可以把资产直接添加到 Assets<T> let material_handle = materials.add(StandardMaterial { base_color: Color::rgb(0.8, 0.7, 0.6), ..Default::default() }); // 生成“猴子” commands.spawn_bundle(PbrBundle { mesh: monkey_handle, material: material_handle.clone(), transform: Transform::from_xyz(-3.0, 0.0, 0.0), ..Default::default() }); // 生成“方块” commands.spawn_bundle(PbrBundle { mesh: cube_handle, material: material_handle.clone(), transform: Transform::from_xyz(0.0, 0.0, 0.0), ..Default::default() }); // 生成“球体” commands.spawn_bundle(PbrBundle { mesh: sphere_handle, material: material_handle, transform: Transform::from_xyz(3.0, .0., 0.0), ..Default::default() }); // 生成“点光源” commands.spawn_bundle(PointLightBundle { transform: Transform::from_xyz(4.0, 5.0, 4.0), ..Default::default() }); // 生成“相机” commands.spawn_bundle(PerspectiveCameraBundle { transform: Transform::from_xyz(0.0, 3.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), ..Default::default() }); }
Latest commit e928acb
use bevy::{asset::AssetServerSettings, prelude::*}; /// 热重载能够修改磁盘上的资产,并且在游戏运行时“实时重载”资产,这样无需重新启动游戏就能立即查看更改结果 /// 这个示例说明了热重新加载网格的变化 fn main() { App::new() // 监视磁盘上资产的变化 .insert_resource(AssetServerSettings { watch_for_changes: true, }) .add_app_plugins(DefaultPlugins) .add_startup_system(初始设置) .run(); } fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { // 加载网格 let scene_handle = asset_server.load("modles/monkey/Monkey.gltf#Scene0"); // 任何对网格的修改都会自动重新加载!请尝试修改Monkey.gtf,会立即看到变化 // 网格 commands.spawn_scene(scene_handle); // 点光源 commands.spawn_bundle(PointLightBundle { transform: Transform::from_xyz(4.0, 5.0, 4.0), ..Default::default() }); // 摄像机 commands.spawn_bundle(PerspectiveCameraBundle { transform: Transform::from_xyz(2.0, 2.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y), }); }
Latest commit 7989cb2
检测组件的变化。
use bevy::prelude::*; use rand::Rng; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_startup_system(初始设置); app.add_system(修改组件); app.add_system(变化检测); app.add_system(变化追踪); app.run(); } #[derive(Component, Debug)] struct MyComponent(f32); fn 初始设置(mut commands: Commands) { commands.spawn(MyComponent(0.0)); commands.spawn(Transform::IDENTITY); } fn 修改组件(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) { for (entity, mut component) in &mut query { if rand::thread_rng().gen_bool(0.1) { info!("正在修改实体: {:?}", entity); component.0 = time.elapsed_seconds(); } } } // 这是查询过滤器,只有满足条件才能查询到实体 fn 变化检测(query: Query<(Entity, &MyComponent), Changed<MyComponent>>) { for (entity, component) in &query { info!("实体 {:?} 修改了组件: {:?}", entity, component); } } // 不设置过滤条件 fn 变化追踪( query: Query<( Entity, Option<&MyComponent>, Option<ChangeTrackers<MyComponent>>, )>, ) { for (entity, component, trackers) in &query { info!("{:?}: {:?}: -> {:?}", entity, component, trackers); } }
Latest commit 8c81e81
use bevy::{ app::{AppExit, ScheduleRunnerPlugin, ScheduleRunnerSettings}, ecs::schedule::ReportExecutionOrderAmbiguities, log::LogPlugin, prelude::*, utils::Duration, }; use rand::random; /// 这是Bevy实体组件系统(ECS)的介绍 /// 所有Bevy应用程序的逻辑都是用ECS模式构建的,所以一定要注意! /// /// 为什么用ECS? /// 略 /// /// ECS定义: /// /// 组件:Rust数据类型 /// /// 实体:组件的集合就是实体(本质是一个唯一的ID) /// 例如,Entity1{ Name("Alice"), Position(0, 0) },Entity2{ Name("Bil"), Position(10, 5) } /// /// 资源:全局数据 /// 例如,asset_storage,events,system state等 /// /// 系统:在实体、组件和资源上运行的逻辑 /// // 组件 // // 游戏里有很多“玩家”,每个玩家都有“名字” #[derive(Component)] struct Player { name: String, } // 每个玩家都有“分数” #[derive(Component)] struct Score { value: usize, } // 资源 // // 储存游戏信息 #[derive(Default)] struct GameState { // 当前回合 current_round: usize, // 玩家总数 total_players: usize, // 胜利的玩家 winning_player: Option<String>, } // 储存游戏规则 struct GameRules { // 胜利所需分数 winning_score: usize, // 最大回合 max_rounds: usize, // 最大玩家数量 max_players: usize, } // 系统 // // 这是最简单的一种系统 fn print_message_system() { println!("这游戏真好玩!"); } // 系统也可以读取和修改资源。此系统每次更新都会开始新的“一轮”游戏: // 提示:“mut”表示资源是“可变的” // Res<GameRules>是只读的,ResMut<GameState>可以修改资源 fn new_round_system(game_rules: Res<GameRules>, mut game_state: ResMut<GameState>) { game_state.current_round += 1; println!( "Begin round {} of {}", game_state.current_round, game_rules.max_rounds ); } // 访问带有“Player”和“Score”组件的实体,更新实体的分数 fn score_system(mut query: Query<(&Player, &mut Score)>) { for (player, mut score) in query.iter_mut() { let scored_a_point = random::<bool>(); if scored_a_point { score.value += 1; println!("{} 得分!他们的分数是:{}", player.name, score.value); } else { println!("{}没有得分!他们的分数是:{}", player.name, score.value); } } } // 访问所有带有“Player”和“Score”组件的实体,也会访问“GameRules”资源,以确定玩家是否已经获胜 fn score_check_system( game_rules: Res<GameRules>, mut game_state: ResMut<GameState>, query: Query<(&Player, &Score)>, ) { for (player, score) in query.iter() { if score.value == game_rules.winning_score { game_state.winning_player = Some(player.name.clone()); } } } // 如果满足条件,结束游戏,然后触发一个AppExit事件,App退出(查看“event.rs”示例) fn game_over_system( game_rules: Res<GameRules>, game_state: Res<GameState>, mut app_exit_events: EventWriter<AppExit>, ) { if let Some(ref player) = game_state.winning_player { println!("{} 赢得游戏!", player); app_exit_events.send(AppExit); } else if game_state.current_round == game_rules.max_rounds { println!("回合结束。平局!"); app_exit_events.send(AppExit); } println!(); } // 这个系统在app启动时运行一次 // 只运行一次的系统一般用来创建游戏的初始状态(state),“启动”系统和“普通”系统的区别是它们的注册方式不同: // “启动”系统:app.add_startup_system(startup_system) // “普通”系统:app.add_system(normal_system) fn startup_system(mut commands: Commands, mut game_state: ResMut<GameState>) { // 创建游戏规则资源 commands.insert_resource(GameRules { max_rounds: 10, winning_score: 4, max_players: 4, }); // 把一些玩家添加到“World”,玩家默认有0分 commands.spawn_batch(vec![ ( Player { name: "Alice".to_string(), }, Score { value: 0 }, ), ( Player { name: "Bob".to_string(), }, Score { value: 0 }, ), ]); // 设置最大玩家数量为“2” game_state.total_players = 2; } // 这个系统用命令缓冲区来(潜在地)在每次迭代时向游戏增加一个新玩家(也就是用command来修改world) // 普通系统不能安全地直接访问World实例,因为实例是并行运行的 // World包含所有组件,所以在并行中修改它的任意部分都是不安全的 // 命令缓冲区可以把所有“修改”排列起来修改World,从而不用直接访问World fn new_player_system( mut commands: Commands, game_rules: Res<GameRules>, mut game_state: ResMut<GameState>, ) { // 随机添加一个新玩家 let add_new_player = random::<bool>(); if add_new_player && game_state.total_players < game_rules.max_players { game_state.total_players += 1; commands.spawn_bundle(( Player { name: format!("Player {}", game_state.total_players), }, Score { value: 0 }, )); println!("Player {} joined the game!", game_state.total_players); } } // 如果要对World或Resource进行完全的、即时的读/写访问,可以用“独占系统” // 警告:本地线程会阻止其他系统的并行执行,直到线程完成,如果需要多线程性能,一般避免使用本地线程 #[allow(dead_code)] fn exclusive_player_system(world: &mut World) { // 和“new_player_system”的功能一样 let total_players = world.get_resource_mut::<GameState>().unwrap().total_players; let should_add_player = { let game_rules = world.get_resource::<GameState>().unwrap(); let add_new_player = random::<bool>(); add_new_player && total_players < game_rules.max_players }; // 随机添加一个新玩家 if should_add_player { world.spawn().insert_bundle(( Player { name: format!("Player {}", total_players), }, Score { value: 0 }, )); let mut game_state = world.get_resource_mut::<GameState>().unwrap(); game_state.total_players += 1; } } // 有时系统需要单独的“本地”状态,Bevy的ECS为此提供了Local<T>资源 // Local<T>对系统来说是唯一的,并自动初始化(如果它们还不存在) // 如果拥有某个系统的ID,也可以用`Resources::get_local()`直接从Resources集合中访问本地资源 // 仅在以下情况适用: // 1.拥有同某个系统的多个实例,并且这些系统都需要单独的状态 // 2.已经拥有某个资源的全局版本,不想在当前的系统覆盖它 // 3.不想把系统要使用的资源注册成全局资源 #[derive(Default)] struct State { counter: usize, } // 注意:这段代码的作用是为了说明问题所在,和这个游戏没有任何关系 #[allow(dead_code)] fn local_state_system(mut state: Local<State>, query: Query<&Player, &Score>) { for (player, score) in query.iter() { println!("Processed: {} {}", player.name, score.value); } println!("this system ran {} times", state.counter); state.counter += 1; } #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] enum MyStage { BeforeRound, AfterRound, } #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] enum MyLabels { ScoreCheck, } // Bevy应用程序的入口 fn main() { // Bevy的App是用建造者模式来创建的,可以把系统、资源和插件添加到App App::new() // 可以这样把资源添加到app .insert_resource(State { counter: 0 }) // 把“系统设置”作为资源,用来配置系统 .insert_resource(ScheduleRunnerSettings::run_loop(Duration::from_secs(5))) // 插件只是一组app构造器的调用 // 下面的插件会每5秒(上面已经设置为5秒)运行一次app的“system schedule” .add_plugin(ScheduleRunnerPlugin::default()) // 实现了Default或FromResources特型的资源可以这样添加 .init_resource::<GameState>() // startup系统会先于其他系统运行一次,一般包含用来初始化的代码(例如添加实体或资源等) .add_startup_system(startup_system) // system的调用会把Rust函数转换成ECS系统 .add_system(print_message_system) // 系统执行顺序 // // 系统属于阶段,阶段控制着每一tick内所有系统的执行策略和大致顺序 // “startup阶段”(startup系统注册在这个阶段)总会在普通阶段开始之前完成,并且阶段内的所有系统都完成后才能进入下一阶段 // 一旦每一个阶段都结束,就完成一个循环(main),然后开始新的循环 // // 默认情况下,所有系统都是并行运行的,除非这个系统要对某个数据进行可变访问 // 并行运行很有效率,但有时需要考虑顺序 // 例如,“游戏结束”系统不应该与其他系统并行运行,而是在其他系统之后运行 // // 与其把每个系统分割成不同阶段,不如给相关系统打上`.label`标签,然后用`.before`或`.after`方法来明确排序 // 被排序的系统会形成“排序依赖”关系,然后按顺序执行 // // 大多数情况下,打上排序标签比拆分系统带来更好的性能,因为这样调度算法有更多并行运行的机会 // 但是,阶段仍然是必须的:阶段的结束是一个硬同步点(意味着没有系统在运行),在这里处理系统发出的`Commands` // 这样做的原因是,`Commands`可能会执行一些与运行中的系统不兼容的操作,例如生成或删除实体,添加或删除资源等 // // `add_system(system)`默认把系统添加到UPDATE阶段,也可以手动指定阶段 // 下面的代码相当于:`add_system(score_system)` .add_system_to_stage(CoreStage::Update, score_system) // 也可以创建新的阶段e,下面是这个游戏的阶段顺序 // "before_round": new_player_system, new_round_system // "update": print_message_system, game_over_system // "after_round": score_check_system, game_over_system .add_stage_before( CoreStage::Update, MyStage::BeforeRound, SystemStage::parallel(), ) .add_stage_after( CoreStage::Update, MyStage::AfterRound, SystemStage::parallel(), ) .add_system_to_stage(MyStage::BeforeRound, new_round_system) .add_system_to_stage(MyStage::BeforeRound, new_player_system) .add_system_to_stage( MyStage::BeforeRound, exclusive_player_system.exclusive_system(), ) // 用显式排序的约束来确保game_over系统运行在score_check_system后面。 // 首先,用`.label`标记将要引用的系统,... .add_system_to_stage( MyStage::AfterRound, score_check_system.label(MyLabels::ScoreCheck), ) // 然后用`.before`或`.after`来表明顺序 .add_system_to_stage( MyStage::AfterRound, game_over_system.after(MyLabels::ScoreCheck), ) // 检查系统是否存在执行顺序歧义:用LogPlugin检查控制台产生的输出,并把下面的资源添加到app // 请注意,检查器的报告不全是潜在问题,需要自行判断 .add_plugin(LogPlugin::default()) .insert_resource(ReportExecutionOrderAmbiguities) // 开始运行app .run(); }
Latest commit 73605f4
创建、激活并接收事件。
//! 创建了一个新事件 //! 每秒触发一次事件,收到事件时打印消息 use bevy::prelude::*; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_event::<MyEvent>(); app.add_event::<PlaySound>(); app.init_resource::<EventTriggerState>(); app.add_system(事件触发器); app.add_system(事件侦听器); app.add_system(音响); app.run(); } struct MyEvent { pub message: String, } #[derive(Default)] struct PlaySound; #[derive(Resource)] struct EventTriggerState { event_timer: Timer, } impl Default for EventTriggerState { fn default() -> Self { EventTriggerState { event_timer: Timer::from_seconds(1.0, TimerMode::Repeating), } } } // 每秒发送一次 MyEvent 和 PlaySound fn 事件触发器( time: Res<Time>, mut event_trigger_state: ResMut<EventTriggerState>, mut my_event: EventWriter<MyEvent>, mut play_sound: EventWriter<PlaySound>, ) { // 计时器开始计时 let delta = time.delta(); event_trigger_state.event_timer.tick(delta); // 计时结束后发送事件 if event_trigger_state.event_timer.finished() { my_event.send(MyEvent { message: "MyEvent just happened!".to_string(), }); play_sound.send_default(); } } // 接收到事件时打印消息 fn 事件侦听器(mut events: EventReader<MyEvent>) { for my_event in events.iter() { info!("{}", my_event.message); } } fn 音响(mut events: EventReader<PlaySound>) { for _ in events.iter() { info!("正在播放声音"); } }
Latest commit b724a0f
use bevy::{ core::{FixedTimestep, FixedTimesteps}, prelude::*, }; const LABEL: &str = "my_fixed_timestep"; #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] struct FixedUpdateStage; /// 展示了如何创建一个运行于固定时间步长的系统,而不是每一个tick都运行 fn main() { App::new() .add_plugins(DefaultPlugins) // 这个系统会在每次update时运行(应该会与屏幕刷新率匹配) .add_system(frame_update) // 添加一个新的stage,每两秒运行一次 .add_stage_after( CoreStage::Update, FixedUpdateStage, SystemStage::parallel() .with_run_criteria( FixedTimestep::step(0.5) // 标签是可选的。它可以从系统内部访问当前FixedTimestep的状态 .with_label(LABEL), ) .with_system(fixed_update), ) .run(); } fn frame_update(mut last_time: Local<f64>, time: Res<Time>) { info!( "“update”系统的时间间隔: {}。", time.seconds_since_startup() - *last_time ); *last_time = time.seconds_since_startup(); } fn fixed_update(mut last_time: Local<f64>, time: Res<Time>, fixed_timesteps: Res<FixedTimesteps>) { info!( "“fixed_update”系统的时间间隔: {}。", time.seconds_since_startup() - *last_time, ); let fixed_timestep = fixed_timesteps.get(LABEL).unwrap(); info!("步进百分比: {}。", fixed_timestep.overstep_percentage()); *last_time = time.seconds_since_startup(); }
Latest commit 46c1480
use bevy::prelude::*; /// 创建父实体和子实体的层次 fn main() { App::new() .add_plugin(DefaultPlugin) .add_startup_system(初始设置) .add_system(旋转) .run(); } fn 初始设置( mut commands: Commands, asset_server: Res<AssetServer>, ) { commands.spawn_bundle(OrthographicCameraBundle::new_2d()); let image_handle = asset_server.load("icon.png"); // 生成一个无父实体的根实体 let parent = commands .spawn_bundle(SpriteBundle { transform: Transform::from_scale(Vec3::splat(0.75)), texture: image_handle.clone(), ..Default::default() }) // 把该实体作为父实体,运行lambda来生成它的子实体 .with_children(|child_builder| { // 父实体是一个ChildBuilder,有一个类似Commands的API child_builder.spawn_bundle(SpriteBundle { transform: Transform { translation: Vec3::new(250.0, 0.0, 0.0), scale: Vec3::splat(0.75), ..Default::default() }, texture: image_handle.clone(), sprite: Sprite { color: Color::BLUE, ..Default::default() } ..Default::default() }); }) // 存储父实体,下面会用到 .id(); // 另一种创建层次结构的方法是在实体中添加一个Parent组件,Paren组件会通过其他方法自动添加到父组件中 // 同理,添加一个Parent组件会自动给父实体添加一个Children组件 commands .spawn_bundle(SpriteBundle { transform: Transform { translation: Vec3::new(-250.0, 0.0, 0.0), scale: Vec3::splat(0.75), ..Default::default() }, texture: image_handle.clone(), sprite: Sprite { color: Color::RED, ..Default::default() } ..Default::default() }) // 使用的是上面存储的父实体 .insert(Parent(parent)); // 另一种方法是在生成父实体之后,用push_children函数添加实体 let child = commands .spawn_bundle(SpriteBundle { transform: Transform { translation: Vec3::new(0.0, 250.0, 0.0), scale: Vec3::splat(0.75), ..Default::default() }, texture: image_handle, sprite: Sprite { color: Color::GREEN, ..Default::default() } ..Default::default() }) .id(); // 用子实体的切片来推送 commands.entity(parent).push_children(&[child]); } // 这个系统用来旋转根实体,并分别旋转所有子代实体 fn 旋转( mut commands: Commands, time: Res<Time>, mut parents_query: Query<(Entity, &Children), With<Sprite>>, mut transform_query: Query<&mut Transform, With<Sprite>>, ) { let angle = std::f32::consts::TAU / 4.0; for (parent, children) in parents_query.iter_mut() { if let Ok(mut transform) = transform_query.get_mut(parent) { transform.rotate(Quat::from_rotation_z(-angle * time.delta_seconds())); } // 要遍历实体的子代,只需把Children组件视为一个Vec,或者,可以查询带有Parent组件的实体 for child in children.iter() { if let Ok(mut transform) = transform_query.get_mut(*child) { transform.rotate(Quat::from_rotation_z(angle * 2.0 * time.delta_seconds())); } } // 为了演示删除子实体,下面的代码会在几秒钟后删除子实体 if time.seconds_since_startup() >= 2.0 && children.len() == 3 { let child = children.last().copied().unwrap(); commands.entity(child).despawn_recursive(); } if time.seconds_since_startup() >= 4.0 { // 这会把实体从其父实体的子代列表中移除,并取消该实体拥有的所有子实体 commands.entity(parent).despawn_recursive(); } } }
Latest commit 07ed1d0
#![allow(unused)] fn main() { /// 演示如何迭代查询结果的组合 }
Latest commit ffecb05
use bevy::{prelude::*, tasks::prelude::*}; use rand::random; #[derive(Component)] struct Velocity(Vec2); fn spawn_system( mut commands: Commands, asset_server: Res<AssetServer>, ) { commands.spawn_bundle(OrthographicCameraBundle::new_2d()); let image_handle = asset_server.load("branding/icon.png"); for _ in 0..128 { commands .spawn_bundle(SpriteBundle { texture: image_handle.clone(), transform: Transform::from_scale(Vec3::splat(0.1)), ..Default::default() }) .insert(Velocity( 20.0 * Vec2::new(random::<f32>() - 0.5, random::<f32>() - 0.5), )); } } // 根据sprite的速度来移动它们 fn move_system( pool: Res<ComputeTaskPool>, mut sprites: Query<(&mut Transform, &Velocity)>, ) { // 使用批处理的32个Sprite在ComputeTaskPool上并行计算每个Sprite的新位置 // 这个例子仅用于演示,究竟是使用ParallelIterator还是Iterator,查看ParallelIterator文档 sprites.par_for_each_mut(&pool, 32, |(mut transform, velocity)| { transform.translation += velocity.0.extend(0.0); }); } // 窗口外弹出sprite fn bounce_system( pool: Res<ComputeTaskPool>, windows: Res<Windows>, mut sprites: Query<(&Transform, &mut Velocity)>, ) { let window = windows.get_primary().expect("No primary window."); let width = window.width(); let height = window.height(); let left = width / -2.0; let right = width - 2.0; let bottom = height / -2.0; let top = height / 2.0; sprites // 选择批处理大小为32,以限制ParallelIterator的开销,因为取反向量非常便宜 .par_for_each_mut(&pool, 32, |(transform, mut v)| { if !(left < transform.translation.x && transform.translation.x < right && bottom < transform.translation.y && transform.translation.y < top) { // 为简单起见,只需将速度反过来即可,不要用现实中的弹跳方式 v.0 = -v.0; } }); } /// 用ParallelIterator来说明如何并行查询 fn main() { App::new() .add_plugin(DefaultPlugins) .add_startup_system(spawn_system) .add_system(move_system) .add_system(bounce_system) .run(); }
Latest commit ffecb05
use bevy::prelude::*; /// 查询在当前帧的前一阶段中删除了特定组件的实体 /// 说明了怎样才能知道何时删除了组件,并做出相关处理 fn main() { // 有关已移除的“组件”的信息会在每帧的末尾被丢弃,因此要在帧结束之前处理相关问题 // // 此外,因为是用“命令”移除“组件”,并且在阶段执行完成后应用“命令”, // 所以要在“组件”被移除后的某个阶段内处理相关问题 // // 因此,移除“组件”的系统放在`CoreStage::Update`阶段,移除检测的系统放在`CoreStage::PostUpdate`阶段 App::new() .add_plugins(DefaultPlugins) .add_startup_system(初始设置) .add_system_to_stage(CoreStage::Update, 移除组件) .add_syatem_to_stage(CoreStage::PostUpdate, 移除检测) .run(); } // 这个结构体是示例,先添加给实体,然后移除 #[derive(Component)] struct MyComponent; fn 初始设置( mut commands: Commands, asset_server: Res<AssetServer>, ) { let image_handle = asset_server.load("icon.png"); commands.spwan_bundle(OrthographicCameraBundle::new_2d()); commands .spawn_bundle(SpriteBundle { texture: image_handle, ..Default::default() }) .insert(MyComponent); // 添加组件 } fn 移除组件( time: Res<Time>, mut commands: Commands, query: Query<Entity, With<MyComponent>>, ) { // 两秒钟后移除组件 if time.seconds_since_startup() > 2.0 { if let Some(entity) = query.iter().next() { commands.entity(entity).remove::<MyComponent>(); } } } fn 移除检测( mut materials: ResMut<Assets<ColorMaterial>>, removed: RemovedComponents<MyComponent>, query: Query<(Entity, &Handle<ColorMaterial>)>, ) { // `RemovedComponents<T>::iter()`返回一个迭代器, // 该迭代器的'Entity'在帧的前面某个点删除了'Component``T`(在本例中是'MyComponent`) for entity in removed.iter() { // 把已经移除了`MyComponent`组件的实体和查询中的实体进行比较,如果匹配,就从材质中删除所有红色 if let Ok(mut sprite) = query.get_mut(entity) { sprite.color.set(0.0); } } }
Latest commit b724a0f
use bevy::prelude::*; /// 演示启动系统(应用程序启动时运行一次的系统) fn main() { App::new() .add_startup_system(startup_system) .add_system(normal_system) .run(); } // 使用“.add_startup_system()”时, // “startup_system”默认添加到`StartupStage::Startup`, // 在app开始时运行一次(StartupStage运行得比CoreStage早) fn startup_system() { println!("startup system ran first"); } // 使用“.add_system()”时,“normal_system”默认添加到`CoreStage::Update`,每帧运行一次 fn normal_system() { println!("normal system ran second"); }
Latest commit e6bce74
use bevy::prelude::*; /// 用`[States]`来说明如何从`Menu`(菜单)状态切换到`InGame`(游戏中)状态 fn main() { App::new() .add_plugins(DefaultPlugins) .add_state(AppState::Menu) .add_system_set(SystemSet::on_enter(AppState::Menu).with_system(设置菜单)) .add_system_set(SystemSet::on_update(AppState::Menu).with_system(菜单)) .add_system_set(SystemSet::on_exit(AppState::Menu).with_system(清理菜单)) .add_system_set(SystemSet::on_enter(AppState::InGame).with_system(设置游戏)) .add_system_set( SystemSet::on_update(AppState::InGame) .with_system(移动) .with_system(修改颜色) ) .run(); } #[derive(Debug, Clone, Eq, PartialEq, Hash)] enum AppState { Menu, InGame, } struct MenuData { button_entity: Entity, } const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.35, 0.35); fn 设置菜单( mut commands: Commands, asset_server: Res<AssetServer>, ) { // UI摄像机 commands.spawn_bundle(UiCameraBundle::default()); let button_entity = commands .spawn_bundle(ButtonBundle { style: Style { size: Size::new(Val::Px(150.0), Val::Px(65.0)), // 中心按钮 margin: Rect::all(Val::Auto), // 水平居中子文本 justify_content: JustifyContent::Center, // 竖直居中子文本 align_items: AlignItems::Center, ..Default::default() }, color: NORMAL_BUTTON.into(), ..Default::default() }) .with_children(|child_builder| { child_builder.spawn_bundle(TextBundle { text: Text::with_section( "play", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 40.0, color: Color::rgb(0.9, 0.9, 0.9), }, Default::default(), ), ..Default::default() }); }) .id(); commands.insert_resource(MenuData { button_entity }); } fn 菜单( mut state: ResMut<State<AppState>>, mut interaction_query: Query< (&Interaction, &mut UiColor), (Changed<Interaction>, With<Button>), >, ) { for (interaction, mut color) in interaction_query.iter_mut() { match *interaction { Interaction::Clicked => { *color = PRESSED_BUTTON.into(); state.set(AppState::InGame).unwrap(); } Interaction::Hovered => { *color = HOVERED_BUTTON.into(); } Interaction::None => { *color = NORMAL_BUTTON.into(); } } } } fn 清理菜单( mut commands: Commands, menu_data: Res<MenuData>, ) { commands.entity(menu_data.button_entity).despawn_recursive(); } fn 设置游戏( mut commands: Commands, asset_server: Res<AssetServer>, ) { commands.spawn_bundle(OrthographicCameraBundle::new_2d()); commands.spawn_bundle(SpriteBundle { texture: asset_server.load("icon.png"), ..Default::default() }); } const SPEED: f32 = 100.0; fn 移动( time: Res<Time>, input: Res<Input<KeyCode>>, mut query: Query<&mut Transform, With<Sprite>>, ) { for mut transform in query.iter_mut() { let mut direction = Vec3::ZERO; if input.pressed(KeyCode::Left) { direction.x += -1.0; } if input.pressed(KeyCode::Right) { direction.x += 1.0; } if input.pressed(KeyCode::Up) { direction.y += 1.0; } if input.pressed(KeyCode::Down) { direction.y += -1.0; } if direction != Vec3::ZERO { transform.translation += direction.normalize() * SPEED * time.delta_seconds(); } } } fn 修改颜色( time: Res<Time>, mut query: Query<&mut Sprite>, ) { for mut sprite in query.iter_mut() { sprite .color .set_b((time.seconds_since_startup() * 0.5).sin() as f32 + 2.0); } }
Latest commit e6bce74
use bevy::ecs::system::SystemParam; use bevy::prelude::*; /// 说明如何使用[`SystemParam`]创建自定义系统参数 /// 本例创建了一个`[SystemParam]`结构,用来计算玩家数量 fn main() { App::new() .insert_resource(PlayerCount(0)) .add_startup_system(生成玩家) .add_system(计算玩家数量) .run(); } #[derive(Component)] struct Player; #[derive(Component)] struct PlayerCount(usize); /// [`SystemParam`]结构可以包含任何类型,这些类型也可以包含在一个系统函数签名中 /// 在这个例子中,SystemParam包括一个查询和一个可变的资源 #[derive(SystemParam)] struct PlayerCounter<'w, 's> { players: Query<'w, 's, &'static Player>, count: ResMut<'w, PlayerCount>, } impl<'w, 's> PlayerCounter<'w, 's> { fn count(&mut self) { self.count.0 = self.players.iter().len(); } } /// 生成3个player fn 生成玩家(mut commands: Commands) { commands.spawn().insert(Player); commands.spawn().insert(Player); commands.spawn().insert(Player); } /// [`SystemParam`]可以直接在系统参数中使用 fn 计算玩家数量(mut counter: PlayerCounter) { counter.count(); println!("{}个玩家在游戏中", counter.count.0); }
Latest commit fbab01a
use bevy::app::AppExit; use bevy::ecs::schedule::ShouldRun; use bevy::prelude::*; /// 演示`SystemSet`如何搭配运行准则来使用 /// [`SystemLabel`]可以作为一个标签应用于系统和系统集,然后可以从其他系统中引用 /// TODO:This is useful in case a user wants to e.g. run _before_ or _after_ some label /// 派生[`SystemLabel`]时,需要同时派生`Clone`、`Hash`、`Debug`、`PartialEq`、`Eq` #[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)] struct Physics; #[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)] struct PostPhysics; // 用这个资源来停止此示例 #[derive(Default)] struct Done(bool); /// 这个用来说明:在[`SystemSet']中,单个系统也可以被标记,允许进一步微调运行顺序 #[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)] enum PhysicsSystem { UpdateVelocity, Movement, } /// 这个例子实现了以下方案: /// /// ```none /// Physics (Criteria: App has run < 1.0 seconds) /// \--> update_velocity (via label PhysicsSystem::UpdateVelocity) /// \--> movement (via label PhysicsSystem::Movement) /// PostPhysics (Criteria: Resource `done` is false) /// \--> collision || sfx /// Exit (Criteria: Resource `done` is true) /// \--> exit /// ``` /// /// `Physics`是一个包含两个系统的[`SystemSet`], /// `Physics`的运行准则是“经过2秒后停止”, /// `Physics`中的两个系统(`update_velocity`、`movement`)按指定顺序运行 /// /// `PostPhysics`的运行准则是“`Physics`完成后才开始运行”, /// `PostPhysics`的条件是由“Done”资源确定的,在“false”时运行,“true”时停止 /// `PostPhysics`中的两个系统(`collision`、`sfx`)没有指定运行顺序 /// /// 最后,根据“Done”资源来确认要退出应用程序 fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::<Done>() // 请注意,本例中添加的系统集明确设置了运行准则 // TODO:See the `ecs/state.rs` example for a pattern where run criteria are set implicitly for common use cases- typically state transitions. // 一个系统集最多有一个运行准则,这意味着: // 执行`SystemSet::on_update(...)`之后再执行`.with_run_criteria(...)`,会覆盖状态过度准则 .add_system_set( SystemSet::new() // 系统集内所有系统都会添加这个标签 // 这个标签可以在其他所有地方引用,包括其他系统集 .label(Physics) // 这个准则的作用:仅当"运行一秒"系统的输出为`ShouldRun::Yes`时,当前系统集才会运行 .with_run_criteria(运行一秒) .with_system( 更新速度 // 这个标签只添加给`更新速度`系统 .label(PhysicsSystem::UpdateVelocity), ) .with_system( 移动 // 只添加给`移动`系统 .label(PhysicsSystem::Movement) // 明确地指定执行顺序 .after(PhysicsSystem::UpdateVelocity), ), ) .add_system_set( SystemSet::new() .label(PostPhysics) // 这个系统集在`Physics`后面运行 // 与`.after(..)`类似的有`.before(..)` .after(Physics) // 可以修改现有的运行准则结果 // 这一步的目的是:TODO:Here we create a _not done_ criteria by piping the output of the `is_done` system and inverting the output. // 字符串也可以作为标签使用 .with_run_criteria(RunCriteria::pipe("已完成", 反转.system())) // `collision`和`sfx`没有明确指定顺序,因此运行时两个系统的顺序不确定 .with_system(碰撞) .with_system(sfx), ) .add_system( 退出.after(PostPhysics) .with_run_criteria(已完成.label("已完成")), ) .run(); } /// 运行准则的示例 /// 仅仅运行1秒,然后停止 fn 运行一秒( time: Res<Time>, mut done: ResMut<Done>, ) -> ShouldRun { let elapsed = time.seconds_since_startup(); if elapsed < 1.0 { info!( "应该再次运行。经过时间/剩余时间:{:.2}秒/{:.2}秒", elapsed, 1.0 - elapsed, ); ShouldRun::Yes } else { done.0 = true; ShouldRun::No } } /// 另一个运行准则,只用了一个资源 fn 已完成(done: Res<Done>) -> ShouldRun { if done.0 { ShouldRun::Yes } else { ShouldRun::No } } /// 与[`RunCriteria::pipe`]一起使用,把传递过去的系统反转 fn 反转(input: In<ShouldRun>) -> ShouldRun { match input.0 { ShouldRun::No => ShouldRun::Yes, ShouldRun::Yes => ShouldRun::No, _ => unreachable!(), } } fn 更新速度() { info!("正在更新速度"); } fn 移动() { info!("正在更新移动"); } fn 碰撞() { info!("完成“Physics”,正在检查碰撞"); } fn sfx() { info!("Physics已完成,播放一些sfx"); } fn 退出(mut app_exit_events: EventWriter<AppExit>) { info!("正在退出..."); app_exit_events.send(AppExit); }
Latest commit 9effc3e
use bevy::{log::info, prelude::*}; /// 演示了在系统内部用Timer资源计时并处理Timer资源的状态 fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::<Countdown>() .add_startup_system(setup_system) .add_system(countdown_system) .add_system(timer_system) .run(); } pub struct Countdown { pub percent_trigger: Timer, pub main_timer: Timer, } impl Countdown { pub fn new() -> Self { Self { percent_trigger: Timer::from_seconds(4.0, true), main_timer: Timer::from_seconds(20.0, false), } } } impl Default for Countdown { fn default() -> Self { Self::new() } } fn setup_system(mut commands: Commands) { // 把一个带有Timer组件的实体添加到World: commands.spawn().insert(Timer::from_seconds(5.0, false)); } /// 这个系统用“Time”资源在场景内的实体上标记所有的“Timer”组件,以获得每次更新之间的增量 fn timer_system( time: Res<Time>, mut query: Query<&mut Timer>, ) { for mut timer in query.iter_mut() { if timer.tick(time.delta()).just_finished() { info!("实体的计时器已结束。"); } } } /// 这个系统控制倒计时资源内的计时器并处理其状态 fn countdown_system(time: Res<Time>, mut countdown: ResMut<Countdown>) { countdown.main_timer.tick(time.delta()); // API鼓励这种计时器状态检查(如果只检查一个值) // 此外,`finished()'与'just_finished'做相同的事情,因为计时器正在重复,但这在视觉上更有意义 if countdown.percent_trigger.tick(time.delta()).just_finished() { if !countdown.main_timer.finished() { // 打印主计时器的完成百分比: info!("计时器已完成{}%。", countdown.main_timer.percent() * 100.0); } else { // 计时器已经结束,暂停输出信息: countdown.percent_trigger.pause(); info!("暂停计时器。"); } } }
Latest commit b724a0f
use bevy::{prelude::*, window::ReceivedCharacter}; /// 打印所有输入的字符 fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(print_char_event) .run(); } /// 这个系统把所有输入的字符打印出来 fn print_char_event(mut char_input_events: EventReader<ReceivedCharacter>) { for event in char_input_events.iter() { info!("{:?}: '{}'", event, event.char); } }
Latest commit b724a0f
use bevy::prelude::*; /// 演示如何处理按下/释放按键 fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(keyboard_input_system) .run(); } /// 这个系统打印'A'键的状态 fn keyboard_input_system(keyboard_input: Res<Input<KeyCode>>) { if keyboard_input.pressed(KeyCode::A) { info!("正在按住“A”键。"); } if keyboard_input.just_pressed(KeyCode::A) { info!("已按下“A”键。"); } if keyboard_input.just_released(KeyCode::A) { info!("已松开“A”键。"); } }
Latest commit b724a0f
use bevy::{input::keyboard::KeyboardInput, prelude::*}; /// 打印出所有键盘事件 fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(print_keyboard_event_system) .run(); } /// 这个系统所有输入的键盘事件 fn print_keyboard_event_system(mut keyboard_input_events: EventReader<KeyboardInput>) { for event in keyboard_input_events.iter() { info!("{:?}", event); } }
Latest commit b724a0f
use bevy::prelude::*; /// 演示如何处理按下/释放鼠标按钮 fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(mouse_click_system) .run(); } // 当按下(或松开)鼠标左键时打印消息 fn mouse_click_system(mouse_button_input: Res<Input<MouseButton>>) { if mouse_button_input.pressed(MouseButton::Left) { info!("正在按住鼠标左键。"); } if mouse_button_input.just_pressed(MouseButton::Left) { info!("已按下鼠标左键。"); } if mouse_button_input.just_released(MouseButton::Left) { info!("已松开鼠标左键。"); } }
Latest commit b724a0f
#![allow(unused)] fn main() { }
打印所有鼠标事件(按钮、移动等)
Latest commit 07ed1d0
use bevy::{prelude::*, reflect::TypeRegistry, utils::Duration}; /// 从文件加载和保存场景 fn main() { App::new() .add_plugins(DefaultPlugins) .register_type::<ComponentA>() .register_type::<ComponentB>() //.add_startup_system(保存场景) .add_startup_system(保存场景.exclusive_system()) .add_startup_system(加载场景) .add_startup_system(infotext_system) .add_system(日志) .run(); } // 注册的组件必须实现'Reflect'和'FromWorld'特型。 // “Reflect”特型支持序列化、反序列化和动态属性访问。 // `Reflect`启用一系列很酷的行为,查看 'Reflect.rs'。 // “FromWorld”特型决定了加载组件时如何构造组件 // 对于简单的用例,可以只实现'Default'特型(它会自动实现FromResources) // 最简单的注册组件只需要以下两个派生: #[derive(Component, Reflect, Default)] #[reflect(Component)] // TODO struct ComponentA { pub x: f32, pub y: f32, } // 某些组件具有无法(或不应)写入场景文件的字段,可以用#[reflect(ignore)]属性来忽略, // 这通常也是`FromWorld`特型发挥作用的地方 // `FromWorld`允许在构建组件时访问应用程序当前的ECS`资源` struct ComponentB { pub value: String, #[reflect(ignore)] pub _time_since_startup: Duration, } impl FromWorld for ComponentB { fn from_world(world: &mut World) -> Self { let time = world.get_reource::<Time>().duration(); ComponentB { _time_since_startup: time.time_since_startup(), value: "Default Value".to_string(), } } } fn 加载场景(asset_server: Res<AssetServer>, mut scene_spawner: ResMut<SceneSpawner>) { // 像其他资源一样加载场景 let scene_handle: Handle<DynamicScene> = asset_server.load("scene/load_scene_example.scn.ron"); // SceneSpawner能生成场景:“生成”一个场景就是在World中生成这个场景的新实例,拥有新的实体ID // 这样保证了不会覆盖原有的实体 scene_spawner.spawn_dynamic(scene_handle); // AssetServer监视资产的变化 // 作用是在修改场景的文件时,在游戏中自动重新加载场景 asset_server.watch_for_changes().unwrap(); } // 这个系统记录World中所有的ComponentA // 尝试在load_scene_example.scn中更改ComponentA,会立即在控制台中看到更改 fn 日志(query: Query<(Entity, &ComponentA), Changed<ComponentA>>) { for (entity, component_a) in query.iter() { info!(" Entity({})", entity.id()); info!( " ComponentA:{{x: {} y: {}}}\n", component_a.x, component_a.y ); } } fn 保存场景(world: &mut World) { // 可以从任何ECS世界创建场景 // 可以为场景创建一个新世界,也可以使用当前世界 let mut scene_world = World::new(); let mut component_b = ComponentB::from_world(world); component_b.value = "hello".to_string(); scene_world.spawn().insert_bundle(( component_b, ComponentA { x: 1.0, y: 2.0 }, Transform::identity(), )); scene_world .spawn() .insert_bundle((ComponentA { x: 3.0, y: 4.0 },)); // TypeRegistry资源包含有关所有已注册类型(包括组件)的信息,用于构造场景 let type_registry = world.get_resource::<TypeRegistry>().unwrap(); let scene = DynamicScene::from_world(&scene_world, type_registry); // 场景可以按如下方式序列化: info!("{}", scene.serialize_ron(type_registry).unwrap()); // TODO: save scene } // 这仅对UI中的信息消息是必需的 // 有关独立文本的示例,查看examples/ui/text.rs fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) { commands.spawn_bundle(UiCameraBundle::default()); commands.spawn_bundle(TextBundle { style: Style { align_self: AlignSelf::FlexEnd, ..Default::default() }, text: Text::with_section( "这个窗口没有东西显示!检查控制台输出!", TextStyle { font: asset_server.load("fonts/xxx.ttf"), font_size: 50.0, color: Color::WHITE, }, Default::default() ), ..Default::default() }); }
3D Rotation
Latest commit db0d769
todo:panic!
Latest commit 481eec2
创建和更新按钮(更改颜色和文本)。
use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) // 优化:仅在有用户输入时运行这个 app,可以减轻 CPU/GPU 负担 .insert_resource(WinitSettings::desktop_app()) .add_startup_system(初始设置) .add_system(更改按钮颜色和文本) .run(); } const NORMAL_BUTTON: Color = Color::rgb(0.5, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35); fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { commands.spawn(Camera2dBundle::default()); commands .spawn(ButtonBundle { style: Style { size: Size::new(Val::Px(150.0), Val::Px(65.0)), // 中心按钮 margin: UiRect::all(Val::Auto), // 水平居中子文本 justify_content: JustifyContent::Center, // 垂直居中子文本 align_items: AlignItems::Center, ..default() }, background_color: NORMAL_BUTTON.into(), ..default() }) .with_children(|child_builder| { child_builder.spawn(TextBundle::from_section( "Button", TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 40.0, color: Color::rgb(0.9, 0.9, 0.9), }, )); }); } fn 更改按钮颜色和文本( mut interaction_query: Query< (&Interaction, &mut BackgroundColor, &Children), (Changed<Interaction>, With<Button>), >, mut text_query: Query<&mut Text>, ) { for (interaction, mut color, children) in &mut interaction_query { let mut text = text_query.get_mut(children[0]).unwrap(); match *interaction { Interaction::Clicked => { text.sections[0].value = "Press".to_string(); *color = PRESSED_BUTTON.into(); } Interaction::Hovered => { text.sections[0].value = "Hover".to_string(); *color = HOVERED_BUTTON.into(); } Interaction::None => { text.sections[0].value = "Button".to_string(); *color = NORMAL_BUTTON.into(); } } } }
Latest commit c19aa59
创建并更新文本。
use bevy::diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin}; use bevy::prelude::*; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_plugin(FrameTimeDiagnosticsPlugin); app.add_startup_system(初始设置); app.add_system(修改文本颜色); app.add_system(修改文本内容); app.run(); } // 标记需要更改颜色的文本 #[derive(Component)] struct ColorText; // 标记显示 fps 的文本 #[derive(Component)] struct FpsText; fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { // 摄像机 commands.spawn(Camera2dBundle::default()); // 一节文本 commands.spawn(( TextBundle::from_section( "你好\nbevy!", TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 100.0, color: Color::WHITE, }, ) // 设置文本对齐 .with_text_alignment(TextAlignment::TOP_CENTER) // 设置 TextBundle 的样式 .with_style(Style { position_type: PositionType::Absolute, position: UiRect { bottom: Val::Px(5.0), right: Val::Px(15.0), ..default() }, ..default() }), ColorText, )); // 几节文本 commands.spawn(( TextBundle::from_sections([ TextSection::new( "FPS: ", TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 60.0, color: Color::WHITE, }, ), TextSection::from_style(TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 60.0, color: Color::GOLD, }), ]), FpsText, )); } fn 修改文本颜色(time: Res<Time>, mut query: Query<&mut Text, With<ColorText>>) { for mut text in &mut query { let seconds = time.elapsed_seconds(); // 前面用了 TextBundle::from_section(),因此只有“一节”文本 text.sections[0].style.color = Color::Rgba { red: (1.25 * seconds).sin() / 2.0 + 0.5, green: (0.75 * seconds).sin() / 2.0 + 0.5, blue: (0.50 * seconds).sin() / 2.0 + 0.5, alpha: 1.0, }; } } fn 修改文本内容(diagnostics: Res<Diagnostics>, mut query: Query<&mut Text, With<FpsText>>) { for mut text in &mut query { if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { if let Some(value) = fps.smoothed() { // 前面用了 TextBundle::from_sections(),因此有“几节”文本 // 这里修改的是 “FPS: ” 后面的数字 text.sections[1].value = format!("{value:.2}"); } } } }
Latest commit 4407cdb
UI 的各种功能。
use bevy::input::mouse::{MouseScrollUnit, MouseWheel}; use bevy::prelude::*; use bevy::winit::WinitSettings; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); // 优化:仅在有用户输入时运行这个 app,可以减轻 CPU/GPU 负担 app.insert_resource(WinitSettings::desktop_app()); app.add_startup_system(初始设置); app.add_system(鼠标滚动); app.run(); } fn 初始设置(mut commands: Commands, asset_server: Res<AssetServer>) { // 摄像机 commands.spawn(Camera2dBundle::default()); // 根节点 commands .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), justify_content: JustifyContent::SpaceBetween, ..default() }, ..default() }) .with_children(|child_builder| { // 左垂直填充(边框) child_builder .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(200.0), Val::Percent(100.0)), ..default() }, background_color: Color::rgb(0.65, 0.65, 0.65).into(), ..default() }) .with_children(|child_builder| { // 左垂直填充(内容) child_builder .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), ..default() }, background_color: Color::rgb(0.15, 0.15, 0.15).into(), ..default() }) .with_children(|child_builder| { // 文本 child_builder.spawn( TextBundle::from_section( "文本示例", TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 30.0, color: Color::WHITE, }, ) .with_style(Style { margin: UiRect::all(Val::Px(5.0)), ..default() }), ); }); }); // 右垂直填充 child_builder .spawn(NodeBundle { style: Style { flex_direction: FlexDirection::Column, justify_content: JustifyContent::Center, size: Size::new(Val::Px(200.0), Val::Percent(100.0)), ..default() }, background_color: Color::rgb(0.15, 0.15, 0.15).into(), ..default() }) .with_children(|child_builder| { // 标题 child_builder.spawn( TextBundle::from_section( "滚动列表", TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 25.0, color: Color::WHITE, }, ) .with_style(Style { size: Size::new(Val::Undefined, Val::Px(25.0)), margin: UiRect { left: Val::Auto, right: Val::Auto, ..default() }, ..default() }), ); // 带有隐藏溢出的列表 child_builder .spawn(NodeBundle { style: Style { flex_direction: FlexDirection::Column, align_self: AlignSelf::Center, size: Size::new(Val::Percent(100.0), Val::Percent(50.0)), overflow: Overflow::Hidden, ..default() }, background_color: Color::rgb(0.10, 0.10, 0.10).into(), ..default() }) .with_children(|child_builder| { // 正在移动面板 child_builder .spawn(( NodeBundle { style: Style { flex_direction: FlexDirection::ColumnReverse, flex_grow: 1.0, max_size: Size::UNDEFINED, ..default() }, ..default() }, ScrollingList::default(), )) .with_children(|child_builder| { // 项目列表 for i in 0..30 { child_builder.spawn( TextBundle::from_section( format!("项目 {}", i), TextStyle { font: asset_server.load("fonts/LXGWWenKai.ttf"), font_size: 20.0, color: Color::WHITE, }, ) .with_style(Style { flex_shrink: 0.0, size: Size::new(Val::Undefined, Val::Px(20.0)), margin: UiRect { left: Val::Auto, right: Val::Auto, ..default() }, ..default() }), ); } }); }); }); // 绝对定位 child_builder .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(200.0), Val::Px(200.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(210.0), bottom: Val::Px(10.0), ..default() }, border: UiRect::all(Val::Px(20.0)), ..default() }, background_color: Color::rgb(0.4, 0.4, 1.0).into(), ..default() }) .with_children(|child_builder| { child_builder.spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), ..default() }, background_color: Color::rgb(0.8, 0.8, 1.0).into(), ..default() }); }); // 渲染顺序测试:前面最白,后面最红,(弹性中心) child_builder .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), position_type: PositionType::Absolute, align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, ..default() }) .with_children(|child_builder| { child_builder .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), ..default() }, background_color: Color::rgb(1.0, 0.0, 0.0).into(), ..default() }) .with_children(|child_builder| { child_builder.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(20.0), bottom: Val::Px(20.0), ..default() }, ..default() }, background_color: Color::rgb(1.0, 0.3, 0.3).into(), ..default() }); child_builder.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(40.0), bottom: Val::Px(40.0), ..default() }, ..default() }, background_color: Color::rgb(1.0, 0.5, 0.5).into(), ..default() }); child_builder.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(60.0), bottom: Val::Px(60.0), ..Default::default() }, ..Default::default() }, background_color: Color::rgb(1.0, 0.7, 0.7).into(), ..default() }); // alpha 测试 child_builder.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(80.0), bottom: Val::Px(80.0), ..default() }, ..default() }, background_color: Color::rgb(1.0, 0.9, 0.4).into(), ..default() }); }); }); // bevy logo(弹性中心) child_builder .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), position_type: PositionType::Absolute, justify_content: JustifyContent::Center, align_items: AlignItems::FlexStart, ..default() }, ..default() }) .with_children(|child_builder| { // bevy logo(图像) child_builder.spawn(ImageBundle { style: Style { size: Size::new(Val::Px(500.0), Val::Auto), ..default() }, image: asset_server.load("bevy_logo_dark_big.png").into(), ..default() }); }); }); } #[derive(Component, Default)] struct ScrollingList { position: f32, } fn 鼠标滚动( mut event_reader: EventReader<MouseWheel>, mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>, query_item: Query<&Node>, ) { for event in event_reader.iter() { for (mut scrolling_list, mut style, children, ui_code) in &mut query_list { let items_height: f32 = children .iter() .map(|entity| query_item.get(*entity).unwrap().size().y) .sum(); let panel_height = ui_code.size().y; let max_scroll = (items_height - panel_height).max(0.0); let dy = match event.unit { MouseScrollUnit::Line => event.y * 20.0, MouseScrollUnit::Pixel => event.y, }; scrolling_list.position += dy; scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.0); style.position.top = Val::Px(scrolling_list.position); } } }
Latest commit 01aedc8
创建纯色窗口。
use bevy::prelude::*; fn main() { App::new() .insert_resource(ClearColor(Color::rgb(0.5, 0.5, 0.9))) .add_plugins(DefaultPlugins) .add_startup_system(初始设置) .add_system(修改纯色) .run(); } fn 初始设置(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); } fn 修改纯色( input: Res<Input<KeyCode>>, mut clear_color: ResMut<ClearColor>, ) { if input.just_pressed(KeyCode::Space) { clear_color.0 = Color::PURPLE; } }
Latest commit 635320f
修改窗口设置和鼠标指针。
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}; use bevy::prelude::*; use bevy::window::{CursorGrabMode, PresentMode}; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins.set(WindowPlugin { window: WindowDescriptor { title: "窗口标题".to_string(), width: 800.0, height: 600.0, present_mode: PresentMode::AutoVsync, always_on_top: true, ..default() }, ..default() })); app.add_plugin(LogDiagnosticsPlugin::default()); app.add_plugin(FrameTimeDiagnosticsPlugin); app.add_system(切换垂直同步); app.add_system(修改标题); app.add_system(切换光标可见性); app.add_system(循环切换鼠标指针图标); app.add_system(切换窗口置顶); app.run(); } /// 按下 V 键切换垂直同步模式 /// 结合 `LogDiagnosticsPlugin` 和 `FrameTimeDiagnosticsPlugin`,在控制台观察 fps 变化 fn 切换垂直同步( input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>, ) { if input.just_pressed(KeyCode::V) { let window = windows.primary_mut(); window.set_present_mode(if matches!(window.present_mode(), PresentMode::AutoVsync) { PresentMode::AutoNoVsync } else { PresentMode::AutoVsync }); info!("present_mode: {:?}", window.present_mode()); } } /// 按下 T 键切换窗口置顶模式,当前窗口总是在其他窗口上面 fn 切换窗口置顶( input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>, ) { if input.just_pressed(KeyCode::T) { let window = windows.primary_mut(); let on_top: bool = window.always_on_top(); if on_top { info!("正在锁定窗口..."); } else { info!("正在解锁窗口..."); } window.set_always_on_top(!on_top); } } /// 在程序运行时修改标题 fn 修改标题(time: Res<Time>, mut windows: ResMut<Windows>) { let window = windows.primary_mut(); window.set_title(format!( "已运行 {} 秒", time.elapsed_seconds().round() )); } /// 按下空格键切换光标的可见性 fn 切换光标可见性(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) { let window = windows.primary_mut(); if input.just_pressed(KeyCode::Space) { window.set_cursor_grab_mode(match window.cursor_grab_mode() { CursorGrabMode::None => CursorGrabMode::Locked, CursorGrabMode::Locked | CursorGrabMode::Confined => CursorGrabMode::None, }); window.set_cursor_visibility(!window.cursor_visible()); } } /// 每次点击鼠标按键都按顺序切换一个图标 fn 循环切换鼠标指针图标( mut windows: ResMut<Windows>, input: Res<Input<MouseButton>>, mut index: Local<usize>, ) { const ICONS: &[CursorIcon] = &[ //CursorIcon::Default, CursorIcon::Hand, CursorIcon::Wait, CursorIcon::Text, CursorIcon::Copy, ]; let window = windows.primary_mut(); if input.just_pressed(MouseButton::Left) { *index = (*index + 1) % ICONS.len(); window.set_cursor_icon(ICONS[*index]); } else if input.just_pressed(MouseButton::Right) { *index = if *index == 0 { ICONS.len() - 1 } else { *index - 1 }; window.set_cursor_icon(ICONS[*index]); } }