Adding CFrame instead of setting it

Hey. I’m wondering if it’s possible in Luau to add CFrame instead of setting it? Essentially in my code, the boat will follow the waves for the visual purpose - however since I’m constantly calling :PivotTo() im unable to use updateForces() to steer my ship.

A video following this will be added here:

Code:

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

local WaterHeightCalculator = require(ReplicatedStorage:WaitForChild("WaterHeightCalculator"))

local boat = workspace:WaitForChild("Boat")
local vehicleSeat = boat.VehicleSeat
local primaryPart = boat.PrimaryPart
if not primaryPart then error("Boat model must have a PrimaryPart set!") end

local BASE_XZ_POSITION = Vector3.new(primaryPart.Position.X, 0, primaryPart.Position.Z)
local WATER_LEVEL = primaryPart.Position.Y - 3

local force = 75 * 10^3
local torque = 100 * 10^3

function updateForces()
    vehicleSeat.F.Force = Vector3.new(force * .5 * -vehicleSeat.Steer * vehicleSeat.Throttle, 0, force * vehicleSeat.Throttle)
    vehicleSeat.T.Torque = Vector3.new(0, torque * -vehicleSeat.Steer * vehicleSeat.Throttle, 0)
end

RunService.Heartbeat:Connect(function()
    local waveOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X, BASE_XZ_POSITION.Z)
    local newYPosition = WATER_LEVEL + waveOffset
    local newPosition = Vector3.new(BASE_XZ_POSITION.X, newYPosition, BASE_XZ_POSITION.Z)

    local forwardOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X + 2, BASE_XZ_POSITION.Z)
    local rightOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X, BASE_XZ_POSITION.Z + 2)

    local pitch = math.atan2(forwardOffset - waveOffset, 2)
    local roll = math.atan2(rightOffset - waveOffset, 2)

    local cf = CFrame.new(newPosition) * CFrame.Angles(pitch, 0, roll)
    primaryPart:PivotTo(cf)

    updateForces()
end)

Thanks! :smile:

Could you explain better what you meant by “adding a CFrame”? I didn’t quite understand, sorry.

Try using .Stepped or postphysics instead of heartbeat

1 Like

Hi so basically this code is from a ressource i found here on Devforum → Sampling Terrain Water Height at Any Position

And i seem to have the base system set down, as the boat follows the waves (which is intended). However i think the issue arises when i call the :PivotTo()

primaryPart:PivotTo(cf)

Which constantly overwrites the CFrame to make sure the boat fits with the waves. WIth this though, when it then calls

updateForces()

To make me be able to steer the ship, im unable to because the CFrame is constantly being set with the PivotTo(). Atleast this is the idea i have. So essentially I’m looking for a way to also let me be able to steer the ship while it follows the waves

Tried, no luck. I think the issue stems back to the PivotTo()

This article may help:

I think it would be best to steer the ship using CFrame as well. Using physics constraints on an assembly whilst manipulating its CFrame tends to create this kind of problem, although I’m surprised that @dthecoolest’s suggestion to use Stepped failed to work, since it runs before physics is simulated. You should also try using PreSimulation (dthecoolest also suggested using PostSimulation, which is equivalent to Heartbeat, and runs after physics is simulated), which is a newer alternative to Stepped, and also runs before physics is simulated

How would i go about doing this?

You can use Heartbeat’s delta time parameter to interpolate a value that will act as the ship’s steer angle, and math.clamp to limit it

You can control how fast the ship changes angle by multiplying the delta time with a value that will act as the rate

(Currently I’m on my phone, so there might be some formatting issues or typos):

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

local STEER_ANGLE = math.rad(45) -- Set this to the maximum angle you’d like the ship to be able to steer
local STEER_SPEED = STEER_ANGLE / 1 -- Set 1 to the desired amount of seconds it should take for the ship to reach full steering lock

local WaterHeightCalculator = require(ReplicatedStorage:WaitForChild("WaterHeightCalculator"))

local boat = workspace:WaitForChild("Boat")
local vehicleSeat = boat.VehicleSeat
local primaryPart = boat.PrimaryPart
if not primaryPart then error("Boat model must have a PrimaryPart set!") end

local BASE_XZ_POSITION = Vector3.new(primaryPart.Position.X, 0, primaryPart.Position.Z)
local WATER_LEVEL = primaryPart.Position.Y - 3

local force = 75 * 10^3
local torque = 100 * 10^3

local steerAngle = 0

function updateForces()
    vehicleSeat.F.Force = Vector3.new(force * .5 * -vehicleSeat.Steer * vehicleSeat.Throttle, 0, force * vehicleSeat.Throttle)
    --vehicleSeat.T.Torque = Vector3.new(0, torque * -vehicleSeat.Steer * vehicleSeat.Throttle, 0)
end

RunService.Heartbeat:Connect(function(deltaTime: number)
    local waveOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X, BASE_XZ_POSITION.Z)
    local newYPosition = WATER_LEVEL + waveOffset
    local newPosition = Vector3.new(BASE_XZ_POSITION.X, newYPosition, BASE_XZ_POSITION.Z)

    local forwardOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X + 2, BASE_XZ_POSITION.Z)
    local rightOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X, BASE_XZ_POSITION.Z + 2)

    local pitch = math.atan2(forwardOffset - waveOffset, 2)
    local roll = math.atan2(rightOffset - waveOffset, 2)

    steerAngle = math.clamp(steerAngle + (vehicleSeat.Steer * STEER_SPEED * deltaTime), -STEER_ANGLE, STEER_ANGLE)

    local cf = CFrame.new(newPosition) * CFrame.Angles(pitch, steerAngle, roll)
    primaryPart:PivotTo(cf)

    updateForces()
end)

—-
By the way, if you encounter any odd behavior with the ship’s rotation using the code provided, then replacing CFrame.Angles with CFrame.fromOrientation might fix it by rotating the ship on the Y axis first :slight_smile::+1:

I appreciate the help so much. With this i am now able to rotate/steer the ship from side to side as showed in the video under. However, I’m still having issues actually moving the ship. I tried uncommenting the torque line, didnt work - i don’t see how that would have done it either way.

Any idea on this?

1 Like

Moving the ship using CFrame is trickier than rotating it since the movement’s direction depends on the ship’s current rotation, but I’ll edit this reply with the solution if I find it

—-
@ScriptCasual Actually, @Star_Scriptz’s suggestion should work, since multiplying a CFrame value with another CFrame value is equivalent to using ToWorldSpace, and the movement direction is usually in object space

—-
@ScriptCasual
The resulting code made with the helpful suggestion by Star_Scriptz:

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

local STEER_ANGLE = math.rad(45) -- Set this to the maximum angle you’d like the ship to be able to steer
local STEER_SPEED = STEER_ANGLE / 1 -- Set 1 to the desired amount of seconds it should take for the ship to reach full steering lock

local SHIP_SPEED = 32 -- In studs per second
local SHIP_ACCEL = SHIP_SPEED / 1 -- Replace 1 with how many second should it take for the ship to reach its top speed

local WaterHeightCalculator = require(ReplicatedStorage:WaitForChild("WaterHeightCalculator"))

local boat = workspace:WaitForChild("Boat")
local vehicleSeat = boat.VehicleSeat
local primaryPart = boat.PrimaryPart
if not primaryPart then error("Boat model must have a PrimaryPart set!") end

local BASE_XZ_POSITION = Vector3.new(primaryPart.Position.X, 0, primaryPart.Position.Z)
local WATER_LEVEL = primaryPart.Position.Y - 3

local force = 75 * 10^3
local torque = 100 * 10^3

local steerAngle = 0
local shipSpeed = 0

RunService.Heartbeat:Connect(function(deltaTime: number)
    local waveOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X, BASE_XZ_POSITION.Z)
    local newYPosition = WATER_LEVEL + waveOffset
    local newPosition = Vector3.new(BASE_XZ_POSITION.X, newYPosition, BASE_XZ_POSITION.Z)

    local forwardOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X + 2, BASE_XZ_POSITION.Z)
    local rightOffset = WaterHeightCalculator.calcWaterHeightOffset(BASE_XZ_POSITION.X, BASE_XZ_POSITION.Z + 2)

    local pitch = math.atan2(forwardOffset - waveOffset, 2)
    local roll = math.atan2(rightOffset - waveOffset, 2)

    steerAngle = math.clamp(steerAngle + (vehicleSeat.Steer * STEER_SPEED * deltaTime), -STEER_ANGLE, STEER_ANGLE)

    if vehicleSeat.Throttle == 0 then
          if shipSpeed < 0 then
                 shipSpeed = math.min(shipSpeed + SHIP_ACCEL * deltaTime, 0)
          else
                 shipSpeed = math.max(shipSpeed - SHIP_ACCEL * deltaTime, 0)
          end
    else
          shipSpeed = math.clamp(shipSpeed + (vehicleSeat.Throttle * SHIP_ACCEL * deltaTime), -SHIP_SPEED, SHIP_SPEED)
    end

    local cf = CFrame.new(newPosition) * CFrame.Angles(pitch, steerAngle, roll) * CFrame.new(0, 0, shipSpeed)
    primaryPart:PivotTo(cf)
end)

Just having a glance you will be wanting the ship to move forward on a basic level, that is the -z direction on cframes.
local cf = CFrame.new(newPosition) * CFrame.Angles(pitch, steerAngle, roll)
This is good.

local cf = CFrame.new(newPosition) * CFrame.Angles(pitch, steerAngle, roll) * CFrame.new(0, 0, -speed)

If you just do speed say like 10 this will move it 10 studs forward, Ideally you’ll want to smooth it out and a SMALLER number say using dt or so to actually be moving it and a little value that gets lerped as the player inputs a keybind and then decellerates.

I hope that makes sense if not feel free to ignore it I’m just incredibly bored.

1 Like

I appreciate it, I will be trying some different things and see If i can come up with something myself aswell :smile:

1 Like

Noted, thank you :smile:
char limit

1 Like