Case Study - Terasology/Behaviors GitHub Wiki

This page details the process of creating a new module with creatures, behaviors, and groups. You should understand how Terasology's behavior system works - it is explained here. All source code for the present tutorial is available in the TutorialGroups module.

Context

We are going to show how to create a module from scratch, but using existing creatures and behaviors as a basis. This is a long tutorial as we will explore all steps of the process in detail. The idea is to show not only how behaviors and groups can be used together, but how to reuse existing code in new modules. For this reason, we will use existing code as much as possible - be sure to follow the links as they appear.

Our objective is to create a group of creatures with the same behavior. We will use a variant of deer from the WildAnimals module for that. Also, for this case study:

  • We will use existing creatures from the WildAnimals module
  • We assume that you already prepared your development workspace, as described here
  • We are creating a new module in this tutorial, but you can also use an existing module as a basis.
  • Our module depends on other existing modules. If you don't know how to include other modules as dependencies of a new one, please check this page.

Getting started

We will create a new module called TutorialGroups and add Behaviors and WildAnimals as dependencies. Your module.txt file should look like this:

{
    "id" : "TutorialGroups",
    "version" : "0.1.0-SNAPSHOT",
    "author" : "casals",
    "displayName" : "TutorialGroups",
    "description" : "Tutorial module for the wiki case study",
    "dependencies" : [
            {"id" : "Behaviors", "minVersion" : "0.2.0"},
            { "id": "WildAnimals", "minVersion": "0.2.0" }
        ],
    "serverSideOnly" : false
}

Let us also create a new entity to use in our tests. Since our module depends on WildAnimals, we can use any of the existing deer as a basis for our new entity (if you don't know how entities can be described by prefabs, please check this link). We will create an entity called testDeer, based on the greenDeer prefab. For that, we just need to create a file named testDeer.prefab in our assets/prefabs folder, with the following contents:

{
  "parent" : "greenDeer"
}

This will allow our testDeer to inherit all base components from greenDeer.

Defining a behavior

There are several different behaviors that we could add to our deer. Usually, we would just include a Behavior component in the entity prefab, as shown here (you can learn how to create your behaviors here). However, since we want a broad example of how to use behaviors and groups, let's assume the following situation:

  • We want our deer to be assigned to groups as soon as they are spawned;
  • We want our deer to idly stray around until they are attacked by a player (in which case they flee away); and
  • We want our deer to display group coordination: if one deer is attacked, all deer in the group should flee away from the group.

If we consider only the first two items of our behavior checklist, there's already a behavior that does that - it is called critter, and it is defined in the Behaviors module. Let us take a closer look at the critter.behavior file:

{
  dynamic: [
      {
        guard: {
          componentPresent: "Behaviors:Fleeing",
          child: {
            sequence: [
            check_flee_stop,
            {
              lookup: {
                tree: "Behaviors:flee"
              }
            }
            ]
          }
        }
      },
      {
        lookup: {
          tree: "Behaviors:stray"
        }
      }

  ]
}

The behavior tree described in this file can be interpreted as:

  • There is a dynamic selector on the top of the tree that constantly re-evaluates all children that previously returned FAILURE with each tick;
  • The first sub-node checks if the entity possesses a component called Fleeing, in which case it executes an action (check_flee_stop) and a nested behavior tree (Behaviors:flee);
  • The second sub-node calls another nested behavior tree (Behaviors:stray).

You can read more about each of these node types here. The nested sub-trees are pretty descriptive in what they do: the first one is composed by a set of nodes and actions that makes the entity to flee away from whoever is causing them damage, and the second one just establishes a straying behavior. Also, the check_flee_stop action is implemented in the Behaviors module by the CheckFleeStopAction, and its goal is to determine if the damage received came from a player (instigator), thus determining if the entity should flee or not.

Note that due to the dynamic node selector this tree will be re-evaluated every time it's determined that the entity should not flee (in which case the action returns a FAILURE) status. However, for this check to happen, the entity must possess the Fleeing component. Now - let's look at the original deer prefab (used as a basis for all deer in the WildAnimals module:

{
  "skeletalmesh" : {
    "mesh" : "deer",
    "heightOffset" : -0.8,
    "material" : "deerkin",
    "animation" : "deerIdle",
    "loop" : true
  },
  "Behavior" : {
    "tree" : "Behaviors:critter"
  },
  "FleeOnHit" : {
    "minDistance" : 5
  },
//...
}

Observe that the prefab does not contain any reference to a component named Fleeing - however, it contains a component called FleeOnHit, which seems related to what we need. Also - it is important to notice that this prefab was defined in the WildAnimals module, but there are no references to any component called FleeOnHit. This happens because WildAnimals also depends on the module Behaviors; when your module depends on another one, you can access all of its components and assets. In the case of assets, you need to specify its module of origin (as in Behaviors:stray), but components can be seamlessly referenced and used in your prefabs.

With that in mind, let's go back to the Behaviors module and take a closer look at the FleeOnHit component. Its implementation (FleeOnHitComponent class) is pretty simple: it defines a minimum safe distance to be maintained from the aggressor, and a speed multiplier parameter used to set a default running speed when fleeing. This component is used by a system implemented by BehaviorsEventSystem:

@RegisterSystem(RegisterMode.AUTHORITY)
public class BehaviorsEventSystem extends BaseComponentSystem {

    @In
    private Time time;

    @ReceiveEvent(components = FleeOnHitComponent.class)
    public void onDamage(OnDamagedEvent event, EntityRef entity) {

        // Make entity flee
        FleeingComponent fleeingComponent = new FleeingComponent();
        fleeingComponent.instigator = event.getInstigator();
        fleeingComponent.minDistance = entity.getComponent(FleeOnHitComponent.class).minDistance;
        entity.saveComponent(fleeingComponent);

        // Increase speed by multiplier factor
        CharacterMovementComponent characterMovementComponent = entity.getComponent(CharacterMovementComponent.class);
        characterMovementComponent.speedMultiplier = entity.getComponent(FleeOnHitComponent.class).speedMultiplier;
        entity.saveComponent(characterMovementComponent);

    }
//...
}

There are a few things going on this system. We won't explore how events, systems, and components work together at this moment, but you can learn more about the Entity System architecture used in Terasology here. Let's take a closer look at the onDamage method (which defines what happens to an entity possessing the FleeOnHit component once it receives damage):

  • When the entity is damaged, the system creates a new Fleeing component and attaches it to the entity;
    • The new component possesses relevant information about who caused the damaged (which is used later, as we saw before);
  • The entity's speed is altered according to the speed multiplier defined in the FleeOnHit component.

This pretty much solves what we need in terms of behavior, but we still need to make all deer in the group to flee if one of them is attacked. At the same time, the fleeing behavior can be the same one already implemented in the Behaviors module (we just need to attach the group conditions). That means we need:

  • A way of identifying our group; and
  • A way to make all entities within the group to flee if one of them is attacked.

Groups are, in a nutshell, an easy manner of identifying and handling multiple entities - you can read more about it here. For now, let's define that our group will be called wisedeer. As we said before, we want our deer to belong to this group as soon as they are spawned - so we will amend our testDeer.prefab file to include this information. We also want our deer to have a slightly different fleeing behavior from the one we saw before - but we have already seen a good strategy on processing a damage event, so let's use that. We will also create a modified component called GroupFleeOnHit - it's pretty much the same as the original, but we want to take advantage of the existing implementation without interfering with other modules or behaviors using the same component. Also, it's a great opportunity to add new characteristics.

All things considered, we will need:

  • A new implementation of the modified GroupFleeOnHit component; and
  • A system implementation, similar to the BehaviorsEventSystem, to process what happens in case of damage.

Note that we can assign behaviors to entities either in their prefabs, or - if we are using groups - in our group asset (another JSON-like file). We want to keep this tutorial as simple as possible, so we won't use any group assets at this point - but you can read more about them in the Groups wiki page.

At this point, it is important to notice that there is a reason why we are using the greenDeer as our basis. All RGB deer in the WildAnimals module were built to be used as examples in tutorials; in particular, the greenDeer does not possess the FleeOnHit component. This is important since the GroupFleeOnHit component exists so we can reuse as much code as possible (including the original critter behavior and Fleeing component). In the case an entity possessed both components, we might have conflicting scenarios caused by different superposing settings (such as the speed multiplier). We could also implement a new behavior from scratch, of course - but this case is already covered here, and we don't want this tutorial to get repetitive.

Implementation

With all the considerations above, our new testDeer.prefab file should look like this:

{
  "parent" : "greenDeer",
  "Behavior" : {
    "tree" : "Behaviors:critter"
  },
  "GroupFleeOnHit" : {
    "minDistance" : 5
  },
  "GroupTag" : {
       "groups" : [ "wisedeer" ]
     }
}

We will also need two new classes for our new component and system, respectively. Let's create them in two different packages:

GroupFleeOnHitComponent:

package org.terasology.behaviors.components;

import org.terasology.entitySystem.Component;

/**
 * If this component is attached to an NPC entity it will exhibit the flee-on-hit behavior
 * When hit, the NPC will run with a speed of `speedMultiplier`*normalSpeed
 * till it is at a safe `minDistance` from the damage inflicter- `instigator`.
 * When it reaches a safe distance the instigator is set to null. This component uses
 * @see FleeOnHitComponent as a reference/basis.
 */
public class GroupFleeOnHitComponent implements Component {
    /* Minimum distance from instigator after which the NPC will stop 'flee'ing */
    public float minDistance = 10f;
    /* Speed factor by which flee speed increases */
    public float speedMultiplier = 1.2f;
}

GroupBehaviorsEventSystem:

package org.terasology.behaviors.system;
///import ...
/*
 * Listens for damage events and responds according to the group behavior desired
 */
@RegisterSystem(RegisterMode.AUTHORITY)
public class GroupBehaviorsEventSystem extends BaseComponentSystem {

    @In
    private Time time;
    @In
    private EntityManager entityManager;

    @ReceiveEvent(components = GroupFleeOnHitComponent.class)
    public void onDamage(OnDamagedEvent event, EntityRef entity) {

        //Get all entities belonging to the 'wisedeer' group:
        for (EntityRef entityRef : entityManager.getEntitiesWith(GroupTagComponent.class)) {
            if (entityRef.getComponent(GroupTagComponent.class).groups.contains("wisedeer")) {
                // Make entity flee
                FleeingComponent fleeingComponent = new FleeingComponent();
                fleeingComponent.instigator = event.getInstigator();
                fleeingComponent.minDistance = entityRef.getComponent(GroupFleeOnHitComponent.class).minDistance;
                entityRef.saveComponent(fleeingComponent);

                // Increase speed by multiplier factor
                CharacterMovementComponent characterMovementComponent = entityRef.getComponent(CharacterMovementComponent.class);
                characterMovementComponent.speedMultiplier = entityRef.getComponent(GroupFleeOnHitComponent.class).speedMultiplier;
                entityRef.saveComponent(characterMovementComponent);
            }
        }
    }
}

Observe that there is one difference in our system when compared to the original BehaviorsEventSystem class: it listens to damage events according to the existence of our new component, and then assigns a new Fleeing component to each of the entities belonging to the group.

Conclusion

In this tutorial, we showed how to reuse creatures and behaviors from other modules to create your own group of creatures. Learning about all the different mechanisms in Terasology can be challenging, especially if this is your first time working with this kind of architecture. There are many other different manners of using behaviors and groups; besides the links and modules used here, we recommend that you check the WildAnimalsMadness module.