Beyond Smart Objects: Behavior-Oriented Programming for NPCs in Large Open Worlds
Eric Lengyel · 2016
Let us start with a simple scenario. In a village, there are two kinds of NPCs, farmers and shopkeepers. Farmers spend most of their day on a field and go shopping once they are either hungry, are coming from the fields, or are just missing some items for dinner. Shopkeepers spend most of their days in their shops selling goods. All NPCs have a daily routine (like most living things) where they sleep, eat, work, and get back to sleeping. The village is composed of houses for NPCs as well as shops and nearby fields. A natural high-level decomposition is to separately develop behaviors occurring at individual locations (e.g., house, field, shop). A nice property of this decomposition is that the behaviors for individual locations are well encapsulated and conveniently located at their respective places. If there are no dependencies, all these locations can be created, scripted, and tested separately. Thus, the main logic of an NPC is reduced to choosing a behavior and the location where it should be executed. On a finer level, some code is shared among the locations. For example, both shops and houses have doors, so it makes sense to encapsulate the doortraversing behavior and reuse it in both locations. The behaviors should also work across multiple instances of the given location (e.g., shops with storage racks and shops with storage chests). So if we encapsulate the individual types of “take-item-from-storage” behaviors, the code for all types of shops may be the same and simply reference an array of storage containers. We could also mix chests and racks in a single shop. This kind of gradual hierarchical decomposition of behaviors can have multiple levels and is an important feature of the BO approach. While it is natural for NPCs to be explicitly connected to the houses they live in (and they should use the same house for the whole game), it is not necessary for them to know all the shops in the village. Instead, an entity associated with the whole village may be the only object aware of all shops (plus pubs, churches, etc.) that are available. The NPCs simply ask the village they are currently in to direct them to an available shop, making the addition of more shops to a village easy because they need to be connected only to the village object, and all NPCs can automatically start using them. In our scenario, the individual villages, shops, houses, doors, racks, etc., are BO instances. Some, such as the storage rack, provide only a single behavior, while others provide multiple behaviors (e.g., the shop has behaviors for both a shopkeeper and a customer). Some BOs may also need to be active entities and have a brain, a behavior that the BO executes on its own. All instances of the same type share the same behavior and brain code that is specified in a BO tem- plate. Every instance is connected to its own environment data and has its own internal state. Environment data are references to in-game entities that the BO needs for its execution. For example, environment data might be the point where the shopkeeper should stand, or it might be a set of storage containers. Internal state consists of a set of variables that the brain uses for its execution. For example, a variable might reflect whether a shopkeeper is present in a shop, or it might hold a list of NPCs waiting to be served. Internal state also consists of references to all NPCs executing behaviors of the BO. This way, adding new houses or shops to the game requires only instantiating a BO and connecting it to the relevant environment data.