Spring-based camera recoil not going back to original position

As you can see in the above video, the camera will not automatically return back to the original position. I am currently trying to make my own gun system and im stuck on recoil.

My recoil uses a spring module, specifically, this one:

I’ve tried many different solutions, none of which seemed to have worked. I’ve tried searching the forum for answers on the same or a similar topic, but none either has answers, or none resolve the issue.

My script is structured like so;

-- When script loads, create the spring
local RecoilSpring = Spring.fromFrequency(1, 30, 5)

FireGun():

-- Pushes the sping
RecoilSpring:AddVelocity(2)

GunEquipped():

-- Updates every frame while the weapon is equipped
task.spawn(function()
	while Equipped do	
		Camera.CFrame *= CFrame.Angles(RecoilSpring.Offset, 0, 0)	
		RunService.PreRender:Wait()
	end
end)

How do I properly resolve and correct this behavior?

Try using math.rad(RecoilSpring.Offset), if that doesn’t change it try changing your spring settings. If you make the range of your spring go from [-1,. 1], you can have an angle component somewhere defining the max rotation angle and do something like:

local maxAngle = math.rad(30)

-- Updates every frame while the weapon is equipped
task.spawn(function()
	while Equipped do	
		Camera.CFrame *= CFrame.Angles(maxAngle*RecoilSpring.Offset, 0, 0)	
		RunService.PreRender:Wait()
	end
end)

I tried both, and the issue still remains.

All math.rad would do is convert the number from degrees to radians, which just makes my offset value smaller, it doesn’t seem to change the fact that it still results in an offset, same goes for your max angle suggestion as all it does it multiply the number

What is the range of your spring currently?

Not quite sure, but these are the values I used

The parameters:

function Spring.fromFrequency(mass: number, damping: number, frequency: number, y0: number?, v0: number, goal: number?): SpringObject

I believe the issue lies with how springs work. You get this large initial offset, which seems to be the main cause of the issue when simply offsetting the camera CFrame

The graph of your spring looks like this:

It has a range from around [-0.01, 0.025], which in radians would actually equate to a larger number in degrees (which is why i suggested that first fix). Anyways, the solution to this is to use a range preferably from [-1, 1] and multiply by a rotation component. That way, offset*rotation will give you an output in the range [-rotation, rotation].

You can use this graph here, I made it specifically for this module:

Also, make sure the method you use to rotate the camera correctly manipulates the camera. I have a tutorial on how to update the camera in the post for the module (examples section).

I’m struggling to get the values of -1 to 1 without offsetting it with an initial velocity.

Do I have to add an initial velocity somewhere? Or am I missing something? Also the examples you have aren’t really helpful to me as they appear to just directly overwrite values, except for this example here which uses a camera controller which seems to be helpful, and I have no idea how that works

function RenderStepped(dt)
	if CurrentSpring then
		CameraController.Properties.AdditionalRotation.Y = CurrentSpring.Offset;
	else
		CameraController.Properties.AdditionalRotation.Y = 0;
	end
	CameraController:Update();
end

Try doing something like this:

task.wait(5)

local Spring = require(game.ReplicatedStorage.Spring)

local RunService = game:GetService("RunService")

local Camera = workspace.CurrentCamera

local recoilSpring = Spring.fromFrequency(1, 5, 2)

local initRot = Camera.CFrame.Rotation
recoilSpring:AddVelocity(16.5)

local maxAngle = math.rad(30)

RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value + 1, function(dt)
	Camera.CFrame *= CFrame.Angles(maxAngle*recoilSpring.Velocity*dt, 0, 0)
end)

-- OR

task.spawn(function()
    local dt = 0
    while Equipped do
        Camera.CFrame *= CFrame.Angles(maxAngle*RecoilSpring.Velocity*dt, 0, 0)
        dt = RunService.PreRender:Wait()
    end
end)

Gives pretty good results in a blank baseplate test

Edit: you also need to stop updating the camera after the spring has a very low velocity. you could add a check for the velocity, something like

if ( recoilSpring.Velocity < 0.01 ) then
    stopShowingRecoil()
end

Thanks! That seems to have worked pretty well. but how could I offset the camera positively only? i.e, no bouncing down towards the ground. Is that something I can do with a spring? Or would I have to come up with a different method? I’m trying to match the recoil on a previous gun system I used, but the code on that is a mess, but I do know for a fact it did use a spring

I added a very special function just for that. Once you make your spring with your parameters, call

Spring:SnapToCriticalDamping()

this should snap the springs parameters to critical damping, and now it should do just that.

1 Like

Holy crap wow, that works. It works really well too! Thank you so much for your help man. You saved me so much time

1 Like