Writing a New Conditional Task

This topic is divided into two parts. The first part describes writing a new conditional task, and the second part (available here) describes writing a new action task. The conditional task will determine if any objects are within sight and the action class will towards the object that is within sight. We will also be using variables for both of these tasks. We have also recorded a video on this topic and it is available on YouTube.

The first task that we will write is the Within Sight task. Since this task will not be changing game state and is just checking the status of the game this task will be derived from the Conditional task. Make sure you have the BehaviorDesigner.Runtime.Tasks namespace included:

using UnityEngine;
using BehaviorDesigner.Runtime.Tasks;

public class WithinSight : Conditional
{
}

We now need to create three public variables and one private variable:

using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class WithinSight : Conditional
{
   public float fieldOfViewAngle;
   public string targetTag;
   public SharedTransform target;

   private Transform[] possibleTargets;
}

The field of view angle is the field of view that the object can see. Target tag is the tag of the targets that the object can move towards. Target is a Shared Variable which will be used by both the Within Sight and the Move Towards tasks. If you are using Shared Variables make sure you include the BehaviorDesigner.Runtime namespace. The final variable, possible targets, is a cache of all of the Transforms with the target tag. If you take a look at the task API, you can see that we can create that cache within the the OnAwake or OnStart method. Since the list of possible transforms are not going to be changing as the Within Sight task is enabled/disabled we are going to do the caching within OnAwake:

   public override void OnAwake()
   {
      var targets = GameObject.FindGameObjectsWithTag(targetTag);
      possibleTargets = new Transform[targets.Length];
      for (int i = 0; i < targets.Length; ++i) {
         possibleTargets[i] = targets[i].transform;
      }
   }

This OnAwake method will find all of the GameObjects with the target tag, then loop through them caching their transform in the possible targets array. The possible targets array is then used by the overridden OnUpdate method:

   public override TaskStatus OnUpdate()
   {
      for (int i = 0; i < possibleTargets.Length; ++i) {
         if (WithinSight(possibleTargets[i], fieldOfViewAngle)) {
            target.Value = possibleTargets[i];
            return TaskStatus.Success;
         }
      }
      return TaskStatus.Failure;
   }

Every time the task is updated it checks to see if any of the possible targets are within sight. If one target is within sight it will set the target value and return success. Setting this target value is key as this allows the Move Towards task to know what direction to move in. If there are no targets within sight then the task will return failure. The last part of this task is the WithinSight method:

   public bool WithinSight(Transform targetTransform, float fieldOfViewAngle)
   {
      Vector3 direction = targetTransform.position - transform.position;
      return Vector3.Angle(direction, transform.forward) < fieldOfViewAngle;
   }

This method first gets a direction vector between the current transform and the target transform. It will then compute the angle between the direction vector and the current forward vector to determine the angle. If that angle is less then field of view angle then theĀ target transform is within sight of the current Transform.

That’s it for the Within Sight task. Here’s what the full task looks like:

using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class WithinSight : Conditional
{
   // How wide of an angle the object can see
   public float fieldOfViewAngle;
   // The tag of the targets
   public string targetTag;
   // Set the target variable when a target has been found so the subsequent tasks know which object is the target
   public SharedTransform target;

   // A cache of all of the possible targets
   private Transform[] possibleTargets;

   public override void OnAwake()
   {
      // Cache all of the transforms that have a tag of targetTag
      var targets = GameObject.FindGameObjectsWithTag(targetTag);
      possibleTargets = new Transform[targets.Length];
      for (int i = 0; i < targets.Length; ++i) {
         possibleTargets[i] = targets[i].transform;
      }
   }

   public override TaskStatus OnUpdate()
   {
      // Return success if a target is within sight
      for (int i = 0; i < possibleTargets.Length; ++i) {
         if (WithinSight(possibleTargets[i], fieldOfViewAngle)) {
            // Set the target so other tasks will know which transform is within sight
            target.Value = possibleTargets[i];
            return TaskStatus.Success;
         }
      }
      return TaskStatus.Failure;
   }

   // Returns true if targetTransform is within sight of current transform
   public bool WithinSight(Transform targetTransform, float fieldOfViewAngle)
   {
      Vector3 direction = targetTransform.position - transform.position;
      // An object is within sight if the angle is less than field of view
      return Vector3.Angle(direction, transform.forward) < fieldOfViewAngle;
   }
}

Continue to the second part of this topic, writing the Move Towards task.