An experimental, status effects-as-entities system for Bevy.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at dev 212 lines 7.0 kB view raw
1use crate::ReflectComponent; 2use crate::registry::EffectMergeRegistry; 3use bevy_app::{App, Plugin, PreUpdate}; 4use bevy_ecs::component::Mutable; 5use bevy_ecs::prelude::{Commands, Component, Entity, EntityRef, Query, Res}; 6use bevy_ecs::schedule::IntoScheduleConfigs; 7use bevy_ecs::world::EntityWorldMut; 8use bevy_reflect::Reflect; 9use bevy_time::{Time, Timer, TimerMode}; 10use std::fmt::Debug; 11use std::time::Duration; 12 13pub(crate) struct TimerPlugin; 14 15impl Plugin for TimerPlugin { 16 fn build(&self, app: &mut App) { 17 app.add_systems(PreUpdate, (despawn_finished_lifetimes, tick_delay).chain()); 18 app.world_mut() 19 .get_resource_or_init::<EffectMergeRegistry>() 20 .register::<Lifetime>(merge_effect_timer::<Lifetime>) 21 .register::<Delay>(merge_effect_timer::<Delay>); 22 } 23} 24 25/// A [merge function](crate::EffectMergeFn) for [`EffectTimer`] components ([`Lifetime`] and [`Delay`]). 26pub fn merge_effect_timer<T: EffectTimer + Component<Mutability = Mutable>>( 27 mut existing: EntityWorldMut, 28 incoming: EntityRef, 29) { 30 let incoming = incoming.get::<T>().unwrap(); 31 existing.get_mut::<T>().unwrap().merge(incoming); 32} 33 34/// A [timer](Timer) which is used for status effects and includes a [`TimerMergeMode`]. 35pub trait EffectTimer: Clone { 36 /// Creates a new timer from a duration. 37 fn new(duration: Duration) -> Self; 38 39 /// Creates a new time from a duration, in seconds. 40 fn from_seconds(seconds: f32) -> Self { 41 Self::new(Duration::from_secs_f32(seconds)) 42 } 43 44 /// A builder that overwrites the current merge mode with a new value. 45 fn with_mode(self, mode: TimerMergeMode) -> Self; 46 47 /// Returns reference to the internal timer. 48 fn get_timer(&self) -> &Timer; 49 50 /// Returns mutable reference to the internal timer. 51 fn get_timer_mut(&mut self) -> &mut Timer; 52 53 /// Returns reference to the timer's merge mode. 54 fn get_mode(&self) -> &TimerMergeMode; 55 56 /// Returns mutable reference to the timer's merge mode. 57 fn get_mode_mut(&mut self) -> &mut TimerMergeMode; 58 59 /// Merges an old timer (self) with the new one (incoming). 60 /// Behaviour depends on the current [`TimerMergeMode`]. 61 fn merge(&mut self, incoming: &Self) { 62 match self.get_mode() { 63 TimerMergeMode::Replace => self.clone_from(incoming), 64 TimerMergeMode::Keep => {} 65 TimerMergeMode::Fraction => { 66 let fraction = self.get_timer().fraction(); 67 let duration = incoming.get_timer().duration().as_secs_f32(); 68 69 self.clone_from(incoming); 70 self.get_timer_mut() 71 .set_elapsed(Duration::from_secs_f32(fraction * duration)); 72 } 73 TimerMergeMode::Max => { 74 let old = self.get_timer().remaining_secs(); 75 let new = incoming.get_timer().remaining_secs(); 76 77 if new > old { 78 self.clone_from(incoming); 79 } 80 } 81 TimerMergeMode::Sum => { 82 let duration = self.get_timer().duration() + incoming.get_timer().duration(); 83 self.clone_from(incoming); 84 self.get_timer_mut().set_duration(duration); 85 } 86 } 87 } 88} 89 90macro_rules! impl_effect_timer { 91 ($ident:ident, $timer_mode:expr) => { 92 impl EffectTimer for $ident { 93 fn new(duration: Duration) -> Self { 94 Self { 95 timer: Timer::new(duration, $timer_mode), 96 ..Self::default() 97 } 98 } 99 100 fn with_mode(mut self, mode: TimerMergeMode) -> Self { 101 self.mode = mode; 102 self 103 } 104 105 fn get_timer(&self) -> &Timer { 106 &self.timer 107 } 108 109 fn get_timer_mut(&mut self) -> &mut Timer { 110 &mut self.timer 111 } 112 113 fn get_mode(&self) -> &TimerMergeMode { 114 &self.mode 115 } 116 117 fn get_mode_mut(&mut self) -> &mut TimerMergeMode { 118 &mut self.mode 119 } 120 } 121 }; 122} 123 124/// A timer that despawns the effect when the timer finishes. 125#[doc(alias = "Duration")] 126#[derive(Component, Reflect, Eq, PartialEq, Debug, Clone)] 127#[reflect(Component, PartialEq, Debug, Clone)] 128pub struct Lifetime { 129 /// Tracks the elapsed time. Once the timer is finished, the entity will be despawned. 130 pub timer: Timer, 131 /// Controls the merge behaviour when an effect is [merged](crate::EffectMode::Merge). 132 pub mode: TimerMergeMode, 133} 134 135impl_effect_timer!(Lifetime, TimerMode::Once); 136 137impl Default for Lifetime { 138 fn default() -> Self { 139 Self { 140 timer: Timer::default(), 141 mode: TimerMergeMode::Max, 142 } 143 } 144} 145 146/// A repeating timer used for the delay between effect applications. 147#[derive(Component, Reflect, Eq, PartialEq, Debug, Clone)] 148#[reflect(Component, PartialEq, Debug, Clone)] 149pub struct Delay { 150 /// Tracks the elapsed time. 151 pub timer: Timer, 152 /// Controls the merge behaviour when an effect is [merged](crate::EffectMode::Merge). 153 pub mode: TimerMergeMode, 154} 155 156impl_effect_timer!(Delay, TimerMode::Repeating); 157 158impl Delay { 159 /// Makes the timer [almost finished](Timer::almost_finish), leaving 1ns of remaining time. 160 /// This allows effects to trigger immediately when applied. 161 #[doc(alias = "trigger_on_start", alias = "almost_finish")] 162 pub fn trigger_immediately(mut self) -> Self { 163 self.timer.almost_finish(); 164 self 165 } 166} 167 168impl Default for Delay { 169 fn default() -> Self { 170 Self { 171 timer: Timer::default(), 172 mode: TimerMergeMode::Fraction, 173 } 174 } 175} 176 177/// Controls the merge behaviour of a timer when its effect is [merged](crate::EffectMode::Merge). 178#[derive(Reflect, Eq, PartialEq, Debug, Copy, Clone)] 179#[reflect(PartialEq, Debug, Clone)] 180pub enum TimerMergeMode { 181 /// The new effect's time will be used, ignoring the old one. 182 /// Results in same behaviour as [`EffectMode::Insert`](crate::EffectMode::Insert), but on a per-timer basis. 183 Replace, 184 /// The old effect's time will be used, ignoring the new one. 185 Keep, 186 /// The new timer is used, but with the same fraction of the old timer's elapsed time. 187 Fraction, 188 /// The timer with the larger time remaining will be used. 189 Max, 190 /// The timers' durations will be added together. 191 Sum, 192} 193 194pub(super) fn despawn_finished_lifetimes( 195 mut commands: Commands, 196 time: Res<Time>, 197 mut query: Query<(Entity, &mut Lifetime)>, 198) { 199 for (entity, mut lifetime) in &mut query { 200 lifetime.timer.tick(time.delta()); 201 202 if lifetime.timer.is_finished() { 203 commands.entity(entity).despawn(); 204 } 205 } 206} 207 208pub(super) fn tick_delay(time: Res<Time>, mut query: Query<&mut Delay>) { 209 for mut delay in &mut query { 210 delay.timer.tick(time.delta()); 211 } 212}