Pathfinding | Tweening NPC's speed is not linear

Hello, I am currently coding a game where you run away from killbricks that pathfind to you. This game is working out pretty well, except for the fact that the bricks lag behind you. When you run away from the killbrick at a constant speed, it never really catches up to you. It’s hard to explain, but here’s an example of what I want to achieve:

I’ve tried everything I could, from tutorials to other devforum posts, you can even see that this is my first devforum post, because I couldn’t find any solutions!

Here’s my code (I know, I know. It’s very unoptimized. Please don’t reply with any optimization tips if you’re not trying to help me with my main issue, but I’ll accept them.)

local ps = game:GetService("PathfindingService") -- get this service

local ts = game:GetService("TweenService")

local info = TweenInfo.new(0.1, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, 0, false)

repeat task.wait() until game:GetService("ContentProvider").RequestQueueSize == 0 -- wait until the game fully loads

local closestDistance = math.huge
local closestPlayer = nil
local target

local newPos = nil

--local brickSignal = game.Workspace:WaitForChild("brickSignal")

task.wait(3)

local brickSpeed = 0.1

while task.wait() do
	-- run everything below forever

	--if brickSignal.Value == 1 then
		--task.wait(0.1) -- if you don't want to put too much pressure on the server, add a small cooldown

		closestDistance = math.huge -- reset the closest distance so that we can find it over and over
		closestPlayer = nil

		for i,v in pairs(game.Players:GetPlayers()) do -- get every player in the game
			if v and v.Character and v.Character:FindFirstChild("HumanoidRootPart") then
				local distance = (v.Character:FindFirstChild("HumanoidRootPart").Position - script.Parent.Position).Magnitude -- get every player's distances

				--print(distance) -- debug

				if distance <= closestDistance and distance then -- if the distance of the player is smaller than the previous closest distance, then run the code below
					closestDistance = distance

					closestPlayer = v.Character:FindFirstChild("HumanoidRootPart")
					--print(closestDistance.. " is the closest distance")

					script.Parent.Orientation = closestPlayer.Orientation
				end
			end


		end

		if closestPlayer then
		target = closestPlayer.Position
		
		else
		target = script.Parent.Position
		script.Parent.Position = script.Parent.Position
		end
		-- the target to pathfind to
	
		local path = ps:CreatePath() -- create a path

		local pathfind = path:ComputeAsync(script.Parent.Position, target) -- create a path between this npc's humanoid root part and the closest player's humanoid root part


		for z,waypoint in pairs(path:GetWaypoints()) do -- get all of the waypoints
			local newerPos = waypoint.Position
		 
			local bestTween = ts:Create(script.Parent, info, {Position = newerPos})

			bestTween:Play()

		task.wait(math.clamp(brickSpeed, 0.05, 2)/closestDistance)
		end
		if path.Status == Enum.PathStatus.Success then
			--print("i'm coming for you")
			script.Parent["Audio/stone_drag"].Playing = false
		else
			if closestPlayer then
				newPos = closestPlayer.Position
			else
				newPos = script.Parent.Position
			end


			local bestTween = ts:Create(script.Parent, TweenInfo.new(5, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, 0, false), {Position = newPos})

			--game.Chat:Chat(script.Parent, "better run", Enum.ChatColor.Red)
			script.Parent["Audio/stone_drag"].Playing = true

			bestTween:Play()

		end
		path.Blocked:Connect(function(b)
			warn("path blocked")
	end)




	--end


end

Here’s a visual on the issue:

As you can see, even though the brick is supposed to be extremely fast, it starts lagging behind you. I really have tried everything.

Thank you all and I look forward to replies. :sweat_smile:

1 Like

The reason why it’s lagging behind right now is both because: ping, and because pathfinding has a delay and takes some time to compute.

A simple way to do this would be to have the kill brick be a model with a humanoid to be a character. You need a HRP though. Then you would want to have it Move to you with a velocity and acceleration. If a ray cast between target and kill brick is blocked, you should path find. That is normally how people code those things. If you don’t want to do this, another method would have the kill brick be at a constant speed and direction rather than tweening, or at least when the path is directly to the target.

1 Like

I know that I’m supposed to use a Humanoid, but the point of this project is that I’m making a pathfinding killbrick and nothing else. I know this has been done before without a Humanoid, I’m just still trying to grasp it.

1 Like

Then you should have the kill brick move at a constant speed and direction. Use pathfinding and set some variables like direction and in a heartbeat move it in that direction. See if that works, if not, your last option would be to predict movement on the client.

Alright, I’ll give it a go, thanks.

The issue with this is that you can easily trick the brick by rotating your humanoid root part to a certain direction, and because this game is locked in first person, people will easily be able to trick it. Is there a solution that doesn’t require doing all of that, perhaps offsetting the final position?

If you’re talking about lookvector, don’t use the target’s root’s lookvector. Use the CFrame from Brick to Target. CFrame.new(brickPos, targetPos).LookVector

Here’s a simple script that should give you an idea:

local targetPos
local reachedTargetPos
local speed

function heartbeat()
  local location = CFrame.new(brick.Position, targetPos)
  brick.CFrame = location + location.LookVector * speed
  if (brick.CFrame.Position - targetPos).Magnitude < speed then -- If the distance is speed then we went past it
    reachedTargetPos = true
  else
  reachedTargetPos = false
  end
end

function pathFind()
  -- path finding logic here
  repeat wait() until reachedTargetPos
end

while true do pathFind() end

Edit: I’m on laptop and coding on the browser is hard. Sorry if its not readible

I’m not sure if I did this right; but I still get the same result of the brick being offset if you look in a certain direction.

if closestPlayer and humanoid then
		target = closestPlayer.Position + closestPlayer.CFrame.LookVector * humanoid.WalkSpeed / 2
		
		else
		target = script.Parent.Position
		script.Parent.Position = script.Parent.Position
		end

Don’t use the player’s CFrame for movement,

Use the CFrame from the kill brick to the player. CFrame.new(script.Parent.Position, closestPlayer.Position).LookVector

When I do this, the killbrick just randomly stopped after going to a specific position, there’s no error or anything.

if closestPlayer then
				newPos = CFrame.new(script.Parent.Position, closestPlayer.Position).LookVector
			else
				newPos = script.Parent.Position
			end

This is because of latency. The only way to fix this is to control the enemy from the client.
For example, set the network owner of the mob to a player, and fire a remote event which will control the enemy from the client side of said player.

Yes, but there’s another way to fix it, and if you look at the previous replies, you can see that I am currently having trouble with positioning the brick, as it just stops.

Believe me, I’ve tried to do this before lol
The only way to make a smooth AI which will keep up with the player is by controlling it from the client.
For other stuff youll have do so some [roblox hates this word]y wucky stuff to “predict”, it’ll just make things more confusing.

There is no lag on the client. There will ALWAYS be lag on the server. There is no way to move around server lag.

The reason it “stops” is because its moving to where the server thinks you are.

why not just add a character’s humanoidrootpart’s assemblylinearvelocity to the target position in the pathfinding, i’ve had a similar problem before where pathfinding scripts always pathfind to a position where a player USED to be, but is now in a different spot because they walked away

edit: something like

		if closestPlayer then
		target = closestPlayer.Position+closestPlayer.AssemblyLinearVelocity

second edit: reread the post and changed the script

1 Like

I’m a bit cloudy on the pathfinding subject. Do you have any resources relating this?

1 Like

I can’t currently give a source; but I’ll break it down for you.
The pathfinding service does exactly what you think it does- create the shortest path to a position by creating waypoints, then returning them.

The REAL problem is LATENCY.
You’re on a laptop connecting to servers that are generally miles away.
Because of this, if you move, it’ll take the servers time to replicate movement.
If you’re trying to track someone off of a server script, then the server will always be delayed.

I have made a quick script demonstrating this.


The Red ball is having its position set to mine every heartbeat from a server script.
The Green ball is having its position set to mine every heartbeat from a LOCAL/CLIENT script.

The red ball is visually behind my character, while the green ball is pinpoint.
Controlling the NPC from the client means that everything will happen with no lag.
This is why the NPC looks like its “stopping”. It seems like it’s lagging behind you on the client, but from the servers perspective, it’s already caught up to you.

Interesting, I haven’t thought of it that way. I didn’t even know you could calculate paths from the client, but my only nitpick is that if it runs mostly on the client, exploiters would easily be able to manipulate the brick’s position

If it’s singleplayer, who cares? ¯_(ツ)_/¯
That, or you could just add an anti-cheat server-side system lol

I’m horrible at anti-cheats, and I despise exploiters, I know that if I do this, then scripts will be made where players will instantly die, ruining the experience. This is why I wrote the pathfinding script on the server. I don’t need it to be perfect, I just need it to be playable to an extent so that players aren’t bored.

assemblylinearvelocity is your best friend here if you dont care about a picture perfect system that can still catch up to players