Vehicle moves along linear axis when steering (not rotating)

This seems like the type of thing that has happened before, but I haven’t been able to find a forum post for it yet - if there’s an existing post addressing this issue, please link me to it!

I’m trying to make a vehicle in Roblox Studio, and have created a decent framework for accelerating, decelerating, and reversing. But when I steer the vehicle, it moves along a linear axis in the direction of where it is being steered (seen below).

Things I know don’t work:

  • Creating a differential between the two rear wheels
    Subtracting a defined speed from the inner rear wheel’s angular velocity. Currently using a differential in that the rear left and rear right wheels use separate axles, both of which are acted upon by individual angular velocities that allow it to drive forward/reverse, but there is nothing constraining the two wheels/axles to the same angular velocity (they should be able to spin independently of one another)
  • Using an angular velocity to “assist” (force) the vehicle to turn
    Technically worked, but the turning was very snappy and not what I was looking for. I’d like to refrain from using cheaty solutions like this

Information

The front wheels are unpowered and spin freely, their steering angle determined by a hinge with a servo. The rear wheels are powered, welded to their respective axle which is acted upon by an angular velocity allowing it to spin.

Script (activated by another script when a user enters the driver’s seat, disabled on exit)

script.Starting:Play() -- engine start sound
wait(script.Starting.TimeLength - .5)
script.Idle:Play() -- engine idle sound
script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity = Vector3.new(script.Vars.Gears.Base.Value, 0, 0) -- setting proper velocities as defined in the Vars folder under the script
script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity = Vector3.new(script.Vars.Gears.Base.Value, 0, 0) -- setting proper velocities as defined in the Vars folder under the script

local tireFriction = script.Vars.TireGrip.Value -- unused
local gear = 1 -- current gear

local forwards = true
local steering = false

local function brake()
	script.Parent.Parent.AxleRR.AngularVelocity.Enabled = false
	script.Parent.Parent.AxleRL.AngularVelocity.Enabled = false
	script.Parent.Parent.BrakeDisc.CanCollide = true
	for i,v in pairs(script.Parent.Parent.BrakeLights:GetChildren()) do
		if v.ClassName == "Part" then
			v.Material = Enum.Material.Neon
		end
	end
end
local function offBrake()
	script.Parent.Parent.AxleRR.AngularVelocity.Enabled = false
	script.Parent.Parent.AxleRL.AngularVelocity.Enabled = false
	script.Parent.Parent.BrakeDisc.CanCollide = false
	for i,v in pairs(script.Parent.Parent.BrakeLights:GetChildren()) do
		if v.ClassName == "Part" then
			v.Material = Enum.Material.Glass
		end
	end
	for i,v in pairs(script.Parent.Parent.ReverseLights:GetChildren()) do
		if v.ClassName == "Part" then
			v.Material = Enum.Material.Glass
		end
	end
end
local function accelerateF()
	offBrake()
	forwards = true
	script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity = Vector3.new(math.abs(script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity.X), 0, 0) -- make it forwards
	script.Parent.Parent.AxleRR.AngularVelocity.Enabled = true -- start applying force
	script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity = Vector3.new(math.abs(script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity.X), 0, 0) -- same thing with the other axle
	script.Parent.Parent.AxleRL.AngularVelocity.Enabled = true
end
local function accelerateR()
	offBrake()
	forwards = false
	script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity = Vector3.new(-math.abs(script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity.X), 0, 0) -- make it backwards
	script.Parent.Parent.AxleRR.AngularVelocity.Enabled = true -- start applying force
	script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity = Vector3.new(-math.abs(script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity.X), 0, 0) -- same thing with the other axle
	script.Parent.Parent.AxleRL.AngularVelocity.Enabled = true
	for i,v in pairs(script.Parent.Parent.ReverseLights:GetChildren()) do
		if v.ClassName == "Part" then
			v.Material = Enum.Material.Neon
		end
	end
end
script.Parent.Changed:Connect(function(prop)
	if prop == "Throttle" then
		if script.Parent.Throttle == 1 then -- pressing W
			if not forwards and script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > .1 then -- going backwards
				if script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude < .5 and script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > -.5 then -- moving slowly
					brake()
				else
					brake()
				end
			else -- going forward or stopped
				accelerateF()
			end
		elseif script.Parent.Throttle == -1 then -- pressing S
			if forwards and script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > .1 then -- going forwards
				if script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude < .5 and script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > -.5 then -- moving slowly
					brake()
				else
					brake()
				end
			else -- going backwards or stopped
				accelerateR()
			end
		else -- no input
			if script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude < .5 and script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > -.5 then -- moving slowly
				brake()
			else
				offBrake()
			end
		end
	elseif prop == "Steer" then
		if script.Parent.Steer == 1 then -- pressing D
			steering = true
			script.Parent.Parent.AxleF.FRSteerHinge.TargetAngle = script.Parent.Parent.AxleF.FRSteerHinge.UpperAngle
			script.Parent.Parent.AxleF.FLSteerHinge.TargetAngle = script.Parent.Parent.AxleF.FLSteerHinge.UpperAngle
		elseif script.Parent.Steer == -1 then -- pressing A
			steering = true
			script.Parent.Parent.AxleF.FRSteerHinge.TargetAngle = script.Parent.Parent.AxleF.FRSteerHinge.LowerAngle
			script.Parent.Parent.AxleF.FLSteerHinge.TargetAngle = script.Parent.Parent.AxleF.FLSteerHinge.LowerAngle
		else -- no input
			steering = false
			script.Parent.Parent.AxleF.FRSteerHinge.TargetAngle = 0
			script.Parent.Parent.AxleF.FLSteerHinge.TargetAngle = 0
			wait(script.Parent.Parent.AxleF.FRSteerHinge.UpperAngle/(script.Parent.Parent.AxleF.FRSteerHinge.AngularSpeed * 57.7) + .15)
		end
	end
end)
while script.Manual.Value == false do -- script for automatic transmission
	wait(.25)
	script.Idle.PlaybackSpeed = (script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude / (script.Vars.Gears.Base.Value + script.Vars.Gears.AddedPG.Value * script.Vars.Gears.Value)) * 1.25 + 1
	if forwards then
		print(gear) -- debugging temporary
		if script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity.Magnitude - .45 and gear < script.Vars.Gears.Value then
			wait(.45)
			if script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude > script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity.Magnitude - .45 then
				gear += 1
				script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity = Vector3.new(script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity.X + script.Vars.Gears.AddedPG.Value, 0, 0)
				script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity = Vector3.new(script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity.X + script.Vars.Gears.AddedPG.Value, 0, 0)
				script.Shift:Play()
			end
		elseif script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude < (script.Vars.Gears.Base.Value + (script.Vars.Gears.AddedPG.Value * gear)) - script.Vars.Gears.AddedPG.Value - .45 and gear > 1 then
			wait(.45)
			if script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude < (script.Vars.Gears.Base.Value + (script.Vars.Gears.AddedPG.Value * gear)) - script.Vars.Gears.AddedPG.Value - .45 then
				gear -= 1
				script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity = Vector3.new(script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity.X - script.Vars.Gears.AddedPG.Value, 0, 0)
				script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity = Vector3.new(script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity.X - script.Vars.Gears.AddedPG.Value, 0, 0)
				script.ShiftDown:Play()
			end
		end
	else
		gear = 1
		script.Parent.Parent.AxleRR.AngularVelocity.AngularVelocity = Vector3.new(-script.Vars.Gears.Base.Value, 0, 0)
		script.Parent.Parent.AxleRL.AngularVelocity.AngularVelocity = Vector3.new(-script.Vars.Gears.Base.Value, 0, 0)
	end
end
while true do
	wait(.01)
	script.Idle.PlaybackSpeed = (script.Parent.Parent.AxleRR.AssemblyAngularVelocity.Magnitude / (script.Vars.Gears.Base.Value + script.Vars.Gears.AddedPG.Value * script.Vars.Gears.Value)) * 1.25 + 1
end

This is probably a horrible script but I’ll optimize and make it readable once the car works properly. If you need any more information, let me know. Thank you!