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.