mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Merge branch 'main' into meshlet-implicit-tangents
This commit is contained in:
commit
5a7df13753
16 changed files with 271 additions and 199 deletions
|
@ -3266,7 +3266,6 @@ description = "Shows how to orbit a static scene using pitch, yaw, and roll."
|
|||
category = "Camera"
|
||||
wasm = true
|
||||
|
||||
|
||||
[package.metadata.example.fps_overlay]
|
||||
name = "FPS overlay"
|
||||
description = "Demonstrates FPS overlay"
|
||||
|
|
|
@ -21,6 +21,7 @@ use bevy_ecs::{
|
|||
schedule::SystemSet,
|
||||
system::Resource,
|
||||
};
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
|
||||
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
|
||||
|
@ -94,7 +95,7 @@ impl From<NodeBuilder> for AccessibilityNode {
|
|||
|
||||
/// Resource representing which entity has keyboard focus, if any.
|
||||
#[derive(Resource, Default, Deref, DerefMut, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
#[reflect(Resource, Default)]
|
||||
pub struct Focus(pub Option<Entity>);
|
||||
|
||||
/// Set enum for the systems relating to accessibility
|
||||
|
|
|
@ -51,8 +51,7 @@ impl FileAssetReader {
|
|||
/// Returns the base path of the assets directory, which is normally the executable's parent
|
||||
/// directory.
|
||||
///
|
||||
/// If the `CARGO_MANIFEST_DIR` environment variable is set, then its value will be used
|
||||
/// instead. It's set by cargo when running with `cargo run`.
|
||||
/// To change this, set [`AssetPlugin.file_path`].
|
||||
pub fn get_base_path() -> PathBuf {
|
||||
get_base_path()
|
||||
}
|
||||
|
|
|
@ -766,7 +766,7 @@ pub struct Archetypes {
|
|||
/// find the archetype id by the archetype's components
|
||||
by_components: HashMap<ArchetypeComponents, ArchetypeId>,
|
||||
/// find all the archetypes that contain a component
|
||||
by_component: ComponentIndex,
|
||||
pub(crate) by_component: ComponentIndex,
|
||||
}
|
||||
|
||||
/// Metadata about how a component is stored in an [`Archetype`].
|
||||
|
|
|
@ -456,14 +456,17 @@ impl World {
|
|||
if observers.map.is_empty() && observers.entity_map.is_empty() {
|
||||
cache.component_observers.remove(component);
|
||||
if let Some(flag) = Observers::is_archetype_cached(event_type) {
|
||||
for archetype in &mut archetypes.archetypes {
|
||||
if archetype.contains(*component) {
|
||||
let no_longer_observed = archetype
|
||||
.components()
|
||||
.all(|id| !cache.component_observers.contains_key(&id));
|
||||
if let Some(by_component) = archetypes.by_component.get(component) {
|
||||
for archetype in by_component.keys() {
|
||||
let archetype = &mut archetypes.archetypes[archetype.index()];
|
||||
if archetype.contains(*component) {
|
||||
let no_longer_observed = archetype
|
||||
.components()
|
||||
.all(|id| !cache.component_observers.contains_key(&id));
|
||||
|
||||
if no_longer_observed {
|
||||
archetype.flags.set(flag, false);
|
||||
if no_longer_observed {
|
||||
archetype.flags.set(flag, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -477,6 +480,8 @@ impl World {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::vec;
|
||||
|
||||
use bevy_ptr::OwningPtr;
|
||||
|
||||
use crate as bevy_ecs;
|
||||
|
@ -503,13 +508,12 @@ mod tests {
|
|||
struct EventA;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct R(usize);
|
||||
struct Order(Vec<&'static str>);
|
||||
|
||||
impl R {
|
||||
impl Order {
|
||||
#[track_caller]
|
||||
fn assert_order(&mut self, count: usize) {
|
||||
assert_eq!(count, self.0);
|
||||
self.0 += 1;
|
||||
fn observed(&mut self, name: &'static str) {
|
||||
self.0.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -534,61 +538,72 @@ mod tests {
|
|||
#[test]
|
||||
fn observer_order_spawn_despawn() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.despawn(entity);
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add", "insert", "replace", "remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_insert_remove() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
entity.insert(A);
|
||||
entity.remove::<A>();
|
||||
entity.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add", "insert", "replace", "remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_insert_remove_sparse() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<R>| res.assert_order(2));
|
||||
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(3));
|
||||
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
entity.insert(S);
|
||||
entity.remove::<S>();
|
||||
entity.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add", "insert", "replace", "remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_replace() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
// and therefore does not automatically flush.
|
||||
|
@ -597,53 +612,56 @@ mod tests {
|
|||
let mut entity = world.entity_mut(entity);
|
||||
entity.insert(A);
|
||||
entity.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["replace", "insert"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_recursive() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
world.observe(
|
||||
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
|
||||
res.assert_order(0);
|
||||
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("add_a");
|
||||
commands.entity(obs.entity()).insert(B);
|
||||
},
|
||||
);
|
||||
world.observe(
|
||||
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
|
||||
res.assert_order(2);
|
||||
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("remove_a");
|
||||
commands.entity(obs.entity()).remove::<B>();
|
||||
},
|
||||
);
|
||||
|
||||
world.observe(
|
||||
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
|
||||
res.assert_order(1);
|
||||
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("add_b");
|
||||
commands.entity(obs.entity()).remove::<A>();
|
||||
},
|
||||
);
|
||||
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
|
||||
res.assert_order(3);
|
||||
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
|
||||
res.observed("remove_b");
|
||||
});
|
||||
|
||||
let entity = world.spawn(A).flush();
|
||||
let entity = world.get_entity(entity).unwrap();
|
||||
assert!(!entity.contains::<A>());
|
||||
assert!(!entity.contains::<B>());
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add_a", "add_b", "remove_a", "remove_b"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_multiple_listeners() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_1"));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
|
||||
|
||||
world.spawn(A).flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0);
|
||||
// Our A entity plus our two observers
|
||||
assert_eq!(world.entities().len(), 3);
|
||||
}
|
||||
|
@ -651,40 +669,44 @@ mod tests {
|
|||
#[test]
|
||||
fn observer_multiple_events() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
let on_remove = world.init_component::<OnRemove>();
|
||||
world.spawn(
|
||||
// SAFETY: OnAdd and OnRemove are both unit types, so this is safe
|
||||
unsafe {
|
||||
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
|
||||
.with_event(on_remove)
|
||||
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| {
|
||||
res.observed("add/remove");
|
||||
})
|
||||
.with_event(on_remove)
|
||||
},
|
||||
);
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.despawn(entity);
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add/remove", "add/remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_multiple_components() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
world.init_component::<A>();
|
||||
world.init_component::<B>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.entity_mut(entity).insert(B);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["add_ab", "add_ab"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_despawn() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
|
||||
let observer = world
|
||||
.observe(|_: Trigger<OnAdd, A>| panic!("Observer triggered after being despawned."))
|
||||
|
@ -697,11 +719,11 @@ mod tests {
|
|||
#[test]
|
||||
fn observer_despawn_archetype_flags() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let entity = world.spawn((A, B)).flush();
|
||||
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove_a"));
|
||||
|
||||
let observer = world
|
||||
.observe(|_: Trigger<OnRemove, B>| panic!("Observer triggered after being despawned."))
|
||||
|
@ -710,31 +732,31 @@ mod tests {
|
|||
|
||||
world.despawn(entity);
|
||||
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["remove_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_multiple_matches() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
|
||||
|
||||
world.spawn((A, B)).flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["add_ab"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_no_target() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
||||
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
|
||||
res.0 += 1;
|
||||
res.observed("event_a");
|
||||
});
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -742,24 +764,24 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger(EventA);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_entity_routing() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
|
||||
let entity = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventA>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
||||
.id();
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
||||
assert_eq!(obs.entity(), entity);
|
||||
res.0 += 1;
|
||||
res.observed("a_2");
|
||||
});
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -767,17 +789,17 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventA, entity);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_dynamic_component() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let component_id = world.init_component::<A>();
|
||||
world.spawn(
|
||||
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| res.0 += 1)
|
||||
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<Order>| res.observed("event_a"))
|
||||
.with_component(component_id),
|
||||
);
|
||||
|
||||
|
@ -790,20 +812,20 @@ mod tests {
|
|||
|
||||
world.trigger_targets(EventA, entity);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_dynamic_trigger() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
let event_a = world.init_component::<EventA>();
|
||||
|
||||
world.spawn(ObserverState {
|
||||
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
|
||||
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
|
||||
runner: |mut world, _trigger, _ptr, _propagate| {
|
||||
world.resource_mut::<R>().0 += 1;
|
||||
world.resource_mut::<Order>().observed("event_a");
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
@ -813,22 +835,22 @@ mod tests {
|
|||
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
|
||||
);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -836,22 +858,22 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["child", "parent"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_redundant_dispatch_same_entity() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -859,22 +881,25 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child, child]);
|
||||
world.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child", "parent", "child", "parent"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_redundant_dispatch_parent_child() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -882,24 +907,27 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child, parent]);
|
||||
world.flush();
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child", "parent", "parent"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_halt() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child");
|
||||
trigger.propagate(false);
|
||||
},
|
||||
)
|
||||
|
@ -910,30 +938,30 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["child"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_join() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child_a = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child_a");
|
||||
})
|
||||
.id();
|
||||
|
||||
let child_b = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child_b");
|
||||
})
|
||||
.id();
|
||||
|
||||
|
@ -942,17 +970,20 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child_a, child_b]);
|
||||
world.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child_a", "parent", "child_b", "parent"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_no_next() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let entity = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -960,24 +991,26 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, entity);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_parallel_propagation() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent_a = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("parent_a");
|
||||
})
|
||||
.id();
|
||||
|
||||
let child_a = world
|
||||
.spawn(Parent(parent_a))
|
||||
.observe(
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child_a");
|
||||
trigger.propagate(false);
|
||||
},
|
||||
)
|
||||
|
@ -985,12 +1018,14 @@ mod tests {
|
|||
|
||||
let parent_b = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("parent_b");
|
||||
})
|
||||
.id();
|
||||
|
||||
let child_b = world
|
||||
.spawn(Parent(parent_b))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child_b"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -998,15 +1033,18 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child_a, child_b]);
|
||||
world.flush();
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child_a", "child_b", "parent_b"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_world() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"));
|
||||
|
||||
let grandparent = world.spawn_empty().id();
|
||||
let parent = world.spawn(Parent(grandparent)).id();
|
||||
|
@ -1017,18 +1055,18 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event", "event", "event"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_world_skipping() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(
|
||||
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<R>| {
|
||||
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
|
||||
if query.get(trigger.entity()).is_ok() {
|
||||
res.0 += 1;
|
||||
res.observed("event");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -1042,6 +1080,6 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event", "event"], world.resource::<Order>().0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -450,7 +450,7 @@ impl ShapeSample for Capsule2d {
|
|||
if capsule_area > 0.0 {
|
||||
// Check if the random point should be inside the rectangle
|
||||
if rng.gen_bool((rectangle_area / capsule_area) as f64) {
|
||||
let rectangle = Rectangle::new(self.radius, self.half_length * 2.0);
|
||||
let rectangle = Rectangle::new(self.radius * 2.0, self.half_length * 2.0);
|
||||
rectangle.sample_interior(rng)
|
||||
} else {
|
||||
let circle = Circle::new(self.radius);
|
||||
|
|
|
@ -1632,7 +1632,7 @@ mod tests {
|
|||
|
||||
// TypeInfo (instance)
|
||||
let value: &dyn Reflect = &123_i32;
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<i32>());
|
||||
|
||||
// Struct
|
||||
|
@ -1653,7 +1653,7 @@ mod tests {
|
|||
assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path());
|
||||
|
||||
let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 };
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyStruct>());
|
||||
|
||||
// Struct (generic)
|
||||
|
@ -1675,7 +1675,7 @@ mod tests {
|
|||
foo: String::from("Hello!"),
|
||||
bar: 321,
|
||||
};
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyGenericStruct<String>>());
|
||||
|
||||
// Struct (dynamic field)
|
||||
|
@ -1705,7 +1705,7 @@ mod tests {
|
|||
foo: DynamicStruct::default(),
|
||||
bar: 321,
|
||||
};
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyDynamicStruct>());
|
||||
|
||||
// Tuple Struct
|
||||
|
@ -1731,7 +1731,7 @@ mod tests {
|
|||
assert!(info.field_at(1).unwrap().type_info().unwrap().is::<f32>());
|
||||
|
||||
let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!"));
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyTuple>());
|
||||
|
||||
// List
|
||||
|
@ -1746,7 +1746,7 @@ mod tests {
|
|||
assert_eq!(usize::type_path(), info.item_ty().path());
|
||||
|
||||
let value: &dyn Reflect = &vec![123_usize];
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyList>());
|
||||
|
||||
// List (SmallVec)
|
||||
|
@ -1763,7 +1763,7 @@ mod tests {
|
|||
|
||||
let value: MySmallVec = smallvec::smallvec![String::default(); 2];
|
||||
let value: &dyn Reflect = &value;
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MySmallVec>());
|
||||
}
|
||||
|
||||
|
@ -1779,7 +1779,7 @@ mod tests {
|
|||
assert_eq!(3, info.capacity());
|
||||
|
||||
let value: &dyn Reflect = &[1usize, 2usize, 3usize];
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyArray>());
|
||||
|
||||
// Cow<'static, str>
|
||||
|
@ -1791,7 +1791,7 @@ mod tests {
|
|||
assert_eq!(std::any::type_name::<MyCowStr>(), info.type_path());
|
||||
|
||||
let value: &dyn Reflect = &Cow::<'static, str>::Owned("Hello!".to_string());
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyCowStr>());
|
||||
|
||||
// Cow<'static, [u8]>
|
||||
|
@ -1806,7 +1806,7 @@ mod tests {
|
|||
assert_eq!(std::any::type_name::<u8>(), info.item_ty().path());
|
||||
|
||||
let value: &dyn Reflect = &Cow::<'static, [u8]>::Owned(vec![0, 1, 2, 3]);
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyCowSlice>());
|
||||
|
||||
// Map
|
||||
|
@ -1824,7 +1824,7 @@ mod tests {
|
|||
assert_eq!(f32::type_path(), info.value_ty().path());
|
||||
|
||||
let value: &dyn Reflect = &MyMap::new();
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyMap>());
|
||||
|
||||
// Value
|
||||
|
@ -1836,7 +1836,7 @@ mod tests {
|
|||
assert_eq!(MyValue::type_path(), info.type_path());
|
||||
|
||||
let value: &dyn Reflect = &String::from("Hello!");
|
||||
let info = value.get_represented_type_info().unwrap();
|
||||
let info = value.reflect_type_info();
|
||||
assert!(info.is::<MyValue>());
|
||||
}
|
||||
|
||||
|
|
|
@ -478,6 +478,13 @@ impl<const N: usize> From<[Access<'static>; N]> for ParsedPath {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for ParsedPath {
|
||||
type Error = ReflectPathError<'a>;
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
ParsedPath::parse(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParsedPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for OffsetAccess { access, .. } in &self.0 {
|
||||
|
@ -585,6 +592,21 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_from() {
|
||||
assert_eq!(
|
||||
ParsedPath::try_from("w").unwrap().0,
|
||||
&[offset(access_field("w"), 1)]
|
||||
);
|
||||
|
||||
let r = ParsedPath::try_from("w[");
|
||||
let matches = matches!(r, Err(ReflectPathError::ParseError { .. }));
|
||||
assert!(
|
||||
matches,
|
||||
"ParsedPath::try_from did not return a ParseError for \"w[\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsed_path_parse() {
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug,
|
||||
tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Set, Struct, Tuple, TupleStruct,
|
||||
TypeInfo, TypePath, Typed, ValueInfo,
|
||||
tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, Struct, Tuple,
|
||||
TupleStruct, TypeInfo, TypePath, Typed, ValueInfo,
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
|
@ -408,7 +408,7 @@ where
|
|||
message = "`{Self}` does not implement `Reflect` so cannot be fully reflected",
|
||||
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
|
||||
)]
|
||||
pub trait Reflect: PartialReflect + Any {
|
||||
pub trait Reflect: PartialReflect + DynamicTyped + Any {
|
||||
/// Returns the value as a [`Box<dyn Any>`][std::any::Any].
|
||||
///
|
||||
/// For remote wrapper types, this will return the remote type instead.
|
||||
|
|
|
@ -30,12 +30,7 @@ impl<'a> Serializable<'a> {
|
|||
))
|
||||
})?;
|
||||
|
||||
let info = value.get_represented_type_info().ok_or_else(|| {
|
||||
make_custom_error(format_args!(
|
||||
"type `{}` does not represent any type",
|
||||
value.reflect_type_path(),
|
||||
))
|
||||
})?;
|
||||
let info = value.reflect_type_info();
|
||||
|
||||
let registration = type_registry.get(info.type_id()).ok_or_else(|| {
|
||||
make_custom_error(format_args!(
|
||||
|
|
|
@ -136,6 +136,27 @@ impl MaybeTyped for DynamicArray {}
|
|||
|
||||
impl MaybeTyped for DynamicTuple {}
|
||||
|
||||
/// Dynamic dispatch for [`Typed`].
|
||||
///
|
||||
/// Since this is a supertrait of [`Reflect`] its methods can be called on a `dyn Reflect`.
|
||||
///
|
||||
/// [`Reflect`]: crate::Reflect
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` can not provide dynamic type information through reflection",
|
||||
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
|
||||
)]
|
||||
pub trait DynamicTyped {
|
||||
/// See [`Typed::type_info`].
|
||||
fn reflect_type_info(&self) -> &'static TypeInfo;
|
||||
}
|
||||
|
||||
impl<T: Typed> DynamicTyped for T {
|
||||
#[inline]
|
||||
fn reflect_type_info(&self) -> &'static TypeInfo {
|
||||
Self::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`TypeInfo`]-specific error.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TypeInfoError {
|
||||
|
|
|
@ -171,7 +171,7 @@ impl<T: TypedProperty> Default for NonGenericTypeCell<T> {
|
|||
/// # fn reflect_owned(self: Box<Self>) -> ReflectOwned { todo!() }
|
||||
/// # fn clone_value(&self) -> Box<dyn PartialReflect> { todo!() }
|
||||
/// # }
|
||||
/// # impl<T: Reflect + TypePath> Reflect for Foo<T> {
|
||||
/// # impl<T: Reflect + Typed + TypePath> Reflect for Foo<T> {
|
||||
/// # fn into_any(self: Box<Self>) -> Box<dyn Any> { todo!() }
|
||||
/// # fn as_any(&self) -> &dyn Any { todo!() }
|
||||
/// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() }
|
||||
|
|
|
@ -260,8 +260,7 @@ pub fn extract_uinode_background_colors(
|
|||
uinode.border_radius.top_right,
|
||||
uinode.border_radius.bottom_right,
|
||||
uinode.border_radius.bottom_left,
|
||||
]
|
||||
.map(|r| r * ui_scale.0);
|
||||
];
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
|
@ -387,8 +386,7 @@ pub fn extract_uinode_images(
|
|||
uinode.border_radius.top_right,
|
||||
uinode.border_radius.bottom_right,
|
||||
uinode.border_radius.bottom_left,
|
||||
]
|
||||
.map(|r| r * ui_scale.0);
|
||||
];
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
|
@ -525,8 +523,7 @@ pub fn extract_uinode_borders(
|
|||
uinode.border_radius.top_right,
|
||||
uinode.border_radius.bottom_right,
|
||||
uinode.border_radius.bottom_left,
|
||||
]
|
||||
.map(|r| r * ui_scale.0);
|
||||
];
|
||||
|
||||
let border_radius = clamp_radius(border_radius, uinode.size(), border.into());
|
||||
|
||||
|
|
|
@ -19,10 +19,7 @@ fn setup(
|
|||
// For example, the next line will load GltfAssetLabel::Primitive{mesh:0,primitive:0}.from_asset("ROOT/assets/models/cube/cube.gltf"),
|
||||
// where "ROOT" is the directory of the Application.
|
||||
//
|
||||
// This can be overridden by setting the "CARGO_MANIFEST_DIR" environment variable (see
|
||||
// https://doc.rust-lang.org/cargo/reference/environment-variables.html)
|
||||
// to another directory. When the Application is run through Cargo, "CARGO_MANIFEST_DIR" is
|
||||
// automatically set to your crate (workspace) root directory.
|
||||
// This can be overridden by setting [`AssetPlugin.file_path`].
|
||||
let cube_handle = asset_server.load(
|
||||
GltfAssetLabel::Primitive {
|
||||
mesh: 0,
|
||||
|
|
|
@ -1,16 +1,36 @@
|
|||
//! Shows how to orbit camera around a static scene using pitch, yaw, and roll.
|
||||
//!
|
||||
//! See also: `first_person_view_model` example, which does something similar but as a first-person
|
||||
//! camera view.
|
||||
|
||||
use std::{f32::consts::FRAC_PI_2, ops::Range};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{input::mouse::AccumulatedMouseMotion, prelude::*};
|
||||
|
||||
#[derive(Debug, Default, Resource)]
|
||||
#[derive(Debug, Resource)]
|
||||
struct CameraSettings {
|
||||
pub orbit_distance: f32,
|
||||
// Multiply keyboard inputs by this factor
|
||||
pub orbit_speed: f32,
|
||||
pub pitch_speed: f32,
|
||||
// Clamp pitch to this range
|
||||
pub pitch_range: Range<f32>,
|
||||
pub roll_speed: f32,
|
||||
pub yaw_speed: f32,
|
||||
}
|
||||
|
||||
impl Default for CameraSettings {
|
||||
fn default() -> Self {
|
||||
// Limiting pitch stops some unexpected rotation past 90° up or down.
|
||||
let pitch_limit = FRAC_PI_2 - 0.01;
|
||||
Self {
|
||||
// These values are completely arbitrary, chosen because they seem to produce
|
||||
// "sensible" results for this example. Adjust as required.
|
||||
orbit_distance: 20.0,
|
||||
pitch_speed: 0.003,
|
||||
pitch_range: -pitch_limit..pitch_limit,
|
||||
roll_speed: 1.0,
|
||||
yaw_speed: 0.004,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -24,18 +44,10 @@ fn main() {
|
|||
|
||||
/// Set up a simple 3D scene
|
||||
fn setup(
|
||||
mut camera_settings: ResMut<CameraSettings>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// Limiting pitch stops some unexpected rotation past 90° up or down.
|
||||
let pitch_limit = FRAC_PI_2 - 0.01;
|
||||
|
||||
camera_settings.orbit_distance = 10.0;
|
||||
camera_settings.orbit_speed = 1.0;
|
||||
camera_settings.pitch_range = -pitch_limit..pitch_limit;
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Camera"),
|
||||
Camera3dBundle {
|
||||
|
@ -94,15 +106,15 @@ fn instructions(mut commands: Commands) {
|
|||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"W or S: pitch",
|
||||
"Mouse up or down: pitch",
|
||||
TextStyle::default(),
|
||||
));
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"A or D: yaw",
|
||||
"Mouse left or right: yaw",
|
||||
TextStyle::default(),
|
||||
));
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"Q or E: roll",
|
||||
"Mouse buttons: roll",
|
||||
TextStyle::default(),
|
||||
));
|
||||
});
|
||||
|
@ -111,40 +123,29 @@ fn instructions(mut commands: Commands) {
|
|||
fn orbit(
|
||||
mut camera: Query<&mut Transform, With<Camera>>,
|
||||
camera_settings: Res<CameraSettings>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||
mouse_motion: Res<AccumulatedMouseMotion>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let mut transform = camera.single_mut();
|
||||
|
||||
let mut delta_pitch = 0.0;
|
||||
let delta = mouse_motion.delta;
|
||||
let mut delta_roll = 0.0;
|
||||
let mut delta_yaw = 0.0;
|
||||
|
||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||
delta_pitch += camera_settings.orbit_speed;
|
||||
if mouse_buttons.pressed(MouseButton::Left) {
|
||||
delta_roll -= 1.0;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||
delta_pitch -= camera_settings.orbit_speed;
|
||||
if mouse_buttons.pressed(MouseButton::Right) {
|
||||
delta_roll += 1.0;
|
||||
}
|
||||
|
||||
if keyboard_input.pressed(KeyCode::KeyQ) {
|
||||
delta_roll -= camera_settings.orbit_speed;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyE) {
|
||||
delta_roll += camera_settings.orbit_speed;
|
||||
}
|
||||
// Mouse motion is one of the few inputs that should not be multiplied by delta time,
|
||||
// as we are already receiving the full movement since the last frame was rendered. Multiplying
|
||||
// by delta time here would make the movement slower that it should be.
|
||||
let delta_pitch = delta.y * camera_settings.pitch_speed;
|
||||
let delta_yaw = delta.x * camera_settings.yaw_speed;
|
||||
|
||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||
delta_yaw -= camera_settings.orbit_speed;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||
delta_yaw += camera_settings.orbit_speed;
|
||||
}
|
||||
|
||||
// Incorporating the delta time between calls prevents this from being framerate-bound.
|
||||
delta_pitch *= time.delta_seconds();
|
||||
delta_roll *= time.delta_seconds();
|
||||
delta_yaw *= time.delta_seconds();
|
||||
// Conversely, we DO need to factor in delta time for mouse button inputs.
|
||||
delta_roll *= camera_settings.roll_speed * time.delta_seconds();
|
||||
|
||||
// Obtain the existing pitch, yaw, and roll values from the transform.
|
||||
let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||
|
@ -159,5 +160,7 @@ fn orbit(
|
|||
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll);
|
||||
|
||||
// Adjust the translation to maintain the correct orientation toward the orbit target.
|
||||
transform.translation = Vec3::ZERO - transform.forward() * camera_settings.orbit_distance;
|
||||
// In our example it's a static target, but this could easily be customised.
|
||||
let target = Vec3::ZERO;
|
||||
transform.translation = target - transform.forward() * camera_settings.orbit_distance;
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ fn main() {
|
|||
registry.register_type_data::<Zombie, ReflectDamageable>();
|
||||
|
||||
// Then at any point we can retrieve the type data from the registry:
|
||||
let type_id = value.get_represented_type_info().unwrap().type_id();
|
||||
let type_id = value.reflect_type_info().type_id();
|
||||
let reflect_damageable = registry
|
||||
.get_type_data::<ReflectDamageable>(type_id)
|
||||
.unwrap();
|
||||
|
@ -133,7 +133,7 @@ fn main() {
|
|||
// Now we can use `ReflectHealth` to convert `dyn Reflect` into `dyn Health`:
|
||||
let value: Box<dyn Reflect> = Box::new(Skeleton { health: 50 });
|
||||
|
||||
let type_id = value.get_represented_type_info().unwrap().type_id();
|
||||
let type_id = value.reflect_type_info().type_id();
|
||||
let reflect_health = registry.get_type_data::<ReflectHealth>(type_id).unwrap();
|
||||
|
||||
// Type data generated by `#[reflect_trait]` comes with a `get`, `get_mut`, and `get_boxed` method,
|
||||
|
|
Loading…
Reference in a new issue