Download link to the example project at the bottom of this article.
Disclaimer: This a reconstruction of the AI in Hotline Miami in my own perspective, and by my own analysis. This is only speculation on my part and its not associated in any way to the original designers, sorry if something is off the mark.
Hotline Miami is fantastic game, I’ve played both and I am still replaying them to date.
The fact is, the core mechanic in the game is the combat against this NPC’s.
While i was playing the 2nd one, I noticed a lot of interesting things about their behaviors, and since I am deeply interested in learning AI I decided to give it a shot.
In this article, I will not go in depth into code, but in how the NPC is designed, and how it can be recreated using the Unity Engine.
The first step in my analysis is the NPC states. I noticed 6 of them: Idle, Inspect, Attack, Knocked Down, Looking for Weapon and Dead.
In this article I will avoid the Knocked Down and Looking for Weapon behaviors, I may add them in the future.
Judging from this 4 states(Idle, Inspect, Attack, Dead ), I decided to use a State Machine for the NPC, the reason for this its because it has very little number of states, and its easier/faster to implement and will get the job done.
After analysing the 4 states in the NPC, I noticed that the Idle State has 3 possible sub states: Patrol, Roamer and Static. Each one of them with behaves very different from the others.
For the states transitions, the NPC will switch from Idle to Inspect when it is alerted, it will switch from Inspect to Attack when its target is on range, Inspect will go back to Idle when the target its lost and Attack will go to Inspect when the attack is finished.
All states will go to Dead state when the NPC is damaged.
So in the end our State machine will look like this:
1 ) IDLE BEHAVIORS
Idles are extremely important in Hotline Miami, they will define the main behavior of the NPC, and allow the level Designer to come up with interesting situations for the player.
After thinking about it for a little while i decided to separate each idle as a single state. However, every NPC can only have one type of Idle movement, and all transitions will just go back to the selected idle.
This made it way much easier to implement and less confusing.
The 3 types of Idles are defined as follows:
Idle – Patrol
- NPC’s follow a designated path
- When the NPC is alerted, it will inspect the point of interest.
- When inspect is timed out, the NPC returns to its patrol path.
I made a simple node script which holds the reference to the next node. We will call the group of interconnected nodes a Patrol Path.
- Then the NPC will have a direct reference to any node in this patrol path.
- All the nodes have to be connected manually to each other.
- Every node has only one next node.
State Start: The NPC will go to the reference node position.
State Update: If i reached my destination, replace my current node, with the next node, then go to that position.
State End: Do Nothing
Idle – Roamer
- NPC’s walk forward in a random interval (1-3 seconds).
- Everytime the NPC starts walking it turns randomly.
- If a NPC’s hits a wall it gets reflected to the other direction
- When the NPC is stood still it randomly turns in any direction
This was the tougher state to implement, mostly because it looks very erratic and random at the beginning, however i think i got a pretty close result.
I used 2 different timers in this case, one when the NPC is walking, and one when the NPC is waiting.
State Start: The NPC turns randomly in any direction, then it will ask for a random inverval of “Walk Time”.
State Update: If the NPC its still in inside the walk time, it will walk forward, if it reaches a wall, reflect the NPC and continue walking.
When the “Walk Time” expires, ask for a new “Wait Time”, and also define a “Turn Time” which will be half of the “Wait Time” value.
When the “Turn Time” expires, the npc will turn randomly.
When the “Wait TIme” expires, reset the “Walk Time”, turn randomly and start moving forward.
State End: Do nothing.
Idle – Static
- The NPC will stay still on the spot it spawned.
- It will only be alerted if there is something in sight.
- If something is in sight it will inspect it.
- If the ispect is timed out, the NPC will return to its starting position.
State Start: The NPC will go to its spawning position at the beggining of the state, if its already in position it won’t move.
State Update: Do Nothing
State End: Do Nothing
2) INSPECT BEHAVIOR
- When the NPC is alerted, it will go to the alerted position.
- When the NPC reaches its destination, if there is nothing to do it will go back to what it was doing
This is one of the easiest state that looks harder to do.
The first hard question is about pathfinding since that is a must in this state.
Fortunately we are using the Unity Engine, and unity has a great pathfinding solution with the NavMesh, if you havent heard of it i suggest you check it out first.
After creating our level geometry and baking our NavMesh, all our NPC will have a NavMeshAgent to go around the world, this way we can tell them to go anywhere and they will find the path.
This is the only way NPC’s will traverse the world, through the NavMeshAgent.
Now that the pathfinding is covered, the next step is making the NPC look clumsy when searching, the key factor here is making the NPC go to the alert position and not holding a reference to the alerted object.
This is key to make the NPC not to cheat, and also will help us a bunch on the Attack State.
The Inspect-State is defined as follows:
State Start: Go to the inspect position.
State Update: If the player is inside my weapon radius switch to Attack.
else If i have reached my destination, start a timer and when it is done, go back to Idle. Else, go to inspect position.
State End: Do Nothing
There is also an external method which is called by the NPC sensors (More on that below), that will make the NPC update the inspect position.
3) ATTACK BEHAVIOR
The attack behavior has 2 significant versions:
Melee Attack Ranged Attack
Both of the attack types work equally, the only difference is the range of the weapon, the delay of the action and the overall time for the attack to finish.
We will code the weapons individually, and add this 3 key values as well as the type of damage it will do (bullets or circular hitTest for melee).
There are 3 main public methods weapons should have:
- GetWeaponRange: Returns the range for the weapon to be used correctly.
- Shoot: It handles the delay and timing on when to do the weapon action.
- IsFinished: The action is done.
In the case of a machine gun, the action will be shoot just 1 bullet, inspect will switch to attack for every shot.
This state will be called only from the Inspect-State and will do so when the player is within the weapons range.
State Start: Shoot The Weapon, tell the animator to swich to attack state (For the sprite animation).
State Update: If the weapon is finished, go to Inspect.
State End: Tell the animator the attack state is over (To stop the sprite animation).
4) DEAD BEHAVIOR
There is no much to say here, the NPC dies, all our work represented in dead pixels.
The state change will be handled by a public function called by the player when melee by the player or by the player bullets when shot.
It will shutdown all the NPC functions and leave the sprite to be remembered.
Sensors are separate scripts that will call important methods from the Player Behavior.
Sensors can provoke a state change on our NPC.
We can notice 2 key sensors required in the NPC, the sight and hearing.
NPC’s in hotline Miami have a really wide Field of View, this makes them very dangerous and aware of their surroundings.
To be honest i am not sure of the exact FoV values, however they behave very similar on our NPCs on the right.
The size of their Field of view also allows them to be very hard to shake off by the player when he/she is being chased.
I simulated the sight sensor with a Sphere Raycast, NPC’s do this every frame (this can be tinkered to be less frequent) and check if the player is inside this sphere, if it is, then they will check if the point is inside their line of sight angle.
The code for getting the angle from the target relative to the player looks like this:
Vector3 direction; direction = overlapedObjects [i].transform.position - transform.position; float targetAngle = Vector3.Angle (direction, transform.forward);
If the player is inside this angle, there is another we have to do, check if it can be directly seen by the NPC (so there is no wall in between), we will do a raycast from the NPC’s position to the target position, if its true then the player is spotted.
When the player is spotted, a method inside the NPC Behavior will be called, this will update the inspect position and if the character is Idle, it will switch to an Inspect State.
The dynamic Field of View is just for visual purposes, since its much more expensive to render than the sphere cast. It is made using 2 semi circles with different radius, a dynamic mesh and a bunch of raycasts. I may go on detail about the dynamic FoV later.
The hearing sensor is much more easier than the sight sensor, I was actually very motivated to start this project due to the fact that i saw how easy it was to do, and it worked so great inside the game.
The key things we will notice from this sensor is that it only works with player firearms, and also it doesn’t alert the NPC in Idle-Static state.
It could be made in a separate module, but the easiest way to do it is to do a simple Sphere Raycast every time the player shoots a weapon, when he/she does, we look for every enemy inside this radius and will send them the new Inspect Position, and change them to a Inspect State.
If the NPC is in Idle-Static, it will just ignore this request.
For tutorial purposes, you will see the radius is pretty small in the .gif, however in Hotline Miami it appears to be like twice that radius.
We can notice, Hotline Miami’s AI is amazingly crafted and designed without being too complicated to implement.
The solutions and behaviors or the NPC’s are very straight forward, but the decision behind how this behaviors should work between each other, its were the hard work exists.
- Add the 2 missing states, Knocked Down and Looking for Weapon.
- Switch how current bullet hit tests work.
- Switch the project to Physics 2D in Unity.
- Do more levels.
I hope you found this article useful, and thanks for reading this far.
If you notice anything off, have any doubts or an idea for my next topic please contact me.
If you are interested in doing something more using this AI, I would love to know. As long as it isnt a Hotline Miami clone.
Overall I really enjoyed developing and analyzing Hotline Miami for this project, and i really wanted to release something interesting about AI.
I would love to thank specifically Jonatan Söderström and Dennis Wedin for their excellent work on Hotline Miami, and inspiring me to learn through their game.
Also all the people related to the Holtine Miami project like the guys at Devolver Digital.
Everything else was coded by me.