2022-07-04 13:04:15 +00:00
|
|
|
//! Renders a lot of animated sprites to allow performance testing.
|
|
|
|
//!
|
|
|
|
//! It sets up many animated sprites in different sizes and rotations, and at different scales in the world,
|
|
|
|
//! and moves the camera over them to see how well frustum culling works.
|
|
|
|
//!
|
|
|
|
//! To measure performance realistically, be sure to run this in release mode.
|
|
|
|
//! `cargo run --example many_animated_sprites --release`
|
|
|
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use bevy::{
|
|
|
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
|
|
|
math::Quat,
|
|
|
|
prelude::*,
|
|
|
|
render::camera::Camera,
|
|
|
|
};
|
|
|
|
|
|
|
|
use rand::Rng;
|
|
|
|
|
|
|
|
const CAMERA_SPEED: f32 = 1000.0;
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
App::new()
|
|
|
|
// Since this is also used as a benchmark, we want it to display performance data.
|
|
|
|
.add_plugin(LogDiagnosticsPlugin::default())
|
|
|
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
|
|
|
.add_plugins(DefaultPlugins)
|
|
|
|
.add_startup_system(setup)
|
|
|
|
.add_system(animate_sprite)
|
|
|
|
.add_system(print_sprite_count)
|
|
|
|
.add_system(move_camera.after(print_sprite_count))
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn setup(
|
|
|
|
mut commands: Commands,
|
|
|
|
assets: Res<AssetServer>,
|
|
|
|
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
|
|
|
) {
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
|
|
|
let tile_size = Vec2::splat(64.0);
|
|
|
|
let map_size = Vec2::splat(320.0);
|
|
|
|
|
|
|
|
let half_x = (map_size.x / 2.0) as i32;
|
|
|
|
let half_y = (map_size.y / 2.0) as i32;
|
|
|
|
|
|
|
|
let texture_handle = assets.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
Merge TextureAtlas::from_grid_with_padding into TextureAtlas::from_grid through option arguments (#6057)
This is an adoption of #3775
This merges `TextureAtlas` `from_grid_with_padding` into `from_grid` , adding optional padding and optional offset.
Since the orignal PR, the offset had already been added to from_grid_with_padding through #4836
## Changelog
- Added `padding` and `offset` arguments to `TextureAtlas::from_grid`
- Removed `TextureAtlas::from_grid_with_padding`
## Migration Guide
`TextureAtlas::from_grid_with_padding` was merged into `from_grid` which takes two additional parameters for padding and an offset.
```
// 0.8
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1);
// 0.9
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None)
// 0.8
TextureAtlas::from_grid_with_padding(texture_handle, Vec2::new(24.0, 24.0), 7, 1, Vec2::new(4.0, 4.0));
// 0.9
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, Some(Vec2::new(4.0, 4.0)), None)
```
Co-authored-by: olefish <88390729+oledfish@users.noreply.github.com>
2022-09-24 12:58:06 +00:00
|
|
|
let texture_atlas =
|
|
|
|
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
|
2022-07-04 13:04:15 +00:00
|
|
|
let texture_atlas_handle = texture_atlases.add(texture_atlas);
|
|
|
|
|
|
|
|
// Spawns the camera
|
2022-09-25 18:03:53 +00:00
|
|
|
|
|
|
|
commands.spawn(Camera2dBundle::default());
|
2022-07-04 13:04:15 +00:00
|
|
|
|
|
|
|
// Builds and spawns the sprites
|
|
|
|
for y in -half_y..half_y {
|
|
|
|
for x in -half_x..half_x {
|
|
|
|
let position = Vec2::new(x as f32, y as f32);
|
|
|
|
let translation = (position * tile_size).extend(rng.gen::<f32>());
|
|
|
|
let rotation = Quat::from_rotation_z(rng.gen::<f32>());
|
|
|
|
let scale = Vec3::splat(rng.gen::<f32>() * 2.0);
|
Replace the `bool` argument of `Timer` with `TimerMode` (#6247)
As mentioned in #2926, it's better to have an explicit type that clearly communicates the intent of the timer mode rather than an opaque boolean, which can be only understood when knowing the signature or having to look up the documentation.
This also opens up a way to merge different timers, such as `Stopwatch`, and possibly future ones, such as `DiscreteStopwatch` and `DiscreteTimer` from #2683, into one struct.
Signed-off-by: Lena Milizé <me@lvmn.org>
# Objective
Fixes #2926.
## Solution
Introduce `TimerMode` which replaces the `bool` argument of `Timer` constructors. A `Default` value for `TimerMode` is `Once`.
---
## Changelog
### Added
- `TimerMode` enum, along with variants `TimerMode::Once` and `TimerMode::Repeating`
### Changed
- Replace `bool` argument of `Timer::new` and `Timer::from_seconds` with `TimerMode`
- Change `repeating: bool` field of `Timer` with `mode: TimerMode`
## Migration Guide
- Replace `Timer::new(duration, false)` with `Timer::new(duration, TimerMode::Once)`.
- Replace `Timer::new(duration, true)` with `Timer::new(duration, TimerMode::Repeating)`.
- Replace `Timer::from_seconds(seconds, false)` with `Timer::from_seconds(seconds, TimerMode::Once)`.
- Replace `Timer::from_seconds(seconds, true)` with `Timer::from_seconds(seconds, TimerMode::Repeating)`.
- Change `timer.repeating()` to `timer.mode() == TimerMode::Repeating`.
2022-10-17 13:47:01 +00:00
|
|
|
let mut timer = Timer::from_seconds(0.1, TimerMode::Repeating);
|
2022-07-04 13:04:15 +00:00
|
|
|
timer.set_elapsed(Duration::from_secs_f32(rng.gen::<f32>()));
|
|
|
|
|
Spawn now takes a Bundle (#6054)
# Objective
Now that we can consolidate Bundles and Components under a single insert (thanks to #2975 and #6039), almost 100% of world spawns now look like `world.spawn().insert((Some, Tuple, Here))`. Spawning an entity without any components is an extremely uncommon pattern, so it makes sense to give spawn the "first class" ergonomic api. This consolidated api should be made consistent across all spawn apis (such as World and Commands).
## Solution
All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input:
```rust
// before:
commands
.spawn()
.insert((A, B, C));
world
.spawn()
.insert((A, B, C);
// after
commands.spawn((A, B, C));
world.spawn((A, B, C));
```
All existing instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api. A new `spawn_empty` has been added, replacing the old `spawn` api.
By allowing `world.spawn(some_bundle)` to replace `world.spawn().insert(some_bundle)`, this opened the door to removing the initial entity allocation in the "empty" archetype / table done in `spawn()` (and subsequent move to the actual archetype in `.insert(some_bundle)`).
This improves spawn performance by over 10%:
![image](https://user-images.githubusercontent.com/2694663/191627587-4ab2f949-4ccd-4231-80eb-80dd4d9ad6b9.png)
To take this measurement, I added a new `world_spawn` benchmark.
Unfortunately, optimizing `Commands::spawn` is slightly less trivial, as Commands expose the Entity id of spawned entities prior to actually spawning. Doing the optimization would (naively) require assurances that the `spawn(some_bundle)` command is applied before all other commands involving the entity (which would not necessarily be true, if memory serves). Optimizing `Commands::spawn` this way does feel possible, but it will require careful thought (and maybe some additional checks), which deserves its own PR. For now, it has the same performance characteristics of the current `Commands::spawn_bundle` on main.
**Note that 99% of this PR is simple renames and refactors. The only code that needs careful scrutiny is the new `World::spawn()` impl, which is relatively straightforward, but it has some new unsafe code (which re-uses battle tested BundlerSpawner code path).**
---
## Changelog
- All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input
- All instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api
- World and Commands now have `spawn_empty()`, which is equivalent to the old `spawn()` behavior.
## Migration Guide
```rust
// Old (0.8):
commands
.spawn()
.insert_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
commands.spawn_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
let entity = commands.spawn().id();
// New (0.9)
let entity = commands.spawn_empty().id();
// Old (0.8)
let entity = world.spawn().id();
// New (0.9)
let entity = world.spawn_empty();
```
2022-09-23 19:55:54 +00:00
|
|
|
commands.spawn((
|
|
|
|
SpriteSheetBundle {
|
2022-07-04 13:04:15 +00:00
|
|
|
texture_atlas: texture_atlas_handle.clone(),
|
|
|
|
transform: Transform {
|
|
|
|
translation,
|
|
|
|
rotation,
|
|
|
|
scale,
|
|
|
|
},
|
|
|
|
sprite: TextureAtlasSprite {
|
|
|
|
custom_size: Some(tile_size),
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
..default()
|
Spawn now takes a Bundle (#6054)
# Objective
Now that we can consolidate Bundles and Components under a single insert (thanks to #2975 and #6039), almost 100% of world spawns now look like `world.spawn().insert((Some, Tuple, Here))`. Spawning an entity without any components is an extremely uncommon pattern, so it makes sense to give spawn the "first class" ergonomic api. This consolidated api should be made consistent across all spawn apis (such as World and Commands).
## Solution
All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input:
```rust
// before:
commands
.spawn()
.insert((A, B, C));
world
.spawn()
.insert((A, B, C);
// after
commands.spawn((A, B, C));
world.spawn((A, B, C));
```
All existing instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api. A new `spawn_empty` has been added, replacing the old `spawn` api.
By allowing `world.spawn(some_bundle)` to replace `world.spawn().insert(some_bundle)`, this opened the door to removing the initial entity allocation in the "empty" archetype / table done in `spawn()` (and subsequent move to the actual archetype in `.insert(some_bundle)`).
This improves spawn performance by over 10%:
![image](https://user-images.githubusercontent.com/2694663/191627587-4ab2f949-4ccd-4231-80eb-80dd4d9ad6b9.png)
To take this measurement, I added a new `world_spawn` benchmark.
Unfortunately, optimizing `Commands::spawn` is slightly less trivial, as Commands expose the Entity id of spawned entities prior to actually spawning. Doing the optimization would (naively) require assurances that the `spawn(some_bundle)` command is applied before all other commands involving the entity (which would not necessarily be true, if memory serves). Optimizing `Commands::spawn` this way does feel possible, but it will require careful thought (and maybe some additional checks), which deserves its own PR. For now, it has the same performance characteristics of the current `Commands::spawn_bundle` on main.
**Note that 99% of this PR is simple renames and refactors. The only code that needs careful scrutiny is the new `World::spawn()` impl, which is relatively straightforward, but it has some new unsafe code (which re-uses battle tested BundlerSpawner code path).**
---
## Changelog
- All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input
- All instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api
- World and Commands now have `spawn_empty()`, which is equivalent to the old `spawn()` behavior.
## Migration Guide
```rust
// Old (0.8):
commands
.spawn()
.insert_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
commands.spawn_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
let entity = commands.spawn().id();
// New (0.9)
let entity = commands.spawn_empty().id();
// Old (0.8)
let entity = world.spawn().id();
// New (0.9)
let entity = world.spawn_empty();
```
2022-09-23 19:55:54 +00:00
|
|
|
},
|
|
|
|
AnimationTimer(timer),
|
|
|
|
));
|
2022-07-04 13:04:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// System for rotating and translating the camera
|
|
|
|
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
|
|
|
|
let mut camera_transform = camera_query.single_mut();
|
|
|
|
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.5));
|
|
|
|
*camera_transform = *camera_transform
|
|
|
|
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Component, Deref, DerefMut)]
|
|
|
|
struct AnimationTimer(Timer);
|
|
|
|
|
|
|
|
fn animate_sprite(
|
|
|
|
time: Res<Time>,
|
|
|
|
texture_atlases: Res<Assets<TextureAtlas>>,
|
|
|
|
mut query: Query<(
|
|
|
|
&mut AnimationTimer,
|
|
|
|
&mut TextureAtlasSprite,
|
|
|
|
&Handle<TextureAtlas>,
|
|
|
|
)>,
|
|
|
|
) {
|
|
|
|
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() {
|
|
|
|
timer.tick(time.delta());
|
|
|
|
if timer.just_finished() {
|
|
|
|
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
|
|
|
|
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deref, DerefMut)]
|
|
|
|
struct PrintingTimer(Timer);
|
|
|
|
|
|
|
|
impl Default for PrintingTimer {
|
|
|
|
fn default() -> Self {
|
Replace the `bool` argument of `Timer` with `TimerMode` (#6247)
As mentioned in #2926, it's better to have an explicit type that clearly communicates the intent of the timer mode rather than an opaque boolean, which can be only understood when knowing the signature or having to look up the documentation.
This also opens up a way to merge different timers, such as `Stopwatch`, and possibly future ones, such as `DiscreteStopwatch` and `DiscreteTimer` from #2683, into one struct.
Signed-off-by: Lena Milizé <me@lvmn.org>
# Objective
Fixes #2926.
## Solution
Introduce `TimerMode` which replaces the `bool` argument of `Timer` constructors. A `Default` value for `TimerMode` is `Once`.
---
## Changelog
### Added
- `TimerMode` enum, along with variants `TimerMode::Once` and `TimerMode::Repeating`
### Changed
- Replace `bool` argument of `Timer::new` and `Timer::from_seconds` with `TimerMode`
- Change `repeating: bool` field of `Timer` with `mode: TimerMode`
## Migration Guide
- Replace `Timer::new(duration, false)` with `Timer::new(duration, TimerMode::Once)`.
- Replace `Timer::new(duration, true)` with `Timer::new(duration, TimerMode::Repeating)`.
- Replace `Timer::from_seconds(seconds, false)` with `Timer::from_seconds(seconds, TimerMode::Once)`.
- Replace `Timer::from_seconds(seconds, true)` with `Timer::from_seconds(seconds, TimerMode::Repeating)`.
- Change `timer.repeating()` to `timer.mode() == TimerMode::Repeating`.
2022-10-17 13:47:01 +00:00
|
|
|
Self(Timer::from_seconds(1.0, TimerMode::Repeating))
|
2022-07-04 13:04:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// System for printing the number of sprites on every tick of the timer
|
|
|
|
fn print_sprite_count(
|
|
|
|
time: Res<Time>,
|
|
|
|
mut timer: Local<PrintingTimer>,
|
|
|
|
sprites: Query<&TextureAtlasSprite>,
|
|
|
|
) {
|
|
|
|
timer.tick(time.delta());
|
|
|
|
|
|
|
|
if timer.just_finished() {
|
|
|
|
info!("Sprites: {}", sprites.iter().count(),);
|
|
|
|
}
|
|
|
|
}
|