Book Image

Unity 5.x Game AI Programming Cookbook

By : Jorge Palacios
5 (1)
Book Image

Unity 5.x Game AI Programming Cookbook

5 (1)
By: Jorge Palacios

Overview of this book

Unity 5 comes fully packaged with a toolbox of powerful features to help game and app developers create and implement powerful game AI. Leveraging these tools via Unity’s API or built-in features allows limitless possibilities when it comes to creating your game’s worlds and characters. This practical Cookbook covers both essential and niche techniques to help you be able to do that and more. This Cookbook is engineered as your one-stop reference to take your game AI programming to the next level. Get to grips with the essential building blocks of working with an agent, programming movement and navigation in a game environment, and improving your agent's decision making and coordination mechanisms - all through hands-on examples using easily customizable techniques. Discover how to emulate vision and hearing capabilities for your agent, for natural and humanlike AI behaviour, and improve them with the help of graphs. Empower your AI with decision-making functions through programming simple board games such as Tic-Tac-Toe and Checkers, and orchestrate agent coordination to get your AIs working together as one.
Table of Contents (15 chapters)
Unity 5.x Game AI Programming Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Creating a jump system


Imagine that we're developing a cool action game where the player is capable of escaping using cliffs and rooftops. In that case, the enemies need to be able to chase the player and be smart enough to discern whether to take the jump and gauge how to do it.

Getting ready

We need to create a basic matching-velocity algorithm and the notion of jump pads and landing pads in order to emulate a velocity math so that we can reach them.

Also, the agents must have the tag Agent, the main object must have a Collider component marked as trigger. Depending on your game, the agent or the pads will need the Rigidbody component attached.

The following is the code for the VelocityMatch behavior:

using UnityEngine;
using System.Collections;

public class VelocityMatch : AgentBehaviour {
    
    public float timeToTarget = 0.1f;

    public override Steering GetSteering()
    {
        Steering steering = new Steering();
        steering.linear = target.GetComponent<Agent>().velocity - agent.velocity;
        steering.linear /= timeToTarget;
        if (steering.linear.magnitude > agent.maxAccel)
            steering.linear = steering.linear.normalized * agent.maxAccel;

        steering.angular = 0.0f;
        return steering;
    }
}

Also, it's important to create a data type called JumpPoint:

using UnityEngine;

public class JumpPoint
{
    public Vector3 jumpLocation;
    public Vector3 landingLocation;
    //The change in position from jump to landing
    public Vector3 deltaPosition;

    public JumpPoint ()
        : this (Vector3.zero, Vector3.zero)
    {
    }

    public JumpPoint(Vector3 a, Vector3 b)
    {
        this.jumpLocation = a;
        this.landingLocation = b;
        this.deltaPosition = this.landingLocation - this.jumpLocation;
    }
}

How to do it...

We will learn how to implement the Jump behavior:

  1. Create the Jump class deriving from VelocityMatch, with its member variables:

    using UnityEngine;
    using System.Collections.Generic;
    
    public class Jump : VelocityMatch
    {
        public JumpPoint jumpPoint;
        //Keeps track of whether the jump is achievable
        bool canAchieve = false;
        //Holds the maximum vertical jump velocity
        public float maxYVelocity;
        public Vector3 gravity = new Vector3(0, -9.8f, 0);
        private Projectile projectile;
        private List<AgentBehaviour> behaviours;
        
        // next steps
    }
  2. Implement the Isolate method. It disables all the agent behaviors, except for the Jump component:

    public void Isolate(bool state)
    {
        foreach (AgentBehaviour b in behaviours)
            b.enabled = !state;
        this.enabled = state;
    }
  3. Define the function for calling the jumping effect, using the projectile behavior we learned before:

    public void DoJump()
    {
        projectile.enabled = true;
        Vector3 direction;
        direction = Projectile.GetFireDirection(jumpPoint.jumpLocation, jumpPoint.landingLocation, agent.maxSpeed);
        projectile.Set(jumpPoint.jumpLocation, direction, agent.maxSpeed, false);
    }
  4. Implement the member function for setting up the behaviors' target for matching its velocity:

    protected void CalculateTarget()
    {
        target = new GameObject();
        target.AddComponent<Agent>();
    
        //Calculate the first jump time
        float sqrtTerm = Mathf.Sqrt(2f * gravity.y * jumpPoint.deltaPosition.y + maxYVelocity * agent.maxSpeed);
        float time = (maxYVelocity - sqrtTerm) / gravity.y;
    
        //Check if we can use it, otherwise try the other time
        if (!CheckJumpTime(time))
        {
            time = (maxYVelocity + sqrtTerm) / gravity.y;
        }
    }
  5. Implement the function for computing the time:

    //Private helper method for the CalculateTarget function
    private bool CheckJumpTime(float time)
    {
        //Calculate the planar speed
        float vx = jumpPoint.deltaPosition.x / time;
        float vz = jumpPoint.deltaPosition.z / time;
        float speedSq = vx * vx + vz * vz;
    
        //Check it to see if we have a valid solution
        if (speedSq < agent.maxSpeed * agent.maxSpeed)
        {
            target.GetComponent<Agent>().velocity = new Vector3(vx, 0f, vz);
            canAchieve = true;
            return true;
        }
        return false;
    }
  6. Override the Awake member function. The most important thing here is caching the references to other attached behaviors, so Isolate function makes sense:

    public override void Awake()
    {
        base.Awake();
        this.enabled = false;
        projectile = gameObject.AddComponent<Projectile>();
        behaviours = new List<AgentBehaviour>();
        AgentBehaviour[] abs;
        abs = gameObject.GetComponents<AgentBehaviour>();
        foreach (AgentBehaviour b in abs)
        {
            if (b == this)
                continue;
            behaviours.Add(b);
        }
    }
  7. Override the GetSteering member function:

    public override Steering GetSteering()
    {
        Steering steering = new Steering();
        
        // Check if we have a trajectory, and create one if not.
        if (jumpPoint != null && target == null)
        {
            CalculateTarget();
        }
        //Check if the trajectory is zero. If not, we have no acceleration.
        if (!canAchieve)
        {
            return steering;
        }
    
        //Check if we've hit the jump point
        if (Mathf.Approximately((transform.position - target.transform.position).magnitude, 0f) &&
            Mathf.Approximately((agent.velocity - target.GetComponent<Agent>().velocity).magnitude, 0f))
        {
            DoJump();
            return steering;
        }
        return base.GetSteering();
    }

How it works...

The algorithm takes into account the agent's velocity and calculates whether it can reach the landing pad or not. The behavior's target is the one responsible for executing the jump, and if it judges that the agent can, it tries to match the targets' vertical velocity while seeking the landing pad's position.

There is more

We will need a jump pad and a landing pad in order to have a complete jumping system. Both the jump and landing pads need the Collider component marked as trigger. Also, as stated before, they will probably need to have a Rigidbody component, too, as seen in the image below.

The pads we will need a MonoBehaviour script attached as explained below.

The following code is to be attached to the jump pad:

using UnityEngine;

public class JumpLocation : MonoBehaviour
{
    public LandingLocation landingLocation;

    public void OnTriggerEnter(Collider other)
    {
        if (!other.gameObject.CompareTag("Agent"))
            return;
        Agent agent = other.GetComponent<Agent>();
        Jump jump = other.GetComponent<Jump>();
        if (agent == null || jump == null)
            return;
        Vector3 originPos = transform.position;
        Vector3 targetPos = landingLocation.transform.position;
        jump.Isolate(true);
        jump.jumpPoint = new JumpPoint(originPos, targetPos);
        jump.DoJump();
    }
}

The following code is to be attached to the landing pad:

using UnityEngine;

public class LandingLocation : MonoBehaviour
{
    public void OnTriggerEnter(Collider other)
    {
        if (!other.gameObject.CompareTag("Agent"))
            return;
        Agent agent = other.GetComponent<Agent>();
        Jump jump = other.GetComponent<Jump>();
        if (agent == null || jump == null)
            return;
        jump.Isolate(false);
        jump.jumpPoint = null;
    }
}

See Also

The Shooting a projectile recipe