Making hoverbike capable of traversing slopes

I currently have a hoverbike, however, I want it to be able to go up wedges and other slopes. I thought about raycasting below the bike to determine the slope and then update the orientation of the bike, however, that would only work for parts and not terrain slopes (I think). Is there a different way I should go about this?

Current bike functionality: https://cdn.discordapp.com/attachments/779571552102383706/1114698014058090576/2023-06-03_18-28-34.mp4

Code:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")

local settings = {
	height = 2,
	reverse = true,
	speed = 50,
	rev = -10,
	acceleration = 0.3,
	deceleration = 0.5,
	turn = 2,
	force = 4000000,
}

local Speeder = script.Parent
local Seat = Speeder:WaitForChild("VehicleSeat")
local BodyPosition = Seat:FindFirstChildOfClass("BodyPosition")
local BodyGyro = Seat:FindFirstChildOfClass("BodyGyro")
local BodyVelocity = Seat:FindFirstChildOfClass("BodyVelocity")
local ProximityPrompt = Seat:FindFirstChildOfClass("Attachment").ProximityPrompt
local Headlight = Speeder:WaitForChild("Headlight").SurfaceLight

local Animation = Instance.new("Animation")
Animation.AnimationId = "rbxassetid://13640690698"

local float = 0.25
local speed = 0
local height = settings.height
local connection
local tween
local track

local function cast(first: CFrame, second: CFrame, ignore: any, distance: number)
	local ray = Ray.new(first.Position, (second.Position - first.Position).Unit * distance)
	local object, position = workspace:FindPartOnRay(ray, ignore)

	return workspace:FindPartOnRay(ray, ignore)
end

local function cleanup()
	if connection then
		connection:Disconnect()
		connection = nil
	end
	
	if tween then
		tween:Cancel()
		tween = nil
	end
	
	if track then
		track:Stop()
		track = nil
	end
end

Seat:GetPropertyChangedSignal("Occupant"):Connect(function()
	local occupant = Seat.Occupant

	if occupant then
		local animator = occupant:FindFirstChildOfClass("Animator")

		if (not animator) then
			return
		end
		
		cleanup()

		track = animator:LoadAnimation(Animation)
		track.Looped = true
		track:Play()
		
		Seat.Anchored = false
		Headlight.Enabled = true
		ProximityPrompt.Enabled = false
		BodyVelocity.MaxForce = Vector3.new(100000,0,100000)

		connection = RunService.Stepped:Connect(function()
			local emit = Seat.CFrame * CFrame.new(0,-1,-4)
			local object, position = cast(emit,emit * CFrame.new(0,-100,0), Speeder, settings.height)

			if object then
				BodyPosition.Position = Vector3.new(position.X,position.Y + settings.height,position.Z)
				BodyPosition.MaxForce = Vector3.new(0,40000000,0)
			else
				BodyPosition.MaxForce = Vector3.new(0,0,0)
			end

			if Seat.Throttle == 1 then
				if speed + settings.acceleration > settings.speed then
					speed = settings.speed
				elseif speed + settings.acceleration <= settings.speed then
					speed = speed + settings.acceleration
				end
			elseif Seat.Throttle == -1 then
				if speed - settings.deceleration > 0 then
					speed = speed - settings.deceleration
				elseif speed - settings.deceleration <= 0 then
					if settings.reverse == true then
						if speed - settings.deceleration > settings.rev then
							speed = speed - settings.deceleration
						elseif speed - settings.deceleration <= settings.rev then
							speed = settings.rev
						end
						speed = speed - settings.deceleration
					else
						speed = 1
					end
				end
			end

			if Seat.Steer == 1 then
				BodyGyro.CFrame = BodyGyro.CFrame * CFrame.Angles(0,-0.05,0)
			elseif Seat.Steer == -1 then
				BodyGyro.CFrame = BodyGyro.CFrame * CFrame.Angles(0,0.05,0)
			end

			BodyVelocity.Velocity = Seat.CFrame.lookVector * speed
		end)
	else
		Seat.Anchored = true
		Headlight.Enabled = false
		ProximityPrompt.Enabled = true
		BodyVelocity.MaxForce = Vector3.new(0,0,0)
		BodyPosition.MaxForce = Vector3.new(40000000,40000000,40000000)

		cleanup()
		
		connection = RunService.Heartbeat:Connect(function()
			if tween then
				return
			end
			
			if float == 0.25 then 
				float = -0.25 
			else 
				float = 0.25
			end
			
			tween = TweenService:Create(Seat,TweenInfo.new(2,Enum.EasingStyle.Sine,Enum.EasingDirection.InOut),{CFrame = Seat.CFrame + Vector3.new(0,float,0)})
			tween:Play()
			
			tween.Completed:Connect(function()
				tween = nil
			end)
		end)
	end
end)

ProximityPrompt.Triggered:Connect(function(player: Player)
	if Seat.Occupant then
		return
	end

	local character = player.Character
	local humanoid = character:FindFirstChildOfClass("Humanoid")

	if (not character) or (not humanoid) then
		return
	end

	Seat:Sit(humanoid)
end)

connection = RunService.Heartbeat:Connect(function()
	if tween then
		return
	end

	if float == 0.25 then 
		float = -0.25 
	else 
		float = 0.25
	end

	tween = TweenService:Create(Seat,TweenInfo.new(2,Enum.EasingStyle.Sine,Enum.EasingDirection.InOut),{CFrame = Seat.CFrame + Vector3.new(0,float,0)})
	tween:Play()

	tween.Completed:Connect(function()
		tween = nil
	end)
end)

use the raycasting ray.new not onpart

I’m unsure if this is the solution you’re looking for but you can try turning the canCollide off for the parts of the bike that are colliding with the slope.

Raycasting works the same for terrain as it does parts.

You might also want to check this out.