Heres a little breakdown of a system I’ve built thats allowed me to have thousands of enemies in the level but only spawn the ones close to me.
A global manager that gets the location of every AI in my level and puts their location into an array of transforms. This only needs to happen once, on event begin play or construction script.
Break your level up into grids. Each grid will have 2 volumes. A larger trigger volume, and a smaller volume that contains all your NPC’s. When you enter the larger trigger volume, pull all the AI transforms that are in the smaller volume, into an array. Then every 5 seconds, your player character can check to see which of the transforms in that area are within a “spawning distance” and spawn those characters as you approach them. If the player leaves the trigger volume, stop checking the list of transforms for the specific volume that he left. Using this method, the player should only ever be checking a maximum of 4 separate volumes, if he is at the intersection point of 4 grid squares. Checking the list of transforms every 5 seconds means we can avoid using Event Tick as much as possible.
At this point, you can pass the logic onto the spawned NPC. Every 5 seconds, have the NPC loop a timer that checks to see how far away the player is, and if hes greater than “x”, destroy themselves, but pass their stored transform upon destruction back to the NPC manager. This way when the player character goes back, the NPC will spawn back right where they de-spawned. (This is only necessary if the AI moves at all, if its just a vendor that will never move, don’t worry about modifying the transform). If the NPC dies for whatever reason, you can remove the transform directly from the global manager, so the player never checks for it again until the next play through.
This system was allowing me to have 50 AI spawned at a time, and about 2-3,000 AI locations stored in memory that would spawn dynamically as I approached them, and de-spawn if I run far enough away, and even remember the location they de-spawned at if I decide to come back through the area. Using this approach I was able to maintain 120FPS constant in an otherwise empty level. This was also on a BP project mind you, so maybe a C++ project could allow for even better performance.
Take a look at this for a system that might do somewhat of a similar thing Open World AI Spawn System in Blueprints - UE Marketplace
Hope this helps,