Optimized Static Mesh Projectile Pooling

Blueprint Only
I am experimenting with figuring out an optimized way to handle projectile pooling with static meshes
Let’s say 1000’s of projectiles for sake of argument
I have done some research, but still not clearly understanding the best way to go about this

I have made an Object Class “Projectile_Manager” to handle the pooling logic as well as updating projectile positions
Projectiles are simple static meshes with simple collision (capsules or sphere)

At first, I was just going to test spawning Static Mesh Actors (Nanite Enabled) but then figured better optimization would be to somehow use Instanced Static Meshes, but in researching, it seems you cannot specifically disable visibility for specific instances for instanced static meshes? (Disregard changing alpha on material, as the object would still be rendered no? Not to mention higher cost for transparency?)

With UE5 and Nanite, would spawning Static Mesh Actors not be so much of a concern anymore? And thus, save me development time instead of developing a static mesh instancing system?

For static Mesh Instancing, if I cannot set visibility for individual instances, would I just simply need to move the transform of projectiles of screen so they will be culled ad I wont have to set visibility?

And, ISM have to come from a static mesh component, which I was thinking perhaps I would have my projectile manager be a single actor hidden in game, but then all the instances would be hidden as well no?

It would take me forever to test all the scenarios, so was hoping I could get some feedback on pros and cons or what is/not possible?

Right now, I am working with the projectiles as Data Assets for the projectiles information, but have also wondered if I should just make a custom child class of StaticMesh Actor for each projectile type with a standard base class instead of using data-assets? I use a soft reference to the data asset to keep track of projectiles using a map of data asset → struct(static mesh actor array)

1 Like

Just pool actors with static meshes for projectiles.
No need for nanite or ism.

You can feed the mesh soft ref from a data asset along with the projectile parameters and then populate the appropriate pool.

1 Like

Thanks.
After setting some things up last night, I came across these conclusions

I will be making my Projectile Manager an Actor Class and Not an Object Class

  • Reason: Object class cannot spawn actors, and I was unable through Object class, create an array of actor classes and get a reference to the actor to manipulate the actor directly, I could only get a copy, and then had to call a function on the actor to do “Add Actor World Offset”

I am still toying with Data Assets for Projectiles vs Static Mesh Actor Classes, if you know pro’s vs con’s for this?
My impressions so far:
SM Actors

  • If I create a child base class of Static Mesh Actor for all Projectiles, and then create separate child classes of my new base class for each type of projectile, it seems easier to then spawn projectiles just by passing the class of the projectile
  • Using SM Actor classes, I can conveniently contain all the code for the behavior of that projectile class in a single class, and inherit common behavior from parent classes
  • Downside - I would have to create a data table or still use data assets to store the values for each individual projectile
  • I can use Tick to run projectiles, and set tick intervals per projectile type (or even per projectile)

Data Assets

  • I could have a single SM Actor Class for all Projectiles
  • Seperate and Hierarchical classes for Data Assets, with classes for various types and actual Daa Assets for each actual projectile based on type
  • Seperation of Code (Projectile Manager Runs all Code for Projectiles), Entity (SM Actors) and Data (Data Assets)
  • Seems a better workflow to take advantage of ECS programming methods??
  • If code is run outside the SM Actors, and my Projectile Managre is updating projectile movement (transforms), this would make it easier to spread updates over different tick yes/no? Like, it could update 3 projectiles per tick, updating thenext 3 on the next tick, or however it endsup being best to distribute the load?

I am very interested in ECS workflows, and optimizing performance using such methods, but am extremely knew to this, and not sure if it is possible? From what I DO know, it is important to make use of CPU cache, which means pooling same type code operations to be processed together?

So, if I have a nice seperation of code and data, I could, for instance, take an array of projectiles with same movement type (say, set velocity in X-axis), then combine every 3 actors X location into a Vect3 and make an array, then make another corresponding array of the X offset for those same 3 actors in another vect3 array, then run a function that processes those two arrays, adding each vector together, to create an output vector array, then run a function taking the output vector array and updating the location of the actors? Would this help keep data needed in chunks in the CPU cache for faster processing? Or am I getting way to complicated with this and won;t help any?

Thanks for input, much appreciated

more formally you want software design patterns, the object-pool is one such: Object pool pattern - Wikipedia

ref: How to design a parking lot using object-oriented principles? | GeeksforGeeks

This is basic pooling concepts, which I am already familiar with
I was looking more for rendering and code optimizations

For example, if there was a way to use Niagara Particles, I assume that would be the most optimal way…
add a “Collision” module to your Niagara emitter and then use Niagara’s event export functionality to send data to a blueprint actor on collision

I have not used Niagara yet, and did not think it was possible to use for a projectile systems, but thinking I may have to look further into this, as all I need is collision, and to send an event to the projectile manager when collision detected and it seems Niagara is able to do this? Interesting…

Niagara could possible work fr basic projectiles, but then I could resort to SM Actors for more complex projectiles such as homing etc.

Some really cool forum guy asked these same questions once upon a time (but he never got an answer): Niagara, Bullets, and object-pools

Data Channels might be the way to go from some object-manager → Niagara.

Data Channels have to do with Niagrara? So, when searching, search how to use Niagara Data Channels?

Ifound this: Particle Editor Extension by Cultrarius
and this: https://www.youtube.com/watch?v=aA_8NLzbUTA&t=1s

This post suggests that Niagara is natively not very well off to use for collision, and to be fair almost every method I see has data calc’d on the CPU And feed to a Niagara system: https://www.reddit.com/r/unrealengine/comments/13bwayt/collision_calculation_niagara_collision_or_sphere/

data channels seem to be a ‘fast-lane’ to move data from the CPU to Niagara: Niagara Data Channels Intro | Tutorial If one is managing many items to be fed to niagara for a display-layer, DC’s seem to be the way to go.

Data Channels are interesting
But, as mentioned, does not seem like Niagara should be used for gameplay

I did buy the Apparatus plugin ages ago for ECS framework, but I would like to keep this project native and blueprint only. I would be curious how to implement ECS techniques manually through blueprint, if that is even possible. Something simple just for the purpose of handling a great deal of simple projectiles, and revert back to SM Actors for complex behavioral projectiles like homing missiles etc.

I’ve seen plenty of tutorials that use Niagara, it does have some limitations, but it’s suitable for game-making, etc. I’ve not seen a lot of widespread pushback in this regard?

Either way, Data Channels are an attempt to make a performant solution between the sim and the display layer. For instanced meshes like bullets I don’t see why it couldn’t be used.

I DO have a tutorial around here somewhere where the guy does just that (niagara based object/bullet pool) but I cannot locate it; hope I bookmakred it…

I’m using an Actor Component as manager (AC_Bullet Pool). When a pawn is spawned that will use projectiles I add the component to the class. The AC will spawn a specific number of specific projectile class actors based on the Pawn proxy type.

In multiplayer there are 3 proxies. Autonomous (actual client), Simulated and Authority. In my setup all authoritative shooting is handled by the servers authoritative proxy. The client does a fakey and sims are mainly immersion based. This requires 3 different projectile classes where each is limited to their role.

For example the clients projectile is a collision component with a mesh. Whereas the sim proxies projectile has a collision component and audio component. Servers is just a collision component.

For clients and sims the AC class only loads 20-30 projectiles. While the server gets 200+ depending on the number of connected clients.

All of the projectile classes have a custom “Life Span” functionality that automates return to pool based on the shot velocity and a max range for shots. For example I use IRL muzzle velocities and rates of fire on all my weapons. If I was to shoot straight up in the air the projectile could have a very long life span. By dynamically limiting lifespan based on weapon specs you can increase efficiency and performance of the pool.

Hit Detection, Hit A/V FX, projectile config, reset, dormancy etc is handled by AC_BulletPool. Projectile classes themselves are slimmed down as much as possible. I want them back at the pool ready for the next shot request.

Interesting, I like the idea for multiplayer having just the collision

My current test setup is this:

I have a single projectile manager actor class -
It has 4 main variables:
Active Projectile Blueprints (Actor with Static Mesh Component)
Active Projectile Data Assets
Inactive Projectile Blueprints
Inactive Projectile Data Assets

Projectile Actor has max lifespan, which is simply done with a delay node, and when projectile is reactivated, a reset event is called that resets the delay, and when delay ends, the actor calls deactivate projectile on the projectile maager

This way, I can reuse the blueprints for any projectile, and reuse the data assets separately
The Projectile Blueprint has a variable to store the Data Asset Reference as a soft reference while hard reference kept in projectile manager

I did a simple test, spawning 10 projectiles (with projectile blueprint having a simple world offset on tick) per tick

Using the standard destroy actor and builtin initial lifespan, I was getting 38-40 fps
with the projectile manager I was getting 47 fps

Both generated around 900 projectiles

I was expecting a bigger difference, not sure if my method is not that great (used filter on array to filter for projectile class) or if UE is that good as is

I was trying to switch to using a map of actor class → struct of array of actors
but for some reason, it will only write to array once…have a different post on that, but figured a map migt help some since maps from what I know, are very fast lookups, and I figured was more expensive to filter the array for class - discovered I forgot to overwrite the key-value in map :frowning:

I would be interested if there is a data-oriented way using Blueprint only and avoid tick to have projectile manager update the projectiles movement instread of using tick and not usig the Apparatus plugin

Each projectile needs to update itself. I’m using projectile movement component (PMC) at the moment.

To properly test the pooled method you need to do long range shots where the projectile exists for long durations of time, thus long simulation times. The more you have being simulated, the more processing power you eat.

Also note that when you test multiplayer in PIE your doing double duty. 2 players equals 3 simulations on the same shot that your PC is doing. Shooters processing + Sims processing + Servers processing… for each shot fired.

Here’s a reply that goes into more detail of an older variant of my process.

This is all well and good for basics, but I do not want each projectile updating itself. Fr now, for testing the pooling, I have a simple Add Actor World Offset on tick of the projectile, I am specifically trying to keep all code off of the projectile Blueprints, and will have seperate blueprints for diferent types of pojectiles, like one with only a static mesh, ones with only a Niagara Emitter, and ones with both and a basic with non (to use for instant projectiles doing line traces and spawning effect emmitter)

I want all my data seperate, and i am using Data Assets, ad then if a projectile type needs instanced data, I can construct object of class of the Data Asset to get an instance to store data.

For projectile logic code, that will be handled by the projectile manager.

I am trying to seup so I can eventually, hopefully use an ECS framework or do some custom code to update projectile positions faster than using code on each projectile.

I have done simple pooling like your posting before, and simple projectile logic like you are posting, now I want to do a more performant way, which is why I did this post.

To figure out how to optimize for performance wth 1000’s of projectiles.

I am pooling my Actors and Data separately.

Right now I am getting roughly 800 projectiles (2 sec lifespan) at once creating 10 per tick and frame rates around 40fps (although sometimes 47) in editor PIE, so not really the greatest profiling method. I am not getting much better results than native garbage collection, which I find surprising.

As for the Data Objects, only 1is being created snce I am not instancing those ATM, every projectile accesses the same Data Asset just to get initial values.

All my data is separate. Each weapon on spawn creates a config (structs) pulled from data tables. A projectile configuration is stored in the weapon class based the specific weapon (M416 vs 1911 pistol)… The class that’s actually working with the pool.

Upon firing, the weapon class determines trajectory (Aim Velocity, LifeSpan etc) based on the config. It then passes the config’d values to the pool on projectile request. A single projectile class works for all calibers etc.

Data tables can be updated at run time. This makes it possible to adjust stats without a patch. Simple editing of a spreadsheet then push.

This is Data-Oriented Design. Pure ECS is just a variant with its strength coming from how things are a laid out in memory. You can’t do that with BP (Allocations & Memory Layout). It requires a lot of low level C++ programming.

Standard design principle is to bind data to logic that operates only on that data. Keep components as small as possible with one clearly defined responsibility.


Performance wise I get a 1-10 FPS dip when mag dumping 700 rnds/m at 790m/s muzzle velocity on Epic scalability settings. Depends on what else is in scene of course.

The only framerates I’m concerned with are the shooting client and the server. The only projectiles for the vast majority of time being simulated on each client is their own shots. Servers simulate all shots. Sim Proxy shots technically do not need to send a projectile down range. All you need from Sims is the action of shooting. Muzzle Flash, Audio, and recoil animation.

The only time you’d ever want to actually send a projectile down range on another clients simulation (Sim Shots) is if it would fly close enough for that client to hear a whizz as it flew by.

To help achieve this determination my character class’s begin play sets a proxy role enum. Proxy Role is then used by sims when they spawn to identify who is the autonomous (actual client) in the simulation.

Now all the sim proxy characters in my simulation know that my character is the local authority… Autonomous Proxy. I can do simple sphere traces to determine if any of their shots would register a muzzle sound (attenuation radius of shot) and/or if the projectile would fly by close enough to be heard in flight.

These two traces alone massively reduce the number of projectiles being simulated on a per client basis. Plus the added bonus of not playing additional audio / visual fx that will not be seen or heard by the local client.

For hit FX (Audio, Visual) the server can multicast the relevant data to sims.
struct [Location, Normal, PhysMat]

Here’s a demo I just recorded. I disabled muzzle flash/audio and recoil to help see the pooled projectile.