Yes! I did check but forgot to mention here. I describe it as:
number of times that command can be called in a single frame/update
Yes! I did check but forgot to mention here. I describe it as:
number of times that command can be called in a single frame/update
Hey all, thanks so much for all your help and feedback, I finally got it working more or less the way I want it. It's still a little rough but I wanted to share it with you all to hear your thoughts or feedback.
Meet glitterbomb:
The main class is called ParticleEmitter, which you initialize by passing it an image. There's also a class called AnimatedParticleEmitter that does the same thing but with an image table. You can manually position the emitter, or give it a velocity so it moves each frame.
Right now, the default behavior is to emit particles randomly given certain parameters (discussed below) but could easily be changed to whatever pattern you want.
Right now, the settings are:
You can either set these variables on initialization by passing them as a table, or can set them manually with functions like setEmissionRate, setEmissionForce, etc. I've included a demo app so you can see some of the functionality and examples of how to use it.
Right now, I've found that I can get over 100 particles on device at 30FPS, and closer to 50 particles if they're animated (note: if anyone has thoughts on how to improve it I would love to hear it!)
I've found that animating scale and opacity was too expensive to do, so instead I recommend baking any size or opacity changes into your animation. To that end, I also created a second mini "app" called glitterglue. With glitterglue, you can create a ParticleEmitter, but rather than draw onscreen it draws a particle frame by frame to a sprite sheet so you can import it as an ImageTable to an AnimatedParticleEmitter later (I'm sure there's a more elegant way of going about this but that's what I have so far). Right now, it just animates size and opacity, but I'm sure could be extended to rotation or other animations.
Anyways, would love for you all to take a look, try it out, and let me know what you think!
Hey all, so I've made a few more improvements and feel like it's ready for people to use. There are a few more features I'm trying to get in, but in the meantime I wanted to share some findings I made during the process of optimizing it to run on device (big thanks to Matt for his benchmarking tool):
Curious to hear if you all have any thoughts!
Great! Thanks for the detailed summary.
Does this increase the number of particles you're pushing around?
edit: from 30 particles up to 100! Nice.
Oh sorry yes! Forgot to mention it here, but originally it could support around 30 particles at a time and now itās over 100. I also added the ability to load imagetables for animated particles (as well as a utility to create imagetables that animate a particleās size/opacity) and even there it can support 50+ animated particles.
The one remaining issue is there is a framerate spike every 5-10 seconds which seems to be due to the garbage collector? But other than that Iām very pleased with where itās at.
You can change the frequency of the garbage collector, or trigger it yourself. If you're reusing particles then the question is "what garbage is building up?"
Triggering the GC yourself allows you to see how much has been freed. Let's track it down!
Did you see pdParticles? pdParticles - Playdate particle system
Interesting! Is there a way to see what garbage the GC is collecting?
And I did see that! Itās funny that I built this because there was no particle system solution, then the next day someone else made one
I really like the stylized look of their particles, and I may try adding something like that here.
Hey all, so I managed to figure out the GC issue and I'm up to about 150 particles which is awesome. I'm working on adding some additional features, and I had a question I wanted to run by you all:
I want to be able to apply other forces besides constant downwards gravity -- for example, a variable wind force, or a relative orbital gravity based on the particle's position. I wanted to make this part as extensible and generalizable as possible to make it easy for other people to select which force they want to use, or implement a new one of their own. However in doing so, I've found that it negatively impacted performance and I wanted to get your input on what you'd prefer if you were using this in your own game:
Option 1
Gravity logic is implemented directly in the particle update loop:
Update Loop
Check if particle should be destroyed
Apply force
Update position
This is how it's currently implemented.
Option 2
Gravity logic is moved to a separate function to make it easier to customize
Update Loop
Check if particle should be destroyed
Call ForceFunction()
Update position
ForceFunction()
Apply force
That way, you can easily write multiple force functions (gravity, wind, etc.) and let users choose which they want to use or write their own. I even created a wrapper function so all a user would need to do is set the wrapper to a different function to apply different a force.
The downside is because you're calling a function 100+ times a frame, it impacts performance, with a max of 140 particles instead of 150.
Option 3
Instead of calling the force function within the update loop, we just mark particles that need to be updated and then call the force function once at the end. The force function then loops through all the particles that need to be updated and applies force to them:
Update Loop
Check if particle should be destroyed
Add particle to update list
Call ForceFunction()
ForceFunction()
Loop through particles that need updating
Apply force
Update position
Since you're only calling the update function once, it's slightly faster than Option 2 (145 max particles instead of 140)
However it makes writing a new force function more complex since you need to add logic to loop through all the particles, as well as to update the particle's position.
I'm curious if you all think the ease of updating is worth the performance tradeoff for option 2, or if you'd prefer option 3 which is a tradeoff between the two.
150, nice!
Interested to hear about your garbage collection changes.
Just checking, is ForceFunction() defined as local?
local
is fasterlocal <const>
perhaps even fasterThe issue with the GC turned out to be that I had several functions that returned values as tables which I wrote in the form of return {x=0,y=0}
. As it turns out, this creates a new table every time it was called and, like the force function here, it was being called 100+ times a frame which is what caused the GC build up. It wasnāt the prettiest, but to solve it I created a āholderā table at init that gets reused instead of creating a new one every time (e.g. holderTable.x=0 holderTable.y=0 return holderTable
). Not only did it solve the GC issue but it also made things run ~50% faster.
And right now the force function is a class method, but maybe Iāll try declaring it as a const and see what happens!
For your force function, have you considered having a callback one could specify which defaults to nil
? That way your update
function could check for the existence of the callback, call it if it exists, and otherwise run the internal force logic.