Tutorial on how to make an actually good camera shake when walking

So, i’ve seen a lot, when i say a lot, i mean A LOT, of new programmers trying to know how to make an actually good camera shake. I used to be this type of person some years ago, and it was hard to find an actually good camera shake when walking. Now, i know how to make this system, but i know that a lot of people don’t know how to create it, neither how it works. So i’ve made an actual readable script explaining most of the steps on how the camera shake actually works (you need to understand SINE and COSINE basics btw)

--[[
Hey! I'm spooky, thank you for using my script, this means a lot to me, it's good to know that im helping someone. If possible, do an upvote in the Roblox DevForum post about this script. ;)
]]--

--// CAMERA SHAKE SCRIPT + EXPLAINED (PUT THIS IN A LOCAL SCRIPT INSIDE StarterCharacterScripts)
--// SINE AND COSINE UNDERSTANDMENT IS REQUIRED TO UNDERSTAND THIS SCRIPT

--// VARIABLES

local CHARACTER = script.Parent
local HUMANOID = CHARACTER:WaitForChild("Humanoid")
local IS_ANIMATION_PLAYING
local SINE_MAX_VALUE = 0
local COSINE_MAX_VALUE = 0
local SHAKE_INTENSITY = 1 -- DEFAULT IS 1

--// FUNCTIONS

local function CAMERA_SHAKE_FUNCTION()
	
	--// CONVERTING TIMESTAMP TO NUMBER
	
	local ACTUAL_TIME = DateTime.now()
	local ACTUAL_TIME_IN_STRING = tostring(ACTUAL_TIME)
	local ACTUAL_TIME_IN_NUMBER = tonumber(ACTUAL_TIME_IN_STRING)
	
	--// EXPLAINING HOW THIS WORKS
	
	--[[
Well, if you actually paid attention to your math classes, instead of playing Clash Royale on your phone during the class, 
you would know how to code this script. But since you didn't, i'm going to teach you. 

So first of all the function SINE is a periodic function, 
this means that no matter how big the value is, or how small the value is, the result will ALWAYS be something between -1 to 1. 
Since no matter how big the value is, we can use the current ACTUAL TIME as a value for the SINE function. 

To show you how this works, you can execute this script in your command line:
	
	----------------
	
	
	for i = 1, 100 do
		local ACTUAL_TIME = DateTime.now()
		local ACTUAL_TIME_IN_STRING = tostring(ACTUAL_TIME)
		local ACTUAL_TIME_IN_NUMBER = tonumber(ACTUAL_TIME_IN_STRING)
	
		print(
			math.sin(ACTUAL_TIME_IN_NUMBER)
		)
		task.wait()
	end
	
	-----------------
	
Your output after running it on the command line will be some numbers between
-1 to 1. If you put those numbers on a graphic (you can use the site
"https://www.desmos.com/calculator" to check the graphic of the function SINE and COSINE),
you will have a beautiful and smooth periodic function, that's the function that
we are going to use. Since these numbers stays between -1 to 1,
we can use them on the camera offset!
The problem is that we going to need to make some changes.

First of all, have in mind that the time that we have, is the current time,
but this time also counts milliseconds, this makes the number really HUGE,
that also affects the shake effect, it affects because it makes the SINE
function variate between -1 and 1 too fast. So we need to divide it by
another number, which makes the time a smaller number (also counting milliseconds,
but the millisecond are going to be decimals) that doesn't variate that fast.
The number that i found perfect for it is 200, but you can change it if you want.

Ok, now we have a good value for SINE, but the problem is that the numbers
goes to -1 to 1, for the camera, -1 to 1 is a HUGE variation, you can try
yourself removing the "/ 3" thingy from the script, it will look really strange.
So to make this SINE value smaller, im going to divide it by 3, so it goes to
-.333... to .333... instead of -1 to 1. Ok, now we have the beautiful sine value working,
and only remain me to explain one last function, math.clamp().

This thing doesn't really change anything in the shake, it's basically limits
where the SINE value can go. In the script i use it more like an OFF/ON thing,
if the player is running than the limit is 1, if the player is not, the limit is 0
(this means no shake in the camera).  Ok, so now we finally have or fully SINE_VALUE.
The COSINE value is basically the exaclty same thing as the sine, but it's for X.
	]]--
	
	local SINE_VALUE =	math.clamp(
		math.sin(
			ACTUAL_TIME_IN_NUMBER / (SHAKE_INTENSITY * 200)
		)  / 3,
		-SINE_MAX_VALUE,
		SINE_MAX_VALUE
	)

	local COSINE_VALUE = math.clamp(
		math.cos(
			ACTUAL_TIME_IN_NUMBER / (SHAKE_INTENSITY * 200)
		)  / 3,
		-COSINE_MAX_VALUE,
		COSINE_MAX_VALUE
	)
	
	--// SET THE CAMERA OFFSET (THIS MAKES THE SHAKE WORK)

	HUMANOID.CameraOffset = HUMANOID.CameraOffset:Lerp( -- LERP IS FOR SMOOTHNESS
		Vector3.new(COSINE_VALUE, SINE_VALUE, 0),
		.1
	)
	
	--//
	
	task.wait()
	CAMERA_SHAKE_FUNCTION() -- MAKE THE FUNCTION LOOP
end

local function CHECK_IF_PLAYER_IS_WALKING()

	
	if HUMANOID.MoveDirection.magnitude > 0 and not IS_ANIMATION_PLAYING then
		
		IS_ANIMATION_PLAYING = true
		
		SINE_MAX_VALUE = 1
		COSINE_MAX_VALUE = .2 --// COSINE MAX VALUE IS .2, BUT YOU CAN CHANGE IT TO WHATEVER YOU WANT TBH
		
	elseif HUMANOID.MoveDirection.magnitude <= 0 and IS_ANIMATION_PLAYING then
		
		IS_ANIMATION_PLAYING = false
		
		SINE_MAX_VALUE = 0
		COSINE_MAX_VALUE = 0
		
	end
	
	task.wait()
	CHECK_IF_PLAYER_IS_WALKING() -- MAKE THE FUNCTION LOOP
end

-- PLAY BOTH FUNCTION IN BACKGROUND

task.spawn(CAMERA_SHAKE_FUNCTION)
task.spawn(CHECK_IF_PLAYER_IS_WALKING)

I would love to know if i helped someone with the code, and also i’m not a native english speaker, so some words in the script might be wrong. If possible, give me ideas on how i could make the explanation more clear, so more people can understand it.

5 Likes

just some suggestions to improve the readability and ease of understanding

this is too long for a multi-line comment, and people would need to scroll horizontally to read it; you can break it down in chunks to make it easier to read:

--[[
Well, if you actually paid attention to your math classes, instead of playing Clash Royale on your phone during the class, 
you would know how to code this script. But since you didn't, i'm going to teach you. 

So first of all the function SINE is a periodic function, 
this means that no matter how big the value is, or how small the value is, the result will ALWAYS be something between -1 to 1. 
Since no matter how big the value is, we can use the current ACTUAL TIME as a value for the SINE function. 

To show you how this works, you can execute this script in your command line:

this is just my pet peeve that others might find chunking the comments unnecessary

1 Like

Thanks for the suggestion, i’m going to aply to the script right now. I did this ways because i wrote the comments in studio, and the studio cuts already those lines to chunks, but it’s just visual, so the entire code fits to the screen. I’m applying it to the code right now.

1 Like