Search Unity

Design Question: Managing multiple NPCs

Discussion in 'Game Design' started by Volcanicus, Aug 7, 2019.

  1. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    I am calling on your experience with Unity to tackle this problem.
    I have ~20 scenes in which NPCs exists. Sometimes, the same NPC will exist in 2 scenes.
    Each have their dialog, quests and schedules. Some roam, some sit, some stand. Each's schedules is recorded on a .csv file and read.

    What would be the best way to manage the NPCs on a schedule and why:
    1) Each scene contains each NPCs that are required (read: already placed, no spawning event). If the player is in the scene between certain in-game hours or is doing a quest, the NPC will have its navmesh and other scripts activated and will be rendered and scaled from 0 to 1. Otherwise, NPCs are disabled, scaled to 0 and parked elsewhere in the scene until needed. This may be the least efficient method but the most easily manageable with the least amount of debugging.

    2) Each scene contains spawn points that will spawn or de-spawn NPCs based around their schedules. This will require creating and managing NPC prefabs based around their schedules. Each time, an NPC will be created and later destroyed. This seems most efficient in terms of resources however, how efficient is it? In terms of debugging, many tests will have to be done to make sure the NPCs do not interfere with each other.

    I cannot think of any other design to manage these NPCs.

    Any suggestions?
     
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Another approach is to provide areas that NPCs can do things in. These are sometimes called anchors or affordance objects. They can run on a schedule. For example, a cow could have a high priority anchor that turns on at dawn and is available to the NPCs on the farm. When an NPC goes to that anchor, it would make the NPC play a milking animation for 60 seconds, after which the anchor would turn off. One of the NPCs will choose to go to the anchor because it's a high priority. At night, a bed anchor could turn on, causing an NPC to go to the bed and play a sleep animation. This gives a little more flexibility than hard-coded schedules, and it may be easier to coordinate multiple NPCs.

    Whatever design you choose, you'll need to prioritize actions. If an NPC is walking to the cow to milk it, and trolls enter the farm and stand in the NPC's way, the NPC should prioritize fighting (or fleeing) instead of blindly continuing to walk to the cow. Hierarchical FSMs are a common way to implement this.
     
  3. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,023
    What is the problem? It seems the only comparison so far has been the amount of debugging, which I don't quite understand how you arrived at.

    I tend to think that using prefabs is pretty much always better, especially in terms of making sure that scenes are synced to changes you've made in your game. That would seem to reduce debugging.

    Personally, I think the best approach for AI design is to create time-based model of the AI behaviour cycle, and instantiate prefabs according to the current status of the model when the player is in the vicinity. I also prefer to keep everything in one scene, since that reduces workload duplication even more.

    I did some AI for ship trading a while back. If I remember correctly, the AI script simply iterated a Vector3 position within a solar system according to ship velocity, warp acceleration and deceleration, etc. When the player was near it, this model would stop iterating, spawn an AI and hand over control to the AI, which could then engage the player and do whatever. When the player was far enough away, the AI fed its current status back to the model, which continued iterating according to its parameters.

    That way, the player could find an AI that it had lost contact with as long as they had some idea of where the AI had gone.
     
  4. DBarlok

    DBarlok

    Joined:
    Apr 24, 2013
    Posts:
    268
    Im wondering the same in one of my prototypes. I think it's better to have prefabs after you manage
    to make all the schedule for ONE npc from beggining to end. If you already did this, based on the .csv
    parsing, then, i think the best approach it's to instantiate. Because if you put 500 npcs fixed in one scene
    or even 10 scenes, and you want to change something or fix something in the entire NPC system, you will lose your hair.

    It doesn't sound too elegant to hide the NPC using the scale at all. It's good for a fix if you don't have other way around, but if you are pre-planning this, i don't think so about using the scale to hide anything 3d at all.

    What if you want to upgrade this system to have 5000 npcs? Instantiate it's one line of code and this NPC prefab will have all logic in his components, even to look for waypoints, navmesh (this area i don't manage so well right now to write about this), or anchors (new concept to me for npcs), even reading his own schedule. You can have one component to parse this Schedule on Awke, that will be read after the NPC_manager decides to instantiate this NPC based on some game rule and from this component, the Schedule_Manager component you will call with SendMessage or whatever method it fits you best, the NPC_state component and NPC_trigger component. Or, you can have just one NPC_Class with everything inside it.

    Anyway, the prefab instantiating approach seems better to me, in theory.

    BUT, this will lead to put spawn points everywhere and of course, conflict
    with NPC spawning. Difficult and too rigid maybe.

    So, i will use the prefab approach and spawn by hand and work everything inside ONE
    scene first. If this works, then it will be easy to expand later, if expansion if neccesary.
     
    Last edited: Sep 30, 2019
  5. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,278
    I personally split my world into chunks, each being a scene. However, NPC's exist in an abstract state away from the scene - in what I call a record table. Whenever I want, I can spawn an NPC or teleport them or whatever, regardless if they have a physical body (spawned), or if their position is in a loaded chunk/scene.

    I have personally found great success with this! It also lets me have objects anywhere, whether an asset or a component, reference an NPC, since they are not a component. (No cross-scene-reference issues).

    This allows me to allow NPC's to have simple "background logic" if they so wish, appearing in an entirely different chunk due to their schedule demanding it, or traveling to another point on the map because of a quest, and so on. It also allows them to go in/out of dungeons (which is on a separate chunk), even if their physical body doesn't exist. Note that I keep background logic / processing at a extreme minimum, since 99% of npcs essentially need 0.

    Finally, it's often easy to overthink these kinds of things. I often spiral into "well npcs should be able to do this and this and this", when often its so much easier to fake it, and 99% of players would never even notice. Try to take baby steps, and then stop once you're happy with the outcome. At least, that helps me from going insane.

    Also, there's another question of whether players will notice these complex schedules: Oblivion had some amazing schedules, where npcs would travel to different towns to get supplies, which is such an awesome concept: but what are the chances that you would see it, and then realize what they are doing? Next to none. Even with Oblivions amazing schedule system, its npcs are remembered to be dumb and hilarious. In other words, sometimes its a good idea to focus on the ground level basics instead of highly complex systems that result in little gain. I very much understand though, since I am always thinking of overly complex systems as well, and have often programmed them only to find that they aren't that useful. Just a thought, not saying a schedule system is bad or not worth, that's only something you can decide depending on your game!
     
    Last edited: Oct 23, 2019
  6. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    In the end, what I did was have the NPCs work as default dolls that roam around and when called, they go from scale 0 to 1. Since they are dolls, my system feeds them the info they need to speak.

    I had to compare the difference in performance between the garbage collection and loading too much but the loading was trivial and when the garbage collection kicked in, the game was way slower when I created and destroyed the dolls.
     
  7. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    Also schedule are a LUT, you don't even need to simulate the npc, since you just need to do one query in the schedule, to know what to do. Complex action are basically just multiple parallel LUT with precondition to know which one to look at. And if you have waypoint, you can normalize and store it in the LUT, retrieving it just mean matching it to the normalize time and lerping the way point to the current time.

    I plan to use that to fake a procedural stateless town simulation
     
    joshcamas likes this.