How To prevent boat from changing y axis

I am making a boat system which allows players to drive boats on a part ocean, not terrain water. In order to do this I am using body velocity and body gyro to move the boat.

However, I have the issue where whenever the player drives the boat into an obstacle, it rotates the boat upwards and now increases the boats y axis, making the boat sail outside of the water (see video below)

I dont believe this is anything to do with the body gyro or velocity as whenever the player does this their properties seem to remain constant.

Here is the script I use to perform boat movement:

local seat = script.Parent
local data = seat.Configuration
local boat = seat.Parent
local heroSeats = boat.heroSeats
local owner = nil
local running = false
local x = 0
local y = boat.PrimaryPart.Orientation.Y

local playerPetsFolder = workspace.Player_Pets

local tweenService = game:GetService("TweenService")

local tweenInfo = TweenInfo.new(
	2,
	Enum.EasingStyle.Sine,
	Enum.EasingDirection.Out
)

local function handleSittingHeroes()
	local playerPets = playerPetsFolder:FindFirstChild(owner.Name):GetChildren()
	local currentPet = 0
	if #playerPets >= 1 then
		for i, seat in pairs(heroSeats:GetChildren()) do
			if seat:FindFirstChildWhichIsA("Weld") then continue end
			currentPet += 1
			local pet = playerPets[currentPet]
			if pet then
				pet.PrimaryPart.CFrame = seat.CFrame
				pet.PrimaryPart.AssemblyLinearVelocity = Vector3.new(0,0,0)
				pet.PrimaryPart.AssemblyAngularVelocity = Vector3.new(0,0,0)
				
				pet.PrimaryPart.AlignPosition.Enabled = false
				pet.PrimaryPart.AlignOrientation.Enabled = false
				
				seat:Sit(pet.Humanoid)
			end

		end
	end
	
	if currentPet < #playerPets then
		print("some pets were missed")
	end
end

seat.ChildAdded:connect(function(w)
	if w.className == "Weld" and w.Name == "SeatWeld" then
		local char = w.Part1.Parent
		local player = game.Players:FindFirstChild(char.Name)


		if player ~= nil then
			
			
			
			if player.Name ~= boat.Name then--player doesnt own boat - kick them out
				w:Destroy()
				char.Humanoid.Jump = true
				return
			end
			
			--setting up boat ready to drive
			
			seat.Anchored = false
			
			owner = player
			
			player.tempValues.isSeated.Value = true
			player.tempValues.canSwing.Value = false
			handleSittingHeroes()
			
			
			boat.PrimaryPart:SetNetworkOwner(player)
			seat.ChildRemoved:connect(function(w2)
				if w2 == w then
					player.tempValues.isSeated.Value = false
					player.tempValues.canSwing.Value = true
					player = nil
				end
			end)

			seat.BodyVelocity.maxForce = Vector3.new(1, 1, 1) * data.MaxForce.Value
			seat.BodyGyro.maxTorque = Vector3.new(1, 1, 1) * 1000000000000
			local spd = 0

		
			while owner == player do--while player is driving boat

				seat.BodyVelocity.velocity = Vector3.new(seat.CFrame.lookVector.X,0,seat.CFrame.lookVector.Z) * data.currentSpeed.Value
				wait()
			end

			--handling wheen player exits boat

			seat.BodyVelocity.velocity = Vector3.new(0, 0, 0)
			seat.BodyVelocity.maxForce = Vector3.new(0, 0, 0)
			seat.BodyGyro.maxTorque = Vector3.new(0, 0, 0)
			data.currentSpeed.Value = 0
			seat.Anchored = true

			seat.AssemblyLinearVelocity = Vector3.new(0,0,0)
			seat.AssemblyAngularVelocity = Vector3.new(0,0,0)
		end
	end
end)

local function speedHandler()
	if seat.Throttle == 1 then--accelerating
		if data.currentSpeed.Value < data.MaxSpeed.Value then
			if data.currentSpeed.Value + data.Acceleration.Value > data.MaxSpeed.Value then
				data.currentSpeed.Value = data.MaxSpeed.Value
			else
				data.currentSpeed.Value += data.Acceleration.Value 
			end
		end
	elseif seat.Throttle == 0 then--slow boat down
		if data.currentSpeed.Value < 0 then
			if data.currentSpeed.Value + (data.Acceleration.Value*3) > 0 then
				data.currentSpeed.Value = 0
			else
				data.currentSpeed.Value += data.Acceleration.Value*3
			end
		elseif data.currentSpeed.Value > 0 then
			if data.currentSpeed.Value - data.Acceleration.Value*3 < 0 then
				data.currentSpeed.Value = 0
			else
				data.currentSpeed.Value -= data.Acceleration.Value*3
			end
		end
	else--reverse
		if data.currentSpeed.Value > -data.MaxSpeed.Value then--can still speed up reverse
			if data.currentSpeed.Value - data.Acceleration.Value < -data.MaxSpeed.Value then
				data.currentSpeed.Value = -data.MaxSpeed.Value
			else
				data.currentSpeed.Value -= data.Acceleration.Value 
			end
		end
	end

end

seat.Changed:connect(function()
	if not running then
		local cur = seat.Steer
		local cur2 = seat.Throttle
		running = true

		while (cur ~= 0 or cur2 ~= 0) do
			

			
			y = y - seat.Steer * data.TurnSpeed.Value
			
			
			if seat.Steer == -1 then
				x = 5
			elseif seat.Steer == 1 then
				x = -5
			else
				x = 0
			end


			speedHandler()
			
			seat.BodyGyro.cframe = CFrame.new(0, 0, 0) * CFrame.Angles(0, math.rad(y), math.rad(x))
			

			wait()
		end

		running = false
	end
end)

If anyone has any thoughts on how I could fix this it would be greatly appreciated. I’ve had a deep look on the devforum and havent been able to find a solution.

1 Like

Make a separate collision group for the boats, or check if the boat is not higher than the level of water and if there isn’t a boat under it, if both of these conditions are met, return/unanchor the boat so it falls back down into the water.

I believe this can be done with raycasting.

1 Like

The boat doesn’t really fall so to say, the water itself is cancollide off. The bodyVelocity keeps the boat floating in the water.

Ive tried constantly updating the boats cframe to a set y axis but for some reason it glitches out the boat preventing it from moving.

1 Like

I guess if you want to make boats go through each other, make separate collision groups for them. Again, try my raycasting idea, that may work.

1 Like

maybe use an alignPosition instead so you can set the y axis to a constant value

1 Like

I’d suggest to change the CanCollide property and set it to false. I can’t really think of other solutions but If I would be in your place I’d do this

I’ve managed to fix the boat to a specific Y coordinate. I done this by using align position setting it to the boats current pos but with the set Y axis.

However, now I just need to stop the boat rotating upwards on the x axis when it collides with an obstacle, see video below:

I’ve tried using align orientation, plane constraints and constantly updating the cframe of the boat, none seem to work, at least not to a satisfactory standard.

The closest I got was using align orientation, however I needed to set a high responsiveness which would make all the boats turns feel instantaneous and unnatural.

What would be the best way to limit the boats rotation on the x axis?

Could you share with us the code you tried to use for constantly updating the cframe?

sure, I updated the code in ~ line 93 in the origional code:

			while owner == player do--while player is driving boat
			
				alignPosition.Position = Vector3.new(seat.Position.X,yPos,seat.Position.Y)
				seat.BodyVelocity.velocity = Vector3.new(seat.CFrame.lookVector.X,0,seat.CFrame.lookVector.Z) * data.currentSpeed.Value
			
				
				boat:PivotTo(CFrame.new(boat.PrimaryPart.Position.X,boat.PrimaryPart.Position.Y,boat.PrimaryPart.Position.Z) * CFrame.Angles(0,math.rad(boat.PrimaryPart.Orientation.Y),math.rad(boat.PrimaryPart.Orientation.Z)))
				wait()
			end

result:

Consider using the RunService.PostSimulation event instead of a while loop. This will guarantee that no roblox physics will override the changes and cause jittery motion as a result. Also instead of an AlignPosition you could also just do the per-frame CFrame setting for the position as well. BasePart.Orientation is deprecated.

local primaryPart = boat.PrimaryPart
local lockConnection = runService.PostSimulation:Connect(function()
    local angleX, angleY, angleZ = primaryPart.CFrame:ToEulerAnglesXYZ()
    local newCFrame = CFrame.new(primaryPart.Position.X, yPos, primaryPart.Position.Z) * CFrame.Angles(0, angleY, angleZ)

    boat:PivotTo(newCFrame)
    primaryPart.AssemblyLinearVelocity = Vector3.new(primaryPart.AssemblyLinearVelocity.X, 0, primaryPart.AssemblyLinearVelocity.Z) -- eliminates upwards motion without AlignPosition
    primaryPart.AssemblyAngularVelocity = Vector3.new(primaryPart.AssemblyAngularVelocity.X, 0, primaryPart.AssemblyAngularVelocity.Z)  -- eliminates upwards rotational motion without BodyVelocity
end)

I seem to still run into the same sort of issue with using this, the boat just glitches out.

I’d have to assume that must be something with the inaccuracy of CFrame:ToEulerAnglesXYZ() then. Does replacing

    local newCFrame = CFrame.new(primaryPart.Position.X, yPos, primaryPart.Position.Z) * CFrame.Angles(0, angleY, angleZ)

with

    local newCFrame = CFrame.new(primaryPart.Position.X, yPos, primaryPart.Position.Z) * primaryPart.CFrame.Rotation

remove the jitteryness?

I seem to still be experiencing the same issue

Are you connecting and disconnecting the events properly? Plus, the AssemblyLinearVelocity and AssemblyAngularVelocity properties should be modified in the RunService.PreSimulation event so that they get applied to physics properly. Apologies for that mistake.

local primaryPart = boat.PrimaryPart
local velocityLockConnection = runService.PreSimulation:Connect(function()
    primaryPart.AssemblyLinearVelocity = Vector3.new(primaryPart.AssemblyLinearVelocity.X, 0, primaryPart.AssemblyLinearVelocity.Z) -- eliminates upwards motion without AlignPosition
    primaryPart.AssemblyAngularVelocity = Vector3.new(primaryPart.AssemblyAngularVelocity.X, 0, primaryPart.AssemblyAngularVelocity.Z)  -- eliminates upwards rotational motion without BodyVelocity
end)
local lockConnection = runService.PostSimulation:Connect(function()
    local angleX, angleY, angleZ = primaryPart.CFrame:ToEulerAnglesXYZ()
    local newCFrame = CFrame.new(primaryPart.Position.X, yPos, primaryPart.Position.Z) * CFrame.Angles(0, angleY, angleZ)

    boat:PivotTo(newCFrame)
end)