How to make AI sentient in Unity, Part I
In the first of two parts miniseries about AI senses, I'm going to guide you through an implementation of eyes and ears for NPCs, that takes advantage of inheritance and UnityEvents. In the subsequent part, I'll show you how to create some reactive behaviors using the state pattern.
Marian Pekár
Read more posts by this author.
Marian Pekár

Today, I’d like to show you one way to grant sentience to your AI agent in Unity so it can T-800 the world.
Ok, not really. However, I’m going to guide you through an implementation of eyes and ears you can use for your NPCs, so they will be able to react when they see or hear a specific object in a scene, which is also pretty cool, right? 🙂
In this Part I, we’re going to see the base class Sense, and Eyes and Ears that inherit from it. We’re also going to see how to take advantage of UnityAction to keep the implementations of senses and reactions nicely decoupled.
In the subsequent and final Part II, we’re going to dive into the implementation of some actual reactive AI behavior and find out how to harness the power of a simple State pattern, which is enough if we don’t plan any complex AI.
If you do plan to have a game with complex AI behavior, look for behavior trees, Goal Oriented Action Planning (GOAP), or Hierarchical Task Network (HTN).
I assume you have at least a basic understanding of Unity engine and C# programming language, though I tried to make this tutorial reasonably beginner-friendly. Feedback is always welcomed.
Before we’ll continue, I encourage you to get the final example from GitHub and open it in Unity 2020.3.17f1, so you can see the parts of code I’ll be describing in the context.
So if you’re ready, without further ado, let’s get started .
Detectable and PlayerController components
First, we don’t want our AI to uncontrollably react on every GameObject in the scene, so we need to mark only the Player as detectable.
It can be achieved with Tags, for instance, but marking objects by adding a custom component gives us a possibility to store and pass some useful data.
In this example, it’s the flag CanBeHear that is set by PlayerController only when a player is moving and unset when the player stays still. You can see in the hierarchy that both Detectable and PlayerController are attached to the Player object.
Detectable is a component that marks object as detectable and provides data related to detection.
As for the PlayerController, I’m not going to describe its implementation in much detail, it’s very basic and not what we’re focusing on in this post.
But notice how we set detectable.CanBeHear to true only if the verticalAxis is bigger than zero, in other words, only when the player is moving.
This player controller depends on the Detectable component, so it’s a good practice to decorate the class with the RequireComponent attribute, just as you can see on the first line below.
If you add in the Inspector to a GameObject a component with RequireComponent attributes, all components that are required will be added automatically. It also prevents you from accidentally removing components that other components depend on.
The base class Senses
Before we’ll be talking about Eyes and Ears, let’s point out that although they need to have a slightly different implementation, they both can share a fair bit of common logic.
UML class diagram shows member variables and methods of classes and relations among them.
That’s a good place for using inheritance. You might have heard that you should always favor composition over inheritance and that some modern languages like Go don’t even have the inheritance.
But the short-sighted conclusion «inheritance is bad» is just wrong. Inheritance represents is a relationship like a cat is an animal, or in our case, eyes and ears are senses, while composition represents has a relationship, like a car has an engine, or in Unity, a GameObject can have MeshRenderer, Rigidbody, Collider, and many other components.
In fact, in relationship with GameObject, our Eyes and Ears are also components and Sense is a MonoBehavior, which makes Eyes and Ears MonoBehaviors as well.
Both inheritance and composition are useful concepts of OOP and both are often used together. When used right they are very powerful when misused they both can lead to an unmanageable mess. But let’s get back to the original topic.
In the Sense class code below, notice how we have no logic in the protected virtual HasDetected method. Because we won’t be attaching to any GameObject the base class itself, but its child classes Eyes and Ears which overwrites this method with their own logic as we’ll soon see.
The usefulness of the IsDetectionContinuous flag we’ll see in Part II in context with behavior. For now, just note that on Ears it’s set to true and on Eyes, it’s set to false. Ears and Eyes with respective components are in the scene hierarchy child objects of the Enemy.
The Ears component
Games are a lot about simplification and this is how ears can work in a simplified game world.
With Sense as a base class, the Ears class derived from it is actually pretty simple, all it needs is to provide overwrite for HasDetected method where true is returned when detectable is within the Distance and its flag CanBeHear is set.
With Distance function provided by UnityEngine.Vector3 struct, our implementation of detection for ears is just a single line of code.
The Eyes component
The idea behind the Eyes component is also very simple. Apart from Distance we just need FOV and also test if detectable is occluded.
As you probably already guess, the implementation of the Eyes class is not a one-liner. However, it’s still quite simple. The detectable zone just needs to be determined not only by distance but also by field of view.
When a detectable object is inside the zone, the Eyes component needs to perform just one extra test to find out whether the object is occluded or not.
When you have compound conditions like this with && between them, sort them so they start from left with the simplest one. This way you’ll save some cycles, because, since the first false condition, the others don’t matter and won’t be executed.
As you can see, HasDetected method is still a one-liner, but both IsInVisibleArea and IsNotOcclued methods need to be implemented by us.
In the IsInVisibleArea method, we have the same test as in the Ears component to find out, whether a detectable is in the visible distance and we also test if the detectable is inside the field of view.
To figure it out, we need direction between detectable and eyes, which we get simply by vector subtraction. Then we calculate the dot product between normalized direction and forward vector of Eyes (which is also a unit vector).
Finding direction between two points using vector subtraction.
Finally, we compare the dot product with FieldOfViewDot, which we set in the Start method as one minus half of the FieldOfView remapped from the original range to a range from 0 to 1. If it’s bigger or equal, detectable is inside the field of view.
Dot products of various unit vectors (vectors of length 1). It goes from 1 to -1 as the angle changes from 0 to 180 degrees, then it goes back from -1 to 1 between 180 and 360 degree, which makes sense, since you can think about dot product as «how much one vector projects to another».
The dot product is particularly useful in this case. Since we don’t need to care whether the detectable is on the left or on the right side, there’s no need to calculate the actual angle between two vectors.
Of course, we can set FieldOfView directly in the range between 0 to 1, get rid of FieldOfViewDot and cut out the value remapping, but it’s much more intuitive to think about FOV in terms of angles.
Exposing values in human-friendly units while internally using different ones for better performance is a common practice often see also with angles and radians.
Unity have us covered here, once again with Vector3.Distance, and also with Vector3.Dot methods. There’s also Vector3.Angle method for cases when dot product is not enough. That’s nice, even though the math behind it is relatively basic. I’m including it here, as a little bonus:
The cosine of the angle between two vectors can be found by dividing their dot product by product of their magnitudes. To get the angle we use arccos, the inverse function of cosine.
As mentioned before, in the last test Eyes component check if detectable is occluded. This is implemented in the IsNotOccluded method with Unity’s Physics.Raycast.
X-Ray vision, invisibility, glass, more detectable objects, etc.
Before we wrap up Part I, let’s briefly talk about some edge-cases, limitations, and extra features.
To tackle invisibility, you can add another member next to CanBeHear in the Detectable component and in the the Eyes component perform a similar check as you’ve seen in Ears component.
For X-Ray vision, the simplest approach would be to add a Boolean variable to the Eyes component, something like HasXRay, and skip IsNotOccluded method call when the flag is set.
If you have transparent objects in your scene, like glass doors or windows, you can simply add them to Ignore Raycast layer.
Objects in the Ignore Raycast layer are ignored by Physics Raycast by default.
Having more than one detectable object would be a bit tricky, but one reasonable approach would be, instead of adding complexity to senses, creating another layer that would provide detectable from a pool of detectables on runtime. This way, there will be still only one detectable associated with a set of senses at a time and implementation of senses would stay intact.
That’s it for today. I hope you’ve enjoyed it. If so, stay tuned for Part II, where we’re going to dissect the implementation of an AI controller and behaviors like patrol between points, chase player, and investigate location.
Make FPS With Enemy AI in Unity
First-Person Shooter (FPS) is a subgenre of shooter games where the player is controlled from a first-person perspective.
To make an FPS game in Unity we will need a player controller, an array of items (weapons in this case), and the enemies.
Step 1: Create the Player Controller
Here we will create a controller that will be used by our player.
- Create a new Game Object (Game Object -> Create Empty) and name it «Player»
- Create new Capsule (Game Object -> 3D Object -> Capsule) and move it inside «Player» Object
- Remove Capsule Collider component from Capsule and change its position to (0, 1, 0)
- Move the Main Camera inside «Player» Object and change its position to (0, 1.64, 0)
- Create a new script, name it «SC_CharacterController» and paste the code below inside it:
SC_CharacterController.cs
- Attach SC_CharacterController script to «Player» Object (You will notice that it also added another component called Character Controller, change its center value to (0, 1, 0))
- Assign the Main Camera to the Player Camera variable in SC_CharacterController
The Player controller is now ready:

Step 2: Create the Weapon System
The player weapon system will consist of 3 components: a Weapon manager, a Weapon script, and a Bullet script.
- Create a new script, name it «SC_WeaponManager» and paste the code below inside it:
SC_WeaponManager.cs
- Create a new script, name it «SC_Weapon» and paste the code below inside it:
SC_Weapon.cs
- Create a new script, name it «SC_Bullet» and paste the code below inside it:
SC_Bullet.cs
Now, you will notice that SC_Bullet script has some errors. That’s because we have one last thing to do, which is to define IEntity interface.
Interfaces in C# are useful for when you need to make sure that the script which uses it, has certain methods implemented.
The IEntity interface will have one method which is ApplyDamage, that’s later will be used to inflict damage to enemies and our player.
- Create a new script, name it «SC_InterfaceManager» and paste the code below inside it:
SC_InterfaceManager.cs
Setting Up a Weapon Manager
Weapon manager is an Object that will reside under the Main Camera Object and will contain all the weapons.
- Create a new GameObject and name it «WeaponManager»
- Move the WeaponManager inside the Player Main Camera and change its position to (0, 0, 0)
- Attach SC_WeaponManager script to «WeaponManager»
- Assign the Main Camera to the Player Camera variable in SC_WeaponManager
Setting Up a Rifle
- Drag and drop your gun model into the scene (or simply create a Cube and stretch it if you do not have a model yet).
- Scale the model so its size is relative to a Player Capsule
In my case I will be using a custom-made Rifle model (BERGARA BA13):

- Create a new GameObject and name it «Rifle» then move the rifle model inside it
- Move the «Rifle» Object inside the «WeaponManager» Object and place it in front of the Camera like this:

To fix the object clipping, simply change the Camera’s near clipping plane to something smaller (in my case I set it to 0.15):

- Attach SC_Weapon script to a Rifle Object (You will notice that it also added an Audio Source component, this is needed to play the fire and reload audios).
As you can see, SC_Weapon has 4 variables to assign. You can assign Fire audio and Reload audio variables right away if you have suitable Audio Clips in your project.
The Bullet Prefab variable will be explained later in this tutorial.
For now, we will just assign the Fire point variable:
- Create a new GameObject, rename it to «FirePoint» and move it inside Rifle Object. Place it right in front of the barrel or slightly inside, like this:

- Assign FirePoint Transform to a Fire point variable at SC_Weapon
- Assign Rifle to a Secondary Weapon variable in SC_WeaponManager script
Setting Up a Submachinegun
- Duplicate the Rifle Object and rename it to Submachinegun
- Replace the gun model inside it with a different model (In my case I will use the custom-made model of TAVOR X95)

- Move Fire Point transform till it fits the new model

- Assign Submachinegun to a Primary Weapon variable in SC_WeaponManager script

Setting Up a Bullet Prefab
Bullet prefab will be spawned according to a Weapon’s fire rate and will use Raycast to detect whether it hit something and inflict damage.
- Create a new GameObject and name it «Bullet»
- Add Trail Renderer component to it and change its Time variable to 0.1.
- Set the Width curve to a lower value (ex. Start 0.1 end 0), to add a trail that pointy look
- Create new Material and name it bullet_trail_material and change its Shader to Particles/Additive
- Assign a newly created material to a Trail Renderer
- Change the Color of Trail Renderer to something different (ex. Start: Bright Orange End: Darker Orange)

- Save the Bullet Object to Prefab and delete it from the Scene.
- Assign a newly created Prefab (drag & drop from the Project view) to Rifle and Submachinegun Bullet Prefab variable


The weapons are now ready.
Step 3: Create the Enemy AI
The enemies will be simple Cubes that follow the Player and attack once they are close enough. They will attack in waves, with each wave having more enemies to eliminate.
Setting Up Enemy AI
Below I have created 2 variations of the Cube (The Left one is for the alive instance and the Right one will be spawned once the enemy is killed):

- Add a Rigidbody component to both dead and alive instances
- Save the Dead Instance to Prefab and delete it from Scene.
Now, the alive instance will need a couple more components to be able to navigate the game level and inflict damage to the Player.
- Create a new script and name it «SC_NPCEnemy» then paste the code below inside it:
SC_NPCEnemy.cs
- Create a new script, name it «SC_EnemySpawner» then paste the code below inside it:
SC_EnemySpawner.cs
- Create a new script, name it «SC_DamageReceiver» then paste the code below inside it:
SC_DamageReceiver.cs
- Attach SC_NPCEnemy script to alive enemy instance (You’ll notice it added another component called NavMesh Agent, which is needed to navigate the NavMesh)
- Assign the recently created dead instance prefab to the Npc Dead Prefab variable
- For the Fire Point, create a new GameObject, move it inside the alive enemy instance and place it slightly in front of the instance, then assign it to the Fire Point variable:

- Finally, Save the alive instance to Prefab and delete it from Scene.
Setting Up Enemy Spawner
Now let’s move to SC_EnemySpawner. This script will spawn enemies in waves and also will show some UI information on the screen, such as Player HP, current Ammo, how many Enemies are left in a current wave, etc.
- Create a new GameObject and name it «_EnemySpawner»
- Attach SC_EnemySpawner script to it
- Assign the newly created enemy AI to the Enemy Prefab variable
- Assign the texture below to the Crosshair Texture variable

- Create a couple of new GameObjects and place them around the Scene then assign them to the Spawn Points array
You’ll notice that there is one last variable left to assign which is the Player variable.
- Attach SC_DamageReceiver script to a Player instance
- Change Player instance tag to «Player»
- Assign Player Controller and Weapon Manager variables in SC_DamageReceiver

- Assign Player instance to a Player variable in SC_EnemySpawner

And lastly, we have to bake the NavMesh in our scene so the enemy AI will be able to navigate.
Also, don’t forget to mark every static Object in Scene as Navigation Static before baking NavMesh:
Learn To Create Enemy AI Systems With A Few Lines Of Code In Unity Game Engine
There are different types of enemy AI that you can create in Unity, from the very basic enemies that move between two points all the way to machine learning where your enemies are learning from the events in the game and behaving accordingly.
In this post we are going to learn about AI in Unity by creating basic and intermediate enemy AI behaviour.
Download Assets And Complete Project For This Tutorial

Unity Enemy AI Tutorial
Related products
Create A Parasite Platformer Game In Unreal Engine – Complete Project And Assets
Create A Side Scroller C++ Game In Unreal Engine
Unreal Engine Enemy AI C++ And Blueprints Tutorial
Enemy_AI_With_Behavior_Trees_Project.zip
Important Information Before We Start
One of the labels for this tutorial is beginner, however this is not a tutorial for complete beginners.
I expect you to know how to create basic games in Unity, but you are a beginner when it comes to AI programming in Unity. So it is mandatory that you know how to code in C# and how to use Unity and its interface.
If you don’t know any of these things, you can learn how to code in C# in my C# tutorial series starting with variables, and then you can move on to create your first game in Unity with my Rainy Knifes tutorial.
Starting With Basic Enemy AI — Shooting



Going back in the SpiderShooter script, we are going to create the shooting functionality by adding the following lines of code:
The Shoot function simply uses the Instantiate function to create a new copy out of the spiderBullet game object. It will spawn it at the bulletSpawnPos variable position, and it will set the rotation values to 0 for X, Y, and Z using Quaternion.identity.
Inside the Update we will create a timer that will shoot the bullet every X seconds:
As you can see, after every X amount of seconds the spider is shooting the bullet.
If you don’t like the wait time between each shoot, you can change the values for min and max shoot wait time because we added SerializeField in their declaration which means we can change their values in the Inspector tab:

Optimizing Enemy AI Shooting — Object Pooling Technique
While the shooting functionality works, it can lead to our game being slow if we have too much shooting enemies in the game.
The reason for that is because we are using the Instantiate function which creates new objects every time, plus we are not disposing the bullets that we already created and this can lead to many game objects being in the game, not doing anything or having any functionality yet taking our game resources and this can make our game slower.
To fix this problem, we use a programming technique called pooling. The idea of pooling is to create a pool of objects, in our case a pool of bullets, and when we need a new bullet, we will reuse one of the bullets stored in the pool.
If by any chance all the bullets in the bullet are not available for use e.g. they are currently being used, then we will create a new bullet and store it in the pool and repeat the process.
To create this system, first we need to add new variables in the SpiderShooter script. Above the Start function add the following lines:
First we create a new list that will store game objects. A list is like an array, with the difference that a list is flexible, meaning we can add new and remove old elements from that list.
Between the <> we type the type of object we want to store in the list, in our case a GameObject. This can be modified in case we have a Bullet script for example, and we only want to store game objects that have the Bullet script attached on them, then, instead of typing:
First we create the newBullet variable and we don’t set a value for it, which means it is equal to null. We need the newBullet variable so that we can add the newly created bullet in the list.
Of course, since this is programming, there are always multiple ways how you can achieve a certain result. We can rewrite this code so that we don’t have to create the newBullet variable at all:
We can use Instantiate function as a parameter in the Add function from the list, because the Instantiate function returns the game object it has created, and the Add function of the list stores the object in the list.
To deactivate the newly created bullet, we can use i variable declared in the for loop and access the bullet we just created to deactivate it.
The reason why we deactivate the bullets as soon as we create them is because if don’t do that, they will be spawned in the level from the very start which is not something that we want.
You can test that by removing
from the code and see the outcome. Rewrite the Shoot function so that it uses the bullets from the bullets list instead of instantiating new ones:
We are going to use a while loop to loop through the list and search for a bullet that is not active in the scene e.g. it used SetActive function and passed false as the parameter.
Because of that, every time we call the Shoot function first we need to set the value of canShoot to true. We also set the value of bulletIndex to zero(0) because we will start searching from the first element in the list.
The activeInHierarchy property of the game object returns true if the game object is active in the scene e.g. game, and it returns false if the game object is not active in the scene.
You will notice that we used an exclamation mark in front of the activeInHierarchy property, and the exclamation mark will make what’s after it, the opposite, meaning if activeInHierarchy returns true, then the exclamation mark will make it the opposite which is false, and if activeInHierarchy returns false, the exclamation mark will make it the opposite which is true.
So essentially we are searching for a game object, in our case a bullet, that is NOT active in the hierarchy so that we can activate it and use it. And this is the whole point of pooling technique because we are reusing game objects instead of creating new ones which saves performance.
We are using the bulletIndex to access the specific index in the list, and since we set the starting value of bulletIndex to 0 on line 4, we will first test if the element at index 0 is not active in the hierarchy.
To activate a game object, we simply call SetActive and pass true as the parameter and it will make the game object active in the game again.
Since we are simulating the effect of a bullet, we need to reposition the bullet we just activated so that it falls from the bulletSpawnPos, and this will make it look like the spider is shooting new bullets.
When we finish with that, we need to set canShoot to false, so that we don’t spawn more than one bullet, and we use break to exit outside the while loop.
When the code reaches the break statement, it will simply stop executing the loop, and all the code that is below the break statement will not get executed.
In our case, since we are using the canShoot variable to control the while loop, we can remove the break statements, but I put them in the code for this example just to explain what they are doing and that you can use them for that purpose.
In case we don’t find any bullets that are not active in the hierarchy, then we will create a new bullet and store it in the bullets list. This way, we will only create new bullets if all current bullets are active and being used, and this is very hard to happen when we get to a certain amount of bullets in the game.
Before we test out the game, one thing to note is that I added SerializeField above the bullets list declaration, I did this so that we can see directly in the Inspector tab when the bullets are created and added to the list, so when we test the game make sure that you pay attention to that.
Now run the game and let’s test it out:
When started the game we had only two spider bullets in the bullets list, and the more the spider enemy shoot, the more bullets were created in the list.
The reason for this is, we need to deactivate the bullet objects. Inside the Assets -> Scripts folder, create a new C# script and name it SpiderBullet. Open the SpiderBullet script in Visual Studio and add the following lines of code:
In OnTriggerEnter2D we are testing if the bullet collides with the ground, or if the bullet collides with the player object, and if that happens we will deactivate the bullet.
Of course, in a real game, you would not use hard code string values instead you would have a more efficient way to compare strings to each other but I am not going to go into that in this tutorial.
Make sure that you attach the BulletScript to the Spider Shooter Bullet prefab inside the Assets -> Prefabs folder.