AI Killbot goes into crazy velocity for unknown reason

Hello devs,

Today when I’m trying to write a Killbot AI (if you don’t know what is Killbot it is just a classic NPC or robot, details here), I found out the bot could sometimes, for unknown reason, reach an extremely high velocity, here’s a video for reference:

(skip to 0:10 if you like)

Here’s the movement AI script:

function fire(target)
	local dir = target - sphere.Position
	dir = computeDirection(dir)

	local missile = Rocket:clone()

	local spawnPos = sphere.Position

	local pos = spawnPos + (dir * 5)

	--missile.Position = pos
	missile.CFrame = CFrame.new(pos, pos + dir)

	missile.RocketScript.Disabled = false
	missile.Parent = game.Workspace

	missile:SetNetworkOwner(nil)
end

function computeDirection(vec)
	local lenSquared = vec.magnitude * vec.magnitude
	local invSqrt = 1 / math.sqrt(lenSquared)
	return Vector3.new(vec.x * invSqrt, vec.y * invSqrt, vec.z * invSqrt)
end

function scanForHumans()
	local humansFound = {}

	local players = game.Players:children()

	if (#players < 1) then return end

	myTarget = players[math.random(1, #players)].Character
end

function moveKillbot()
	warn("move")
	local x = math.random(-30,30)
	local y = math.random(-8,8)
	local z = math.random(-30,30)

	local newPos = Vector3.new(sphere.Position.X+x,sphere.Position.Y+y,sphere.Position.Z+z)

	local TI = TweenInfo.new(
		math.abs((sphere.Position-newPos).Magnitude)/10,
		Enum.EasingStyle.Quad,
		Enum.EasingDirection.InOut
	)
	
	local result = workspace:Raycast(sphere.Position, newPos - sphere.Position)

	if result and (result.Instance and math.abs((sphere.Position-newPos).Magnitude) > result.Distance) then
		task.wait(1)
		moveKillbot()
		return
	end
	
	local tween = TS:Create(sphere,TI,{Position = newPos})
	tween:Play()

	tween.Completed:Connect(function() tween:Destroy() end)
end

task.spawn(function()
	while task.wait() do
		if myTarget ~= nil then
			sphere.CFrame = CFrame.lookAt(sphere.Position, myTarget.PrimaryPart.Position)
			print(sphere.CFrame)
		end
	end
end)

while true do  -- loop forever
	scanForHumans()

	if myTarget then
		fire(myTarget.PrimaryPart.Position)
		wait(math.random(2,3))

		moveKillbot()
	end

	task.wait(0.03) 
end

(some parts that are not related were cut)

Lastly, here’s the hierachy of the Bot Instance:
Screenshot 2025-04-22 163205

Any help is appreciated!!!

8 Likes

i don’t really know but it may be related to a precise angle that causes itself to be flung that badly

2 Likes

Well I believe the problem most likely might stem from the computeDirection function, specifically this part, here:

local lenSquared = vec.magnitude * vec.magnitude
local invSqrt = 1 / math.sqrt(lenSquared)

This can cause high values IF the vector vec has a very very small magnitude (basically, as close to zero as possible) That would lead to a large velocity when applying the direction.
I think you should add a check to ensure the vector’s magnitude isn’t too small before going any further

2 Likes

I’m pretty sure the issue is actually that you are making the tween faster based on the distance to the player.

Because you also randomly select position values every frame within the range (-30, 30). (another issue i see)

Imagine that the player is really close, and then the bot instantly tries to move 30 studs away because you have made the tween faster based on the distance to the player.

Usually the way this is done is with a cooldown. The killbot will select a direction to move, and then wait to select a new direction, and directions should be normalized vectors. (so your movement tweens are consistent)

2 Likes

I’m actually trying to make the tween faster if the distance of traveling is longer, not based on the distance to the player.

1 Like

Try doing something like this for moveKillbot:

local Random = Random.new(math.random(2147483647))


local MovementDirection = Random:NextUnitVector()

local ChangeDirectionCooldown = 0

local Epoch = tick()

function moveKillbot()

	warn("move")


	if tick() - Epoch > ChangeDirectionCooldown then

		MovementDirection = Random:NextUnitVector()

        ChangeDirectionCooldown = math.random(3)

        Epoch = tick()
	end

	local newPos = sphere.Position + MovementDirection
	
	local toNewPos = newPos - sphere.Position


	if workspace:Raycast(sphere.Position, toNewPos) then -- The ray will only go as far as the toNewPos vector's magnitude
		
		MovementDirection = Random:NextUnitVector()
		
		return
	end
	

	local TI = TweenInfo.new(
		
		0.1,

		Enum.EasingStyle.Linear
	)

	local tween = TS:Create(sphere, TI, {Position = newPos})
	
	tween:Play()

	tween.Completed:Wait()
end

This makes it so the bot is always moving with a consistent velocity

Also, I should mention that the tween should not complete faster based on the distance. The tween wil already look faster because it takes the same amount of time to travel more distance. But when you are using direction vectors instead, this is negligible

2 Likes

Right now, in your moveKillbot function, you’re calculating the tween duration based on the distance to the new position divided by 10, which makes the bot move at a constant speed. To make the bot move faster for longer distances, you can tweak that calculation.

Instead of dividing by 10, you could scale the duration based on the distance, like this: math.abs((sphere.Position - newPos).Magnitude) / 50. This will make the bot move faster as the distance increases. You can adjust the 50 value to control the speed, and add a math.max(0.5, ...) to make sure the bot doesn’t move too fast for shorter distances. This will keep the bot moving at a reasonable speed even for small distances.

Also, in your scanForHumans function, you’re picking a random player to target. That’s fine, but if you want a more precise targeting system, we could tweak it further, though that’s not part of your movement issue.
Try changing your tween’s duration calculation to something like this:

local TI = TweenInfo.new(
    math.max(0.5, math.abs((sphere.Position - newPos).Magnitude) / 50), -- Duration now increases with distance
    Enum.EasingStyle.Quad,
    Enum.EasingDirection.InOut
)
2 Likes

and remove the math.abs() while you’re at it :slight_smile:

2 Likes
local TI = TweenInfo.new(
    math.max(0.5, (sphere.Position - newPos).Magnitude / 50)
    Enum.EasingStyle.Quad,
    Enum.EasingDirection.InOut
)
2 Likes

It was just a joke :+1: thank you though!

2 Likes