Ensuring Custom Fish Character Always Faces Forward While Swimming

(I’ve been stuck with this problem for ages, so I’ll try to be as clear as possible. Hopefully, I’ll get the solution soon :heart: )

HELLO FELLOW DEVELOPERS :speaking_head:

I’m working on a custom fish character that should always face forward, even when swimming. However, Roblox’s default swimming behavior tilts characters horizontally when they swim forward, which isn’t suitable for my fish character. I want the fish to maintain a forward-facing orientation at all times while swimming (bear in mind; it can also walk on land)

Illustrations (please forgive my poor drawing skills :skull:):

Default Roblox swimming system:

What happens with the custom fish character:

:x:

What SHOULD happen with the custom fish character:

:white_check_mark:

What I’ve Tried:

I’ve attempted to use AlignOrientation and LinearVelocity to control the fish’s swimming behavior and orientation. The goal is to ensure the character maintains its orientation while moving smoothly. However, I’ve encountered several issues:

  • AlignOrientation just doesn’t work (may have done it wrong lol :sweat_smile:)
  • the character’s movement appears smooth for the local player but not for other playrs
  • the character’s orientation updates are sometimes jittery

I also looked for solutions on the devforum, but it seems like i’m the only one encountering this problem…

My Current Code:

(also contains a boost system, but that doesn’t matter)

--// SERVICES //--
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")

--// PLAYER //--
local player = Players.LocalPlayer

--// VARIABLES //--
local Boost_Key = Enum.KeyCode.LeftShift
local NormalSpeed = 50
local BoostSpeed = 80

local lerpFactor = 0.1
local swimSpeed = NormalSpeed

--// FUNCTIONS //--
local function setupSwimmingControl(character)
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    local primaryPart = character.PrimaryPart

    if not humanoid or not primaryPart then return end

    local Bubbles = character.Head.Particle.Bubbles

    local isSwimming = false
    local isMoving = false
    local linearVelocity
    local alignOrientation
    local currentLookDirection = primaryPart.CFrame.LookVector

    local function setupLinearVelocity()
        local attachment = Instance.new("Attachment", primaryPart)

        linearVelocity = Instance.new("LinearVelocity")
        linearVelocity.Name = "SwimVelocity"
        linearVelocity.MaxForce = math.huge
        linearVelocity.RelativeTo = Enum.ActuatorRelativeTo.World
        linearVelocity.Attachment0 = attachment
        linearVelocity.Parent = primaryPart

        alignOrientation = Instance.new("AlignOrientation")
        alignOrientation.MaxTorque = math.huge
        alignOrientation.Responsiveness = 200 -- Adjust as needed
        alignOrientation.Attachment0 = attachment
        alignOrientation.Parent = primaryPart
    end

    local function updateVelocity()
        if linearVelocity then
            if isSwimming and isMoving then
                local moveDirection = humanoid.MoveDirection
                linearVelocity.VectorVelocity = moveDirection * swimSpeed
            else
                linearVelocity.VectorVelocity = Vector3.new(0, 0, 0)
            end
        end
    end

    local function updateOrientation()
        if isSwimming then
            local moveDirection = humanoid.MoveDirection
            local targetLookDirection = Vector3.new(moveDirection.X, 0, moveDirection.Z).unit

            if isMoving then
                currentLookDirection = currentLookDirection:Lerp(targetLookDirection, lerpFactor)
            else
                currentLookDirection = currentLookDirection:Lerp(primaryPart.CFrame.LookVector, lerpFactor)
            end
            
            primaryPart.CFrame = CFrame.new(primaryPart.Position, primaryPart.Position + currentLookDirection)
        end
    end

    humanoid.StateChanged:Connect(function(_, newState)
        isSwimming = newState == Enum.HumanoidStateType.Swimming
        if isSwimming then
            if not linearVelocity then
                setupLinearVelocity()
            end
        else
            if linearVelocity then
                linearVelocity:Destroy()
                alignOrientation:Destroy()
                linearVelocity = nil
                alignOrientation = nil
            end
            currentLookDirection = primaryPart.CFrame.LookVector
        end
        updateVelocity()
    end)

    humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(function()
        isMoving = humanoid.MoveDirection.Magnitude > 0
        updateVelocity()
        if isSwimming then
            updateOrientation()
        end
    end)

    RunService.RenderStepped:Connect(function()
        if isSwimming and (isMoving or currentLookDirection.Magnitude > 0.001) then
            updateOrientation()
        end
        Bubbles.Enabled = isSwimming and isMoving
    end)
end

local function StartBoosting(input: InputObject, isTyping)
    if isTyping then return end
    if not Boost_Key then
        warn("Please enter a valid KeyCode")
        return
    end
    if input.KeyCode == Boost_Key then
        swimSpeed = BoostSpeed
    end
end

local function StopBoosting(input: InputObject, isTyping)
    if isTyping then return end
    if not Boost_Key then
        warn("Please enter a valid KeyCode")
        return
    end
    if input.KeyCode == Boost_Key then
        swimSpeed = NormalSpeed
    end
end

--// SETUP //--
UIS.InputBegan:Connect(StartBoosting)
UIS.InputEnded:Connect(StopBoosting)

player.CharacterAdded:Connect(function(character)
    setupSwimmingControl(character)
end)

if player.Character then
    setupSwimmingControl(player.Character)
end

Additionally, here is the explorer:
image

Issues Encountered:

  1. Tilting Horizontally: The fish character sometimes tilts horizontally despite using AlignOrientation.
  2. Smoothness: The movement is smooth for the local player but appears laggy and jittery for other players.
  3. Orientation Updates: The orientation updates are abrupt and cause jittering, especially noticeable when starting or stopping movement.

Questions:

  1. How can I prevent the fish character from tilting horizontally and ensure it always faces forward?
  2. How can I improve the smoothness of the character’s movement and orientation updates, especially for other players in the game?

I appreciate any insights or suggestions. THANK YOU!

1 Like

#please-dont-ignore-my-post-i-am-desperate

only thing i can really suggestion since ROBLOX swimming isn’t really an option given the rig is to make your own custom swim system, but instead of using the new BodyMovers use the legacy ones. the new ones seem to have a lot of issues and if you want it to look smooth and replicated nicely to all other players, BodyVelocity/BodyPosition and BodyGyro will more than likely be your go to

This sounds like pure agony and pain… Is there really no other alternative? Also, pretty sure bodyvelocity, bodyposition and bodygyro are all deprecated.

there may be another way but it’s the way i’d do it because im more familiar with it. and deprecated doesn’t mean unusable, i use it in a lot of my projects still because i just dislike the new ones. i can never get them to work as intended and being tied to attachments is quite annoying.

with the legacy body movers you can quite easily customize it to your hearts desire

1 Like

Managed to make it work. I really appreciate your help!

1 Like