Making a better roblox custom enemy ai script

I've spent way too many hours trying to get a roblox custom enemy ai script to actually work without the NPC walking straight into a wall or spinning in circles like it's having some kind of digital breakdown. Let's be real, the default "zombie" scripts you find in the toolbox are usually pretty ancient and don't really do much beyond moving toward the nearest player in a straight line. If you're building a game where you want the enemies to actually feel like a threat—or at least like they have a brain—you've got to step away from the basic stuff and write something custom.

The cool thing about Roblox is that they give us some decent tools like PathfindingService, but the trick is knowing how to stitch those tools together so your enemy doesn't lag the entire server or act completely braindead.

Why the basic scripts usually fail

If you've ever just used a while true do loop with a MoveTo command, you know the struggle. The NPC will chase you, sure, but the second there's a fence or a crate in the way, it just gives up and keeps walking into it. It's frustrating for the player and looks amateurish.

A solid roblox custom enemy ai script needs to handle a few specific things: it needs to "see" the player, it needs to find a path around obstacles, and it needs to know when to stop chasing so it doesn't follow someone across the entire map forever. We're aiming for something that feels reactive.

Getting the foundation ready

Before you even touch the script, you need a rig. Whether it's an R15 dummy or a custom monster you modeled in Blender, it needs a Humanoid and a HumanoidRootPart. Without those, the built-in physics won't really know what to do with your code.

I usually start by tossing a Script inside the NPC model. I'll also create a few variables at the top for the services I'm going to use. You're definitely going to need PathfindingService and probably Players.

Setting up the detection logic

The first thing your AI needs to do is find a target. You could just loop through every player in the game and find the closest one, which is what most people do. It's simple, it works, and for most small games, it won't kill your performance.

But here's a tip: don't check for the closest player every single frame. If you're running that check 60 times a second, you're just wasting resources. Checking every 0.2 or 0.5 seconds is plenty. The player won't notice the delay, and your server will thank you.

Making the AI actually "see"

One of the biggest upgrades you can give your roblox custom enemy ai script is a line-of-sight check. It feels pretty cheap when a monster starts chasing you through three brick walls just because you happen to be the closest person.

To fix this, we use Raycasting. Basically, you fire an invisible line from the NPC's eyes to the player's head. If the ray hits a wall first, the NPC can't see you. If it hits the player, the chase is on. This adds a layer of stealth to your game, allowing players to actually hide behind objects, which makes the gameplay way more engaging.

Handling PathfindingService

Pathfinding is where things get a bit technical. PathfindingService:CreatePath() is your best friend here. It calculates a series of "waypoints" that the NPC needs to follow to get to the target without hitting a wall.

However, paths can fail. Sometimes the target is in a spot the NPC can't reach, like on top of a thin pole or across a gap. Your script needs to handle these failures gracefully. If the path status isn't "Success," you might want the NPC to wander randomly or return to its original post instead of just freezing in place.

The State Machine approach

If you want to keep your code clean, I highly recommend using a "State Machine" logic. It sounds fancy, but it's just a way of organizing what the AI is doing at any given moment. Instead of one giant, messy block of code, you break it down into states like "Idle," "Chasing," and "Attacking."

  • Idle: The NPC hangs out, maybe walks around a small area. It's constantly looking for a player.
  • Chasing: Once it spots someone, it switches to this state and starts calculating paths to get to them.
  • Attacking: When it gets within a certain distance, it stops moving and triggers an animation or deals damage.

Using a simple variable like local state = "Idle" and a few if/then statements makes it so much easier to debug. If your NPC isn't attacking, you know exactly which part of the script to look at.

Tackling performance issues

If you're planning on having thirty enemies on the screen at once, you're going to run into lag if you aren't careful. Pathfinding is "expensive" for the server to calculate. One trick I like to use is only updating the path if the player has moved a significant distance.

If the player is standing still, don't keep recalculating the path to the same spot. Also, if the player is very far away, you might want to put the AI to "sleep" or significantly slow down how often it thinks. There's no point in having a complex roblox custom enemy ai script running at full speed for an NPC that the player can't even see.

Making the movement smooth

Sometimes the NPC looks a bit "jittery" when following waypoints. This usually happens because it stops for a split second at every waypoint before moving to the next. To smooth this out, you can check the distance to the next waypoint. Once the NPC is within a few studs of it, start moving toward the following waypoint immediately. It makes the movement look much more fluid and natural, almost like a human is controlling it.

Adding some personality

Once the basics are down, you can start adding the "juice." Maybe your enemy screams when it sees a player, or maybe it gets a speed boost if it's been chasing you for more than ten seconds. These little touches are what turn a generic script into something people actually remember.

You can also use CollectionService to tag your NPCs. This way, you can have one master script that controls all the enemies in your game rather than pasting the same script into every single model. It makes updating your AI way easier—you change the code in one place, and every enemy gets the update instantly.

Dealing with the "Stuck" problem

Even with the best pathfinding, NPCs will eventually get stuck. It's just the nature of Roblox physics. A good way to handle this is to track the NPC's position. If it hasn't moved more than a stud in the last two seconds but it's supposed to be moving, it's probably stuck.

When this happens, you can try a few things: 1. Make the NPC jump (this fixes like 80% of problems). 2. Recalculate the path entirely. 3. Briefly disable collisions with small objects. 4. If all else fails, just respawn the NPC or teleport it a few studs toward its goal.

Final thoughts on the process

Writing a roblox custom enemy ai script is really an iterative process. You're never going to get it perfect on the first try. You'll write some code, test it, watch your NPC walk off a cliff, laugh, and then go back to fix the logic.

The goal isn't necessarily to make the "smartest" AI in the world; it's to make an AI that's fun to play against. Sometimes that means making the enemy a little bit slower or giving it a "cooldown" so the player has a chance to breathe.

Keep your code organized, don't over-calculate things, and always keep the player's experience in mind. Once you get the hang of PathfindingService and Raycasting, you can pretty much create any kind of enemy you can imagine, from a simple guard to a complex boss with multiple phases. It just takes a bit of patience and a lot of playtesting.