# Help on Understanding Gun Sway Script with math.sin

You can write your topic however you want, but you need to answer these questions:

1. What do you want to achieve? Keep it simple and clear!
Understanding the code below which is working perfectly for gun sway
2. What is the issue? Include screenshots / videos if possible!
I want to understand how does this code work with every aspect of it.
3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
Yes, and asked my friends.
After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that itâs easier for people to help you!

Hello, I needed gun sway script so I checked on internet for approaching methods to do this. I found a youtube tutorial which contains the code below. But I didnât understand some parts of it. I need some clean explanations on it please

``````local swayAMT = -0.4
local swayCF = CFrame.new()
local lastCameraCF = CFrame.new()

RunService.RenderStepped:Connect(function()
local rot = camera.CFrame:ToObjectSpace(lastCameraCF)
local X,Y,Z = rot:ToOrientation()
swayCF = swayCF:Lerp(CFrame.Angles(math.sin(X) *  swayAMT, math.sin(Y) * swayAMT, 0), 0.1)
lastCameraCF = camera.CFrame
for i, v in pairs(camera:GetChildren()) do
if v:IsA("Model") then
v:SetPrimaryPartCFrame(camera.CFrame * swayCF * aimCF)
``````

Firstly why would we use :ToObjectSpace on a empty CFrame ?

`````` local lastCameraCF = CFrame.new() --doesn't it mean empty CFrame whose positions and rotations are 0 ?

local rot = camera.CFrame:ToObjectSpace(lastCameraCF) -- so why bother to use this function ? camera.CFrame is the same with this function since world's origin is also 0,0,0.
``````

Secondly why are we using `math.sin` ? I saw on internet that it is also used for sin waves besides calculating sine. What is the goal for using it ?

1 Like

You canât use ToObjectSpace on a CFrame. You can only do it with two CFrames.

Even on the first RenderStepped, when `lastCameraCF` is `CFrame.new()`, the other CFrame (`camera.CFrame`) is probably not.

There are situations where it kiiiinda makes sense to do something like `camera.CFrame:ToObjectSpace(CFrame.new())`, or at least itâs not completely useless although Iâd argue itâs confusing and unusual since itâs equivalent to just `camera.CFrame:inverse()`. I.e. `print(part.CFrame:ToObjectSpace(CFrame.new()) == part.CFrame:Inverse())` should always print `true`.

Yes. Notice that a new value is assigned to that variable every frame, so most frames it does not have the value of a CFrame with no translation or rotation.

so why bother to use this function ?

Like I explained before, the two CFrames are not equal `CFrame.new()`.

since worldâs origin is also 0,0,0.`

A nitpick, but itâs not just about translation/position, but also about rotation/orientation.

Because itâs a cyclic (ârepeatingâ), continuous (âsmoothâ) function that oscillates around 0 and goes to extremes of 1 and -1. That makes it handy for applying effects that can be expressed as numbers in a way where it varies smoothly between not being there (0), being there (1) and being there but opposite (-1).

I donât know why the programmer chose to have the sway depend on the current camera orientation (the X, Y, Z variables), Iâd personally have it depend on time instead. What does it actually look like in game?

2 Likes

I also used a similar method

I also compared it with aother tutorial which used mouse delta instead which acomplishes the same effect of getting sway in the direction of camera movement, which is what the to objectspace was doing.

Also here is an example of how ToObjectSpace can be used to find thr CFrame delta from CFrame A to CFrame B, or the rotational difference:

2 Likes

Firstly thanks for your reply. Here how it works in game :

Also can you explain me how this code understands if I move the mouse and acts correctly ? When we use `local rot = camera.CFrame:ToObjectSpace(lastCameraCF)` doesnât it mean that rotâs orientation becomes the opposite direction of our camera so why donât we look at our back with this?

Please tell me if Iâm wrong; so this code takes the opposite rotation of our camera and puts them into variables(X,Y,Z).

``````local rot = camera.CFrame:ToObjectSpace(lastCameraCF)
local X,Y,Z = rot:ToOrientation()
``````

In the line below, we rotate an empty CFrame with these variables with every rendered frame.

``````swayCF = swayCF:Lerp(CFrame.Angles(math.sin(X) *  swayAMT, math.sin(Y) * swayAMT, 0), 0.1)
``````

And then we say this i donât know why :
`lastCameraCF = camera.CFrame`
What is the point of this code ? In the first rendered frame, we will use `camera.CFrame:ToObjectSpace()` on a empty CFrame`(lastCameraCF)`, but in the second rendered frame, it wonât be an empty CFrame due to this, it will give 0 for all values of CFrame in second rendered frame of this code :
`local rot = camera.CFrame:ToObjectSpace(lastCameraCF)`

Or is it used for not swaying when we donât move, right ? Because if we donât move, `camera.CFrame` wonât change so X,Y,Z values will be 0.

Because itâs a cyclic (ârepeatingâ), continuous (âsmoothâ) function that oscillates around 0 and goes to extremes of 1 and -1. That makes it handy for applying effects that can be expressed as numbers in a way where it varies smoothly between not being there (0), being there (1) and being there but opposite (-1).

I see now. Thanks a lot.

2 Likes

Thanks, I will have a look. Though i donât know if i can integrate spring module for my script.

2 Likes

And then we say this i donât know why :
`lastCameraCF = camera.CFrame`

The code works by figuring out how the camera has rotated since the previous (âlastâ, I prefer the less ambiguous âpreviousâ) frame. It does that by comparing the âcurrentâ CF to the previous CF, so it needs to store the âold/previous/lastâ CF for the next frame. It does that after itâs done processing the current frame, otherwise it would be pointless

When we use `local rot = camera.CFrame:ToObjectSpace(lastCameraCF)` doesnât it mean that rotâs orientation becomes the opposite direction of our camera so why donât we look at our back with this?

doesnât it mean that rotâs orientation becomes the opposite direction of our camera

No, `rot` becomes `lastCameraCF` but relative to `camera.CFrame`. In other words, "the transformation from `camera.CFrame` to `lastCameraCF`". Since `lastCameraCF` is the CFrame of the camera at the previous frame, `rot` becomes the transformation from the previous frameâs CF to the current frameâs CF. Or âhow the camera has moved since the last frameâ. Well, itâs really the inverse of that because the original programmer flipped the arguments but oh well. Iâm a bit confused RN by the double negatives, but i think that explains why the original programmer had to have a negative `swayAMT`. `transform` would be a way better variable name, or even better `transformFromPrevFrame`.

After the assignment, `camera.CFrame == lastCameraCF * rot` holds true. Another statement that illustrates what I mean (a and b are Parts):

`print(a.CFrame * a.CFrame:ToObjectSpace(b.CFrame) == b.CFrame) --Always true`

so why donât we look at our back with this?

Not sure what you meant by this or âopposite direction of the cameraâ, sorry. If you explain that in more detail maybe I can help clear it up, but maybe itâs not needed.

It takes the inverse of the cameraâs transformation from the last frame (`rot`) (inverse because of the flipped arguments I mentioned earlier) and turns that into Euler angles, which is a way of representing orientation or rotation in the way youâre used to from the properties window. âX, Y, Zâ angles or preferably âpitch, yaw, rollâ angles which is usually clearer Aircraft principal axes - Wikipedia

In the line below, we rotate an empty CFrame with these variables with every rendered frame.
`swayCF = swayCF:Lerp(CFrame.Angles(math.sin(X) * swayAMT, math.sin(Y) * swayAMT, 0), 0.1)`

Do you mean `swayCF` is âemptyâ? Itâs only empty on the first frame, on subsequent frames itâs whatever it was set to on the previous frame. Do you mean that the CF constructed with `CFrame.Angles` is âemptyâ? Well itâs clearly not since itâs being constructed with non-zero parameters.

Itâs not entirely accurate to say that `swayCF` gets rotated by these variables. After all, the line doesnât say something like `swayCF *= CFrame.Angles(blablabla)`. Itâs more like it gets set to a specific orientation, which would be accurate if it werenât for the Lerp call which just smooths out the movement which is absolutely necessary because mouse movement is super jittery. Plus it makes it look like the viewmodel has momentum.

Setting the sway to an orientation that follows the rotation (âmovementâ but for orientation) of the camera causes the viewmodel to âleadâ where the camera is pointing.

Looking closely at it, I think itâs a mistake to use `math.sin` in this case. The fastest I was able to rotate the camera in an experiment was about 0.6 radians. Call it 1.0 to be generous. Hereâs `x` and `sin(x)` plotted in `[-1;1]`:

As you can see, theyâre pretty close so having the `sin` call in there doesnât do a lot. It does kind of reduce the output a bit at the extreme ends, so it could be a valid approach to make the sway not go beyond a certain limit even with extreme camera movement. But the effect is so small I donât think itâs worth the confusion, and it doesnât work at even more extreme movements:

At inputs outside `[-pi/2; pi]`, `sin` starts moving the wrong direction! So if you move the camera move than 90 degrees in a frame, the sway goes in the opposite direction?! Not sure if thatâs the intentional, artistic choice of the original programmer but I donât think thatâs a good idea. First, itâs confusing to read compared to just math.clamp and takes all this analysis to figure out, second it doesnât have much effect in the domain thatâs usually relevant, so itâs very little gain for a decent amount of technical debt, and third it kinda breaks in edge cases. Iâd remove it and just have this instead:

``````swayCF = swayCF:Lerp(CFrame.Angles(X *  swayAMT, Y * swayAMT, 0), 0.1)
``````

In my earlier response I said some things about `math.sin`, theyâre still true but donât really apply in this situation. I didnât fully understand back then what it was doing in the code, sorry if my response caused any confusion.

What is the point of this code ? In the first rendered frame, we will use `camera.CFrame:ToObjectSpace()` on a empty CFrame `(lastCameraCF)`

Again, `ToObjectSpace` works on TWO CFrames, not just one. So that LoC doesnât do it âon a CFâ, it does it on a pair of CFs. The camera CFrame is probably not empty, so the computation probably actually does do something. Even if it didnât do anything to the camera CF, it would still have a purpose because it sets `lastCFrame` so something can actually happen on the next frame. To be fair itâs a tiiiny bit confusing that the original programmer set `lastCameraCF` to `CFrame.new()` at the top of the script, because thatâs not a sensible âfirst previousâ CFrame. A version that fixes this could look like

``````RunS.RenderStepped:Connect(function()
--If this is the first frame then there's no sensible way of computing the sway, so just set the variable for next frame and pretend like there has been no movement since the last frame ("previous = current").
_previousCameraCF = _previousCameraCF or camera.CFrame

local rot = ... everything else like it was
end)
``````

This shows a clear intent for how the sway should be handled in the edge case where there is no âpreviousâ frame. This actually fixes minor a bug too, itâs not just just about coding style but thinking clearly about what the code does so good job spotting that thereâs something weird. What happens if the player spawns in with a camera thatâs yawed 179 degrees? Theyâll get a massive spike of sway on the first frame, causing the viewmodel to jerk a bit to the left or right. It wonât be too extreme or even noticable because itâs just for a single frame and then it quickly falls back to a normal state but hey a bug is a bug Another benefit is that thereâs no ugly state variable at the top of the script (I mean you could have it but I wouldnât). A down side is that itâs still a global variable that might get accidentally set by a different function. Thereâs loads of ways to âcaptureâ that variable in a block so only the relevant code can see it, but this is already a big of a tangent, sorry xD

BTW the `a = a or b` thing might look a bit confusing at first and you could argue that it is, but itâs idiomatic (common) Lua so experienced coders will quickly recognize that itâs a way of providing a default value in situations where a value isnât already provided.

is it used for not swaying when we donât move, right ? Because if we donât move, `camera.CFrame` wonât change so X,Y,Z values will be 0.

No, none of this code has anything to do with how the camera translates though space (X, Y, Z coordinates, from walking and stuff), only with how the camera rotates (from mouse movement, or touch or controller or VR or whatever). If you try walking but not moving the mouse you should see no sway at all. Thatâs why I donât think X, Y and Z are good variable names in this case. Pitch, yaw and roll would make it clearer that itâs talking about rotation and not translation.

The `ToOrientation` call completely ignores the position component of the input CFrame, so if the camera moved a bit to the left or w/e that has no effect on the sway. You could use the same approach to make a âwalk swayâ though. Itâd be a great challenge to see how well you understand all this stuff.

Anyway hope this wall of text helps clear some things up xD And I hope I got everything right Ask away if you have follow up questions

3 Likes

Firstly, thank you again for your reply. I think I have understood how this code work thanks to you except the Lerp function which smooths and make the camera get back to its original place (at least thatâs all I noticed after I tried the code without Lerp and just used `sway.CFrame *CFrame.Angles(blablabla)`)

As you said, :ToObjectSpaceâs arguments are used inversed in this code but it doesnât really matter since we can just make `swayAMT` negative. So I tried how things would change and schematised on paint. Let me know if I understand it correctly. :

Letâs say we are directly looking our front whose pitch, yaw and roll values are 0. If we turn around 30 degree in an instant (actually this value is much smaller in roblox because we used RenderStepped which divides the change of orientation that happens in 1 frame with our fps rate. But to be able to explain it easily letâs say we rotated camera with our mouse 30 degree on yaw axis in 1 frame) we would sway the camera 3 degrees on yaw axis if `swayAMT` and Lerpâs Alpha(its second parameter, that is) is 0.1. Because in this code below : We are multiplying the angle with those numbers. But we can sway in 2 directions(I showed them as red lines that are called 1 and 2) which we can choose one of them by making the `swayCF`'s values negative/positive or changing the arguments of `:ToObjectSpace`, both will work. I donât know if I get it correctly. Also I totally negliged math.sin in this example.

``````swayCF = swayCF:Lerp(CFrame.Angles(pitch *  swayAMT, yaw * swayAMT, 0), 0.1)
``````

I donât think thatâs true. The only way to make the sway go in the opposite direction is taking its opposite number (like 4 / -4). If you move more than 90 degrees, the sway amount will decrease gradually until 180 degrees. sin(0) = 0. sin(90) = 1, the peak sway amount. sin(180) = 0. So to make math.sin make sense, we must limit camera movement amount between 0-90, which we donât need anything to do since, as you said 0.6 rad (34 degree~~) is the fastest amount you could have gotten. Maybe it is possible to exceed 90 degree at highest sensitivity but, who plays roblox with that sensitivity xD.

If I understand you correctly, did you mean that rotating camera, for example 120 degrees will give the same sway amount as rotating 60 degrees even though rotating 120 degrees is two times faster than 60 degrees. Because sin(120) = sin(60) = â3/2.

And math.sin actually works like `Unit` of magnitude if you think in that way. Like everything is between -1 and 1 and you can multiply it with whatever you want.

What you have said makes sense actually, I donât know why it didnât work when I switched partsâ places.

The CFrames of the two parts

Here, it doesnât work.

But it works perfectly here.

I didnât get this part. Can you elaborate please ? Instead of using lerp with 0.1 as a second parameter, why donât we just multiply `swayAMT` with 0.1 ? So they will give the same number, right ?

I canât thank you enough.

1 Like

No problem ^.^

the Lerp function which smooths and make the camera get back to its original place

No, what makes it go back to having no sway is that when you donât move the cam, `lastCameraCF` equals `camera.CFrame` so `rot` becomes `CFrame.new()`. The Lerp only smooths the sway.

âŚ we would sway the camera 3 degrees on yaw axis âŚ

Yep, exactly.

we can sway in 2 directions

Yeah, if you flip the direction you should get a âlagging behindâ effect instead of âleading in front ofâ effect. Which you prefer is an artistic choice. Maybe pistols, SMGs and carbines lead because theyâre light and maneuverable, while LMGs and such lag behind to make them feel heavier.

I donât think thatâs true âŚ 0, 90, 180âŚ

Yeah youâre right, thatâs a much better way of saying it xD

Nope. Take a look at `sin(x)` and `x` around the domain it doesnât go weird (this time in degrees):

Near 0, the two functions behave identically. But closer to -90 or +90, `sin(x)` is like `x` but kind of smoothly dampened. Smoothly damping things is sometimes nice. Say you clamped the `yaw` rotation to `[-90; 90]` degrees:

If you just clamp it you get those sharp edges. But if you carefully use `sin` you can get a âperfectlyâ smooth transition where it gradually keeps going up but goes up slower and slower (and then finally stops at +1).

Again it doesnât matter too much in this case as discussed, but itâs a totally valid technique that might come in handy. TBH Iâd probably use an S curve or smoothstep instead for this application, but one more tool in the toolbox doesnât hurt. Hereâs a comparison:

Dashed lines are the derivatives. Red is sigmoid/S-curve, blue is sin, green is smoothstep. As you can see smoothstep is pretty much identical to sin, but itâs computation actually only uses multiplication and addition so it should be a bit faster. S-curve has different properties, it actually never reaches -1 or 1, but itâs derivative has no sudden changes.

Try this:

``````function CFrameFuzzyEq(a:CFrame, b: CFrame, epsilon: number?): boolean
return a.Position:FuzzyEq(b.Position, epsilon)
and a.LookVector:FuzzyEq(b.LookVector, epsilon)
and a.UpVector:FuzzyEq(b.UpVector, epsilon)
end

while wait() do
print(CFrameFuzzyEq(a.CFrame * a.CFrame:ToObjectSpace(b.CFrame), b.CFrame)) --ACTUALLY always true
end
``````

Floating point error caused the two CFs to sometimes be not exactly equal. I think xD

I wasmostly just nitpicking about the phrasing. In my mind, ârotated byâ means âto transform byâ, which we do with the `*=` (multiply and assign) operator, while âorienting toâ means âto set the transformationâ which we do with the â=â operator. That doesnât have anything to do with Lerp. I just meant that, if you were to ignore the Lerp, that line doesnât as much ârotate byâ as it âorients toâ. IMO, itâs just a matter of saying things precisely, not a huge deal as long as you understand whatâs actually happening.

Instead of using lerp with 0.1 as a second parameter, why donât we just multiply `swayAMT` with 0.1 ? So they will give the same number, right ?

Do you mean

`swayCF = swayCF:Lerp(CFrame.Angles(X * swayAMT, Y * swayAMT, 0), 0.1)` (A)

should be the same as

`swayCF = CFrame.Angles(X * swayAMT * 0.1, Y * swayAMT * 0.1, 0)` (B)

? Or perhaps that it should be the same as

`swayCF *= CFrame.Angles(X * swayAMT * 0.1, Y * swayAMT * 0.1, 0)` (C)

I mean, you could try it and see what happens xD Thatâd help build intuition about it for sure, Iâd do it if I were you. It wonât be the same though. The most similar to A is B. Itâs like A, but unsmoothed and only 0.1 times as much. Since mouse movement is not smooth you should clearly be able to see the jitter. C is completely different. It doesnât set the sway, it rotates it by the camera rotation. So if you keep turning to the right the sway keeps increasing without returning to 0 automatically.

Here Iâve drawn the curve that `y=0` followed by repeated `y = Lerp(1.0, c)` effectively creates:

It moves `y` towards the target smoothly but never gets there. If itâs far a way from the target then it moves fast, and the closer it gets the slower it moves. Itâs an exponential decay function (I think, not 100% sure actually), just for the sake of knowing the names of different âsmoothing functionsâ xD

2 Likes

Yes, (B) is what I exactly meant by my question. Mustnât A and B be same, right ? I tried them but B is jittery. I mean, `Lerp` gets a point between 2 locationâs `distance` with the input we give it between 0 and 1. If we give it 0.1, output will be `distance/10` so there should be no difference when we do it manually like in B. I checked it with using Lerp with 2 different partâs positions.

``````workspace.Part1.Position = workspace.Part1.Position:Lerp(workspace.Part2.Position, 0.1)
``````

It just teleports 0.1 of the distance, there was no smooth movement. I feel like Iâm missing an obvious point.

Wow, there is a lot of easing style. I saw tons of them from the wikipedia link that you have sent. Though they look like identical a little xD. Are they built-in in roblox or do we need to write its formula ?

The rest is totally comprehended. I never thought I would spend this much time for such little code Thanks again.

1 Like

`sin` is an easing style, and I think smoothstep would be equivalent to the âcubicâ easing style. For the S-curve youâll have to write it yourself.

Mustnât A and B be same

Nope. B would be the same as

`swayCF = CFrame.new():Lerp( CFrame.Angles(X * swayAMT, Y * swayAMT, 0), 0.1 )`

The difference here is whatâs in front of the `Lerp`. The reason that A smooths the movement out is that itâs effectively âevery frame, move `sway` 10% towards the target swayâ. Of course on the next frame `sway` is a bit closer, so after 2 frames it will have moved 1-(1 - 0.1)^2 = 19% towards the target sway, 3 frames 1-(1-0.1)^3=27.1%, etc. Every frame it gets closer, slowing down as it gets closer. This is what gives rise to the exponential behavior.

B would just be âevery frame, move `sway` to 10% of the target swayâ. It gets there instantly, and it never goes beyond 10% of the target sway amount. Getting there instantly explains the jitteriness. A takes a while to get there, and goes to 100% of the target sway amount (well, it takes forever to get there but thatâs the limit it goes towards).

I feel like Iâm missing an obvious point.

Nothingâs obvious until youâve learned it. The critical point is what goes before the `Lerp`.

I never thought I would spend this much time for such little code Thanks again.

Yeeeeeah coding feels like that sometimes. However itâs deep insight that youâll take with you for all the next problems you hit, again more stuff for the toolbox. After coding for a while it speeds up a lot.

1 Like

So Iâve been thinking what youâve said for 3 days and got it finally if the image below correct.

Thank you again.

1 Like

Nope, not AFAICT. Say you turn 30 degrees on the 1st frame. A gives 3 degrees, because `0.9 * 0 + 0.1 * 30 = 3`. B gives 3 degrees, because `30 * 0.1 = 3`. C gives 3 degrees because `0 + 30 * 0.1 = 3`. So all good for frame 1. On frame 2 you still turn in the same direction, but half as fast. A gives 4.2 degrees, because `0.9 * 3 + 0.1 * 15 = 4,2`. B is correct, so is C. On the 3rd frame you turn 90 degrees (super fast), so A gives `0.9 * 4.2 + 0.1 * 90 = 12,76` degrees. B and C are correct.

Hereâs a spreadsheet giving those exact results, and for a bunch more frames which is neat for playing around:

dampening-strats-2 - Copy.txt (42.7 KB) (rename to .ods and open in LibreOffice Calc or Excel or maybe Google Sheets)

The numbers in the Rotation row can be manually changed, and the Orientation, A, B and C rows are computed from that. In the code so far weâve used 0.1 as a constant, I made it so that can be changed in the top left corner. The right graph shows rotation and orientation, the left graph shows each dampening strategy. A is desirable because it returns to 0 and smoothly follows the rotation. B looks identical to the rotation in the screen shot, but notice that the Y axes have way different scales.

The noise thing is just some amount of random noise that gets added to the rotation. Thatâs relevant because mouse movement is so jittery, which is kind of like random noise. Hereâs how the strats behave with constant 0.1 and 10 noise:

1 Like

Finally, I understoodâŚ I donât know how to thank you enough !!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.