GSoC Week 1 and 2: Wild Animals Behavior System
The start to GSoC has been smooth. Currently as I write this wrap up post for week one and two, my work has already crossed the week 3 mark according to my proposal.
What have I been up to?
I’ve been adding new features to the WildAnimals module. The module only had a deer that idly moved around and did nothing else. When hit it would lose health and ultimately just disappear as a glitch when it’s health reached 0.
A lot has happened since and at this point there are 4 variants of the deer that exhibit very different behaviors- deer
, insensitiveDeer
, aggressiveDeer
and hostileDeer
.
Before we get into details of how these were built, let’s have a look at a few preview videos:
Importing an animal in game
First an animal model is made in blender. After an artist finishes up a model, gets it rigged, UV Mapped and adds animations, it is pushed to the Meta repository that holds all the blender files for creature models. There is a comprehensive guide on how to add creatures in game here that I’ve written. Also there is one written by flo, here. Thanks to quaternius, I’ve been getting some help with animal models.
Working with the animal in game
Each animal has a prefab file located here that details the characterstics of the animal as components. To know more about components and the Entity system architecture, go here.
Once an animal prefab is made, it can be spawned in game by typing spawnPrefab prefabName
in the console.
Event driven Behavior System
The four different deer I mentioned really have the same animal model, animations and texture. They only vary in behavior. This behavior handling forms the crux of the WildAnimals module. Let’s dive into a little more detail.
The Behavior System built in the engine allows something known as behavior trees, to define a certain behavior.
This forum post and youtube video would really give you a feel for behavior trees.
These behavior trees are central to the event driven behavior system. Each behavior tree defines a particular type of behavior that can be applied to an entity, for eg-
- A “stray” behavior can have a deer idly roam around, walk and stand for a while.
- A “flee” behavior can have the deer run away from the player until it’s at a safe (defined minimum) distance.
- A “hostile” behavior can have the deer close in on the player and then inflict damage.
The “stray” behavior tree that exists in the Pathfinding module module looks like this:
Each of these behavior trees can be applied to the entity by simply defining it in the entity’s prefab under the BehaviorComponent. But what’s needed for the animals, is a behavior system that controls what behavior the animal has at any point in time. A system that controls when the behavior needs to switch, what should trigger such a switch and which behavior takes a higher precedence. Enter Event Driven Behavior System.
This wiki page describes in detail how events work.
The WildAnimals module has different packages for different behaviors like “FleeOnHit”, “AttackOnHit”, “AttackInProximity”, “StrayIfIdle”, etc. Each of these packages consists of a system and a component. If such a component is attached to an entity, the respective system takes care of handling the behavior changes associated with that package.
Let's take the example of the "FleeOnHit" and "StrayIfIdle" package, the first such packages.
-
The FleeOnHit package has a FleeOnHitComponent, which when attached to any wild animal entity would let it exhibit the "flee" behavior when it is hit.
-
The StrayIfIdle package has a StrayIfIdleComponent, which when attached to any wild animal entity would let it exhibit the stray behavior when it is idle and has no other behavior that could get triggered.
-
For the deer, by default it has a behavior of "stray" defined in it's prefab.
- The FleeOnHitSystem watches for the OnDamageEvent to happen to an entity which has the WildAnimalComponent and FleeOnHitComponent. It saves the event instigator (damage inflictor) to the FleeOnHitComponent and then sends an UpdateBehaviorEvent to the entity to trigger a change if it's needed.
-
The UpdateBehaviorEvent is received by the FleeOnHitSystem. It checks for the instigator inside "FleeOnHit" to be non-null, triggers the behavior switch to "flee" and consumes the UpdateBehaviorEvent.
- The StrayIfIdleSystem also receives the UpdateBehaviorSystem, but with a lower priority.
-
When the deer reaches a safe (defined minimum) distance from the player, the instigator in the FleeOnHitComponent is set to null and another UpdateBehaviorEvent is fired from the CheckFleeStopNode (a part of the "flee" Behavior Tree).
- This event is not consumed by the FleeOnHitSystem event handler as the instigator in FleeOnHitComponent is null. It then is received by the StrayIfIdleSystem event handler, which triggers a behavior switch to "stray" and consumes the event.
The behavior switches simply happen based on the different priorities the event handlers in the different packages have.
Advantages of the Event Driven Behavior System
There are quite a few advantages of using this approach for dealing with complex and multiple behaviors.
- Easily Extendable and Reusable: A package consisting of a particular conditional behavior can be used for multiple animals/entities. For eg- the “FleeOnHit” package made for the deer can easily be added to other animals like cows, dogs etc. Adding a behavior is as simple as attaching a component.
- Efficient: There are no constant checks and running loops. The behavior switch is instant.
- Simple Behavior Trees: The behavior trees remain bare-bones and minimal, as they have to deal with only one behavior and not worry about switching behavior.
- Neat Prefabs: The prefab for the animal now gives a good picture of what all behaviors it exhibits. A simple deer’s prefab would just have the “FleeOnHit” and “StrayIfIdle” components. A deer’s prefab could look like this:
"FleesOnHit": { "minDistance": 15.0 }, "EatsFromBlockIfHungry": { "block": "grass", "maxWalkDistance": 4 }, "AttractedByHoldItems": { "items": ["apple", "nut"] }
- Clear order of precedence: Different behaviors can be given different weights as per priority. A hungry deer when hurt would run for it’s life instead of looking for food. A priority list of behaviors could look like this:
ensure safety (flee if recently damaged) ensure not hungry ensure not thirsty ensure not tired ensure not curious (has not watched player / player action / player item recently)
- Easy development: With this system, someone who’s making the “EatIfHungry” package does not have to worry about what will happen if the animal is hit when it’s hungry. All he has to do is assign the event handler for the “EatIfHungry” package a lesser priority than the “FleeOnHit” package’s event handler.
The post went longer than I had expected it to. The next post would contain details of the deer that have been implemented and maybe new creatures! Most deer at this point are just placeholders for other animals whose models aren’t ready yet. Thanks to flo for all his help lately. The whole event driven behavior system idea was born out of a discussion with him. He’s been really helpful in getting my PRs reviewed and merged too.
In other news, I was away for 3 days (10th to 12th May) for the Microsoft code.fun.do SHOWCASE event that was held in Hyderabad. The trip was fun with sponsored flight trips and hotel stay. Have to catch up with lost time on GSoC though.