[Surfing] Apply velocity relative to the rootparts direction and not world axis

I’ve been trying to replicate the source engines movement system to make a surfing game but have been having trouble making the velocity be applied in reference to the root part and not the world.

My code:

local RepStore = game.ReplicatedStorage
local RunService = game:GetService("RunService")
local InputService = game:GetService("UserInputService")

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local rootpart = character:WaitForChild("HumanoidRootPart")
local camera = workspace.CurrentCamera

local MAX_SPEED = 50
local MAX_ACC = 10 * MAX_SPEED

-- Initialize Character
local force = Instance.new("BodyForce")
force.Name = "Force"
force.Parent = rootpart

local inputKeys = {
    [Enum.KeyCode.W] = Vector3.new(0,0,1),
    [Enum.KeyCode.A] =  Vector3.new(-1,0,0),
    [Enum.KeyCode.S] = Vector3.new(0,0,-1),
    [Enum.KeyCode.D] = Vector3.new(1,0,0)

function getwishdir()
    local wishdir = Vector3.new()
    for key, dir in pairs(inputKeys) do
        if InputService:IsKeyDown(key) then
            wishdir += dir
    return wishdir

function update_vel(wishdir, vel, frame_time)
    local current_speed = vel:Dot(wishdir)
    local add_speed = math.clamp(MAX_SPEED - current_speed, 0, MAX_ACC * frame_time)
    return vel + add_speed * wishdir

    local vel = rootpart.Velocity
    local newVel = update_vel(getwishdir(), vel, deltaTime)
    rootpart.Velocity = newVel

Here are two methods for getting direction of input or to world space.

The first one is relative to the camera like the default character movement, second one is relative to a parts CFrame.

With some fixing up you can also make the first method relative to a part frame as well by replacing the camera CFrame with the CFrame of a part within the function. The key math is this CFreame function to make movement relative and not worldspace


You could use Raycast to get the direction and detect if it hits something, I believe that’s the most efficient way and easiest