How do make an actor garbage collector?

Hello everyone.

In my multiplayer game, I want to delete left over objects once a game round is over. I’m looking to make a garbage collector per se.

I’ve already written one, but it does not work. The way it works is rather simple:

(1) All weapons/actors that are a target for GC must be fully replicated and get spawned by the server, upon spawn, they are added to an array that contains objects that will be deleted by the GC system.

(2) The game state, which holds the GC code since it exists everywhere. We ensure that we are in a server context, then add it to our server’s GC removal array.

(3) When a round ends in our game mode, we trigger a call for the server to destroy GC objects in our game state.

(4) Then, the server cycles through each object in the array, and if they are valid, they get destroyed. Clearing the list after a few seconds.


Note that the specific custom event here isn’t explicitly in the server context since it is re-used by a local version for debree and other visual effects, however, we originally fire it in the server context and thus this event will be in that context too.

On paper, based on my understanding of Unreal’s networking, this setup should work. If the server deletes a replicated actor, it will be deleted across all clients. While doing it via a multicast could cause memory issues as the server could ask to delete objects that are supposed to be replicated but were deleted on the client-side for that specific client.

However, this set up does not work. If an object is created by the listen server’s client, it will never be deleted. While objects dropped by clients do get deleted. I have no idea why, I have tried many things over the week, but nothing seems to work.

If anyone could point out the error in my ways, I would be very appreciative.

Seems about right. More generally, actors spawned on server should be deleted on server. Actors spawned on client should be deleted on client. Replication isn’t really relevant here.

You shouldn’t ever have to switch contexts in your system. Spawn actor → Add to GC, should be enough, no need for server events.

Also, game state class cannot trigger server events from client, so this :


will not work when executed from client context. However it’s probably not the cause of your bug because you’re already calling it from server context…

I’m more intrigued by this :


Where is this called from ? If called from client context, again, it won’t run. Maybe that’s why your stuff doesn’t work. Otherwise I don’t really see anything wrong.

Also, there’s no point in delaying 5 seconds before clearing. You can loop and clear immediately.

Lastly, I see you added a bunch of print strings, which is good. How about them ? What does your setup print ? You might want to even add some more to figure out what’s wrong.


On another note, may I suggest a slightly different approach - Actors have a Tags variable which is simply but an array of strings, which you can fill at will. So instead of tracking them in an array, just give them a “GC” tag after spawn. Afterwards, you can retrieve them with GetAllActorsWithTag(“GC”).

1 Like

Hello Chatouille,

Apologies for replying late, I use the unnecessary context switching to make the blueprint a bit more generic and avoid needing to make boilerplate variations, should I just delete it?

Here is an example of that, the garbage burning is originally called in the server context, but I re-use the same blueprint to also be able to call it through another custom event that switches the context to a multicast. As for QueueActorForGC, I probably should just get rid of it and handle it directly, right?


PerformGCCleanup is called by the game mode when the round enters a specific state in the logic. I didn’t add that to the original post to avoid information overload.


Since it is called by the game mode, it should only ever be handled by the server. The context switch is technically redudant, but I added it any ways just to be safe, since Unreal can be rather pouty about these kinds of things.

As for the prints, nothing prints anywhere ever. I think it is due to the context, the listen server client refuses to show stuff meant for the server itself. The code executes, but the prints don’t show.

The approach of using tags is one that I think I did consider early on, but was told by another developer that it was really inefficient to do a string lookup for every actor on a map, particularly if I have a lot of objects, so I pivoted towards this more explicit approach where we already know what we want to delete.

I have one that uses an interface to call destroy actor in the item class.

World items (loot not collected) have a custom collision (proj settings). The GM spawns a garbage collector actor in the center of the game world (not replicated). This actor has a sphere collision with a 1cm radius, non blocking, only overlaps the custom collision.

On activation the collision is scaled up over time.
On Begin Overlap takes Hit Actor → does implement interface → Calls the destroy event.

Benefit here is you’re not storing an array then looping it. Arrays are memory eaters. Loops are fully executed in a single tick. The next tick cannot start until the loop finishes.

Say you have 1,000 actors to remove and the server tick is 30Hz (33.333ms interval). In most cases that loop will take more than a single ticks duration to finish. Essentially this increases the tick interval and will cause rubber banding and other noticeable hitches.

Expensive Loops should be done in C++.

Testing code

1 Like

Yeah using a multicast from server to trigger GC execution on all clients is perfectly fine, I was more put-off by other events looking to be the other way around - but if they’re called from GameMode, and as long as you are aware the Server flag is redundant, then it’s fine.

The fact that nothing prints is concerning. Print String should show up in all viewports. Server viewport should show both server and client prints. Client viewport should also show both server and client prints. The prints are prefixed by where the print string is executed (Server: or Client:).
If you don’t see any print, check the Output Log maybe ? All print strings are also logged, under the BlueprintUserMessages tag. Imho figuring this out is a priority, printing strings/logs is an important tool for debugging, especially in multiplayer contexts where BP debugger is more difficult to use.


This is true, and what RevOverDrive said is also true, but that doesn’t really sound like a concern if you do swipes outside of gameplay (ie. inbetween rounds).

If you really wanna dive into performance, here’s another thing to take into account :

GetAllActorsWithTag loops through all alive actors and does the string comparisons in C++. With your array approach you are looping through your array but require an IsValid check for each item. Blueprint is significantly slower than C++, so due to that check, there’s a possibility that array approach ends up being slower. Obviously it depends on the amount of alive objects vs. the amount of entries in the array. If LOTS of spawning/destroying happen during gameplay round (eg. weapon projectiles), chances are your array might end up even bigger than the amount of alive actors.

Ultimately only you can figure out what’s best for your use case, or if you really need to spend time on it or not.

It could be that somehow the context switch is causing it to silently fail (but still execute the blueprint in unexpected ways or somehting). I’m honestly not sure.
I’ll try all the methods and see what works. For now, I’ll mark your reply as the solution until I’ve managed to nail down what’s the cause.