Spawning NPCs from an actor with an unreliable lifespan - NPCSpawnProxy

Why should I care?

The Problem

When creating content, such as scripted open world encounters or specially created content, you may find yourself in a situation where you have an actor that controls an encounter and is responsible for spawning NPCs, and later despawning them when it is done.

However, some actors that do this have an unreliable lifespan: It is possible that they may be destroyed or unloaded while an Async SpawnNPC action is executing. When this happens, the results are undefined and may result in an orphaned NPC hanging around in the world.

The Solution: NPCSpawnProxy

NPCSpawnProxy provides a spawnable companion actor that tracks your actor’s lifespan and handles all asynchronous NPC spawning and standardized setup internally. In the event that an NPC spawn completes outside your actor’s lifespan, it handles cleanup on its own.

As a significant bonus, this shifts handling async spawning away from your control blueprint, and makes the most complex setups like spawning 8 of the same NPC at different spawn points trivial to implement.

NOT a replacement for spawnpoints

Please note that the NPCSpawnProxy is in no way a replacement for the existing camp/territory/spawnpoint system. It exists as a tool for custom content and encounters.

How to use

Setup

Spawn the Proxy

The proxy is an actor that automatically manages its own lifespan based on the lifespan of your controller. It needs to be spawned before you will use it. BeginPlay is the perfect spot for this. Note that the proxy only works on the Server, so make sure you only spawn it there.

Class: /Game/Systems/Spawning/SpawnProxy/BP_NPCSpawnProxy.BP_NPCSpawnProxy

Note: Make sure you pass Self as the proxy's Owner. This is how the proxy knows what actor it belongs to.

Spawning a Single NPC

One of the most common usecases for spawning an NPC is to create a single unique NPC at a specific known location.

Create an Async spawn request

With the proxy created, we can start requesting NPC spawns. Creating the request is quite simple, involving just a single function call. It may help to expand out the Params struct inline, as shown in this screenshot:

The returned request object contains the params you requested, as well as the state of the spawn. These can be thrown away when you are done with them (simply clear references to them), or you can keep them for ease later.

The optional Payload parameter will accept any object, and can be used to carry arbitrary data with it. What you do with it is entirely up to you.

Bind and Activate the spawn request

When created, a spawn request is inactive and requires binding and then Activation. This may seem a little odd, but it ensures that timing-based edge cases can be avoided.

Most scripts will bind into OnSpawnSuccess and then call Activate:

As soon as Activate is called, the NPC may spawn. As such, it is vital that you bind to OnSpawnSuccess first.

Note that it is a fact of life that NPCs may fail to spawn. It is often out of our control when this happens, but it is typically worth logging or otherwise noting for debugging purposes. This can be tracked with the OnSpawnFailed delegate.

Spawning a set of identical NPCs

Many encounters will have setup that require many of the same NPC. Typically to make this situation work requires an awkward event that repeatedly calls itself until it has requested and finished setting up all of the NPCs - as async calls cannot be processed in ForLoop or ForEachLoop.

NPCSpawnProxy allows for cleaner implementation that does not have this limitation.

Async spawn requests in ForLoop/ForEachLoops

As the spawn requests are not themselves inherently asynchronous actions, but rather point to asynchronous actions, you can operate on them in a tightly nested ForLoop/ForEachLoop. Additionally, you can even work with them in functions, though that is not recommended as it makes tracing the spawn success/failure events very awkward.

In this example, we have 8 spawn points (represented as ArrowComponents in our controller blueprint) and we want to spawn 1 copy of the NPC on each of the 8 spawn points.

Keep in mind that the actual spawning of the NPCs is still asynchronous and may occur in any order.

Lifespan Obligations

When using the NPCSpawnProxy, your controller script has some obligations:

Aborting Early

Sometimes you may need to abort spawning NPCs. This process destroys the SpawnProxy when it is done, as it assumes that no further spawning will be needed.

Note that per the above obligations, any NPCs that have had their successful spawn reported are your script’s responsibility to cleanup at this point