mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
QuerySingle
family of system params (#15476)
# Objective Add the following system params: - `QuerySingle<D, F>` - Valid if only one matching entity exists, - `Option<QuerySingle<D, F>>` - Valid if zero or one matching entity exists. As @chescock pointed out, we don't need `Mut` variants. Fixes: #15264 ## Solution Implement the type and both variants of system params. Also implement `ReadOnlySystemParam` for readonly queries. Added a new ECS example `fallible_params` which showcases `SingleQuery` usage. In the future we might want to add `NonEmptyQuery`, `NonEmptyEventReader` and `Res` to it (or maybe just stop at mentioning it). ## Testing Tested with the example. There is a lot of warning spam so we might want to implement #15391.
This commit is contained in:
parent
89925ee351
commit
c1486654d7
8 changed files with 363 additions and 22 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -1892,6 +1892,17 @@ description = "Run systems only when one or multiple conditions are met"
|
|||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "fallible_params"
|
||||
path = "examples/ecs/fallible_params.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.fallible_params]
|
||||
name = "Fallible System Parameters"
|
||||
description = "Systems are skipped if their parameters cannot be acquired"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "startup_system"
|
||||
path = "examples/ecs/startup_system.rs"
|
||||
|
|
|
@ -543,11 +543,10 @@ impl<'w> From<TicksMut<'w>> for Ticks<'w> {
|
|||
///
|
||||
/// If you need a unique mutable borrow, use [`ResMut`] instead.
|
||||
///
|
||||
/// # Panics
|
||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
|
||||
/// This will cause systems that use this parameter to be skipped.
|
||||
///
|
||||
/// Panics when used as a [`SystemParameter`](crate::system::SystemParam) if the resource does not exist.
|
||||
///
|
||||
/// Use `Option<Res<T>>` instead if the resource might not always exist.
|
||||
/// Use [`Option<Res<T>>`] instead if the resource might not always exist.
|
||||
pub struct Res<'w, T: ?Sized + Resource> {
|
||||
pub(crate) value: &'w T,
|
||||
pub(crate) ticks: Ticks<'w>,
|
||||
|
@ -622,11 +621,10 @@ impl_debug!(Res<'w, T>, Resource);
|
|||
///
|
||||
/// If you need a shared borrow, use [`Res`] instead.
|
||||
///
|
||||
/// # Panics
|
||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
|
||||
/// This will cause systems that use this parameter to be skipped.
|
||||
///
|
||||
/// Panics when used as a [`SystemParam`](crate::system::SystemParam) if the resource does not exist.
|
||||
///
|
||||
/// Use `Option<ResMut<T>>` instead if the resource might not always exist.
|
||||
/// Use [`Option<ResMut<T>>`] instead if the resource might not always exist.
|
||||
pub struct ResMut<'w, T: ?Sized + Resource> {
|
||||
pub(crate) value: &'w mut T,
|
||||
pub(crate) ticks: TicksMut<'w>,
|
||||
|
@ -684,11 +682,10 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
|
|||
/// the scheduler to instead run the system on the main thread so that it doesn't send the resource
|
||||
/// over to another thread.
|
||||
///
|
||||
/// # Panics
|
||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist.
|
||||
/// This will cause systems that use this parameter to be skipped.
|
||||
///
|
||||
/// Panics when used as a `SystemParameter` if the resource does not exist.
|
||||
///
|
||||
/// Use `Option<NonSendMut<T>>` instead if the resource might not always exist.
|
||||
/// Use [`Option<NonSendMut<T>>`] instead if the resource might not always exist.
|
||||
pub struct NonSendMut<'w, T: ?Sized + 'static> {
|
||||
pub(crate) value: &'w mut T,
|
||||
pub(crate) ticks: TicksMut<'w>,
|
||||
|
|
|
@ -58,8 +58,9 @@ pub mod prelude {
|
|||
},
|
||||
system::{
|
||||
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
|
||||
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut,
|
||||
Resource, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction,
|
||||
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, QuerySingle, ReadOnlySystem,
|
||||
Res, ResMut, Resource, System, SystemIn, SystemInput, SystemParamBuilder,
|
||||
SystemParamFunction,
|
||||
},
|
||||
world::{
|
||||
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,
|
||||
|
|
|
@ -8,7 +8,11 @@ use crate::{
|
|||
},
|
||||
world::unsafe_world_cell::UnsafeWorldCell,
|
||||
};
|
||||
use core::borrow::Borrow;
|
||||
use core::{
|
||||
borrow::Borrow,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
/// [System parameter] that provides selective access to the [`Component`] data stored in a [`World`].
|
||||
///
|
||||
|
@ -1629,3 +1633,37 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
|
|||
value.transmute_lens_filtered()
|
||||
}
|
||||
}
|
||||
|
||||
/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`].
|
||||
///
|
||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists.
|
||||
/// This will cause systems that use this parameter to be skipped.
|
||||
///
|
||||
/// Use [`Option<QuerySingle<D, F>>`] instead if zero or one matching entities can exist.
|
||||
///
|
||||
/// See [`Query`] for more details.
|
||||
pub struct QuerySingle<'w, D: QueryData, F: QueryFilter = ()> {
|
||||
pub(crate) item: D::Item<'w>,
|
||||
pub(crate) _filter: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<'w, D: QueryData, F: QueryFilter> Deref for QuerySingle<'w, D, F> {
|
||||
type Target = D::Item<'w>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, D: QueryData, F: QueryFilter> DerefMut for QuerySingle<'w, D, F> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, D: QueryData, F: QueryFilter> QuerySingle<'w, D, F> {
|
||||
/// Returns the inner item with ownership.
|
||||
pub fn into_inner(self) -> D::Item<'w> {
|
||||
self.item
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,10 @@ pub trait System: Send + Sync + 'static {
|
|||
/// is not a strict requirement, both [`System::run`] and [`System::run_unsafe`]
|
||||
/// should provide their own safety mechanism to prevent undefined behavior.
|
||||
///
|
||||
/// This method has to be called directly before [`System::run_unsafe`] with no other (relevant)
|
||||
/// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior,
|
||||
/// the validity of the param may change.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data
|
||||
|
|
|
@ -7,10 +7,10 @@ use crate::{
|
|||
entity::Entities,
|
||||
query::{
|
||||
Access, AccessConflicts, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter,
|
||||
QueryState, ReadOnlyQueryData,
|
||||
QuerySingleError, QueryState, ReadOnlyQueryData,
|
||||
},
|
||||
storage::{ResourceData, SparseSetIndex},
|
||||
system::{Query, SystemMeta},
|
||||
system::{Query, QuerySingle, SystemMeta},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
|
||||
};
|
||||
use bevy_ecs_macros::impl_param_set;
|
||||
|
@ -227,12 +227,21 @@ pub unsafe trait SystemParam: Sized {
|
|||
/// The [`world`](UnsafeWorldCell) can only be used to read param's data
|
||||
/// and world metadata. No data can be written.
|
||||
///
|
||||
/// When using system parameters that require `change_tick` you can use
|
||||
/// [`UnsafeWorldCell::change_tick()`]. Even if this isn't the exact
|
||||
/// same tick used for [`SystemParam::get_param`], the world access
|
||||
/// ensures that the queried data will be the same in both calls.
|
||||
///
|
||||
/// This method has to be called directly before [`SystemParam::get_param`] with no other (relevant)
|
||||
/// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior,
|
||||
/// the validity of the param may change.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The passed [`UnsafeWorldCell`] must have read-only access to world data
|
||||
/// registered in [`init_state`](SystemParam::init_state).
|
||||
/// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state).
|
||||
/// - all `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype).
|
||||
/// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype).
|
||||
unsafe fn validate_param(
|
||||
_state: &Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
|
@ -356,6 +365,140 @@ fn assert_component_access_compatibility(
|
|||
panic!("error[B0001]: Query<{query_type}, {filter_type}> in system {system_name} accesses component(s){accesses} in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001");
|
||||
}
|
||||
|
||||
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
|
||||
// this Query conflicts with any prior access, a panic will occur.
|
||||
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
||||
for QuerySingle<'a, D, F>
|
||||
{
|
||||
type State = QueryState<D, F>;
|
||||
type Item<'w, 's> = QuerySingle<'w, D, F>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
Query::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
unsafe fn new_archetype(
|
||||
state: &mut Self::State,
|
||||
archetype: &Archetype,
|
||||
system_meta: &mut SystemMeta,
|
||||
) {
|
||||
// SAFETY: Delegate to existing `SystemParam` implementations.
|
||||
unsafe { Query::new_archetype(state, archetype, system_meta) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
state: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
state.validate_world(world.id());
|
||||
// SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere.
|
||||
let result =
|
||||
unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) };
|
||||
let single =
|
||||
result.expect("The query was expected to contain exactly one matching entity.");
|
||||
QuerySingle {
|
||||
item: single,
|
||||
_filter: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn validate_param(
|
||||
state: &Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell,
|
||||
) -> bool {
|
||||
state.validate_world(world.id());
|
||||
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
||||
// and the query is read only.
|
||||
let result = unsafe {
|
||||
state.as_readonly().get_single_unchecked_manual(
|
||||
world,
|
||||
system_meta.last_run,
|
||||
world.change_tick(),
|
||||
)
|
||||
};
|
||||
result.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
|
||||
// this Query conflicts with any prior access, a panic will occur.
|
||||
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
||||
for Option<QuerySingle<'a, D, F>>
|
||||
{
|
||||
type State = QueryState<D, F>;
|
||||
type Item<'w, 's> = Option<QuerySingle<'w, D, F>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
QuerySingle::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
unsafe fn new_archetype(
|
||||
state: &mut Self::State,
|
||||
archetype: &Archetype,
|
||||
system_meta: &mut SystemMeta,
|
||||
) {
|
||||
// SAFETY: Delegate to existing `SystemParam` implementations.
|
||||
unsafe { QuerySingle::new_archetype(state, archetype, system_meta) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
state: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
state.validate_world(world.id());
|
||||
// SAFETY: State ensures that the components it accesses are not accessible elsewhere.
|
||||
let result =
|
||||
unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) };
|
||||
match result {
|
||||
Ok(single) => Some(QuerySingle {
|
||||
item: single,
|
||||
_filter: PhantomData,
|
||||
}),
|
||||
Err(QuerySingleError::NoEntities(_)) => None,
|
||||
Err(QuerySingleError::MultipleEntities(e)) => panic!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn validate_param(
|
||||
state: &Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell,
|
||||
) -> bool {
|
||||
state.validate_world(world.id());
|
||||
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
||||
// and the query is read only.
|
||||
let result = unsafe {
|
||||
state.as_readonly().get_single_unchecked_manual(
|
||||
world,
|
||||
system_meta.last_run,
|
||||
world.change_tick(),
|
||||
)
|
||||
};
|
||||
!matches!(result, Err(QuerySingleError::MultipleEntities(_)))
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
|
||||
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
|
||||
for QuerySingle<'a, D, F>
|
||||
{
|
||||
}
|
||||
|
||||
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
|
||||
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
|
||||
for Option<QuerySingle<'a, D, F>>
|
||||
{
|
||||
}
|
||||
|
||||
/// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access.
|
||||
///
|
||||
/// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as
|
||||
|
@ -1172,11 +1315,10 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
|
|||
/// the scheduler to instead run the system on the main thread so that it doesn't send the resource
|
||||
/// over to another thread.
|
||||
///
|
||||
/// # Panics
|
||||
/// This [`SystemParam`] fails validation if non-send resource doesn't exist.
|
||||
/// This will cause systems that use this parameter to be skipped.
|
||||
///
|
||||
/// Panics when used as a `SystemParameter` if the resource does not exist.
|
||||
///
|
||||
/// Use `Option<NonSend<T>>` instead if the resource might not always exist.
|
||||
/// Use [`Option<NonSend<T>>`] instead if the resource might not always exist.
|
||||
pub struct NonSend<'w, T: 'static> {
|
||||
pub(crate) value: &'w T,
|
||||
ticks: ComponentTicks,
|
||||
|
|
|
@ -288,6 +288,7 @@ Example | Description
|
|||
[Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components
|
||||
[ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS
|
||||
[Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception
|
||||
[Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired
|
||||
[Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick
|
||||
[Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types
|
||||
[Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities
|
||||
|
|
147
examples/ecs/fallible_params.rs
Normal file
147
examples/ecs/fallible_params.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
//! This example demonstrates how fallible parameters can prevent their systems
|
||||
//! from running if their acquiry conditions aren't met.
|
||||
//!
|
||||
//! Fallible parameters include:
|
||||
//! - [`Res<R>`], [`ResMut<R>`] - If resource doesn't exist.
|
||||
//! - [`QuerySingle<D, F>`] - If there is no or more than one entities matching.
|
||||
//! - [`Option<QuerySingle<D, F>>`] - If there are more than one entities matching.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use rand::Rng;
|
||||
|
||||
fn main() {
|
||||
println!();
|
||||
println!("Press 'A' to add enemy ships and 'R' to remove them.");
|
||||
println!("Player ship will wait for enemy ships and track one if it exists,");
|
||||
println!("but will stop tracking if there are more than one.");
|
||||
println!();
|
||||
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
// We add all the systems one after another.
|
||||
// We don't need to use run conditions here.
|
||||
.add_systems(Update, (user_input, move_targets, move_pointer).chain())
|
||||
.run();
|
||||
}
|
||||
|
||||
/// Enemy component stores data for movement in a circle.
|
||||
#[derive(Component, Default)]
|
||||
struct Enemy {
|
||||
origin: Vec2,
|
||||
radius: f32,
|
||||
rotation: f32,
|
||||
rotation_speed: f32,
|
||||
}
|
||||
|
||||
/// Player component stores data for going after enemies.
|
||||
#[derive(Component, Default)]
|
||||
struct Player {
|
||||
speed: f32,
|
||||
rotation_speed: f32,
|
||||
min_follow_radius: f32,
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
// Spawn 2D camera.
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
|
||||
// Spawn player.
|
||||
let texture = asset_server.load("textures/simplespace/ship_C.png");
|
||||
commands.spawn((
|
||||
Player {
|
||||
speed: 100.0,
|
||||
rotation_speed: 2.0,
|
||||
min_follow_radius: 50.0,
|
||||
},
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: bevy::color::palettes::tailwind::BLUE_800.into(),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_translation(Vec3::ZERO),
|
||||
texture,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
// System that reads user input.
|
||||
// If user presses 'A' we spawn a new random enemy.
|
||||
// If user presses 'R' we remove a random enemy (if any exist).
|
||||
fn user_input(
|
||||
mut commands: Commands,
|
||||
enemies: Query<Entity, With<Enemy>>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let mut rng = rand::thread_rng();
|
||||
if keyboard_input.just_pressed(KeyCode::KeyA) {
|
||||
let texture = asset_server.load("textures/simplespace/enemy_A.png");
|
||||
commands.spawn((
|
||||
Enemy {
|
||||
origin: Vec2::new(rng.gen_range(-200.0..200.0), rng.gen_range(-200.0..200.0)),
|
||||
radius: rng.gen_range(50.0..150.0),
|
||||
rotation: rng.gen_range(0.0..std::f32::consts::TAU),
|
||||
rotation_speed: rng.gen_range(0.5..1.5),
|
||||
},
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: bevy::color::palettes::tailwind::RED_800.into(),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_translation(Vec3::ZERO),
|
||||
texture,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::KeyR) {
|
||||
if let Some(entity) = enemies.iter().next() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System that moves the enemies in a circle.
|
||||
// TODO: Use [`NonEmptyQuery`] when it exists.
|
||||
fn move_targets(mut enemies: Query<(&mut Transform, &mut Enemy)>, time: Res<Time>) {
|
||||
for (mut transform, mut target) in &mut enemies {
|
||||
target.rotation += target.rotation_speed * time.delta_seconds();
|
||||
transform.rotation = Quat::from_rotation_z(target.rotation);
|
||||
let offset = transform.right() * target.radius;
|
||||
transform.translation = target.origin.extend(0.0) + offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// System that moves the player.
|
||||
/// The player will search for enemies if there are none.
|
||||
/// If there is one, player will track it.
|
||||
/// If there are too many enemies, the player will cease all action (the system will not run).
|
||||
fn move_pointer(
|
||||
// `QuerySingle` ensures the system runs ONLY when exactly one matching entity exists.
|
||||
mut player: QuerySingle<(&mut Transform, &Player)>,
|
||||
// `Option<QuerySingle>` ensures that the system runs ONLY when zero or one matching entity exists.
|
||||
enemy: Option<QuerySingle<&Transform, (With<Enemy>, Without<Player>)>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let (player_transform, player) = &mut *player;
|
||||
if let Some(enemy_transform) = enemy {
|
||||
// Enemy found, rotate and move towards it.
|
||||
let delta = enemy_transform.translation - player_transform.translation;
|
||||
let distance = delta.length();
|
||||
let front = delta / distance;
|
||||
let up = Vec3::Z;
|
||||
let side = front.cross(up);
|
||||
player_transform.rotation = Quat::from_mat3(&Mat3::from_cols(side, front, up));
|
||||
let max_step = distance - player.min_follow_radius;
|
||||
if 0.0 < max_step {
|
||||
let velocity = (player.speed * time.delta_seconds()).min(max_step);
|
||||
player_transform.translation += front * velocity;
|
||||
}
|
||||
} else {
|
||||
// No enemy found, keep searching.
|
||||
player_transform.rotate_axis(Dir3::Z, player.rotation_speed * time.delta_seconds());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue