-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to trigger an EffectSpawner multiple times per frame? #255
Comments
This is an interesting one. I thought about it a bit. I think the proposed approach of having a pool of The problem you will run into if you try to have two or more different emission sources in the same frame and you want to use a single particle effect is that the emitter's |
Here is my quick and dirty solution, that seems to work well enough: use bevy::prelude::{Commands, Entity, Query, Transform};
use bevy_hanabi::{EffectSpawner, ParticleEffectBundle};
/// A wrapper around `ParticleEffectBundle` that allows spawning multiple copies
/// of the same effect in the same frame.
///
/// Some caveats:
/// 1. It is expected that the effect's spawner has `starts_immediately: true`.
/// This is left to the caller to verify.
/// 2. `ParticleEffectPool::reset` should be called once per frame, before any
/// calls to `ParticleEffectPool::trigger`. Failing to do so will result in a
/// memory leak.
pub struct ParticleEffectPool {
bundle: ParticleEffectBundle,
effects: Vec<Entity>,
index: usize,
}
impl Clone for ParticleEffectPool {
fn clone(&self) -> Self {
Self::new(self.bundle.clone())
}
}
impl From<ParticleEffectBundle> for ParticleEffectPool {
fn from(value: ParticleEffectBundle) -> Self {
Self::new(value)
}
}
impl ParticleEffectPool {
pub fn new(bundle: ParticleEffectBundle) -> Self {
Self {
bundle,
effects: vec![],
index: 0,
}
}
pub fn reset(&mut self) {
self.index = 0;
}
pub fn trigger(
&mut self,
commands: &mut Commands,
transform: Transform,
effects: &mut Query<(&mut Transform, &mut EffectSpawner)>,
) {
if self.index < self.effects.len() {
let entity = self.effects[self.index];
self.index += 1;
if let Ok((mut effect_transform, mut effect_spawner)) = effects.get_mut(entity) {
*effect_transform = transform;
effect_spawner.reset();
} else {
tracing::warn!("Missing effect");
}
} else {
let mut bundle = self.bundle.clone();
bundle.transform = transform;
let entity = commands.spawn(bundle).id();
self.effects.push(entity);
self.index += 1;
}
}
} Is some variant of this something that you would like to see in |
Over in the love2d world, we have this function called ParticleSystem:emit, which just takes in how many particles you want to spawn and immediately creates them, regardless of spawner/emitter settings. I have a use-case where I want to emit bursts of particles, e.g. using a |
@Reiuji-ch thanks for the details. It's interesting to compare what other libraries are doing. I'm assuming love2d is CPU-based though, no? Because that would be a lot easier to implement with a CPU based particle solution. The problem with that approach in Hanabi / Bevy is that this doesn't map well at all with a buffered architecture (main and render apps) of Bevy, and the CPU/GPU duality. Here's what happens:
Game over. The emitter emits a single burst of N+M particles from point B alone. There's no way to even observe position A in the first place, because the For that approach to work, you also need an array of transforms per effect. And having variable-size arrays for GPU is horrible to manage in an efficient way. That the first issue (technical complexity). The second issue is that I'd argue this goes against the very design of an emitter. If you really want to spawn 2 bursts of particles at 2 separate locations in the same frame, then you have 2 particles emitters here. I'm not saying the use case is invalid. I'm totally open to suggestions on things we can improve to make things easier to handle. But handling multiple emissions per frame per effect sounds like a lot of added complexity of a use case which is not that much common. |
@djeedai I figured there was something like that which complicated it a lot. I agree that it adds unnecessary complexity. I suppose a simple hack for my use-case would be something like this: commands.spawn((ParticleEffectBundle {
effect: ParticleEffect::new(effect_asset_handle_resource.0.clone())
.with_spawner(Spawner::once(100.0.into(), true)),
transform: transform.clone(),
..default()
}, GarbageCollectInSeconds(5.0))); Just spawn an ephemeral effect at every location I want to emit a burst, then have a system despawn them once all the particles have timed out. |
First of all, thanks a lot for this library, I've been having a lot of fun with it!
I think the spawn_on_command example is a good showcase of what I'm after -- it has a ball bouncing, and we spawn particles every time it hits a wall. But what if we had a bunch of balls bouncing, and wanted to spawn particles every time any of them hit something?
This works fine, unless two of them happen to hit a wall in the same frame, then we end up only spawning particles for one of them. One idea is to have a sort of "spawner pool", where we keep multiple copies of an
EffectSpawner
around, and create a new one any time we want to spawn particles more times in a frame than we have before.But this seems non-ideal, and I'd probably end up wrapping effect I'm using in it, so I thought I'd reach out and see if you have any better ideas.
The text was updated successfully, but these errors were encountered: