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]);
    }
}