Help on how to improve my gun positional sway

Issue:

Example of what it should be:

My issue is that when I push my camera down the gun keeps going down too but then when I push my camera upwards then the gun wont go up.

Here is my current code.

local Plrz = game:GetService("Players")
local Rep = game:GetService("ReplicatedStorage")
local UIs = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local Camera = workspace.CurrentCamera
local Player = Plrz.LocalPlayer
local Char = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Char:WaitForChild("Humanoid")

local FrameWork_T = {
	CurrTool = nil,
	V_Model = nil,
	Settings = nil,
}

local Aiming = false
local AimCF = CFrame.new()
local AimOffset = Vector2.new()

Char.ChildAdded:Connect(function(Tool)
	if Tool:IsA("Tool") then
		UIs.MouseIconEnabled = true
		Player.CameraMode = Enum.CameraMode.Classic
		FrameWork_T.V_Model = Rep.Viewmodels:FindFirstChild("V_"..Tool.Name):Clone()
		FrameWork_T.V_Model.Parent = Camera
		FrameWork_T.Settings = require(Tool.Settings)
		CurrTool = Tool
		print("Equipped "..CurrTool.Name)
	end
end)

Char.ChildRemoved:Connect(function(Tool)
	if Tool:IsA("Tool") then
		UIs.MouseIconEnabled = true
		Player.CameraMode = Enum.CameraMode.Classic
		Camera:FindFirstChild("V_"..Tool.Name):Destroy()
		FrameWork_T.V_Model = nil
		FrameWork_T.Settings = nil
		CurrTool = nil
		print("Unequipped "..Tool.Name)
	end
end)

RunService.RenderStepped:Connect(function(DT)
	if Humanoid.Health > 0 and FrameWork_T.V_Model ~= nil then
		
		local MouseDelta = UIs:GetMouseDelta()
		
		AimOffset = AimOffset + Vector2.new(MouseDelta.X, MouseDelta.Y)
		
		local SwayCF = CFrame.Angles(math.rad(math.clamp(-AimOffset.Y/10, -10, 10)), math.rad(math.clamp(-AimOffset.X/10, -50, 15)), math.rad(0))
		
		if Aiming == true then
			local AimPart
			
			if FrameWork_T.V_Model:FindFirstChild("AimAtt") then
				AimPart = FrameWork_T.V_Model.AimAtt:FindFirstChild("AimPart")
			else
				AimPart = FrameWork_T.V_Model:FindFirstChild("AimPart")
			end
			
			local Offset = AimPart.CFrame:ToObjectSpace(FrameWork_T.V_Model.PrimaryPart.CFrame)
			AimCF = AimCF:Lerp(Offset, 1-FrameWork_T.Settings.AimSmooth^DT)
			TweenFOV(0.3, 90)
		else
			local Offset = CFrame.new()
			AimCF = AimCF:Lerp(Offset, 1-FrameWork_T.Settings.AimSmooth^DT)
			TweenFOV(0.3, 70)
		end
		
		FrameWork_T.V_Model:SetPrimaryPartCFrame(Camera.CFrame * AimCF * SwayCF)
		
	end
end)

I used this for a better understanding on how to make it.

1 Like

It looks like your gun is following the camera downward, but not upward. This likely happens because of how AimOffset is being modified and how SwayCF is applied.

Issue

  • Gun moves down with camera:
    • AimOffset accumulates MouseDelta.Y, increasing downward movement.
    • The SwayCF calculation correctly applies downward tilt.
  • Gun doesn’t move up properly:
    • Your math.clamp() on SwayCF limits upward rotation but might not correctly allow positive values for moving up.
    • If AimOffset.Y is increasing in only one direction (negative when looking down), it may not correctly apply the upward movement.

Fix

Modify the SwayCF calculation to correctly apply the camera’s movement in both directions.

:one: Fix SwayCF Calculation

Modify this line:

local SwayCF = CFrame.Angles(math.rad(math.clamp(-AimOffset.Y/10, -10, 10)), math.rad(math.clamp(-AimOffset.X/10, -50, 15)), math.rad(0))

To:

local SwayCF = CFrame.Angles(
    math.rad(math.clamp(AimOffset.Y / 10, -10, 10)),  -- Flip the sign to ensure correct upward movement
    math.rad(math.clamp(-AimOffset.X / 10, -50, 15)),
    0
)

:small_blue_diamond: Change -AimOffset.Y to AimOffset.Y (this fixes the downward movement overpowering upward movement).


:two: Reset AimOffset.Y More Gradually

In RunService.RenderStepped, AimOffset keeps accumulating. Try resetting it slightly each frame to prevent extreme drift:

AimOffset = AimOffset:Lerp(Vector2.new(0, 0), 0.05)  -- Smoothly reset sway

This prevents excessive downward tilt buildup.


Final Adjustments

If the gun still doesn’t move up properly, increase the clamp values:

local SwayCF = CFrame.Angles(
    math.rad(math.clamp(AimOffset.Y / 8, -15, 15)),  -- Increased range
    math.rad(math.clamp(-AimOffset.X / 10, -50, 15)),
    0
)

:small_blue_diamond: This allows the gun to move more freely while still keeping it realistic.


Summary of Fixes

  1. Change -AimOffset.Y to AimOffset.Y in SwayCF.
  2. Reset AimOffset.Y slightly each frame with Lerp().
  3. Increase clamp values if movement is still limited.

Let me know if the issue persists! :rocket:

3 Likes

Are you 100% sure you wrote that?

3 Likes

If I wanted to have a sway that would return to the initial middle position this would be a solution which is why I will definitly be using this for Aiming down sights, but this did not really solve my issue.

I made another video describing my issue in a better way.

The issue is that the Math.Clamp is not working really, my gun still moves farther and around me so in this video I removed the math.clamp and showed that all the math.clamp did was make it look like it couldn’t go any further but it still is going further.

I need to figure out a way to make a border for the gun so when it goes farther than the Math.Clamp it’ll go back.

Code as of right now:

local Plrz = game:GetService("Players")
local Rep = game:GetService("ReplicatedStorage")
local UIs = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local Camera = workspace.CurrentCamera
local Player = Plrz.LocalPlayer
local Char = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Char:WaitForChild("Humanoid")

local FrameWork_T = {
	CurrTool = nil,
	V_Model = nil,
	Settings = nil,
}

local Aiming = false
local AimCF = CFrame.new()
local AimOffset = Vector2.new()

Char.ChildAdded:Connect(function(Tool)
	if Tool:IsA("Tool") then
		UIs.MouseIconEnabled = true
		Player.CameraMode = Enum.CameraMode.Classic
		FrameWork_T.V_Model = Rep.Viewmodels:FindFirstChild("V_"..Tool.Name):Clone()
		FrameWork_T.V_Model.Parent = Camera
		FrameWork_T.Settings = require(Tool.Settings)
		CurrTool = Tool
		print("Equipped "..CurrTool.Name)
	end
end)

Char.ChildRemoved:Connect(function(Tool)
	if Tool:IsA("Tool") then
		UIs.MouseIconEnabled = true
		Player.CameraMode = Enum.CameraMode.Classic
		Camera:FindFirstChild("V_"..Tool.Name):Destroy()
		FrameWork_T.V_Model = nil
		FrameWork_T.Settings = nil
		CurrTool = nil
		print("Unequipped "..Tool.Name)
	end
end)

RunService.RenderStepped:Connect(function(DT)
	if Humanoid.Health > 0 and FrameWork_T.V_Model ~= nil then
		
		local LastCameraCF = Camera.CFrame
		
		local MouseDelta = UIs:GetMouseDelta()
		
		AimOffset = AimOffset + Vector2.new(MouseDelta.X, MouseDelta.Y)
		
		local Rot = Camera.CFrame:ToObjectSpace(LastCameraCF)
		local x, y, z = Rot:ToOrientation()
		
		local SwayCF = CFrame.Angles(
			math.rad(math.clamp(-AimOffset.Y / 8, -15, 15)),
			math.rad(math.clamp(-AimOffset.X / 10, -50, 15)),
			0
		)
		
		print(SwayCF)
		
		if Aiming == true then
			local AimPart
			
			if FrameWork_T.V_Model:FindFirstChild("AimAtt") then
				AimPart = FrameWork_T.V_Model.AimAtt:FindFirstChild("AimPart")
			else
				AimPart = FrameWork_T.V_Model:FindFirstChild("AimPart")
			end
			
			local Offset = AimPart.CFrame:ToObjectSpace(FrameWork_T.V_Model.PrimaryPart.CFrame)
			AimCF = AimCF:Lerp(Offset, 1-FrameWork_T.Settings.AimSmooth^DT)
			TweenFOV(0.3, 90)
			AimOffset = AimOffset:Lerp(Vector2.new(0, 0), 0.05)
		else
			local Offset = CFrame.new()
			AimCF = AimCF:Lerp(Offset, 1-FrameWork_T.Settings.AimSmooth^DT)
			TweenFOV(0.3, 70)
		end
		
		FrameWork_T.V_Model:SetPrimaryPartCFrame(Camera.CFrame * AimCF * SwayCF)
		
	end
end)

I thank you for your help this really did help me get a better understanding of what I am doing wrong.

2 Likes

Alright listen up, here’s the deal — your math.clamp is only stopping the sway angle from going beyond some limit, but you’re not stopping the actual offset value AimOffset from growing every frame. So what happens? Your gun looks like it’s capped because the sway angles get capped, but since the offset itself keeps piling up, the gun still moves farther and farther away. That’s why it feels like math.clamp is doing nothing — it kinda is, for what you actually want.

Here’s the fix: You gotta clamp the AimOffset itself, not just the angles you calculate with it. Clamp it right after you add the mouse movement to it, every frame. That way, your offset will never get bigger than your set border. If you don’t do that, the offset just grows endlessly and the gun keeps drifting away no matter what.

So basically, instead of just doing:

AimOffset = AimOffset + Vector2.new(MouseDelta.X, MouseDelta.Y)

You do this:

AimOffset = AimOffset + Vector2.new(MouseDelta.X, MouseDelta.Y)

AimOffset = Vector2.new(
    math.clamp(AimOffset.X, -50, 15),
    math.clamp(AimOffset.Y, -15, 15)
)

This keeps the AimOffset values locked inside the limits you want. Then your sway angles use this clamped offset and the gun won’t move beyond those borders.

Think of it like this: the clamp on angles just stops the look from going beyond limits, but the position offset is what moves the gun around. You have to clamp the thing that actually moves the gun, not just the angles you use to rotate it.

This simple step solves the problem cleanly and makes the gun movement feel tight and controlled without drifting anywhere.

Didn’t post the first time sorry if its duplicated

Y’all really make me wanna just respond like a kid sometimes. It makes me wonder, does Roblox really not have many people who write tutorials properly or just want to help others? I spent over an hour putting something together, only for it to be met with criticism. I honestly don’t care — hopefully what I put my time into helped, and if it didn’t, guess what? I don’t care!!! You don’t think I wrote it? Guess what — I don’t careeeeeeeeeeeee. I won’t be responding to comments on the help I give anymore unless it’s asking me to expand or help with what I already provided. Like I said! Don’t even reply to this cause I genuinely don’t care what you think. Just letting you all know notifications are cleared, so don’t feel hurt if I don’t respond to your childish antics.