mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 15:14:50 +00:00
992681b59b
*This PR description is an edited copy of #5007, written by @alice-i-cecile.* # Objective Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds. While ergonomic, this results in several drawbacks: * it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource * it is challenging to discover if a type is intended to be used as a resource * we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component). * dependencies can use the same Rust type as a resource in invisibly conflicting ways * raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values * we cannot capture a definitive list of possible resources to display to users in an editor ## Notes to reviewers * Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits. *ira: My commits are not as well organized :')* * I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does. * I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981. ## Changelog `Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro. ## Migration Guide Add `#[derive(Resource)]` to all types you are using as a resource. If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics. `ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing. Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead. Co-authored-by: Alice <alice.i.cecile@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: devil-ira <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
277 lines
8 KiB
Rust
277 lines
8 KiB
Rust
//! Loads animations from a skinned glTF, spawns many of them, and plays the
|
|
//! animation to stress test skinned meshes.
|
|
|
|
use bevy::{
|
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
|
prelude::*,
|
|
window::PresentMode,
|
|
};
|
|
|
|
#[derive(Resource)]
|
|
struct Foxes {
|
|
count: usize,
|
|
speed: f32,
|
|
moving: bool,
|
|
}
|
|
|
|
fn main() {
|
|
App::new()
|
|
.insert_resource(WindowDescriptor {
|
|
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".to_string(),
|
|
present_mode: PresentMode::AutoNoVsync,
|
|
..default()
|
|
})
|
|
.add_plugins(DefaultPlugins)
|
|
.add_plugin(FrameTimeDiagnosticsPlugin)
|
|
.add_plugin(LogDiagnosticsPlugin::default())
|
|
.insert_resource(Foxes {
|
|
count: std::env::args()
|
|
.nth(1)
|
|
.map_or(1000, |s| s.parse::<usize>().unwrap()),
|
|
speed: 2.0,
|
|
moving: true,
|
|
})
|
|
.insert_resource(AmbientLight {
|
|
color: Color::WHITE,
|
|
brightness: 1.0,
|
|
})
|
|
.add_startup_system(setup)
|
|
.add_system(setup_scene_once_loaded)
|
|
.add_system(keyboard_animation_control)
|
|
.add_system(update_fox_rings.after(keyboard_animation_control))
|
|
.run();
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct Animations(Vec<Handle<AnimationClip>>);
|
|
|
|
const RING_SPACING: f32 = 2.0;
|
|
const FOX_SPACING: f32 = 2.0;
|
|
|
|
#[derive(Component, Clone, Copy)]
|
|
enum RotationDirection {
|
|
CounterClockwise,
|
|
Clockwise,
|
|
}
|
|
|
|
impl RotationDirection {
|
|
fn sign(&self) -> f32 {
|
|
match self {
|
|
RotationDirection::CounterClockwise => 1.0,
|
|
RotationDirection::Clockwise => -1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Ring {
|
|
radius: f32,
|
|
}
|
|
|
|
fn setup(
|
|
mut commands: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
foxes: Res<Foxes>,
|
|
) {
|
|
warn!(include_str!("warning_string.txt"));
|
|
|
|
// Insert a resource with the current scene information
|
|
commands.insert_resource(Animations(vec![
|
|
asset_server.load("models/animated/Fox.glb#Animation2"),
|
|
asset_server.load("models/animated/Fox.glb#Animation1"),
|
|
asset_server.load("models/animated/Fox.glb#Animation0"),
|
|
]));
|
|
|
|
// Foxes
|
|
// Concentric rings of foxes, running in opposite directions. The rings are spaced at 2m radius intervals.
|
|
// The foxes in each ring are spaced at least 2m apart around its circumference.'
|
|
|
|
// NOTE: This fox model faces +z
|
|
let fox_handle = asset_server.load("models/animated/Fox.glb#Scene0");
|
|
|
|
let ring_directions = [
|
|
(
|
|
Quat::from_rotation_y(std::f32::consts::PI),
|
|
RotationDirection::CounterClockwise,
|
|
),
|
|
(Quat::IDENTITY, RotationDirection::Clockwise),
|
|
];
|
|
|
|
let mut ring_index = 0;
|
|
let mut radius = RING_SPACING;
|
|
let mut foxes_remaining = foxes.count;
|
|
|
|
info!("Spawning {} foxes...", foxes.count);
|
|
|
|
while foxes_remaining > 0 {
|
|
let (base_rotation, ring_direction) = ring_directions[ring_index % 2];
|
|
let ring_parent = commands
|
|
.spawn_bundle((
|
|
Transform::default(),
|
|
GlobalTransform::default(),
|
|
Visibility::default(),
|
|
ComputedVisibility::default(),
|
|
ring_direction,
|
|
Ring { radius },
|
|
))
|
|
.id();
|
|
|
|
let circumference = std::f32::consts::TAU * radius;
|
|
let foxes_in_ring = ((circumference / FOX_SPACING) as usize).min(foxes_remaining);
|
|
let fox_spacing_angle = circumference / (foxes_in_ring as f32 * radius);
|
|
|
|
for fox_i in 0..foxes_in_ring {
|
|
let fox_angle = fox_i as f32 * fox_spacing_angle;
|
|
let (s, c) = fox_angle.sin_cos();
|
|
let (x, z) = (radius * c, radius * s);
|
|
|
|
commands.entity(ring_parent).with_children(|builder| {
|
|
builder.spawn_bundle(SceneBundle {
|
|
scene: fox_handle.clone(),
|
|
transform: Transform::from_xyz(x as f32, 0.0, z as f32)
|
|
.with_scale(Vec3::splat(0.01))
|
|
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
|
|
..default()
|
|
});
|
|
});
|
|
}
|
|
|
|
foxes_remaining -= foxes_in_ring;
|
|
radius += RING_SPACING;
|
|
ring_index += 1;
|
|
}
|
|
|
|
// Camera
|
|
let zoom = 0.8;
|
|
let translation = Vec3::new(
|
|
radius * 1.25 * zoom,
|
|
radius * 0.5 * zoom,
|
|
radius * 1.5 * zoom,
|
|
);
|
|
commands.spawn_bundle(Camera3dBundle {
|
|
transform: Transform::from_translation(translation)
|
|
.looking_at(0.2 * Vec3::new(translation.x, 0.0, translation.z), Vec3::Y),
|
|
..default()
|
|
});
|
|
|
|
// Plane
|
|
commands.spawn_bundle(PbrBundle {
|
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 500000.0 })),
|
|
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
|
..default()
|
|
});
|
|
|
|
// Light
|
|
commands.spawn_bundle(DirectionalLightBundle {
|
|
transform: Transform::from_rotation(Quat::from_euler(
|
|
EulerRot::ZYX,
|
|
0.0,
|
|
1.0,
|
|
-std::f32::consts::FRAC_PI_4,
|
|
)),
|
|
directional_light: DirectionalLight {
|
|
shadows_enabled: true,
|
|
..default()
|
|
},
|
|
..default()
|
|
});
|
|
|
|
println!("Animation controls:");
|
|
println!(" - spacebar: play / pause");
|
|
println!(" - arrow up / down: speed up / slow down animation playback");
|
|
println!(" - arrow left / right: seek backward / forward");
|
|
println!(" - return: change animation");
|
|
}
|
|
|
|
// Once the scene is loaded, start the animation
|
|
fn setup_scene_once_loaded(
|
|
animations: Res<Animations>,
|
|
foxes: Res<Foxes>,
|
|
mut player: Query<&mut AnimationPlayer>,
|
|
mut done: Local<bool>,
|
|
) {
|
|
if !*done && player.iter().len() == foxes.count {
|
|
for mut player in &mut player {
|
|
player.play(animations.0[0].clone_weak()).repeat();
|
|
}
|
|
*done = true;
|
|
}
|
|
}
|
|
|
|
fn update_fox_rings(
|
|
time: Res<Time>,
|
|
foxes: Res<Foxes>,
|
|
mut rings: Query<(&Ring, &RotationDirection, &mut Transform)>,
|
|
) {
|
|
if !foxes.moving {
|
|
return;
|
|
}
|
|
|
|
let dt = time.delta_seconds();
|
|
for (ring, rotation_direction, mut transform) in &mut rings {
|
|
let angular_velocity = foxes.speed / ring.radius;
|
|
transform.rotate_y(rotation_direction.sign() * angular_velocity * dt);
|
|
}
|
|
}
|
|
|
|
fn keyboard_animation_control(
|
|
keyboard_input: Res<Input<KeyCode>>,
|
|
mut animation_player: Query<&mut AnimationPlayer>,
|
|
animations: Res<Animations>,
|
|
mut current_animation: Local<usize>,
|
|
mut foxes: ResMut<Foxes>,
|
|
) {
|
|
if keyboard_input.just_pressed(KeyCode::Space) {
|
|
foxes.moving = !foxes.moving;
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Up) {
|
|
foxes.speed *= 1.25;
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Down) {
|
|
foxes.speed *= 0.8;
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Return) {
|
|
*current_animation = (*current_animation + 1) % animations.0.len();
|
|
}
|
|
|
|
for mut player in &mut animation_player {
|
|
if keyboard_input.just_pressed(KeyCode::Space) {
|
|
if player.is_paused() {
|
|
player.resume();
|
|
} else {
|
|
player.pause();
|
|
}
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Up) {
|
|
let speed = player.speed();
|
|
player.set_speed(speed * 1.25);
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Down) {
|
|
let speed = player.speed();
|
|
player.set_speed(speed * 0.8);
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Left) {
|
|
let elapsed = player.elapsed();
|
|
player.set_elapsed(elapsed - 0.1);
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Right) {
|
|
let elapsed = player.elapsed();
|
|
player.set_elapsed(elapsed + 0.1);
|
|
}
|
|
|
|
if keyboard_input.just_pressed(KeyCode::Return) {
|
|
player
|
|
.play(animations.0[*current_animation].clone_weak())
|
|
.repeat();
|
|
}
|
|
}
|
|
}
|