Drone AI Pathfinding?

Hello everyone! :wave::slightly_smiling_face:

So I’m currently working on a survival game where the players have to find a way to survive attacking drones, which are able to track paths and sounds. The drones are supposed to behave similar to the egg drones of the Sonic Movie franchise:

Sonic Movie Hype — This is Tom and Sonic's relationship in a...

A video of my progress (the visualized path you see in the video is not part of the drone)

Extra: Uhhhhh

As you can see in the video, the drone can duck if it detects a roof, and can go up when the ground below it goes up as well. Currently, the system searches the most direct way (in a straight line) to the target (which is a Vector3).

The current script

local RunService = game:GetService("RunService")
local Drone = script.Parent
local RootPart = Drone.RootPart
local LinearVelocity = RootPart.LinearVelocity
local AlignOrientation = RootPart.AlignOrientation

local RayParams = RaycastParams.new()
RayParams.FilterDescendantsInstances = {Drone, table.unpack(CollectionService:GetTagged("AI_RaycastIgnore"))}
RayParams.FilterType = Enum.RaycastFilterType.Exclude

local Speed = 10
local HeightResponsiveness = 2.5
local SearchingHeight = 10
local ObservingHeight = 25
local MaxScanDistance = 100
local Waypoints = {
	Vector3.new(-57, 30, -111),
	Vector3.new(10, 46, -90),
	Vector3.new(-55, 16, 30)
}
local currentWaypointIndex = 1
local connection

local Mode = "Searching"


local function getTargetHeight()
	local forwardOffset = 10 -- How many studs ahead to predict
	local droneCFrame = RootPart.CFrame

	local origin = droneCFrame.Position
	local forwardPosition = origin + droneCFrame.LookVector * forwardOffset

	-- Raycast down from current position
	local downResult = workspace:Raycast(origin, Vector3.new(0, -MaxScanDistance, 0), RayParams)

	-- Raycast down from forward position
	local forwardDownResult = workspace:Raycast(forwardPosition, Vector3.new(0, -MaxScanDistance, 0), RayParams)

	-- Optionally raycast up if you care about ceilings
	local upResult = workspace:Raycast(origin, Vector3.new(0, MaxScanDistance, 0), RayParams)
	local forwardUpResult = workspace:Raycast(forwardPosition, Vector3.new(0, MaxScanDistance, 0), RayParams)

	-- Find ground and ceiling ahead
	local groundY, ceilingY

	if forwardDownResult then
		groundY = forwardDownResult.Position.Y
	elseif downResult then
		groundY = downResult.Position.Y
	end

	if forwardUpResult then
		ceilingY = forwardUpResult.Position.Y
	elseif upResult then
		ceilingY = upResult.Position.Y
	end

	-- If both found, hover in the middle
	if groundY and ceilingY then
		return (groundY + ceilingY) / 2
	elseif groundY then
		if Mode == "Searching" then
			return groundY + SearchingHeight
		else
			return groundY + ObservingHeight
		end
	elseif ceilingY then
		if Mode == "Searching" then
			return groundY - SearchingHeight
		else
			return groundY - ObservingHeight
		end
	else
		return origin.Y -- No surfaces found, stay at current Y
	end
end

function updateMovement(targetWaypoint: Vector3)
	connection = RunService.Heartbeat:Connect(function()
		local horizontalDirection = (Vector3.new(targetWaypoint.X, RootPart.Position.Y, targetWaypoint.Z) - Vector3.new(RootPart.Position.X, RootPart.Position.Y, RootPart.Position.Z)).Unit
		local horizontalVelocity = horizontalDirection * Speed

		local desiredY = getTargetHeight()
		local heightError = desiredY - RootPart.Position.Y
		local verticalVelocity = Vector3.new(0, heightError * HeightResponsiveness, 0)

		local finalVelocity = horizontalVelocity + verticalVelocity
		LinearVelocity.VectorVelocity = finalVelocity

		if horizontalDirection.Magnitude > 0.1 then
			local lookAtCFrame = CFrame.lookAt(RootPart.Position, RootPart.Position + horizontalDirection)
			AlignOrientation.CFrame = lookAtCFrame
		end
		
		local distanceToWaypoint = (Vector3.new(RootPart.Position.X, 0, RootPart.Position.Z) - Vector3.new(targetWaypoint.X, 0, targetWaypoint.Z)).Magnitude
		if distanceToWaypoint < 5 then
			LinearVelocity.VectorVelocity = Vector3.zero
			connection:Disconnect()
		end
	end)
end

local targetPos = Waypoints[currentWaypointIndex]
updateMovement(targetPos)

How it is set up

Roblox Studio 27-4-2025 14_10_04

But of course this is not how I want it to function in-game. A drone has to find its own path to the target, including avoiding obstacles.
With Roblox’s current pathfinding system, which is mainly used for ground-based NPCs, it is nearly impossible to achieve flight-based pathfinding without having to make your own system for it.

I hope that someone out there has tips or something else that I could use to achieve the pathfinding functionality that I want to achieve.

5 Likes

Afaik, to reach what you desire, you need to make your own pathfinding system, such as A*.

There isn’t much I can tell you, since all you have to do is just google “A* pathfinding” (add “roblox” to the end as well if you want) and go on from there. Tho, depending on how sophisicated you want to make the pathfinding, it might get quite difficult.
Imo the easiest way would be to just imagine a 3D grid of waypoints along which the pathfinding would move.

2 Likes

You can still use Roblox’s pathfinding service. I would suggest this because it will likely be faster than a pathfinding system implemented in Lua.

If you want to achieve flight-based pathfinding, why not just ignore the waypoint’s Y coordinate when you move your drone? It seems like you already have the vertical movement sorted out, so this should work for you.

For example, try having your drone move toward a position constructed like so: local targetPosition = Vector3.new(waypoint.X, calculatedDroneHeight, waypoint.Z).

Typical pathfinding systems would pass that target vector into Humanoid:MoveTo(), but it seems like you use a linear velocity instead. To emulate this in your system, you have to set the Linear Velocity velocity vector to the vector (targetPosition - dronePosition).Unit * droneSpeed. Remember to account for the time it will take the drone to reach that position.

If that sounds like too much work, you can use an Align Position contraint instead.

1 Like

Pathfinding accounts for whether a character can walk said path, so if the drone is blocked by a tall wall, there would be no path found, because the average character (“human”) can’t magically jump over it; a drone however would be able to, but pathfinding doesn’t know that.

2 Likes

Well, I’ve made some progress. Thanks everyone!

1 Like