Looks like I’m a bit late to the party. Someone already suggested springs. I do have some additional insights and a few pointers, though.
The math behind springs is a bit daunting at first, but it’s not super complicated once you start to understand the physics behind them. I actually wrote about this exact topic a while ago, if you’re interested in the maths behind spring-based recoil animations:
(If you’re looking for spring code to use, scroll to the bottom of the page. The first one has issues which I explain on the page itself).
When you use springs and lerps based on a simple exponential equation, you can get some incredibly smooth animations. I tested around with this a bit a while ago:
(I still have to code the bullets, please don’t kill - well, you can’t, there’s no bullets )
In my case, I used 5 different springs; 2 for the camera (pitch and yaw) and 3 for the gun’s viewport model (pitch, yaw and backwards movement):
The idea is that you keep track of the view angles and add the positions of those springs to the angle the actual camera is at. That way, the recoil can heavily throw off the camera but will always return to the direction the player was originally facing.
You want to give the camera a decent upward kick, as well as some sideways kick. For the gun, you give it a decent backwards kick, as well as some upwards rotation and a very small bit of sideways rotation. All of these springs should be relatively stiff and have high damping - in my example, all springs have a spring constant of 20 and a damping coefficient of 10. Lower values on either of these result in a longer time to target, higher values result in a quicker time to target.
With my example code in the first link, you can give the springs a kick using the
Spring:addVelocity(dV) method and get the velocity of the spring at any point in time using the
Spring:step(dT) method. The idea is that you call
Spring:step(dT) every time RenderStepped fires.
For the ADS/hipfire view, I have two separate “target” CFrames relative to the camera, and I change the start and target positions whenever the mouse input changes. As the lerp “factor”, I use the following equation:
factor = 1 - math.exp(-timeSinceChange * speed)
This gives it a sharp response initially, but it slows down when it approaches the target value and feels a lot more natural.
Hopefully you find this helpful! ^^