First person & ShiftLock resets RootJoint Tilting/Bending

  1. I’m making my own swimming physics as Roblox’s built in one was very janky and too difficult to deal with after I made a custom hitbox for my character ( Everything else is all massless and pure visual )
    Also, unlike how it is originally, I can’t rotate the whole character 90 degrees since It starts tweaking so I decided to rotate the torso with the RootJoint.

  2. The problem is: Whenever the player is in first person or uses shiftlock, RootJoint is reset.

  3. I’ve tried so many things and I’m at a point that Idek what I’m doing anymore lol

  • I tried to apply the orientation every frame, heartbeat etc.
  • Mess with AutoRotate property
  • Changing CameraSubject to root part (this worked but I don’t think I should rely on this)
  • NOT setting MouseBehavior to LockCenter ( this also worked, but ruins gameplay )

The script is something like this

local currentLean = 0
local leanAngleMax = math.rad(-90) -- swimming on surface (flat)
local leanAngleSubmerged = math.rad(-45) -- swimming up ( capped at 45 )
local lastInWaterTime = 0
local surfaceGrace = 0.25
local leanSmoothness = 0.15
local lastSend = 0
local sendRate = 0.1

local original = root.RootJoint.C0
RunService.Heartbeat:Connect(function()
	if not root or humanoid.Health <= 0 then return end

	local firstPerson = (Camera.CFrame.Position - Camera.Focus.Position).Magnitude < 1
	local shiftLock = (UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter)

	local inWater, waterState = WaterSystem.IsInWater(root.Position)
	local velocity = root.AssemblyLinearVelocity
	local horizontalVel = Vector3.new(velocity.X, 0, velocity.Z)
	local horizontalSpeed = horizontalVel.Magnitude
	local isMoving = horizontalSpeed > 2

	if inWater then
		lastInWaterTime = time() / 2

		-- Wall jumping or shift locking, these are unrelated
		if waller.Value or shiftLock then
			local tween = TweenService:Create(root.RootJoint, TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { C0 = original })
			tween:Play()
			LeanEvent:FireServer(0, "reset")
			return
		end

		if firstPerson and not inWater then
			local tween = TweenService:Create(root.RootJoint, TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { C0 = original })
			tween:Play()
			LeanEvent:FireServer(0, "reset")
			return
		end

		-- this is the part we bend the player as they swim
		local maxLean = (waterState == "submerged") and leanAngleSubmerged or leanAngleMax
		local targetLean = isMoving and -maxLean or 0
		currentLean = currentLean + (targetLean - currentLean) * leanSmoothness

		local tween = TweenService:Create(root.RootJoint, TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {
			C0 = original * CFrame.Angles(currentLean, 0, 0)
		})
		tween:Play()

		if tick() - lastSend > sendRate then
			lastSend = tick()
			LeanEvent:FireServer(currentLean, "lean")
		end
	else

And this is how it looks in action

I don’t know what Im supposed to do with this being related to MouseBehavior
I would reaally appreciate a fix to this or any idea to get around this whole thing

Sorry, in your code snippet, what is

local original = root.RootJoint.C0

referring to?
I could not find RootJoint in any of the character’s limbs, so I assume it’s a child of the custom hitbox you made.
If that’s the case, are you able to provide the code (if any) that generates this hitbox or the model so I can replicate your video? Thanks in advance

Also, how did you disable roblox’s swimming system? Did you just use :setStateEnabled()?


Just to add: I can’t find any RootJoint.

They are only in R6 avatars inside HumanoidRootPart!

You can also disable swimming with this line in a local script, no loop necessary:
I put it in StarterCharacterScripts

humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)

however it will still affect your movement inside it

omg I’m dumb sorry

Just realized the script thinks I’m in shiftlock only because It detects the mousebehavior so it returns true when I’m in first person as well
I also wrote walljump and shiftlock detection after confirming the player is in water, It still worked outside water though so I just ignored it

Everything is fixed now
that was my fault