How can I make an NPC follow an Object + Optimization help

Sounds simple enough right? . . . not really

I’ve been at this for a while now, but have found no methods that work.

The problem right now is that if I just use MoveTo() it goes to the position the object is at, but it doesnt account for the fact that the object can move.

Once the object has moved, it doesnt matter cause the NPC just walks to the position the Object was at when the MoveTo() Started.

Ive read countless posts, none of them seemed to tackle this challenge.


The current idea I have is that I would just tell when the object moves, and then stop the MoveTo (ps how do I stop the MoveTo??)

but I wasnt sure how optimized that’d be.
Also if anyone wants to help, any ideas to optimize this code?

function Class:EnableAI(Model:Model)
	local Humanoid = Model:FindFirstChildWhichIsA('Humanoid', true)
	
	repeat
		
		-- // Locate Target //
		local Target = Pathfinding:FindNearestPlayer(Model.PrimaryPart.Position); if not Target then continue end
		local TargetRoot = Target.Character.PrimaryPart
		local Complete = false
		
		-- // If Target, Follow it //
		if Target then
			
			-- // Pathfind //
			
			-- || PS this function just fires a Raycast to the Target.Character ||
			if Pathfinding:ObjectInSight(Model.PrimaryPart.Position, Target.Character, workspace.Enemies:GetDescendants()) then -- // Walk Directly towards Target //
				
				-- // I need to change this code to follow the TargetRoot //
				repeat
					Humanoid:MoveTo(TargetRoot.Position)
					task.wait()
				until ((TargetRoot.Position - Model.PrimaryPart.Position).Magnitude < 2) or not Pathfinding:ObjectInSight(Model.PrimaryPart.Position, Target.Character, workspace.Enemies:GetDescendants())
				Complete = true
			else
				
				-- // This is Pathfinding stuff //
				local Path = Pathfinding:FollowPath(Model, Model.PrimaryPart.Position, TargetRoot.Position, true)

				if Path then
					Path.Complete:Once(function()
						Complete = true
					end)

					Path.Failed:Once(function(Quit)

						if Quit then
							warn('No Longer following Path.')
							Complete = true
						end
					end)
				end
			end
		else
			warn('No Target')
			Complete = true
		end
		
		repeat task.wait() until Complete
		task.wait(0.4)
	until Humanoid.Health <= 0
end

thanks :]

Can’t u jus update the position when the position property of the object changes?

That was the idea, Im just not sure if thats the most optimized with my current code, but idk

local RunService = game:GetService("RunService")

function Class:EnableAI(Model: Model)
    local Humanoid = Model:FindFirstChildWhichIsA('Humanoid', true)
    
    if not Humanoid or not Model.PrimaryPart then
        warn("No humanoid or primary part found!")
        return
    end
    
    local function FollowTarget(TargetRoot)
        local updateConnection
        
        updateConnection = RunService.Heartbeat:Connect(function()
            if not TargetRoot or not TargetRoot.Parent or Humanoid.Health <= 0 then
                updateConnection:Disconnect()
                return
            end

            -- Move directly if in sight, otherwise use pathfinding
            if Pathfinding:ObjectInSight(Model.PrimaryPart.Position, TargetRoot.Parent, workspace.Enemies:GetDescendants()) then
                Humanoid:MoveTo(TargetRoot.Position)
            else
                local Path = Pathfinding:FollowPath(Model, Model.PrimaryPart.Position, TargetRoot.Position, true)
                if Path then
                    Path.Complete:Once(function()
                        -- Path completed, continue updating
                    end)
                    
                    Path.Failed:Once(function(Quit)
                        if Quit then
                            warn('Pathfinding failed.')
                        end
                    end)
                end
            end
        end)
    end

    while Humanoid.Health > 0 do
        local Target = Pathfinding:FindNearestPlayer(Model.PrimaryPart.Position)
        if Target and Target.Character and Target.Character.PrimaryPart then
            FollowTarget(Target.Character.PrimaryPart)
        end
        task.wait(0.4) -- Prevent CPU overload
    end
end
1 Like

Ok so, Im not exactly sure why, but for whatever reason after like 5 or 7 seconds the enemy starts pathfinding when Im in front of it I think, but it keeps going like back and fourth for some reason.

ignore the poor quality.

There are also no errors at all, there is something printing in the output which shows that the enemy is pathfinding, so I believe it has something to do with that

Can you just paste the out put and tell me around at what point in the output it stops working.

Thanks

After a while, Im noticing the output starts following a new path 779 times within 1 to 2 seconds.

This happens twice, the first time when it starts going crazy.
Other than that, there isnt much a difference in the rest of the output

bumping the post just in case someone can help out lol

What happens when there is only one npc? Does your code ignore the other npc when it raycast or pathfinding? This is the only thing I see as a possible problem.

The raycasts I can confirm ignore the other NPC’s, all the NPC’s are also in a collision group that makes it so they ignore each other so that might mean that the pathfinding would ignore the other NPCs, but Im not sure.

I did also test this with just one npc but I think it yielded the same result

Sorry for the wait, I go some difficulty with obs, it this what your are trying to do? I don’t make it like your current code, but you can take inspiration i try to name thing the same

There are no comments, you can ask me if there’s something you don’t understand.

local RunService = game:GetService("RunService")

local SimplePath = require(script.SimplePath)

local Path
local Humanoid
local HumanoidRootPart
local Target
local ObjectInSight

local beam
local brickcolor = BrickColor.random()

local function DebugRaycast(Origin, Direction, Length, Result)

	beam = beam or Instance.new("Part")
	beam.Name = "DebugRaycast"
	beam.Size = Vector3.new(0.2, 0.2, Length)
	beam.CFrame = CFrame.lookAt(Origin.Position, Origin.Position + Direction) * CFrame.new(0, 0, -beam.Size.Z / 2)
	beam.BrickColor = brickcolor
	beam.Material = Enum.Material.Neon
	beam.Anchored = true
	beam.CanCollide = false
	beam.CanQuery = false
	beam.CanTouch = false
	beam.Parent = workspace

	--print(Result)

end

local function CheckObjectInSight(Model, Target, IgnoreList)

	local ModelRoot = Model.PrimaryPart
	local TargetRoot = Target.PrimaryPart
	local Direction = (TargetRoot.Position - ModelRoot.Position)

	IgnoreList = IgnoreList or {}
	table.insert(IgnoreList, Model)

	for Index, Object in pairs(Target:GetChildren()) do
		if Object ~= TargetRoot then
			table.insert(IgnoreList, Object)
		end
	end

	local Params = RaycastParams.new()
	Params.FilterDescendantsInstances = IgnoreList
	Params.FilterType = Enum.RaycastFilterType.Exclude

	local Result = workspace:Raycast(ModelRoot.Position, Direction, Params)
	DebugRaycast(ModelRoot, Direction, Result and Result.Distance or Direction.Magnitude, Result)

	if Result and Result.Instance == TargetRoot then
		return true
	else
		return false
	end

end

local function FindNearestPlayer()
	return workspace:FindFirstChild("Ehtrab")
end

local function CancelMove()
	Humanoid:MoveTo(HumanoidRootPart.Position)
end

local function EnableAI(Model)

	Humanoid = Model:FindFirstChildWhichIsA("Humanoid", true)
	if not Humanoid then return end

	HumanoidRootPart = Humanoid.RootPart
	if not HumanoidRootPart then return end

	while true do

		local DeltaTime = RunService.Heartbeat:Wait()

		Target = FindNearestPlayer()
		if not Target then CancelMove() continue end

		local TargetHumanoid = Target:FindFirstChildWhichIsA("Humanoid", true)
		if not TargetHumanoid or TargetHumanoid.Health == 0 then CancelMove() continue end

		local TargetRoot = Target.PrimaryPart
		if not TargetRoot then CancelMove() continue end

		ObjectInSight = CheckObjectInSight(Model, Target, workspace.Ignore:GetChildren())

		if ObjectInSight then

			Humanoid:MoveTo(TargetRoot.Position)

		else

			if not Path then

				Path = SimplePath.new(Model)
				Path.Visualize = true

			end

			Path:Run(TargetRoot)

		end

	end

end

EnableAI(script.Parent)
1 Like

Thanks for the help! I took some inspiration from your code and I was able to get it in running order.

A big problem I noticed was for my ObjectInSight funtion it only checks if the raycast hit the HumanoidRootPart of the target, so I fixed that, and it started working a whole lot better!

So thanks again!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.