AI NPCs and Enemies in Three.js

Updated June 2026
Non-player characters bring game worlds to life through believable behavior, and enemies create the challenges that make gameplay engaging. Building AI for Three.js games involves three core systems: perception (how NPCs detect the player and environment), decision-making (how they choose what to do), and action execution (how they move, attack, and interact). This guide covers the practical implementation of each system using patterns proven in commercial game development.

Game AI is fundamentally different from machine learning AI. Game NPCs do not need to learn, they need to behave in ways that create fun gameplay. A guard that follows a patrol route, investigates suspicious sounds, and chases intruders creates tension and engagement through handcrafted rules, not neural networks. The techniques in this guide are the same ones used in AAA games, adapted for the JavaScript ecosystem and browser performance constraints.

Step 1: Design an AI Architecture

A finite state machine (FSM) is the simplest and most common AI architecture. Each NPC has a current state (idle, patrol, chase, attack, flee) and transitions between states based on conditions (player detected, health low, target lost). Each state defines the NPC's behavior: the patrol state follows a path of waypoints, the chase state moves toward the player, the attack state plays an attack animation and deals damage.

Implement an FSM as a class with an update method that runs the current state's logic and checks transition conditions. Each state is an object or function with enter, update, and exit methods. When a transition fires, the current state's exit runs, the new state becomes current, and its enter method runs. This is clean, debuggable, and sufficient for most enemy types.

For more complex NPCs with many states and transitions, a behavior tree provides better organization. Behavior trees structure decisions as a tree of nodes: selectors (try children until one succeeds), sequences (run children in order, stop on failure), and leaf nodes (actions and conditions). A guard's behavior tree might select between "investigate disturbance" and "follow patrol route", where "investigate" is a sequence of "move to disturbance location" then "look around" then "return to patrol".

Libraries like behaviortree.js provide JavaScript behavior tree implementations, but the pattern is simple enough to build from scratch for most game projects. Start with an FSM for your first enemies and move to behavior trees if the FSM becomes difficult to manage.

Step 2: Build a Perception System

NPCs need sensory input to make decisions. The most important sense for game AI is vision: can the NPC see the player? Implement vision as a cone check combined with a line-of-sight raycast. First, check if the player is within the NPC's view distance and within the view angle (typically 90-120 degrees from the forward direction). Then cast a physics ray from the NPC's eye position to the player to check if any walls or obstacles block the view.

Use Rapier's raycasting for line-of-sight checks because it operates on the physics world, which is simpler and faster than raycasting against the full visual scene graph. Configure collision groups so that the raycast hits walls and terrain but ignores other NPCs, particles, and decorative objects that should not block vision.

Hearing adds another perception layer. When the player fires a weapon, sprints, or breaks an object, emit an "audio event" at that world position with a radius. NPCs within the radius add the event to their awareness and may investigate the source location. This creates gameplay where players can choose stealth (move slowly, avoid noise) or loud approaches (alert enemies but fight directly).

Track awareness as a value between 0 and 1 for each NPC. Seeing the player increases awareness quickly. Hearing a sound increases it moderately. When the player is hidden, awareness decays over time. Transitions between AI states fire at awareness thresholds: above 0.3 triggers "suspicious" (investigate), above 0.8 triggers "alert" (combat). This gradual awareness system feels more natural than binary detection.

Step 3: Add Navigation and Pathfinding

NPCs need to navigate through the game world without walking through walls or falling off edges. Navigation meshes (navmeshes) define the walkable surfaces of a level as a network of connected polygons. Pathfinding algorithms like A* find the shortest path between two points on the navmesh, producing a series of waypoints the NPC follows.

The recast-navigation library (available on npm as @recast-navigation/core) provides navmesh generation and pathfinding for JavaScript. It is a WASM port of the Recast and Detour libraries used in most commercial game engines. Generate a navmesh from your level geometry by providing the triangle data from your Three.js meshes, and Detour handles pathfinding queries on the resulting navmesh.

For simpler games without complex level geometry, waypoint-based navigation is an alternative. Place waypoint nodes manually in the level editor or procedurally, connect them into a graph, and use A* to find paths between nodes. This is less flexible than navmeshes but easier to set up for small levels or grid-based games.

When following a path, NPCs should not rigidly snap from waypoint to waypoint. Smooth the path by removing unnecessary intermediate points (string pulling), and use steering behaviors (next step) to make the movement look natural. Recalculate the path periodically if the target (the player) is moving, but not every frame, as pathfinding is expensive. Every 0.5-1 second is typical for a chasing enemy.

Step 4: Implement Steering Behaviors

Steering behaviors produce natural-looking movement by computing velocity adjustments each frame. Unlike pathfinding, which decides where to go, steering decides how to move moment-to-moment. The two systems complement each other: pathfinding provides the waypoints, and steering smoothly moves the NPC toward each waypoint while avoiding local obstacles.

Seek steers toward a target position by computing the desired velocity (direction to target times maximum speed) and subtracting the current velocity to get the steering force. Flee is the opposite. Arrive is like seek but decelerates as the NPC approaches the target, preventing overshooting. Wander creates meandering movement by projecting a circle in front of the NPC and randomly displacing a target point on its circumference each frame.

Obstacle avoidance casts rays ahead of the NPC to detect nearby obstacles and applies lateral steering forces to avoid them. This handles dynamic obstacles that the navmesh does not account for, like other NPCs, moving platforms, or objects the player has placed. Combine multiple steering behaviors by summing their force vectors and clamping the result to the NPC's maximum acceleration.

Apply steering forces through the physics character controller, not by directly setting the mesh position. This ensures NPCs interact correctly with the physics world, respond to collisions, and move on walkable surfaces. The steering output becomes the desired movement vector that you pass to the character controller each frame.

Step 5: Create Combat AI

Enemy combat should be challenging but fair. The simplest combat AI alternates between approaching the player and attacking when within range. Add variety by giving enemies different attack patterns: melee enemies rush the player and swing, ranged enemies maintain distance and shoot, support enemies heal allies or buff other enemies.

Attack timing should use cooldowns and wind-up animations. When an enemy decides to attack, play a wind-up animation (raising a weapon, charging energy) that gives the player time to react, then execute the attack with a hitbox check or projectile spawn. The wind-up duration determines difficulty: shorter wind-ups are harder to dodge. Vary wind-up timing slightly (by 10-20%) between attacks to prevent players from exploiting perfectly predictable rhythms.

Group coordination makes encounters more interesting than individual enemies acting independently. Assign roles when multiple enemies engage the player: one or two attack while others flank or wait their turn. Prevent all enemies from attacking simultaneously, which overwhelms the player, or all waiting, which feels passive. A simple coordination system uses a shared "attack token" that limits how many enemies can attack at once, with tokens rotating between enemies on a timer.

Difficulty scaling adjusts AI parameters based on the player's performance or a difficulty setting. Harder difficulty increases NPC accuracy, reduces reaction delays, tightens attack cooldowns, and allows more simultaneous attackers. Easier difficulty adds deliberate misses, longer wind-ups, and more cautious positioning. These parameter adjustments are more effective than changing AI structure.

Step 6: Optimize AI Performance

AI processing is CPU-bound, and browser games have a limited frame budget. Running complex decision-making, pathfinding, and perception checks for every NPC every frame will cause stuttering in scenes with more than a handful of characters.

LOD thinking reduces AI complexity based on distance from the player. Nearby NPCs run full perception, pathfinding, and decision-making. NPCs at medium distance run simplified logic (reduced perception checks, less frequent path updates). Distant NPCs run minimal logic or freeze entirely, resuming when the player approaches. This mirrors the LOD concept from rendering, where distant objects use fewer polygons.

Stagger AI updates across frames. Instead of updating all NPCs on the same frame, assign each NPC a frame offset so that their expensive operations (pathfinding, perception sweeps) spread across multiple frames. If you have 30 NPCs, update 5 per frame at 60fps, giving each NPC a full decision update every 6 frames (100ms), which is fast enough for most game scenarios.

Use spatial partitioning (a grid, octree, or the physics engine's broadphase) for proximity queries. Checking if any enemy is within hearing range of a sound event should not require iterating over every NPC in the game. Insert NPCs into a spatial structure and query only the relevant area. Rapier's collision detection can serve as a spatial query system by using sensor colliders as detection zones.

Key Takeaway

Game AI is about crafting behavior that feels intelligent, not about actual intelligence. Finite state machines handle most enemy types, navmesh pathfinding provides reliable navigation, steering behaviors make movement look natural, and performance optimization lets you populate your world with enough characters to make it feel alive.