Creating a Fully Functional Enemy Behavior using RAIN AI Engine
A non-player character (NPC), sometimes known as a non-person character or non-playable character, in a game is any character that is not controlled by a player. In games, this usually means a character controlled by the computer through artificial intelligence.
An enemy that pursues our hero character and acts independently is considered a NPC.
Any extra characters that have some default behavior would also be called an NPC.
e.g. A character that simply provides directions or assistance
You can create a simple (or complex) state machine for a character so that it moves in the scene with some default behavior but creating such behavior with an AI engine such as RAIN's AI is simply fun.
In our sample project we will create a simple enemy NPC that:
a) Runs after our hero character when it detects that our hero is near.
b) Attacks our hero when it gets closer
c) Dies when it loses it's health
d) Kills our hero
e) Returns back to wandering around in our environment
You can access the full project here
You can use this project as a building block for more interesting and entertaining behavior.
Download RAIN From here:
http://rivaltheory.com/rain/download/
Open the project and you can run some of the samples provided with the download.
In my projects I've imported a few free characters.
MAX: https://www.assetstore.unity3d.com/en/#!/content/3012
Import Max into the scene and add an AI module to it as shown below.
Once the Rain AI module has been added to our character Max, you will see it in the hierarchy as shown below.
This AI Module has a lot of fun capabilities:
> Local Variables
> Sensors (Audio, Visual)
> Behavior Tree Editor
> Navigation Mesh
> Mecanim & Legacy Animation Support
For our exercise, we will use as many capabilities as possible to get the full AI experience.
CREATING PLAYER HEALTH VARIABLE
Tracking player's health can be achieved simply by creating a local variable called "myHealth" of type int.
Set a default value = 10
We will use this variable later on to determine what animation the player should be playing.
CREATE AI STATES FROM LEGACY ANIMATION
The Max character that we imported earlier has many animations that we will be using once it meets certain conditions.
The RAIN AI Engine requires you to simple map each animation class with a state.
All you need to do is map each animation clip with a state name.
e.g. "idle" animation clip has been mapped to a "idle" state, and so on.
Notice you also need to select the wrap mode.
For "idle" -> it is set to Loop
For "punch" -> it is set to Default
Once you have these states imported you can start to use them in your behavior tree.
CREATING A NAVIGATION MESH
Go to the new RAIN menu and select "Create Navigation Mesh" as shown below.
It will appear as a standalone component in the hierarchy as shown below:
Simply ensure that it covers the borders of the scene that you want it to cover.
Once the navigation mesh cover the plane that you want it to cover go ahead and generate the navigation mesh by clicking on the "Generate Navigation Mesh" button in the navigator.
That's it.
Your mesh is ready to use.
You won't see any change just yet we still need to develop the character's route, which we will do in the next step as waypoints for the player to move around.
CREATING WAYPOINT ROUTE FOR NPC CHARACTER
Our NPC enemy character can simply roam around the world while waiting for our player to enter it's visible range.
You can simply create a waypoint route by selecting the option at the top.
Once you have selected the waypoint route option engine you can drop points for the route as shown below.
You will be able to view the waypoints on the right in the Inspector view.
That's it.
Now your character will walk around in circles if you simply run the game.
The player won't be walking though, it would be floating.
We will make it walk soon :)
Creating Sensors
The two main decisions that we need to do at the very beginning are:
a) detect if very close :-> Used to determine if our enemy character is close to our player character
b) detect if nearby :-> Used to determine if our enemy character is somewhat near our player character
The behaviors depend on Sensors that we can create for the colliders. Sensors are nothing more than simple colliders that can be applied on the characters.
Let's create two sensors:
i) nearbySensor: To detect when a character is nearby, so that our character can start to chase (run towards) our player.
ii) closeSensor: To detect when the character is very close, so that our character can start running towards it
The nearbySensor and closeSensor will act as visual sensors for our Max character.
Next we will use information from these sensors in our Behavior Tree.
Creating local variables from Sensors
Create a new behavior tree and then create a Parallel node.
Create -> Decisions -> Parallel
Right click in the Parallel Node and create 2 Detect nodes under it:
Create -> Action -> Detect
We will use the first detect node for when the player character is very close.
In our current example, we are calling our Sensor = "closeSensor"
And our Aspect = "heroAspect"
Finally our Form Variable = "varHeroClose".
This means that whenever our "close" sensor detects the "heroAspect" is nearby, it will set the variable "varHeroClose" != null.
We will be using this information later on in making behavior decisions for the enemy character using the condition:
"varHeroClose" = null
OR
"varHeroClose" != null
The next detector nodes will be used to determine if our player is nearby.
This is achieved with the help of the Sensor: "nearbySensor"
Again with the same aspect: "heroAspect"
Finally our Form Variable = "varHeroNearby" is used to save this value.
We will be using this information later on to make behavior decisions for the enemy character.
Next make a "Selector" node under the Parallel root node.
This selector node will be used to run our main logic loop.
Let's see it in action.
Under this main selector node, go ahead and add some constraints.
Constraints will act as a logic step and guide the character to take the appropriate action.
(aka "if-condition" if you are a programmer)
You will want to create 3 constraints:
a) hero not found
b) hero found
c) health zero
The selector will run through these constraint conditions many times and determine when the condition is met and execute the code under it.
MAKING OUR CHARACTER PATROL THE AREA
Our character is now ready to receive instructions from us on what it needs to do when it is executing a simple "hero not found" constraint.
Apply the constraint:
varHeroNearby == null && myHealth >0
We want our player to walk around in circles using the waypoint map that we created earlier.
This can achieved with the following steps.
a) Add a Parallel node under the constraint: we will use this to make the character move along the waypoint & apply the walk animation to it.
b) Add a waypoint Patrol node: so that our character knows where the next waypoint node is. This node sets a new transform value in the variable "nextStop" once our character reaches the previous waypoint.
c) Add a Move-to-waypoint: This moves the character to the "nextStop" waypoint which keeps changing as our character reaches each waypoint.
4) Add an animation node: This will ensure that our character perform the animate action while it is moving towards the next waypoint.
Now if you run the app it would simply make our player patrol along the waypoints and if you looked at the behavior tree it would display it like this.
MAKING OUR CHARACTER CHASE OUR PLAYER
Here's another constraint that is created with the logic for when the hero is found. This is achieved by implementing the constraint:
myHealth > 0 && varHeroNearby != null
Once our hero is found within the enemy's sensor range we want to make it follow our player and then attacks it when it is very close to it.
Here are the steps we will follow:
a) Create a selector node
This selector node will be used to run two condition: either the character is very close or the character was just detected.
b) Constraint: hero not close:
In this case, the condition to check is varHeroClose == null
This tells us that the big sensor detected that our player is nearby, but not within striking close distance.
c) Constraint: hero close
In this case, the condition to check is varHeroClose != null
This tells us that the small sensor detected that our player is within striking distance.
d) Run animation
Once we know that our player is not close, we want to run towards our player.
This is achieved by adding a Run animation to our player
e) Move-to-player
Next we move our enemy towards our player.
This is achieved with the move-to-player animation.
MAKING OUR CHARACTER DIE
Finally the 3rd constraint is implemented, when the health of our character is zero.
myHealth <= 0
Create a sequencer node under this constraint:
Create -> Decisions -> Sequencer
This will allow us to run multiple instructions when a player dies.
Under the sequencer node, let's create an "Animate" node.
Next set the set Animation state = "Death"
Next let's add a Yield state and 2-second timer wait function as shown below.
This will allow other animations to catch up and also will allow us to complete the death animation successfully.
The last action in this sequence is an action sequence.
This will simply allow the character to self-destruct.
Here's how you can make a "Custom Action".
Under Class, select the option to create a new custom class, and select CSharp as the language.
Name the new Action class to "EnemyCustomActionTest".
Here's the code for the class.
using UnityEngine; using System.Collections; using System.Collections.Generic; using RAIN.Core; using RAIN.Action; [RAINAction] public class EnemyCustomActionTest : RAINAction { public override void Start(AI ai) { base.Start(ai); } public override ActionResult Execute(AI ai) { MonoBehaviour.Destroy(ai.Body); return ActionResult.SUCCESS; } public override void Stop(AI ai) { base.Stop(ai); } }v
That's it.
If you run the game now, and set the myHealth variable to 0 in the AI viewer, you will find that the character simply goes thru the 4 sequenced actions as is outlined above.
CREATING BORDER FOR PLAYER'S HEAD
Create a box that fits on the player's head.
Assign to be a child object of the player gameobject.
Mark
~Is Trigger = YES
Leave the MeshRenderer checkbox=true
Later on you can disable the the MeshRenderer and the behavior will still work.
CREATING BORDER FOR ENEMY'S HAND
Create a 3D cube and attach that cube to the hand of our enemy character, "MAX"
Let's call this "HandCube"
Add a Rigidbody component to the cube.
Set the following flags:
~ Is Kinematic = YES
~ IsTrigger = NO
CREATING ATTACK SCRIPT FOR ENEMY'S HAND
Create the following script and attach it to the player's handcube object.
MaxBehavior.js
#pragma strict function Start () { } function Update () { } function OnTriggerEnter (other : Collider) { print ("trigger enter event"); if(other.gameObject.tag == "PlayerHead") { print ("killing parent of collider object"); Destroy(other.transform.parent.gameObject); } }
Here's the final result.
This completes our intro to RivalTheory's RAIN AI Engine. Hopefully, the tutorial will help you build you more engaging games and relatable characters. Leave some comments. Let me know what else you would like to learn about.















