As exciting as my earlier examples with NPC pathing were — and you know they were awesome — I needed to build out the system a bit more because certain functionality required certain other functionality which required certain other functionality…you get the gist.
The trouble started when the NPC reached the end of his route. For a merchant, that meant simulating jumping through a gate. I don’t care that the NPC doesn’t actually go through the gate, but it has to look like he does. One way to do that was to destroy the NPC object. Another way was simply to make the NPC invisible, or more specifically by deactivating it.
With my central data dispatcher model, deleting the NPC would be OK because I’d have it’s data in the Data Controller and could call it up at will. I’d just need to create a new GameObject, set it’s values, and drop it into the world. But in the name of efficiency, instantiating an GameObject from scratch at run-time is expensive, so conventional wisdom says. If you’re creating a lot of instances, the cost just increases.
Data pooling is the practice of creating a bunch of stuff, sticking it in a bucket, and drawing out items as you need them. Once you’re done with the ones you’re working with, you clean them off and put them back in the bucket. I figured that this was a good idea for dealing with NPCs, and it has some interesting side effects as well.
When you create a new game, the system will generate X number of NPC GameObjects. X is currently in flux. The system loops through the data definitions in the Master Database of individual NPCs — currently named for people I follow on social networks — and assigns the info data to each of the GameObjects. Those GO’s are set inactive to put them to “sleep”, and are stored in a collection within the Data Controller called “NPCPool”. When I need an NPC, I can pull an inactive copy from the NPCPool, set it active, and away it goes. When I’m done, like when the NPC has to use a jumpgate, I just set the object inactive.
The benefits are that I should save on memory and loading time by populating this collection at the start of the game, but also in data retention. If I set up the NPCs with advanced tracking info — like, say, an actual inventory — then having them persist as GOs in a pool means that certain data will also persist. I could technically load an NPC with goods from a station and have them carry those exact goods to another station somewhere else (in a totally random, underhanded, simulation-type way). Should the NPC meet an unfortunate fate, then the actual contents of their cargo hold can spill out into space for collection.
I don’t know if I’ll go that far, though, because I can simulate goods moving through the trade lanes behind the scenes easier than worrying about which NPC has how many goods and where they need to pathfind to in order to sell it. I can also just randomly generate cargo spill on destruction, and aside from reading this paragraph, no player would be any wiser that it doesn’t actually work they way that it seems to work.
So now that I have the pool, a random NPC from the pool is spawned in the scene and placed at an egress point associated with a random jumpgate. The egress point is an empty (non-visible) active GO that serves as a relational position in world space that I can refer to when I need to place or move items through code. Jumping into a system would technically put a player (or NPC) inside the trigger sphere. For a player, they’d get a prompt to jump, but for an NPC being inside the trigger (technically the OnTriggerEnter event handler) would cause them to “jump” — get set inactive. They’d be caught in a never ending loop, so the egress point exists outside the trigger-sphere and will be where players and NPCs are placed when they jump into a sector.
Because the movement calculations exist on the NPC itself, once it’s activated, it just…does it’s thing. Right now the NPC will spawn in and move to a random station, “docking” with the station (vanishing from the player’s view), spending a bit of time doing business, reappear, and move to it’s jumpgate destination. That’s about as far as I’ve gotten for now, as I had to step back, review and refactor the code, so I made sure I wasn’t spaghettiing it up and can understand my own logic in a month’s time.