2022-04-02 22:36:02 +00:00
|
|
|
//! Animation for the game engine Bevy
|
|
|
|
|
|
|
|
#![warn(missing_docs)]
|
|
|
|
|
|
|
|
use std::ops::Deref;
|
2023-01-09 19:24:51 +00:00
|
|
|
use std::time::Duration;
|
2022-04-02 22:36:02 +00:00
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
use bevy_app::{App, Plugin, PostUpdate};
|
2022-04-02 22:36:02 +00:00
|
|
|
use bevy_asset::{AddAsset, Assets, Handle};
|
2022-05-26 00:27:18 +00:00
|
|
|
use bevy_core::Name;
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
use bevy_ecs::prelude::*;
|
2023-01-04 20:43:39 +00:00
|
|
|
use bevy_hierarchy::{Children, Parent};
|
2022-04-02 22:36:02 +00:00
|
|
|
use bevy_math::{Quat, Vec3};
|
add `ReflectAsset` and `ReflectHandle` (#5923)
# Objective
![image](https://user-images.githubusercontent.com/22177966/189350194-639a0211-e984-4f73-ae62-0ede44891eb9.png)
^ enable this
Concretely, I need to
- list all handle ids for an asset type
- fetch the asset as `dyn Reflect`, given a `HandleUntyped`
- when encountering a `Handle<T>`, find out what asset type that handle refers to (`T`'s type id) and turn the handle into a `HandleUntyped`
## Solution
- add `ReflectAsset` type containing function pointers for working with assets
```rust
pub struct ReflectAsset {
type_uuid: Uuid,
assets_resource_type_id: TypeId, // TypeId of the `Assets<T>` resource
get: fn(&World, HandleUntyped) -> Option<&dyn Reflect>,
get_mut: fn(&mut World, HandleUntyped) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(&World, HandleUntyped) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn Reflect) -> HandleUntyped,
set: fn(&mut World, HandleUntyped, &dyn Reflect) -> HandleUntyped,
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = HandleId> + 'w>,
remove: fn(&mut World, HandleUntyped) -> Option<Box<dyn Reflect>>,
}
```
- add `ReflectHandle` type relating the handle back to the asset type and providing a way to create a `HandleUntyped`
```rust
pub struct ReflectHandle {
type_uuid: Uuid,
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<HandleUntyped>,
}
```
- add the corresponding `FromType` impls
- add a function `app.register_asset_reflect` which is supposed to be called after `.add_asset` and registers `ReflectAsset` and `ReflectHandle` in the type registry
---
## Changelog
- add `ReflectAsset` and `ReflectHandle` types, which allow code to use reflection to manipulate arbitrary assets without knowing their types at compile time
2022-10-28 20:42:33 +00:00
|
|
|
use bevy_reflect::{FromReflect, Reflect, TypeUuid};
|
2022-05-26 00:27:18 +00:00
|
|
|
use bevy_time::Time;
|
2022-04-02 22:36:02 +00:00
|
|
|
use bevy_transform::{prelude::Transform, TransformSystem};
|
|
|
|
use bevy_utils::{tracing::warn, HashMap};
|
|
|
|
|
|
|
|
#[allow(missing_docs)]
|
|
|
|
pub mod prelude {
|
2022-11-02 20:40:45 +00:00
|
|
|
#[doc(hidden)]
|
2022-04-02 22:36:02 +00:00
|
|
|
pub use crate::{
|
|
|
|
AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Keyframes, VariableCurve,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// List of keyframes for one of the attribute of a [`Transform`].
|
add `ReflectAsset` and `ReflectHandle` (#5923)
# Objective
![image](https://user-images.githubusercontent.com/22177966/189350194-639a0211-e984-4f73-ae62-0ede44891eb9.png)
^ enable this
Concretely, I need to
- list all handle ids for an asset type
- fetch the asset as `dyn Reflect`, given a `HandleUntyped`
- when encountering a `Handle<T>`, find out what asset type that handle refers to (`T`'s type id) and turn the handle into a `HandleUntyped`
## Solution
- add `ReflectAsset` type containing function pointers for working with assets
```rust
pub struct ReflectAsset {
type_uuid: Uuid,
assets_resource_type_id: TypeId, // TypeId of the `Assets<T>` resource
get: fn(&World, HandleUntyped) -> Option<&dyn Reflect>,
get_mut: fn(&mut World, HandleUntyped) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(&World, HandleUntyped) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn Reflect) -> HandleUntyped,
set: fn(&mut World, HandleUntyped, &dyn Reflect) -> HandleUntyped,
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = HandleId> + 'w>,
remove: fn(&mut World, HandleUntyped) -> Option<Box<dyn Reflect>>,
}
```
- add `ReflectHandle` type relating the handle back to the asset type and providing a way to create a `HandleUntyped`
```rust
pub struct ReflectHandle {
type_uuid: Uuid,
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<HandleUntyped>,
}
```
- add the corresponding `FromType` impls
- add a function `app.register_asset_reflect` which is supposed to be called after `.add_asset` and registers `ReflectAsset` and `ReflectHandle` in the type registry
---
## Changelog
- add `ReflectAsset` and `ReflectHandle` types, which allow code to use reflection to manipulate arbitrary assets without knowing their types at compile time
2022-10-28 20:42:33 +00:00
|
|
|
#[derive(Reflect, FromReflect, Clone, Debug)]
|
2022-04-02 22:36:02 +00:00
|
|
|
pub enum Keyframes {
|
|
|
|
/// Keyframes for rotation.
|
|
|
|
Rotation(Vec<Quat>),
|
|
|
|
/// Keyframes for translation.
|
|
|
|
Translation(Vec<Vec3>),
|
|
|
|
/// Keyframes for scale.
|
|
|
|
Scale(Vec<Vec3>),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Describes how an attribute of a [`Transform`] should be animated.
|
|
|
|
///
|
|
|
|
/// `keyframe_timestamps` and `keyframes` should have the same length.
|
add `ReflectAsset` and `ReflectHandle` (#5923)
# Objective
![image](https://user-images.githubusercontent.com/22177966/189350194-639a0211-e984-4f73-ae62-0ede44891eb9.png)
^ enable this
Concretely, I need to
- list all handle ids for an asset type
- fetch the asset as `dyn Reflect`, given a `HandleUntyped`
- when encountering a `Handle<T>`, find out what asset type that handle refers to (`T`'s type id) and turn the handle into a `HandleUntyped`
## Solution
- add `ReflectAsset` type containing function pointers for working with assets
```rust
pub struct ReflectAsset {
type_uuid: Uuid,
assets_resource_type_id: TypeId, // TypeId of the `Assets<T>` resource
get: fn(&World, HandleUntyped) -> Option<&dyn Reflect>,
get_mut: fn(&mut World, HandleUntyped) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(&World, HandleUntyped) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn Reflect) -> HandleUntyped,
set: fn(&mut World, HandleUntyped, &dyn Reflect) -> HandleUntyped,
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = HandleId> + 'w>,
remove: fn(&mut World, HandleUntyped) -> Option<Box<dyn Reflect>>,
}
```
- add `ReflectHandle` type relating the handle back to the asset type and providing a way to create a `HandleUntyped`
```rust
pub struct ReflectHandle {
type_uuid: Uuid,
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<HandleUntyped>,
}
```
- add the corresponding `FromType` impls
- add a function `app.register_asset_reflect` which is supposed to be called after `.add_asset` and registers `ReflectAsset` and `ReflectHandle` in the type registry
---
## Changelog
- add `ReflectAsset` and `ReflectHandle` types, which allow code to use reflection to manipulate arbitrary assets without knowing their types at compile time
2022-10-28 20:42:33 +00:00
|
|
|
#[derive(Reflect, FromReflect, Clone, Debug)]
|
2022-04-02 22:36:02 +00:00
|
|
|
pub struct VariableCurve {
|
|
|
|
/// Timestamp for each of the keyframes.
|
|
|
|
pub keyframe_timestamps: Vec<f32>,
|
|
|
|
/// List of the keyframes.
|
|
|
|
pub keyframes: Keyframes,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Path to an entity, with [`Name`]s. Each entity in a path must have a name.
|
add `ReflectAsset` and `ReflectHandle` (#5923)
# Objective
![image](https://user-images.githubusercontent.com/22177966/189350194-639a0211-e984-4f73-ae62-0ede44891eb9.png)
^ enable this
Concretely, I need to
- list all handle ids for an asset type
- fetch the asset as `dyn Reflect`, given a `HandleUntyped`
- when encountering a `Handle<T>`, find out what asset type that handle refers to (`T`'s type id) and turn the handle into a `HandleUntyped`
## Solution
- add `ReflectAsset` type containing function pointers for working with assets
```rust
pub struct ReflectAsset {
type_uuid: Uuid,
assets_resource_type_id: TypeId, // TypeId of the `Assets<T>` resource
get: fn(&World, HandleUntyped) -> Option<&dyn Reflect>,
get_mut: fn(&mut World, HandleUntyped) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(&World, HandleUntyped) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn Reflect) -> HandleUntyped,
set: fn(&mut World, HandleUntyped, &dyn Reflect) -> HandleUntyped,
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = HandleId> + 'w>,
remove: fn(&mut World, HandleUntyped) -> Option<Box<dyn Reflect>>,
}
```
- add `ReflectHandle` type relating the handle back to the asset type and providing a way to create a `HandleUntyped`
```rust
pub struct ReflectHandle {
type_uuid: Uuid,
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<HandleUntyped>,
}
```
- add the corresponding `FromType` impls
- add a function `app.register_asset_reflect` which is supposed to be called after `.add_asset` and registers `ReflectAsset` and `ReflectHandle` in the type registry
---
## Changelog
- add `ReflectAsset` and `ReflectHandle` types, which allow code to use reflection to manipulate arbitrary assets without knowing their types at compile time
2022-10-28 20:42:33 +00:00
|
|
|
#[derive(Reflect, FromReflect, Clone, Debug, Hash, PartialEq, Eq, Default)]
|
2022-04-02 22:36:02 +00:00
|
|
|
pub struct EntityPath {
|
|
|
|
/// Parts of the path
|
|
|
|
pub parts: Vec<Name>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A list of [`VariableCurve`], and the [`EntityPath`] to which they apply.
|
add `ReflectAsset` and `ReflectHandle` (#5923)
# Objective
![image](https://user-images.githubusercontent.com/22177966/189350194-639a0211-e984-4f73-ae62-0ede44891eb9.png)
^ enable this
Concretely, I need to
- list all handle ids for an asset type
- fetch the asset as `dyn Reflect`, given a `HandleUntyped`
- when encountering a `Handle<T>`, find out what asset type that handle refers to (`T`'s type id) and turn the handle into a `HandleUntyped`
## Solution
- add `ReflectAsset` type containing function pointers for working with assets
```rust
pub struct ReflectAsset {
type_uuid: Uuid,
assets_resource_type_id: TypeId, // TypeId of the `Assets<T>` resource
get: fn(&World, HandleUntyped) -> Option<&dyn Reflect>,
get_mut: fn(&mut World, HandleUntyped) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(&World, HandleUntyped) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn Reflect) -> HandleUntyped,
set: fn(&mut World, HandleUntyped, &dyn Reflect) -> HandleUntyped,
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = HandleId> + 'w>,
remove: fn(&mut World, HandleUntyped) -> Option<Box<dyn Reflect>>,
}
```
- add `ReflectHandle` type relating the handle back to the asset type and providing a way to create a `HandleUntyped`
```rust
pub struct ReflectHandle {
type_uuid: Uuid,
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<HandleUntyped>,
}
```
- add the corresponding `FromType` impls
- add a function `app.register_asset_reflect` which is supposed to be called after `.add_asset` and registers `ReflectAsset` and `ReflectHandle` in the type registry
---
## Changelog
- add `ReflectAsset` and `ReflectHandle` types, which allow code to use reflection to manipulate arbitrary assets without knowing their types at compile time
2022-10-28 20:42:33 +00:00
|
|
|
#[derive(Reflect, FromReflect, Clone, TypeUuid, Debug, Default)]
|
2022-04-02 22:36:02 +00:00
|
|
|
#[uuid = "d81b7179-0448-4eb0-89fe-c067222725bf"]
|
|
|
|
pub struct AnimationClip {
|
2023-01-04 20:43:39 +00:00
|
|
|
curves: Vec<Vec<VariableCurve>>,
|
|
|
|
paths: HashMap<EntityPath, usize>,
|
2022-04-02 22:36:02 +00:00
|
|
|
duration: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AnimationClip {
|
|
|
|
#[inline]
|
2023-01-04 20:43:39 +00:00
|
|
|
/// [`VariableCurve`]s for each bone. Indexed by the bone ID.
|
|
|
|
pub fn curves(&self) -> &Vec<Vec<VariableCurve>> {
|
2022-04-02 22:36:02 +00:00
|
|
|
&self.curves
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:43:39 +00:00
|
|
|
/// Gets the curves for a bone.
|
|
|
|
///
|
|
|
|
/// Returns `None` if the bone is invalid.
|
|
|
|
#[inline]
|
|
|
|
pub fn get_curves(&self, bone_id: usize) -> Option<&'_ Vec<VariableCurve>> {
|
|
|
|
self.curves.get(bone_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the curves by it's [`EntityPath`].
|
|
|
|
///
|
|
|
|
/// Returns `None` if the bone is invalid.
|
|
|
|
#[inline]
|
|
|
|
pub fn get_curves_by_path(&self, path: &EntityPath) -> Option<&'_ Vec<VariableCurve>> {
|
|
|
|
self.paths.get(path).and_then(|id| self.curves.get(*id))
|
|
|
|
}
|
|
|
|
|
2022-04-27 17:37:30 +00:00
|
|
|
/// Duration of the clip, represented in seconds
|
2022-04-27 23:44:06 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn duration(&self) -> f32 {
|
|
|
|
self.duration
|
2022-04-27 17:37:30 +00:00
|
|
|
}
|
|
|
|
|
2022-04-02 22:36:02 +00:00
|
|
|
/// Add a [`VariableCurve`] to an [`EntityPath`].
|
|
|
|
pub fn add_curve_to_path(&mut self, path: EntityPath, curve: VariableCurve) {
|
|
|
|
// Update the duration of the animation by this curve duration if it's longer
|
|
|
|
self.duration = self
|
|
|
|
.duration
|
|
|
|
.max(*curve.keyframe_timestamps.last().unwrap_or(&0.0));
|
2023-01-04 20:43:39 +00:00
|
|
|
if let Some(bone_id) = self.paths.get(&path) {
|
|
|
|
self.curves[*bone_id].push(curve);
|
|
|
|
} else {
|
|
|
|
let idx = self.curves.len();
|
|
|
|
self.curves.push(vec![curve]);
|
|
|
|
self.paths.insert(path, idx);
|
|
|
|
}
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-09 19:24:51 +00:00
|
|
|
#[derive(Reflect)]
|
|
|
|
struct PlayingAnimation {
|
2022-04-02 22:36:02 +00:00
|
|
|
repeat: bool,
|
|
|
|
speed: f32,
|
|
|
|
elapsed: f32,
|
|
|
|
animation_clip: Handle<AnimationClip>,
|
2023-01-04 20:43:39 +00:00
|
|
|
path_cache: Vec<Vec<Option<Entity>>>,
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
|
2023-01-09 19:24:51 +00:00
|
|
|
impl Default for PlayingAnimation {
|
2022-04-02 22:36:02 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
repeat: false,
|
|
|
|
speed: 1.0,
|
|
|
|
elapsed: 0.0,
|
|
|
|
animation_clip: Default::default(),
|
2023-01-04 20:43:39 +00:00
|
|
|
path_cache: Vec::new(),
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-09 19:24:51 +00:00
|
|
|
/// An animation that is being faded out as part of a transition
|
|
|
|
struct AnimationTransition {
|
|
|
|
/// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.
|
|
|
|
current_weight: f32,
|
|
|
|
/// How much to decrease `current_weight` per second
|
|
|
|
weight_decline_per_sec: f32,
|
|
|
|
/// The animation that is being faded out
|
|
|
|
animation: PlayingAnimation,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Animation controls
|
|
|
|
#[derive(Component, Default, Reflect)]
|
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct AnimationPlayer {
|
|
|
|
paused: bool,
|
|
|
|
|
|
|
|
animation: PlayingAnimation,
|
|
|
|
|
|
|
|
// List of previous animations we're currently transitioning away from.
|
|
|
|
// Usually this is empty, when transitioning between animations, there is
|
|
|
|
// one entry. When another animation transition happens while a transition
|
|
|
|
// is still ongoing, then there can be more than one entry.
|
|
|
|
// Once a transition is finished, it will be automatically removed from the list
|
|
|
|
#[reflect(ignore)]
|
|
|
|
transitions: Vec<AnimationTransition>,
|
|
|
|
}
|
|
|
|
|
2022-04-02 22:36:02 +00:00
|
|
|
impl AnimationPlayer {
|
|
|
|
/// Start playing an animation, resetting state of the player
|
2023-01-09 19:24:51 +00:00
|
|
|
/// This will use a linear blending between the previous and the new animation to make a smooth transition
|
2022-10-24 21:01:09 +00:00
|
|
|
pub fn start(&mut self, handle: Handle<AnimationClip>) -> &mut Self {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation = PlayingAnimation {
|
2022-04-02 22:36:02 +00:00
|
|
|
animation_clip: handle,
|
|
|
|
..Default::default()
|
|
|
|
};
|
2023-01-09 19:24:51 +00:00
|
|
|
|
|
|
|
// We want a hard transition.
|
|
|
|
// In case any previous transitions are still playing, stop them
|
|
|
|
self.transitions.clear();
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Start playing an animation, resetting state of the player
|
|
|
|
/// This will use a linear blending between the previous and the new animation to make a smooth transition
|
|
|
|
pub fn start_with_transition(
|
|
|
|
&mut self,
|
|
|
|
handle: Handle<AnimationClip>,
|
|
|
|
transition_duration: Duration,
|
|
|
|
) -> &mut Self {
|
|
|
|
let mut animation = PlayingAnimation {
|
|
|
|
animation_clip: handle,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
std::mem::swap(&mut animation, &mut self.animation);
|
|
|
|
|
|
|
|
// Add the current transition. If other transitions are still ongoing,
|
|
|
|
// this will keep those transitions running and cause a transition between
|
|
|
|
// the output of that previous transition to the new animation.
|
|
|
|
self.transitions.push(AnimationTransition {
|
|
|
|
current_weight: 1.0,
|
|
|
|
weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(),
|
|
|
|
animation,
|
|
|
|
});
|
|
|
|
|
2022-04-02 22:36:02 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-10-24 21:01:09 +00:00
|
|
|
/// Start playing an animation, resetting state of the player, unless the requested animation is already playing.
|
2023-01-09 19:24:51 +00:00
|
|
|
/// If `transition_duration` is set, this will use a linear blending
|
|
|
|
/// between the previous and the new animation to make a smooth transition
|
2022-10-24 21:01:09 +00:00
|
|
|
pub fn play(&mut self, handle: Handle<AnimationClip>) -> &mut Self {
|
2023-01-09 19:24:51 +00:00
|
|
|
if self.animation.animation_clip != handle || self.is_paused() {
|
2022-10-24 21:01:09 +00:00
|
|
|
self.start(handle);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-01-09 19:24:51 +00:00
|
|
|
/// Start playing an animation, resetting state of the player, unless the requested animation is already playing.
|
|
|
|
/// This will use a linear blending between the previous and the new animation to make a smooth transition
|
|
|
|
pub fn play_with_transition(
|
|
|
|
&mut self,
|
|
|
|
handle: Handle<AnimationClip>,
|
|
|
|
transition_duration: Duration,
|
|
|
|
) -> &mut Self {
|
|
|
|
if self.animation.animation_clip != handle || self.is_paused() {
|
|
|
|
self.start_with_transition(handle, transition_duration);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-04-02 22:36:02 +00:00
|
|
|
/// Set the animation to repeat
|
|
|
|
pub fn repeat(&mut self) -> &mut Self {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation.repeat = true;
|
2022-04-02 22:36:02 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Stop the animation from repeating
|
|
|
|
pub fn stop_repeating(&mut self) -> &mut Self {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation.repeat = false;
|
2022-04-02 22:36:02 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Pause the animation
|
|
|
|
pub fn pause(&mut self) {
|
|
|
|
self.paused = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Unpause the animation
|
|
|
|
pub fn resume(&mut self) {
|
|
|
|
self.paused = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Is the animation paused
|
|
|
|
pub fn is_paused(&self) -> bool {
|
|
|
|
self.paused
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Speed of the animation playback
|
|
|
|
pub fn speed(&self) -> f32 {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation.speed
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the speed of the animation playback
|
|
|
|
pub fn set_speed(&mut self, speed: f32) -> &mut Self {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation.speed = speed;
|
2022-04-02 22:36:02 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Time elapsed playing the animation
|
|
|
|
pub fn elapsed(&self) -> f32 {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation.elapsed
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Seek to a specific time in the animation
|
|
|
|
pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self {
|
2023-01-09 19:24:51 +00:00
|
|
|
self.animation.elapsed = elapsed;
|
2022-04-02 22:36:02 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:43:39 +00:00
|
|
|
fn find_bone(
|
|
|
|
root: Entity,
|
|
|
|
path: &EntityPath,
|
|
|
|
children: &Query<&Children>,
|
|
|
|
names: &Query<&Name>,
|
|
|
|
path_cache: &mut Vec<Option<Entity>>,
|
|
|
|
) -> Option<Entity> {
|
|
|
|
// PERF: finding the target entity can be optimised
|
|
|
|
let mut current_entity = root;
|
|
|
|
path_cache.resize(path.parts.len(), None);
|
|
|
|
// Ignore the first name, it is the root node which we already have
|
|
|
|
for (idx, part) in path.parts.iter().enumerate().skip(1) {
|
|
|
|
let mut found = false;
|
|
|
|
let children = children.get(current_entity).ok()?;
|
|
|
|
if let Some(cached) = path_cache[idx] {
|
|
|
|
if children.contains(&cached) {
|
|
|
|
if let Ok(name) = names.get(cached) {
|
|
|
|
if name == part {
|
|
|
|
current_entity = cached;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
for child in children.deref() {
|
|
|
|
if let Ok(name) = names.get(*child) {
|
|
|
|
if name == part {
|
|
|
|
// Found a children with the right name, continue to the next part
|
|
|
|
current_entity = *child;
|
|
|
|
path_cache[idx] = Some(*child);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
warn!("Entity not found for path {:?} on part {:?}", path, part);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(current_entity)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify that there are no ancestors of a given entity that have an `AnimationPlayer`.
|
|
|
|
fn verify_no_ancestor_player(
|
|
|
|
player_parent: Option<&Parent>,
|
|
|
|
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
|
|
|
|
) -> bool {
|
|
|
|
let Some(mut current) = player_parent.map(Parent::get) else { return true };
|
|
|
|
loop {
|
|
|
|
let Ok((maybe_player, parent)) = parents.get(current) else { return true };
|
|
|
|
if maybe_player.is_some() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if let Some(parent) = parent {
|
|
|
|
current = parent.get();
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-02 22:36:02 +00:00
|
|
|
/// System that will play all animations, using any entity with a [`AnimationPlayer`]
|
|
|
|
/// and a [`Handle<AnimationClip>`] as an animation root
|
|
|
|
pub fn animation_player(
|
|
|
|
time: Res<Time>,
|
|
|
|
animations: Res<Assets<AnimationClip>>,
|
|
|
|
children: Query<&Children>,
|
2023-01-04 20:43:39 +00:00
|
|
|
names: Query<&Name>,
|
|
|
|
transforms: Query<&mut Transform>,
|
|
|
|
parents: Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
|
|
|
|
mut animation_players: Query<(Entity, Option<&Parent>, &mut AnimationPlayer)>,
|
2022-04-02 22:36:02 +00:00
|
|
|
) {
|
2023-01-20 08:47:20 +00:00
|
|
|
animation_players
|
|
|
|
.par_iter_mut()
|
|
|
|
.for_each_mut(|(root, maybe_parent, mut player)| {
|
|
|
|
update_transitions(&mut player, &time);
|
|
|
|
run_animation_player(
|
|
|
|
root,
|
|
|
|
player,
|
|
|
|
&time,
|
|
|
|
&animations,
|
|
|
|
&names,
|
|
|
|
&transforms,
|
|
|
|
maybe_parent,
|
|
|
|
&parents,
|
|
|
|
&children,
|
|
|
|
);
|
|
|
|
});
|
2023-01-09 19:24:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn run_animation_player(
|
|
|
|
root: Entity,
|
|
|
|
mut player: Mut<AnimationPlayer>,
|
|
|
|
time: &Time,
|
|
|
|
animations: &Assets<AnimationClip>,
|
|
|
|
names: &Query<&Name>,
|
|
|
|
transforms: &Query<&mut Transform>,
|
|
|
|
maybe_parent: Option<&Parent>,
|
|
|
|
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
|
|
|
|
children: &Query<&Children>,
|
|
|
|
) {
|
|
|
|
let paused = player.paused;
|
|
|
|
// Continue if paused unless the `AnimationPlayer` was changed
|
|
|
|
// This allow the animation to still be updated if the player.elapsed field was manually updated in pause
|
|
|
|
if paused && !player.is_changed() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the main animation
|
|
|
|
apply_animation(
|
|
|
|
1.0,
|
|
|
|
&mut player.animation,
|
|
|
|
paused,
|
|
|
|
root,
|
|
|
|
time,
|
|
|
|
animations,
|
|
|
|
names,
|
|
|
|
transforms,
|
|
|
|
maybe_parent,
|
|
|
|
parents,
|
|
|
|
children,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Apply any potential fade-out transitions from previous animations
|
|
|
|
for AnimationTransition {
|
|
|
|
current_weight,
|
|
|
|
animation,
|
|
|
|
..
|
|
|
|
} in &mut player.transitions
|
|
|
|
{
|
|
|
|
apply_animation(
|
|
|
|
*current_weight,
|
|
|
|
animation,
|
|
|
|
paused,
|
|
|
|
root,
|
|
|
|
time,
|
|
|
|
animations,
|
|
|
|
names,
|
|
|
|
transforms,
|
|
|
|
maybe_parent,
|
|
|
|
parents,
|
|
|
|
children,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn apply_animation(
|
|
|
|
weight: f32,
|
|
|
|
animation: &mut PlayingAnimation,
|
|
|
|
paused: bool,
|
|
|
|
root: Entity,
|
|
|
|
time: &Time,
|
|
|
|
animations: &Assets<AnimationClip>,
|
|
|
|
names: &Query<&Name>,
|
|
|
|
transforms: &Query<&mut Transform>,
|
|
|
|
maybe_parent: Option<&Parent>,
|
|
|
|
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
|
|
|
|
children: &Query<&Children>,
|
|
|
|
) {
|
|
|
|
if let Some(animation_clip) = animations.get(&animation.animation_clip) {
|
|
|
|
if !paused {
|
|
|
|
animation.elapsed += time.delta_seconds() * animation.speed;
|
2023-01-04 20:43:39 +00:00
|
|
|
}
|
2023-01-09 19:24:51 +00:00
|
|
|
let mut elapsed = animation.elapsed;
|
|
|
|
if animation.repeat {
|
2023-01-04 20:43:39 +00:00
|
|
|
elapsed %= animation_clip.duration;
|
|
|
|
}
|
|
|
|
if elapsed < 0.0 {
|
|
|
|
elapsed += animation_clip.duration;
|
|
|
|
}
|
2023-01-09 19:24:51 +00:00
|
|
|
if animation.path_cache.len() != animation_clip.paths.len() {
|
|
|
|
animation.path_cache = vec![Vec::new(); animation_clip.paths.len()];
|
2023-01-04 20:43:39 +00:00
|
|
|
}
|
2023-01-09 19:24:51 +00:00
|
|
|
if !verify_no_ancestor_player(maybe_parent, parents) {
|
2023-01-04 20:43:39 +00:00
|
|
|
warn!("Animation player on {:?} has a conflicting animation player on an ancestor. Cannot safely animate.", root);
|
|
|
|
return;
|
|
|
|
}
|
2023-01-09 19:24:51 +00:00
|
|
|
|
2023-01-04 20:43:39 +00:00
|
|
|
for (path, bone_id) in &animation_clip.paths {
|
2023-01-09 19:24:51 +00:00
|
|
|
let cached_path = &mut animation.path_cache[*bone_id];
|
2023-01-04 20:43:39 +00:00
|
|
|
let curves = animation_clip.get_curves(*bone_id).unwrap();
|
2023-01-09 19:24:51 +00:00
|
|
|
let Some(target) = find_bone(root, path, children, names, cached_path) else { continue };
|
2023-01-04 20:43:39 +00:00
|
|
|
// SAFETY: The verify_no_ancestor_player check above ensures that two animation players cannot alias
|
|
|
|
// any of their descendant Transforms.
|
2023-01-09 19:24:51 +00:00
|
|
|
//
|
|
|
|
// The system scheduler prevents any other system from mutating Transforms at the same time,
|
2023-01-27 12:12:53 +00:00
|
|
|
// so the only way this fetch can alias is if two AnimationPlayers are targeting the same bone.
|
2023-01-04 20:43:39 +00:00
|
|
|
// This can only happen if there are two or more AnimationPlayers are ancestors to the same
|
|
|
|
// entities. By verifying that there is no other AnimationPlayer in the ancestors of a
|
|
|
|
// running AnimationPlayer before animating any entity, this fetch cannot alias.
|
|
|
|
//
|
|
|
|
// This means only the AnimationPlayers closest to the root of the hierarchy will be able
|
|
|
|
// to run their animation. Any players in the children or descendants will log a warning
|
|
|
|
// and do nothing.
|
|
|
|
let Ok(mut transform) = (unsafe { transforms.get_unchecked(target) }) else { continue };
|
|
|
|
for curve in curves {
|
|
|
|
// Some curves have only one keyframe used to set a transform
|
|
|
|
if curve.keyframe_timestamps.len() == 1 {
|
|
|
|
match &curve.keyframes {
|
2023-01-09 19:24:51 +00:00
|
|
|
Keyframes::Rotation(keyframes) => {
|
|
|
|
transform.rotation = transform.rotation.slerp(keyframes[0], weight);
|
|
|
|
}
|
2023-01-04 20:43:39 +00:00
|
|
|
Keyframes::Translation(keyframes) => {
|
2023-01-09 19:24:51 +00:00
|
|
|
transform.translation =
|
|
|
|
transform.translation.lerp(keyframes[0], weight);
|
|
|
|
}
|
|
|
|
Keyframes::Scale(keyframes) => {
|
|
|
|
transform.scale = transform.scale.lerp(keyframes[0], weight);
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-04 20:43:39 +00:00
|
|
|
continue;
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
2022-04-04 19:45:51 +00:00
|
|
|
|
2023-01-04 20:43:39 +00:00
|
|
|
// Find the current keyframe
|
|
|
|
// PERF: finding the current keyframe can be optimised
|
|
|
|
let step_start = match curve
|
|
|
|
.keyframe_timestamps
|
|
|
|
.binary_search_by(|probe| probe.partial_cmp(&elapsed).unwrap())
|
|
|
|
{
|
|
|
|
Ok(n) if n >= curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished
|
|
|
|
Ok(i) => i,
|
|
|
|
Err(0) => continue, // this curve isn't started yet
|
|
|
|
Err(n) if n > curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished
|
|
|
|
Err(i) => i - 1,
|
|
|
|
};
|
|
|
|
let ts_start = curve.keyframe_timestamps[step_start];
|
|
|
|
let ts_end = curve.keyframe_timestamps[step_start + 1];
|
|
|
|
let lerp = (elapsed - ts_start) / (ts_end - ts_start);
|
|
|
|
|
|
|
|
// Apply the keyframe
|
|
|
|
match &curve.keyframes {
|
|
|
|
Keyframes::Rotation(keyframes) => {
|
|
|
|
let rot_start = keyframes[step_start];
|
|
|
|
let mut rot_end = keyframes[step_start + 1];
|
|
|
|
// Choose the smallest angle for the rotation
|
|
|
|
if rot_end.dot(rot_start) < 0.0 {
|
|
|
|
rot_end = -rot_end;
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
2023-01-04 20:43:39 +00:00
|
|
|
// Rotations are using a spherical linear interpolation
|
2023-01-09 19:24:51 +00:00
|
|
|
let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp);
|
|
|
|
transform.rotation = transform.rotation.slerp(rot, weight);
|
2023-01-04 20:43:39 +00:00
|
|
|
}
|
|
|
|
Keyframes::Translation(keyframes) => {
|
|
|
|
let translation_start = keyframes[step_start];
|
|
|
|
let translation_end = keyframes[step_start + 1];
|
|
|
|
let result = translation_start.lerp(translation_end, lerp);
|
2023-01-09 19:24:51 +00:00
|
|
|
transform.translation = transform.translation.lerp(result, weight);
|
2023-01-04 20:43:39 +00:00
|
|
|
}
|
|
|
|
Keyframes::Scale(keyframes) => {
|
|
|
|
let scale_start = keyframes[step_start];
|
|
|
|
let scale_end = keyframes[step_start + 1];
|
|
|
|
let result = scale_start.lerp(scale_end, lerp);
|
2023-01-09 19:24:51 +00:00
|
|
|
transform.scale = transform.scale.lerp(result, weight);
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-09 19:24:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_transitions(player: &mut AnimationPlayer, time: &Time) {
|
|
|
|
player.transitions.retain_mut(|animation| {
|
|
|
|
animation.current_weight -= animation.weight_decline_per_sec * time.delta_seconds();
|
|
|
|
animation.current_weight > 0.0
|
2023-01-04 20:43:39 +00:00
|
|
|
});
|
2022-04-02 22:36:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds animation support to an app
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct AnimationPlugin {}
|
|
|
|
|
|
|
|
impl Plugin for AnimationPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
app.add_asset::<AnimationClip>()
|
add `ReflectAsset` and `ReflectHandle` (#5923)
# Objective
![image](https://user-images.githubusercontent.com/22177966/189350194-639a0211-e984-4f73-ae62-0ede44891eb9.png)
^ enable this
Concretely, I need to
- list all handle ids for an asset type
- fetch the asset as `dyn Reflect`, given a `HandleUntyped`
- when encountering a `Handle<T>`, find out what asset type that handle refers to (`T`'s type id) and turn the handle into a `HandleUntyped`
## Solution
- add `ReflectAsset` type containing function pointers for working with assets
```rust
pub struct ReflectAsset {
type_uuid: Uuid,
assets_resource_type_id: TypeId, // TypeId of the `Assets<T>` resource
get: fn(&World, HandleUntyped) -> Option<&dyn Reflect>,
get_mut: fn(&mut World, HandleUntyped) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(&World, HandleUntyped) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn Reflect) -> HandleUntyped,
set: fn(&mut World, HandleUntyped, &dyn Reflect) -> HandleUntyped,
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = HandleId> + 'w>,
remove: fn(&mut World, HandleUntyped) -> Option<Box<dyn Reflect>>,
}
```
- add `ReflectHandle` type relating the handle back to the asset type and providing a way to create a `HandleUntyped`
```rust
pub struct ReflectHandle {
type_uuid: Uuid,
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<HandleUntyped>,
}
```
- add the corresponding `FromType` impls
- add a function `app.register_asset_reflect` which is supposed to be called after `.add_asset` and registers `ReflectAsset` and `ReflectHandle` in the type registry
---
## Changelog
- add `ReflectAsset` and `ReflectHandle` types, which allow code to use reflection to manipulate arbitrary assets without knowing their types at compile time
2022-10-28 20:42:33 +00:00
|
|
|
.register_asset_reflect::<AnimationClip>()
|
2022-04-02 22:36:02 +00:00
|
|
|
.register_type::<AnimationPlayer>()
|
2023-03-18 01:45:34 +00:00
|
|
|
.add_systems(
|
|
|
|
PostUpdate,
|
|
|
|
animation_player.before(TransformSystem::TransformPropagate),
|
2022-04-02 22:36:02 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|