What would be a good way to handle Enemy AI?

Recently i have been scripting NPC AI.
I decided to make one script in ServerScriptService handle all of them at once And it was a good choice because now i can handle up to 1000 NPCs at once.

(they are single HumanoidRootParts with Humanoids so with a full character it would be less)

But the problem is that the script goes through one NPC at a time (Although very quickly) it causes a delay when the player is close enough to be noticed by the AI.

Another thing i noticed when testing is that with two players the NPCs only follow one of them.

If the player is within a certain distance then it will use the Humanoid:MoveTo() function on the player’s HumanoidRootPart.

I have tried Reducing the delay and that worked (It also increased the script activity)
The NPCs still Seem a little slow when it comes to following the player.

Would it be better to have a single script inside each NPC that Uses a module script or is there a better solution?

2 Likes

Coroutines are a good method for async coding. You should coroutine.wrap your function, then call it. coroutine | Documentation - Roblox Creator Hub

all ais will work like this,they will find the closet Humanoid to consume to follow the path,im glad roblox did this or the Ai’s will be broke.Just use magnitude to find the closest Hummanoid

You should avoid running logic on all 1000 NPCs at once since it takes a huge toll on performance. You can try sorting your NPCs by their positions into spatial partitions so that the game won’t have to loop the entire list of NPCs to check for distance. This method will only do distance checks on nearby NPCs and avoids running logic on NPCs that are no where near players.

If you can read C#, you should check out this article. It does a good job at teaching you basic way to implement spatial data structures into your game.

You should also have the game loop through surrounding partitions so you don’t get a scenario where you have NPCs that are close enough to attack players, but can’t target a player due to being in different partitions.

My MMORPG I’m working on can currently handle 5000+ NPCs running logic every heartbeat as long as they’re spread out across the map.

7 Likes

Wrap parts of your code with

spawn(function()
    -- AI code in here :P
end)

Since Lua runs on 1 thread, using spawn(function() will give it permission to ‘Hyperthread’ / use more threads to get the job done.

@DragRacer31 That’s not going to change anything. Spawn is pseudothreading, it doesn’t change the fact that Lua is still single-threaded.

In this case, the delay is being caused because of network ownership. If you do not explicitly set the network owner of an NPC’s parts, then the client will be given network ownership as it gets closer to an NPC. The passing of ownership causes that delay.

When you first iterate through your NPCs or how ever you get NPC logic to them, make sure you set the NetworkOwner of all an NPC’s parts to nil. This passes it to the server and disables automatic assignment.

for _, item in pairs(NPC:GetDescendants()) do
    if item:IsA("BasePart") then
        item:SetNetworkOwner(nil)
    end
end
7 Likes

I like to use the Number loop

for Number = 1, #Npcs do
print(Npcs[Number])
end

it is faster than i,v in pairs

It doesn’t matter, the time difference is negligible. I prefer to write clear, readable loops. To me, it makes no sense to use the indice where not required. Therefore, if time does not hold a noticeable difference, I ignore it. Depending on your implementation, this may even error.

I need the extra speed for my NPC script because it has to do one npc at a time so even if the time diference is minimal it adds up when my NPC count is high.

In this case? No. You’re only going to be calling SetNetworkOwner once unless the hierarchy of your NPCs is dynamic and you add things to it. In these kinds of cases, I use CollectionService or a mix of coroutines and functions to handle this process in the background.

With CollectionService, all I do is add a tag to a new NPC registered within the script. A separate script then receives a signal when the NPC is given the tag and proceeds to set network ownership of its BasePart descendants as well as any new BaseParts added to the NPC.

With a custom solution, I simply spawn a function returned from a ModuleScript that handles ownership in tandem with other code running.

Hi, I know this is a bit of a late response, however I am curious how this spatial partition pattern works within Roblox. Due to my mediocre knowledge of C#, I am not too sure how to translate the article you linked, into Lua - let alone even understand it, as the article didn’t go into much depth explaining the code. The article sounds like it has a great solution to my problem, so it would be greatly appreciated if you were to give a little explanation of what it exactly does.