Pathfinding Vehicle - How would it work?

So, I’m working on an AI traffic system. I’m using an A* pathfinding module, but that really shouldn’t change anything.

Currently I’ve just added a humanoid to the vehicle and made it move to a certain position, this works, but isn’t enough for a vehicle. I want my vehicle to actually steer, and accelerate / brake to get to the next point. I’m using Roblox’s new Racing Template which I grabbed the vehicle from.

I’m really new to this stuff, and don’t know how I’d even start with making the vehicle drive. I want it to drive similar to as it would if a player was driving it. So if the max speed was 10, I wouldn’t want it to go over that, and all that stuff.

Here’s a snippet my current code which makes the vehicle walk to the next point.

if (path) then
		table.insert(path, 1, start)
		table.insert(path, goal)
		local pathfolder = Instance.new("Folder", workspace)
		pathfolder.Name = "VisualPath"
		for i = 2,#path do
			local p1 = path[i - 1] -- P1 is where the vehicle is located (last waypoint)
			local p2 = path[i] -- P2 is where it needs to go.

			humNPC:MoveTo(p2) -- Makes the humanoid walk to P2
			humNPC.MoveToFinished:Wait() -- Waits, and loops again so it walks to the next point.
		end
	else
		warn("Failed to find Path.")
	end

What it currently looks like:

How would I get the vehicle to even move (like it would with a player controlling it) in the first place?

You can create an algorithm using Roblox’s physics.
(Instead of directly using :MoveTo(), use LinearVelocity, for example.)

  • The car slowly accelerates and decelerates, like in a real car. Think of how a human would drive a car and design the algorithm to create an AI that imitates a human being.
  • The AI should know where to slow down, and where to speed up to a certain speed, like when it’s making a turn, it should slow down, whereas when it’s going straight on a normal road, it should speed up to 40-50mph for example.
  • When the car starts and stops, it shouldn’t do it immediately, for example, when it starts moving, it should reach a certain amount of speed in a period, rather than immediately.

This won’t be an easy task, as there are many parameters you have to account for if you wish to create a car AI that resembles a human. It should be configured to feel and look natural, the car, for example, should have weight.

It would be best if you continued using the path system you have right now, but changed how you handle how the car works.

I recommend body velocity, although its deprecated it is still better in certain situations like this.

1 Like

Hey! I’m happy to see this question, since I actually built the car with these sorts of use-cases in mind.

You can start out by removing some of the unnecessary client control scripts and adding a new script for the AI driving

To make the car drive around, all you need to do is hook up the Controller:update function

-- AI driver script

local RunService = game:GetService("RunService")

local Controller = require(script.Parent.Parent.Controller)

local function onStepped(_, deltaTime: number)
	Controller:update(deltaTime)
end

RunService.Stepped:Connect(onStepped)

You can now change the steeringInput, throttleInput, nitroInput, and handBrakeInput attributes on Car.Inputs and it will respond accordingly.

Making a proper car driving algorithm is a lot more involved, as you’ll want to do some sort of path planning to optimize the steering/throttle/braking/etc, but here’s a simple example of getting it to drive to a specific target part in workspace.

-- AI driver script

local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local Controller = require(script.Parent.Parent.Controller)
local Constants = require(script.Parent.Parent.Constants)

local car = script.Parent.Parent.Parent
local chassis = car.Chassis
local inputs = car.Inputs
local target = Workspace.Target

local MIN_SPEED_DISTANCE = 20
local MAX_SPEED_DISTANCE = 100

local function onStepped(_, deltaTime: number)
	-- Calculate the direction to the target
	local targetOffset = chassis.CFrame:PointToObjectSpace(target.Position)
	local targetAngle = math.atan2(targetOffset.X, -targetOffset.Z)
	
	-- Update the steering direction
	inputs:SetAttribute(Constants.STEERING_INPUT_ATTRIBUTE, targetAngle)
	
	-- Calculate the distance to the target
	local distance = targetOffset.Magnitude
	-- Calculate a throttle speed based on distance. We want to go slower when closer so
	-- that our turning radius is tighter
	local throttle = math.clamp((distance - MIN_SPEED_DISTANCE) / MAX_SPEED_DISTANCE, 0, 1)
	
	-- Update the throttle
	inputs:SetAttribute(Constants.THROTTLE_INPUT_ATTRIBUTE, throttle)
	
	-- Update the controller
	Controller:update(deltaTime)
end

RunService.Stepped:Connect(onStepped)

You could modify this to pick the next target in your path once the car gets close enough

3 Likes

This is very similar to how I built self driving vehicles in Garry’s Mod

Additional things I would consider

  • Graph of nodes placed for every road intersection with links between neighbors
  • Drive to target by finding nearest node to target, then calculate using Dijkstra’s algorithmn the route towards the vehicle, select the last node in the list as current node to drive towards
  • Add simple ray tracing in front of the vehicle to brake when it detects an obstacle
  • Put a bias on targetAngle so the vehicle drives more on the left or right side of the road
1 Like

Thanks! I’ll try this and let you know how it goes.

Edit: It works! Thank you so much

How would I detect once the car has reached the destination so I can set it to drive to the next point?

Nvm, I’m just checking the distance

How would I make the vehicle stop? Like slow down till a stop? I need this for traffic lights.

If you set the throttleInput attribute to 0 the car will slow to a stop. If you put it in reverse while it’s rolling forward it will automatically brake.

You could do something like

-- inside onStepped

if shouldBeBraking then
    -- apply brakes while moving forward
    local forwardVelocity = chassis.CFrame:VectorToObjectSpace(chassis.AssemblyLinearVelocity)
    -- note: -Z is the forward axis
    if forwardVelocity.Z < -5 then
        inputs:SetAttribute(Constants.THROTTLE_INPUT_ATTRIBUTE, -1)
    else
        inputs:SetAttribute(Constants.THROTTLE_INPUT_ATTRIBUTE, 0)
    end
end

Alternatively you could apply the handbrake, but that might cause it to skid out

1 Like