As mentioned before, either on the site or on another article, Trade Secret is to be a game of stealth. The main goal is to be as stealthy as possible, avoiding detection and, ultimately, capture. To do something like this, I would have to program the enemies with stealth in mind. I look towards the games I have had experience with as examples to what I would want for my enemy AI. These examples include, but are not limited to the below list:
- Theif (namely the most recent one)
- Deus Ex (Human Revolution)
- Far Cry (namely 3, 4, and 5)
- Assassin's Creed (namely 2, and 3)
- Invisible Inc.
I began designing the enemy AI with my experience, along with the limitations of the Unity Engine in mind. I initialize my code with 3 different scripts: a script for sight, a script to handle patrols, and a controller script to combine the formerly mentioned scripts.
I also needed to determine how I wanted to handle states and how the enemy AI would work. I ultimately decided on having four states: idle, patrol, warn, and pursue.
- Idle - Not moving, but can see.
- Patrol - Like Idle, but has a given path loop to go through
- Warn - Upon seeing the player, this state is set
- Pursue - After a predefined time of seeing the player, this is the state, and the character will being chasing the player until the player cannot be seen. (such as going behind a wall or closing a door)
As stated above, one of my scripts will handle how the enemies "see". After some brief looking towards how I could handle sight in gameplay. My initial test was to use Raycasts, which basically draws a line aimed at a specific location (usually forward). Upon tests it seemed very useful in detection of what was in front of it while also not being able to see through any working object.
However, there was one main issue. The issue being that it is just a raycast, or a line generated. Although very percise, my player will have to be directly in front of the center of the enemy.
So what is the solution? Spherecasts! Spherecasts add another layer to the original raycast, which is thickness. The way it works is by basically shooting an invisible sphere (instead of a line) towards the defined distance.
With spherecasting finalized, I would now need to handle the transitions of the 4 different states. Initially, I had chosen to use enums for the various states, however, a fellow developer had told me that doing a class-based state machine would be better. He gave me an example of one and I decided that it would be best to move over to it before I had actually developed the state system.
Now that I had all my states setup (not really, they don't do anything JUST YET) it was time to determine how I was going to switch between the states. First was to connect the Sight to the Controller. Using Unity's message system, I was able to program it to send a message every time the cast was fired. Now it was time to run through some conditions to determine which state will be picked.
Using Time to switch between states
A time-based system seems to be the easiest way to handle this. Tracking the current time and having predefined cooldown and chase timers that would be checked by the engine was how I had finalized the controller with. Every Unity update, the current time would be checked.
- If the player is hit by the cast, a timer counts down to a predefined number and state is changed to Warn.
- If the player stays in the view of the cast past the timer stopping, the state is changed to Chase.
- If the player leaves the cast before the timer stops, the timer is reset and a cooldown timer begins.
- If the cooldown timer stops without the player being seen again, then the enemy goes back to its default state (either Patrol or Idle).
- If at any point the player is hit by the cast, the cooldown timer is reset and the chase timer begins until the player is no longer seen or the timer runs out, whichever comes first.