For me, this solution didn’t work. There was no recoil recovery, and I felt the recoil was a little unrealistic. After analyzing Phantom Forces’ recoil system, I learned that recoil does the following:

- First, the first shot creates an initial “bump” vertically (bumps the camera up)
- Second, there is a recovery phase where the camera is tweened back down to its original position
- While spraying, the camera “bumps” very very slightly side-to-side and up and down, at a seemingly random pattern

I worked with it for a while, and figured out the following code:

```
local RecoilReset = 0.5 -- amount of time it takes for recoil to reset
local recoveryTime = 0.05; -- the amount of time in seconds it takes for recoil control to be triggered
local bump = 1 -- the base amount of bump while spraying (will increase with more recoil)
local Recoil = 25 -- change this to whatever you want your initial bump to be (higher recoil = more bump)
local RecoilPattern = {
{1, Recoil, -1, 0.77, -0.1}, -- initial bump
{6, bump/2, -1, 0.77, -1 * bump* Recoil/25}, -- side to side spray
{11, bump/2, -1, 0.77, bump* Recoil/25}, -- side to side spray
}
local curshot = 0
local lastshot = tick()
local function lerp(a, b, t) -- Gets a number between two points using an alpha
return a * (1 - t) + (b * t)
end
local function ShootRecoil()
curshots = (tick() - lastshot > RecoilReset and 1 or curshots + 1) -- Either reset or or increase the current shot we're at
lastshot = tick()
if curshots == 1 then
spawn(function()
-- this function handles recovery after initial bump.
wait(recoveryTime)
local recoveryAmount = -1/2 * Recoil
local recoveryLeft = recoveryAmount
local currentRecovery = 0.5 * recoveryAmount
while recoveryLeft < -0.01 do
print("ran, with " .. recoveryLeft .. " left")
currentRecovery = currentRecovery/1.5
recoveryLeft = recoveryLeft - currentRecovery
Camera.CFrame = Camera.CFrame * CFrame.Angles(math.rad(currentRecovery), 0, 0)
Run.RenderStepped:Wait()
end
end)
else -- randomize the amount of left-to-right recoil
-- by randomly adding a curshot, we can randomize the side-to-side bump to make it more realistic + uncontrolled
local rand = math.random()
curshots = curshots + math.round(rand)
end
if curshots >= 11 then -- added this to continue side-to-side recoil cycle
curshots = 2
end
for i, v in pairs(RecoilPattern) do
if curshots <= v[1] then -- Found the current recoil we're at
spawn(function()
local num = 0
while math.abs(num - v[2]) > 0.01 do
num = lerp(num, v[2], v[4])
local rec = num / 10
Camera.CFrame = Camera.CFrame * CFrame.Angles(math.rad(rec), math.rad(rec * v[5]), 0)
Run.RenderStepped:Wait()
end
while math.abs(num - v[3]) > 0.01 do
num = lerp(num, v[3], v[4])
local rec = num / 10
Camera.CFrame = Camera.CFrame * CFrame.Angles(math.rad(rec), math.rad(rec * v[5]), 0)
Run.RenderStepped:Wait()
end
end)
break
end
end
end
```

With this code, you can simply call ShootRecoil() every time you want to recoil the camera. Hopefully this helps anyone who the original solution did not help.