I need help using the delta to make the bobbing the same across all frame rates, as the title says. I have been looking at the code now for a while but I really struggle to understand springs and the spring module. Can someone help?
(Also I know my code probably isnt that optimized, I made most of it myself so yeah)
Video of issue
File
Run&Gun Viewmodel Testing.rbxl (115.4 KB)
Raw Code
-- Services
local player = game:GetService("Players").LocalPlayer
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Spring = require(script:WaitForChild("Spring"))
local UserInputService = game:GetService("UserInputService")
local MouseService = game:GetService("MouseService")
local TweenService = game:GetService("TweenService")
local DebrisService = game:GetService("Debris")
local SoundService = game:GetService("SoundService")
-- Essentials
local Camera = game.Workspace.CurrentCamera
local GunFolder = ReplicatedStorage.Guns
local ItemFolder = ReplicatedStorage.Items
local DefaultFOV = 90
local DownsightsFOV = 60
local CanShoot = true
local CurrentGun = nil
local Downsights = false
local Shooting = false
local FireCooldown = 1
local SightsAnim = nil
local FireAnim = nil
local FireSightsAnim = nil
local ReloadAnim = nil
local WalkSpeed = game:GetService("StarterPlayer").CharacterWalkSpeed
local fovIn = TweenService:Create(Camera, TweenInfo.new(0.25), {FieldOfView = DownsightsFOV})
local fovOut = TweenService:Create(Camera, TweenInfo.new(0.1), {FieldOfView = DefaultFOV})
local Ammo = nil
pcall(function()
player.PlayerGui.MobileButtons:Destroy()
end)
local fireButton = script:WaitForChild("MobileButtons").shootButton
local reloadButton = fireButton.reloadButton
if UserInputService.TouchEnabled then
script.MobileButtons.Parent = player.PlayerGui
end
local gui = player.PlayerGui.GameUI.Location.Gun.gunFrame
local ammoDisplay = gui.ammo.amount
local gunIcon = gui
local gunName = gui.gun.id
local character = player.Character or player.CharacterAdded:Wait()
local serverLoadGun = ReplicatedStorage.Events.LoadSlot
local serverPlayAnimations = ReplicatedStorage.Events.playAnimation
-- Viewmodel Setup
pcall(function()
game.Workspace.CurrentCamera.Viewmodel:Destroy()
end)
UserInputService.MouseIconEnabled = false
local Viewmodel = ReplicatedStorage.Viewmodel:Clone()
Viewmodel.Parent = Camera
character:WaitForChild("Body Colors"):Clone().Parent = Viewmodel
for _, descendant in Viewmodel:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.CollisionGroup = "Player"
end
end
local SwaySpring = Spring.new()
local BobSpring = Spring.new()
local RecoilSpring = Spring.new()
local Sound = nil
local Animator = Viewmodel.Humanoid.Animator
local debuffTimer = 1
local bobTime = 0
-- Functions
local function Bob(addition, tickSpeed)
local result = math.sin(tickSpeed * tonumber(addition) * 1.85) * 2
return result
end
local function load(name, consumable)
serverLoadGun:FireServer(name, consumable)
pcall(function()
CurrentGun:Destroy()
end)
if consumable == true then
CanShoot = false
CurrentGun = ItemFolder:FindFirstChild(name):Clone()
FireCooldown = CurrentGun:GetAttribute("Rate")
else
CurrentGun = GunFolder:FindFirstChild(name):Clone()
CanShoot = true
end
CurrentGun.Parent = Viewmodel["Right Arm"]
local RootToArmAttachment = Instance.new("Motor6D")
RootToArmAttachment.Parent = Viewmodel["Right Arm"]
RootToArmAttachment.Part0 = Viewmodel["Right Arm"]
RootToArmAttachment.Part1 = CurrentGun.Handle
RootToArmAttachment.Name = "Handle"
if consumable == false then
if Ammo == nil then
Ammo = CurrentGun:GetAttribute("Ammo")
end
end
-- Animations
local Animations = CurrentGun.Animations
pcall(function()
Viewmodel.Humanoid:WaitForChild("Animator"):LoadAnimation(Animations.Out):Play() -- takeout animation
end)
Viewmodel.Humanoid.Animator:LoadAnimation(Animations.Idle):Play() -- Play idle animation
pcall(function()
SightsAnim = Viewmodel.Humanoid.Animator:LoadAnimation(Animations.Sights)
FireAnim = Viewmodel.Humanoid.Animator:LoadAnimation(Animations.Fire)
FireSightsAnim = Viewmodel.Humanoid.Animator:LoadAnimation(Animations["Fire Downsights"])
ReloadAnim = Viewmodel.Humanoid.Animator:LoadAnimation(Animations.Reload)
end)
-- Sound
Sound = CurrentGun.Handle.FireSound
-- User Interface (GUI)
gunName.Text = name
pcall(function()
ammoDisplay.Text = Ammo.."/"..CurrentGun:GetAttribute("Ammo")
ammoDisplay.TextColor3 = Color3.new(1, 1, 1)
end)
end
local function Effect(boolean, duration)
if CurrentGun == nil or player.CameraMode == Enum.CameraMode.Classic then return end
for i,v in CurrentGun.Handle.Effect:GetChildren() do
v.Enabled = boolean
end
task.wait(duration)
local opposite = not boolean
if boolean == true then opposite = false else opposite = true end
for i,v in CurrentGun.Handle.Effect:GetChildren() do
v.Enabled = opposite
end
end
local function firegun(recoil)
if CanShoot == false then return end
if FireCooldown <= 0 and Ammo > 0 then
game.ReplicatedStorage.Events.fireGun:FireServer(Camera.CFrame.Position, Camera.CFrame.LookVector, CurrentGun:GetAttribute("Damage"))
local Recoil = CurrentGun:GetAttribute("Recoil")
if UserInputService.TouchEnabled == true then
Recoil = false
end
if Recoil == false then
Recoil = 0
end
if Downsights == true then
Recoil = Recoil/1.4
end
Shooting = true
FireCooldown = CurrentGun:GetAttribute("Shooting_Cooldown")
Ammo -= 1
ammoDisplay.Text = Ammo.."/"..CurrentGun:GetAttribute("Ammo")
if Ammo <= 5 then
ammoDisplay.TextColor3 = Color3.new(1, 0, 0)
else
ammoDisplay.TextColor3 = Color3.new(1, 1, 1)
end
if Downsights == true then
FireSightsAnim:Play(.1)
serverPlayAnimations:FireServer(FireSightsAnim.Animation.AnimationId, "FireSights", .1, "play")
else
FireAnim:Play(.1)
serverPlayAnimations:FireServer(FireAnim.Animation.AnimationId, "Fire", .1, "play")
end
Sound:Stop()
Sound.TimePosition = 0
Sound:Play()
Effect(true, .05)
RecoilSpring:shove(Vector3.new(Recoil, math.random(Recoil * -2,Recoil * 2),Recoil * 5))
coroutine.wrap(function()
task.wait(.2)
RecoilSpring:shove(Vector3.new(-2.8, math.random(Recoil * -1,Recoil), Recoil * -5))
end)
if CurrentGun:GetAttribute("Auto") == false then Shooting = false end
if Shooting == true then
repeat
if FireCooldown <= 0 and Ammo > 0 then
Shooting = true
game.ReplicatedStorage.Events.fireGun:FireServer(Camera.CFrame.Position, Camera.CFrame.LookVector, CurrentGun:GetAttribute("Damage"))
FireCooldown = CurrentGun:GetAttribute("Shooting_Cooldown")
Ammo -= 1
ammoDisplay.Text = Ammo.."/"..CurrentGun:GetAttribute("Ammo")
if Ammo <= CurrentGun:GetAttribute("Ammo")*0.25 then
ammoDisplay.TextColor3 = Color3.new(1, 0, 0)
else
ammoDisplay.TextColor3 = Color3.new(1, 1, 1)
end
if Downsights == true then
FireSightsAnim:Play(.1)
serverPlayAnimations:FireServer(FireSightsAnim.Animation.AnimationId, "FireSights", .1, "play")
else
FireAnim:Play(.1)
serverPlayAnimations:FireServer(FireAnim.Animation.AnimationId, "Fire", .1, "play")
end
Sound:Stop()
Sound.TimePosition = 0
Sound:Play()
Effect(true, .05)
RecoilSpring:shove(Vector3.new(Recoil, math.random(Recoil * -2,Recoil * 2),Recoil * 5))
coroutine.wrap(function()
task.wait(.2)
RecoilSpring:shove(Vector3.new(-2.8, math.random(Recoil * -1,Recoil), Recoil * -5))
end)
end
task.wait(FireCooldown)
until Shooting == false
--game.Workspace.CurrentCamera.CFrame *= CFrame.Angles(math.rad(UpdatedRecoilSpring.X), math.rad(UpdatedRecoilSpring.Y), math.rad(UpdatedRecoilSpring.Z))
end
end
end
local function reloadgun()
if CanShoot == false then return end
if Shooting == false and FireCooldown <= 0 then
fovOut:Play()
ReloadAnim:Play(.05)
serverPlayAnimations:FireServer(ReloadAnim.Animation.AnimationId, "Reload", .5, "play")
FireCooldown = CurrentGun:GetAttribute("Reload_Time")
task.wait(FireCooldown)
Ammo = CurrentGun:GetAttribute("Ammo")
ammoDisplay.Text = Ammo.."/"..CurrentGun:GetAttribute("Ammo")
ammoDisplay.TextColor3 = Color3.new(1, 1, 1)
if Downsights == true then
fovIn:Play()
end
end
end
function sights(state)
if CanShoot == false then return end
if state == true then
UserInputService.MouseDeltaSensitivity = .4
Downsights = true
character.Humanoid.WalkSpeed = WalkSpeed/1.75
fovIn:Play()
SightsAnim:Play(.25)
serverPlayAnimations:FireServer(SightsAnim.Animation.AnimationId, "Sights", .25, "play")
else
UserInputService.MouseDeltaSensitivity = 1
Downsights = false
character.Humanoid.WalkSpeed = WalkSpeed
SightsAnim:Stop(.1)
serverPlayAnimations:FireServer(SightsAnim.Animation.AnimationId, "Sights", .25, "stop")
fovOut:Play()
end
end
-- Main
Camera.FieldOfView = DefaultFOV
Viewmodel.Shirt.ShirtTemplate = character:WaitForChild("Shirt").ShirtTemplate
load(script:GetAttribute("gunToLoad"), false)
-- Main Loops
RunService.RenderStepped:Connect(function(delta_time)
if Viewmodel ~= nil then
if CurrentGun == nil then return end
if player.CameraMode == Enum.CameraMode.Classic then
for i,v in Viewmodel:GetDescendants() do
if v:IsA("BasePart") then
v.Transparency = 1
end
end
else
for i,v in Viewmodel:GetDescendants() do
if v:IsA("BasePart") and v.Name ~= "HumanoidRootPart" and v.Name ~= "Handle" and v.Name ~= "Torso" and v.Name ~= "Head" then
v.Transparency = 0
end
end
end
local Delta = game.UserInputService:GetMouseDelta()
bobTime = tick()--(bobTime + 1.5)* delta_time
-- Shoving
SwaySpring:shove(Vector3.new((-Delta.X/500)/0.25, (Delta.Y/500)/0.25, 0))
if character.Humanoid:GetState() == Enum.HumanoidStateType.Running then
BobSpring:shove(Vector3.new(Bob(5,bobTime), Bob(10,bobTime), Bob(5,bobTime)) / 10 * (character.HumanoidRootPart.Velocity.Magnitude) / game.StarterPlayer.CharacterWalkSpeed/1.2)
else
BobSpring:shove(Vector3.new(Bob(5,bobTime), Bob(10,bobTime), Bob(5,bobTime)) / 10 * (5) / game.StarterPlayer.CharacterWalkSpeed/1.2)
end
-- Updating springs
local UpdatedBob = nil
local UpdatedSway = SwaySpring:update(0.0015)
if Downsights == true then
UpdatedBob = BobSpring:update(0.1)
else
UpdatedBob = BobSpring:update(0.0015)
end
-- Applying springs
local success, errorMsg = pcall(function()
Viewmodel.Head:PivotTo(
workspace.CurrentCamera.CFrame *
CFrame.new(UpdatedSway.X, UpdatedSway.Y, UpdatedSway.Z) *
CFrame.new(UpdatedBob.X, UpdatedBob.Y, UpdatedSway.Z)
)
end)
if errorMsg then
Viewmodel:Destroy()
Viewmodel = nil
return
end
if FireCooldown <= 0 then
FireCooldown = 0
if CanShoot == false then
load(player.currentGun.Value, false)
end
else
FireCooldown -= delta_time
end
-- Recoil
local UpdatedRecoilSpring = RecoilSpring:update(0.015)
Viewmodel.HumanoidRootPart.CFrame *= CFrame.Angles(math.rad(UpdatedRecoilSpring.X) * 2, 0, 0)
game.Workspace.Camera.CFrame *= CFrame.Angles(math.rad(UpdatedRecoilSpring.X), math.rad(UpdatedRecoilSpring.Y), math.rad(UpdatedRecoilSpring.Z))
end
end)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if CurrentGun == nil or Viewmodel == nil then return end
if input.KeyCode == Enum.KeyCode.R or input.KeyCode == Enum.KeyCode.ButtonX then
reloadgun()
end
-- Mouse buttons
if input.UserInputType == Enum.UserInputType.MouseButton2 or input.KeyCode == Enum.KeyCode.ButtonL2 then
sights(true)
end
if UserInputService.TouchEnabled == false then
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.KeyCode == Enum.KeyCode.ButtonR2 then
firegun(true)
end
end
end)
fireButton.InputBegan:Connect(function()
sights(true)
firegun(true)
end)
fireButton.MouseButton1Up:Connect(function()
Shooting = false
sights(false)
end)
fireButton.MouseLeave:Connect(function()
Shooting = false
sights(false)
end)
reloadButton.MouseButton1Click:Connect(function()
reloadgun()
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton2 or input.KeyCode == Enum.KeyCode.ButtonL2 then
sights(false)
end
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.KeyCode == Enum.KeyCode.ButtonR2 then
Shooting = false
end
end)
game.ReplicatedStorage.Events.RoundEnd.OnClientEvent:Connect(function()
Viewmodel:Destroy()
end)
character.Humanoid.Died:Connect(function()
Viewmodel:Destroy()
script:Destroy()
end)
And heres the module (named spring)
-- Constants
local ITERATIONS = 8
-- Module
local SPRING = {}
-- Functions
function SPRING.new(self, mass, force, damping, speed)
local spring = {
Target = Vector3.new();
Position = Vector3.new();
Velocity = Vector3.new();
Mass = mass or 5;
Force = force or 50;
Damping = damping or 4;
Speed = speed or 4;
}
function spring.getstats(self)
return self.Mass, self.Force, self.Damping, self.Speed
end
function spring.changestats(self, mass, force, damping, speed)
self.Mass = mass or self.Mass
self.Force = force or self.Force
self.Damping = damping or self.Damping
self.Speed = speed or self.Speed
end
function spring.shove(self, force)
local x, y, z = force.X, force.Y, force.Z
if x ~= x or x == math.huge or x == -math.huge then
x = 0
end
if y ~= y or y == math.huge or y == -math.huge then
y = 0
end
if z ~= z or z == math.huge or z == -math.huge then
z = 0
end
self.Velocity = self.Velocity + Vector3.new(x, y, z)
end
function spring.update(self, dt)
local scaledDeltaTime = dt * self.Speed / ITERATIONS
for i = 1, ITERATIONS do
local iterationForce= self.Target - self.Position
local acceleration = (iterationForce * self.Force) / self.Mass
acceleration = acceleration - self.Velocity * self.Damping
self.Velocity = self.Velocity + acceleration * scaledDeltaTime
self.Position = self.Position + self.Velocity * scaledDeltaTime
end
return self.Position
end
return spring
end
-- Return
return SPRING