How do I make a good NPC system?

I’ve been struggling to make a good custom NPC system. I have done it twice but they had a ton of lag. What I tried doing was having Attributes on the NPC which the server would change. Then the client would check that Attribute and use that to display it on the client. On the client, I would lerp the RootPart but I find cframing parts to be laggy when cframing a ton of parts.

The next attempt was to use Roblox’s physics. I use LinearVelocity and AngularVelocity to make it move and rotate. This worked better than cframing but still lags with under 200 NPC’s.

I was also wondering if raycasting down every frame would be a good choice to detect the ground.

I need some good information on how I would create a better system than my previous ones.

what kind of parts are colliding in the NPC? If your going for numbers I recommend having only 1 box for collisions. I also recommend you run AI behaviors in one script and then add a delay for a certain number of iterations to reduce script lag.

For the non-cframing system, only one part, the RootPart is collidable. I have tried using one script but it performed the same as having seperate scripts.

Here’s a basic structure system you could use:

  • Use PathfindingService to move the NPC.
  • Use Humanoid.FloorMaterial and Humanoid:GetState() to detect the floor.
  • Like @mantorok4866 said, use one box for collisions.

In terms of creating the NPC, I would recommend using an Object-Oriented structure:

  • Have a module containing all the possible subprograms the NPC could need. Use colon notation to define these, so you can use the self parameter to refer to the NPC.
  • Add another subprogram. This will be used to create a new NPC.
  • Use setmetatable({}, NpcClass). This will return the first arguement as well as allowing it to inherit everything from the module. This blank table will contain the NPC’s properties.
  • Now, whenever you need to call something for the NPC, it’s a lot easier. One example is movement.
local pfs = game:GetService("PathfindingService")

local handler = {}
handler.__index = handler
handler.__newindex = function(tbl, key, value)
    if rawlen(tbl) < 15 then --adjust this number as needed, it just limits the number of indexes allowed.
        rawset(tbl, key, value)
    else
        warn("New indexes are not allowed.")
    end
end

--movement
function handler:MoveNpc()
    --move the npc
end

--new NPC
function handler.new()
    local self = setmetatable({}, handler) --it inherits everything from the handler
    self.Health = 100 --example property
    --let's move the NPC. We can have this run in the same thread or run in a seperate thread.
    self:MoveNpc() --same thread
    task.spawn(self.MoveNpc, self) --different thread
end

return handler

Mass lerping and raycasting may have been causing those lagspikes.

I don’t know how to use metatables that well and I also heard that they are a bad practice.

Metatables are not bad practice - in fact they are crucial for OOP in Luau. All the metatable means is the given table will inherit everything from it - it’s easier than rewriting already existing code.

For example:

local someTbl = {
    ["hiThere"] = function()
        print("hi")
    end
}

--[[
now we want to make a new table, which includes everything from the first
as well as some new things. Instead of rewriting everything that's in the first
table, why not just have the new table inherit it?
]]

local tblTwo = setmetatable({}, someTbl)
tblTwo.hiThere() --it's already inherited this subprogram

--now we can make the new one
function tblTwo.something()
    print("We simplified how tblTwo was made.")
end
1 Like

Have you check out Justice the Awesome NPC system by Lewis Schumer. I have used both v2 and v6 of this and can highly recommend it