The problem with sway (when you rotate the camera)
is that the intensity and smoothness of both are not uniformly the same at all fps. For example, the intensity of smoothness and intensity at 30 fps is not the same as at 244 fps. The fewer fps you have, the more intensity and smoothness there will be, but as soon as you have more fps, the intensity and smoothness of the viewmodel will be lower.
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local cam = workspace.CurrentCamera
local run = game:GetService("RunService")
local humanoid = char:WaitForChild("Humanoid")
local equipped = false
local walking = false
local jumping = false
local viewModelFolder = game.ReplicatedStorage:WaitForChild("Viewmodels")
local viewModel = viewModelFolder:WaitForChild("Fists")
local VMLeftArm = viewModel:FindFirstChild("Left Arm")
local VMRightArm = viewModel:FindFirstChild("Right Arm")
local toolHandle = script.Parent:FindFirstChild("Handle")
local lastXOffset, lastYOffset = 0, 0
local accumulatedTime = 0
local smoothingSpeed = 10
local lastCameraCFrame = cam.CFrame
local swayAngleX, swayAngleY = 0, 0
local swayRotationX, swayRotationY, swayRotationZ = 0, 0, 0
local swaySmoothing = 8
local swayIntensity = 1
local rotationSwayIntensity = 1
local rotationSwaySmoothing = 8
local function resetViewModel()
viewModel.Parent = viewModelFolder
end
local function disableCollisionsAndAnchoring()
for _, part in pairs(viewModel:GetChildren()) do
if part:IsA("BasePart") then
part.CanCollide = false
part.Anchored = false
end
end
end
local function getCameraSway(deltaTime)
local currentCFrame = cam.CFrame
local delta = lastCameraCFrame:ToObjectSpace(currentCFrame)
local xRot, yRot, zRot = delta:ToEulerAnglesYXZ()
local targetSwayX = math.clamp(-xRot, -math.rad(5), math.rad(5)) * swayIntensity
local targetSwayY = math.clamp(-yRot, -math.rad(10), math.rad(10)) * swayIntensity
swayAngleX = swayAngleX + (targetSwayX - swayAngleX) * math.clamp(deltaTime * swaySmoothing, 0, 1)
swayAngleY = swayAngleY + (targetSwayY - swayAngleY) * math.clamp(deltaTime * swaySmoothing, 0, 1)
local targetRotX = math.clamp(-xRot, -math.rad(2), math.rad(2)) * rotationSwayIntensity
local targetRotY = math.clamp(-yRot, -math.rad(3), math.rad(3)) * rotationSwayIntensity
local targetRotZ = math.clamp(-zRot, -math.rad(1), math.rad(1)) * rotationSwayIntensity
swayRotationX = swayRotationX + (targetRotX - swayRotationX) * math.clamp(deltaTime * rotationSwaySmoothing, 0, 1)
swayRotationY = swayRotationY + (targetRotY - swayRotationY) * math.clamp(deltaTime * rotationSwaySmoothing, 0, 1)
swayRotationZ = swayRotationZ + (targetRotZ - swayRotationZ) * math.clamp(deltaTime * rotationSwaySmoothing, 0, 1)
lastCameraCFrame = currentCFrame
end
local function updateViewModelPosition(time, deltaTime)
local playerInFirstPerson = (cam.CFrame.Position - char.Head.Position).Magnitude < 1
if equipped and playerInFirstPerson then
local camOffset = CFrame.new(0, 0, 0)
local speed = humanoid.WalkSpeed
local balanceFactor = speed / 15
local xOffset, yOffset = 0, 0
if walking and not jumping then
xOffset = math.sin(time) * (0.04 + balanceFactor * 0.04)
yOffset = math.sin(time * 2) * (0.03 + balanceFactor * 0.03)
end
local t = math.clamp(smoothingSpeed * deltaTime, 0, 1)
lastXOffset = lastXOffset + (xOffset - lastXOffset) * t
lastYOffset = lastYOffset + (yOffset - lastYOffset) * t
camOffset = camOffset * CFrame.new(lastXOffset, lastYOffset, 0)
local swayPositionRotation = CFrame.Angles(swayAngleX, swayAngleY, 0)
local swayRotation = CFrame.Angles(swayRotationX, swayRotationY, swayRotationZ)
viewModel:SetPrimaryPartCFrame(cam.CFrame * camOffset * swayPositionRotation * swayRotation)
viewModel.Parent = cam
if toolHandle then
toolHandle.Transparency = 1
end
else
resetViewModel()
if toolHandle then
toolHandle.Transparency = 0
end
end
end
local function updateViewModelAppearance()
if equipped then
VMLeftArm.Color = char["Left Arm"].Color
VMRightArm.Color = char["Right Arm"].Color
local shirt = char:FindFirstChildOfClass("Shirt")
if shirt then
if VMLeftArm:FindFirstChild("Shirt") and VMRightArm:FindFirstChild("Shirt") then
VMLeftArm.Shirt.Texture = shirt.ShirtTemplate
VMRightArm.Shirt.Texture = shirt.ShirtTemplate
end
end
end
end
run.RenderStepped:Connect(function(deltaTime)
if equipped then
accumulatedTime = accumulatedTime + deltaTime * 6
getCameraSway(deltaTime)
end
updateViewModelPosition(accumulatedTime, deltaTime)
updateViewModelAppearance()
end)
script.Parent.Equipped:Connect(function()
equipped = true
disableCollisionsAndAnchoring()
end)
script.Parent.Unequipped:Connect(function()
equipped = false
resetViewModel()
if toolHandle then
toolHandle.Transparency = 0
end
end)
humanoid.Running:Connect(function(speed)
walking = speed > 0
end)
humanoid.Jumping:Connect(function()
jumping = true
end)
humanoid.FreeFalling:Connect(function()
jumping = true
end)
humanoid.StateChanged:Connect(function(_, newState)
if newState == Enum.HumanoidStateType.Landed or newState == Enum.HumanoidStateType.Running then
walking = humanoid.MoveDirection.Magnitude > 0
jumping = false
elseif newState == Enum.HumanoidStateType.Climbing then
walking = false
jumping = false
end
end)
humanoid.Died:Connect(function()
local tool = script.Parent
if tool:IsDescendantOf(player.Backpack) or tool:IsDescendantOf(player.Character) then
tool:Destroy()
end
resetViewModel()
equipped = false
end)
player.CharacterAdded:Connect(function(newChar)
char = newChar
humanoid = char:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
local tool = script.Parent
if tool:IsDescendantOf(player.Backpack) or tool:IsDescendantOf(player.Character) then
tool:Destroy()
end
resetViewModel()
equipped = false
end)
humanoid.StateChanged:Connect(function(_, newState)
if newState == Enum.HumanoidStateType.Landed or newState == Enum.HumanoidStateType.Running then
walking = humanoid.MoveDirection.Magnitude > 0
jumping = false
elseif newState == Enum.HumanoidStateType.Climbing then
walking = false
jumping = false
end
end)
end)