How can I make my viewmodel gun recoil look better and smoother?

My viewmodel gun recoil doesn’t include a spray pattern and feels choppy rather than smooth and natural. I’m aware that the current recoil implementation isn’t great, but I’d really appreciate some feedback on how I could improve it.
I’ve attached a video and the part of the script that handles the recoil.(Yeah, I know that code is bad.)
Are there any suggestions on how to make the recoil feel more natural and visually satisfying?

edit:And is there any way to make a recoil recovery system?

local XrecoilAmount = 0.6
local YrecoilAmount = 0.2
local returnSpeed = 0.3
local RecoliReturn = false
local baseCFrame 

game:GetService("RunService").RenderStepped:Connect(function()	
	if not Firing then return end
		baseCFrame = camera.CFrame
		RecoliReturn = true
		camera.CFrame = baseCFrame * CFrame.Angles(math.rad(XrecoilAmount + math.random(-0.1,0.1)), math.rad(YrecoilAmount + math.random(-1,1)), 0)	
	if RecoliReturn == true then
		camera.CFrame = camera.CFrame:Lerp(baseCFrame, returnSpeed)
		RecoliReturn = false
	end
end)

Use a spring module such as this one. On firing the gun, apply a velocity to the spring and calculate the recoil CFrame from the spring’s position each step.

For recoil recovery this is what I’d do:

local retainedRecoil = CFrame.new()
local recovery = 0.5 --percent of recoil that is recovered
runService.RenderStepped:Connect(function()
    local pos = recoilSpring.Position
    local recoilTarget = CFrame.Angles(pos.X, pos.Y, pos.Z)

    local recoil = recoilTarget
    recoil *= CFrame.new():Lerp(retainedRecoil, recovery)  --this adds the % of recoil that isnt recovered

    retainedRecoil = recoilTget  

    cam.CFrame *= recoil
end
2 Likes

I’m not very familiar with this module, so I wrote the code like this. However, it feels a bit off, and the recoil is very shaky. Are there any tips for improving this, or any mistakes I might have made in my script? What would be a better way to structure this kind of code?

local retainedRecoil = CFrame.new()
local recovery = 0.2

local spring = require(game.ReplicatedStorage.Spring)
local runService = game:GetService(“RunService”)
local cam = workspace.CurrentCamera

local recoilSpring = spring.new(1, 1, 10, 0, 0, 0)

runService.RenderStepped:Connect(function()
local pos = Vector3.new(recoilSpring.Offset, 0, 0)
local recoilTarget = CFrame.Angles(pos.X, pos.Y, pos.Z)

local recoil = recoilTarget
recoil *= CFrame.new():Lerp(retainedRecoil, recovery) 

retainedRecoil = recoil


cam.CFrame *= recoil

end)

this line will block any kind of user input, leaving your camera unmovable until the lerping is complete

you’d have to do camera.CFrame *= whatever if you want to allow camera movement while the recoil is being applied

renderStepped = rService.PreRender:Connect(function(deltaTime)
		local alpha

		if applying then -- the recoil is being applied
			alpha = math.clamp(elapsed / kickTime, 0, 1) -- kickTime is 0.15, for example
-- clamp between 0, 1 since alpha cannot be lower or higher

			if alpha >= 1 then -- if the recoil has finished kicking
				applying = false
				elapsed = 0
			end
		else -- not applying? recovery
			alpha = 1 - math.clamp(elapsed / recoveryTime, 0, 1)

			if alpha <= 0 then
				-- if the recovery has finished
			end
		end

		elapsed += deltaTime
	end)
1 Like

The shakiness is due to the spring’s settings, try adjusting the damper/stiffness to make it less bouncy. To get the right feel to it you just have to experiment with different settings. Other than that I don’t see any issues with the code so good job.

Spring module info

1 Like

Could you tell me what each variable in this code does? I tried to recreate it exactly the same, but something feels off. I think I might have made a mistake somewhere.

deltaTime is the time that has elapsed since last frame, recommended to use it so the recoil is consistent across all framerates

alpha is the progress of the lerp, it is a float number between 0-1
0 being the start, and 1 being the end

kickTime and recoveryTime are just numbers that decide how long the kick and recovery takes
so kickTime can be 0.25, while recoveryTime can be 0.75

elapsed is the total amount of time in framerate that the lerp has been going on for, this is kinda confusing but yeah

1 Like

After a lot of trial and error tweaking the recoil function variables, I finally managed to create recoil that I’m truly satisfied with. I want to thank the two people who helped me fix and improve this issue I really appreciate your support!

1 Like