At the end it is going to depend on whether or not GPU or CPU is the bottleneck. However, the options I describe below are roughly sorted by decreasing chance of being the most performant option (under the possibly wrong assumption that the GPU is more powerful, and thus the CPU is likely the bottleneck). Which is not a replacement for testing, if you really want the most performant solution.
By the way, it also turns out to be roughly sorted by increasing effort to do within Godot (under the possibly wrong assumption that you find it easier to move things in the editor than writing code, and thus writing code is likely more effort).
Particles is likely the most performant way to do it, and it turns out to be the easier to do within Godot. So start there.
Particles node will animate the particles on the GPU. Because of that, If you can do the effect with
Particles it is likely to be the most performant approach.
One drawback is that it is not supported on the GLES2 backend. So make sure it is supported in the target hardware.
Also, some features are only available for
CPUParticles. Which is the next stop.
Assuming you had your system set up with
Particles and you need something only available for
CPUParticles, you can convert them! There will be a “Particles” option in the editor when the
Particles node is selected, and under it you find “Convert to CPUParticles”.
CPUParticles will minimize render calls, but it will compute all the movement on the CPU. Which means, unlike
CPUParticles eat CPU time. But it also means they have less limitations.
Let us say that
Particles is not good enough, and you don’t want the particles to run on the CPU. You can a shot at doing what they do: Set a shader material with a
MultiMesh (to minimize render calls) and apply displacement in the vertex function to move the instances (
MultiMeshInstance), so that all the movement is computed on the GPU.
By the way, just the fact that
MultiMeshInstance are nodes is overhead.
For randomness you can use a noise texture, for example. There is a
NoiseTexture resource type you can use.
INSTANCE_CUSTOM built-ins in the shader language to have it distinguish the different instances. Note:
INSTANCE_ID is not available in GLES2.
This will take advantage of the GPU for the particle animation… And it is, of course, more work to write the shader code. However, consider this option before
AnimationPlayer. By the way, this option may or may not be more performant than
CPUParticles depending on your bottleneck.
AnimationPlayer will work on the CPU. Although,
AnimationPlayer is arguably the most powerful option -baring using an
AnimationTree– it has a couple significant drawback for this use case:
- It is not made for randomness. Every time the animation plays, it is the same.
- It is a lot of work to setup the animation of all the particles in the editor.
Thus, I would only suggest it if you can create the particle effect in another software and import it in such way that it comes up as an
AnimationPlayer. And even then, it would be the same every time, and it is not taking full advantage of the GPU.
By the way, animate
MultiMeshInstance to minimize render calls.
Furthermore, the power of
AnimationPlayer come from the fact that it can control other nodes. For example you can use it to play sound on cue. Heck, it can call methods!. Thus, if you really need an
AnimationPlayer, you can have it control the shader parameters of the aforementioned setup, so it is only in charge of the parts you can’t do from the shader.
Just move things from code
The likely to be less performant option is to move things from the
_process. This is not far from what
AnimationPlayer does (except, you know, that is core code).
But hey, Turing and Tetris complete language! You can take advantage of
Tween to do interpolations. You can query physic bodies for collisions, you can play sounds, you can write to file and send requests to the network.
You can create the nodes from code, including making all the setups previously mentioned. So you can have code that creates an
AnimationPlayer and then plays it. Which is an opportunity to introduce randomness to the
AnimationPlayer, and also you would be setting up all the particles from code (which you may or may not find easier to do).
MultiMeshInstance being nodes is overhead. You may have a chance at cutting that overhead by talking to the
VisualServer directly. In fact, this is the approach some bullet hell games has taken due to performance (plus talking to the physics server). So there is still a chance this is the most performant way to do it, but you really got to put the effort.