Building a Simple Third-Person Shooter AI in Unity
The allure of creating intelligent adversaries in video games is undeniable. But diving into AI can feel like navigating a minefield of complex algorithms and abstract concepts. What if I told you building a functional, even compelling, third-person shooter AI in Unity is more accessible than you think? This isn’t about conjuring a Skynet-level threat; it’s about laying a solid foundation, understanding the core principles, and building upon them. Let’s get started.
Enemy Movement: From Patrol to Pursuit
The most-downloaded assets on Wayline this month — sorted by what real developers actually use.
Movement is the cornerstone of any AI. Forget complex pathfinding initially. Simple patrol patterns and direct chase sequences are your friends.
Patrolling involves defining a series of waypoints. The AI moves from one to the next in a loop. A basic implementation might look like this (pseudocode, remember comments are key!):
//In C#
public Transform[] waypoints;
public float speed = 3f;
private int currentWaypoint = 0;
void Update() {
//Move towards the current waypoint
transform.position = Vector3.MoveTowards(transform.position, waypoints[currentWaypoint].position, speed * Time.deltaTime);
//Check if we've reached the waypoint
if (Vector3.Distance(transform.position, waypoints[currentWaypoint].position) < 0.1f) {
currentWaypoint = (currentWaypoint + 1) % waypoints.Length; //Cycle to the next waypoint
}
}
The % (modulo) operator is critical here. It ensures the currentWaypoint variable loops back to 0 when it reaches the end of the waypoints array, preventing an out-of-bounds error.
Chasing is triggered when the AI detects the player (more on that later). The AI simply moves directly towards the player’s position. But beware the pitfall of “jittery” movement. If the enemy AI attempts to move every frame directly to the player, it might create unwanted oscillations.
//Chasing
public Transform player; //Assign the player object
public float chaseSpeed = 5f;
void ChasePlayer() {
//Move towards the player
transform.position = Vector3.MoveTowards(transform.position, player.position, chaseSpeed * Time.deltaTime);
}
Common Mistake: Forgetting to account for Time.deltaTime. Without it, movement speed becomes frame-rate dependent, leading to inconsistent behavior across different machines.
Target Detection: The Power of Raycasting
Raycasting is your AI’s eyesight. It allows the AI to “see” objects in the world by firing a virtual ray and detecting what it hits. Critically, implement this using layers. Put the player on a separate layer, so the enemy AI only detects the player.
//Raycasting
public float sightRange = 10f;
public LayerMask playerLayer; //Assign Player layer in the inspector
bool CanSeePlayer() {
RaycastHit hit;
Vector3 direction = player.position - transform.position;
if (Physics.Raycast(transform.position, direction, out hit, sightRange, playerLayer)) {
//Debug.Log("Raycast hit: " + hit.collider.gameObject.name);
if (hit.collider.gameObject.CompareTag("Player")) {
return true;
}
}
return false;
}
Pro Tip: Add a field of view. Raycasting forward is rudimentary. Use Vector3.Angle to calculate the angle between the forward vector and the direction to the player. Only engage if the player is within the desired field of view.
Simple Shooting Mechanics with Cooldowns
No third-person shooter is complete without shooting. But complex ballistic trajectories are unnecessary. A simple linecast (similar to raycasting but returns all collisions) will suffice.
//Shooting
public GameObject bulletPrefab;
public Transform firePoint; // Where the bullet spawns
public float fireRate = 1f;
private float nextFire = 0f;
void Shoot() {
if (Time.time > nextFire) {
nextFire = Time.time + fireRate;
//Instantiate a bullet
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
Rigidbody rb = bullet.GetComponent<Rigidbody>();
rb.AddForce(firePoint.forward * 500f); // Apply force to bullet
}
}
Challenge: Implementing a proper cooldown. Notice nextFire. This prevents the AI from firing continuously. Tweaking fireRate changes the frequency of shots.
Putting It All Together: The AI Brain
The “brain” of your AI is a state machine. At its simplest, it could be a simple if/else statement. But for more complex AI, consider a more robust state machine implementation.
//AI State Machine (Simplified)
enum AIState {
Patrolling,
Chasing,
Attacking
}
AIState currentState = AIState.Patrolling;
void Update() {
switch (currentState) {
case AIState.Patrolling:
Patrol();
if(CanSeePlayer()){
currentState = AIState.Chasing;
}
break;
case AIState.Chasing:
ChasePlayer();
if(!CanSeePlayer()){
currentState = AIState.Patrolling;
}
if (Vector3.Distance(transform.position, player.position) < attackRange) {
currentState = AIState.Attacking;
}
break;
case AIState.Attacking:
Shoot();
if (Vector3.Distance(transform.position, player.position) > attackRange) {
currentState = AIState.Chasing;
}
break;
}
}
Pitfall: The “ping-pong” effect. The AI rapidly switching between states (e.g., Patrolling and Chasing) when near the edge of its detection range. Implement hysteresis – a buffer zone – to prevent this. For example, the AI only switches back to Patrolling if the player is significantly further away than the initial detection range.
Modular Design: The Key to Scalability
The code above is a starting point. The real power comes from modularity. Instead of having a single, monolithic script, break down the AI into smaller, independent components:
- MovementController: Handles all movement logic (patrolling, chasing, etc.).
- VisionSensor: Responsible for detecting targets.
- WeaponController: Manages shooting and weapon-related logic.
- AIStateMachine: Orchestrates the behavior of these components based on the current state.
This separation of concerns makes the AI easier to understand, modify, and extend. Want to add new weapons? Simply modify the WeaponController. Want a new patrol behavior? Modify the MovementController.
Building an AI is a journey, not a destination. By starting with these fundamental concepts and embracing a modular design, you’ll be well-equipped to create increasingly sophisticated and engaging adversaries. Remember that debugging and iteration are essential parts of the process. Good luck!