Creating a Simple, Realistic Camera (With View Bobbing, Dynamic Blur, & Sprinting)

Return

Hey y’all!

It’s the peak of summer, so you should be wearing sunglasses whenever you’re in direct sunlight! Yep, that’s right, it’s National Sunglasses Day, I’ve already started: :123:

image

Wanna hear a story? That was a rhetorical question because you’re going to have to listen anyways. So, I was going to post this tutorial yesterday June 26 because it was National Chocolate Pudding Day, but I was so busy that I started this tutorial in the morning, and couldn’t even finish near midnight. It was like 11 PM (1 hour from the day changing!) and I had only half the tutorial done. It was like so bad and like—

Oh, yeah I’m supposed to be doing a tutorial…


Introduction :slightly_smiling_face:

The normal Roblox camera is not the most interesting sight, it just stays there in one place until you zoom in or out (or drag around). Not very realistic, is it?

Speaking of realism, when you walk in real life, your head bobs up and down and causes your vision to do the same. We’re so used to it since it happens every day, but it’s there. Also, when you quickly turn your head, there’s a quick blur between the initial position and the next one.

Well, that’s what we’ll add to our little camera friend:

  • a camera bobbing system
  • dynamic blur
  • and sprinting

Please note: this is best suited for first-person camera. Although it still functions with third person, it’s not preferred.


Video

As you guys requested below, I have finally been able to create a non-laggy video (it was a hassle to go through, but you don’t need to know). Just make sure you watch it in full screen so that you can see me chatting:

Several things to note about the video that I didn’t mention in it:

  • look at the wall as reference to notice the bobbing
  • the wall may look like it’s shaking but that’s not because of the camera, it’s because it’s unanchored
  • I had to exaggerate the dynamic blur for it to be noticeable (and it’s still very subtle)
  • the default footsteps sound will not match up with the bobbing (you can implement your own footsteps for that)

Quick Tips :exclamation:

  • With effects like these, you’ve got to be careful not to make them too extreme. For example, the bobbing shouldn’t be a 10 stud difference; that can easily cause players to leave your game.

  • Always consider UX and user preferences. Add a settings menu (which is part of the kit provided way below, but won’t be going in-depth with it) that can toggle each of the three effects on or off.

  • Among the three, camera bobbing is the most difficult to get right. There are many configurations of it, each resulting in different “bobs”. This tutorial will only show one type, so you may change the conditions to your desire. When testing this out, don’t be overly attentive on small details like the bobbing being too noticeable. Chances are, the more you try to notice something, the more it becomes emphasized to you. So, take breaks while developing this and look naturally through the eyes of a player.


Prerequisites :warning:

This is supposed to be realistic, and therefore will use come next-level fundamental concepts, so you’ll need to understand these topics:


Quadratic Bézier Curves

When tweening in a curved motion, it’s not the traditional way of plugging in the end position as it will just tween linearly.

You may recall that a function such as f(x) = x has exactly one output for each input, the same goes for curves like parabolas, hyperbolas, and yes our dear Bézier Curves. Any value you plug in should result in another value being returned. There are many types of Bézier Curves, but we’ll be using the quadratic one that looks like a U.

How a Bézier Curve is Created

Linear interpolation is simply going from one point to another with a specified number. This number (conventionally called “time”) is between 0-1 inclusive. It indicates how far you’re from the start point to the end point proportionally.

If you want to go 1/4 of the way from the first to the next point, you’ll have to use 0.25 as the “time”.

Sound a lot like a linear function, right? It’s exactly that: plug in an input value to get an output value. It’s different in the way that the domain (input) is not infinite, but rather restricted from 0-1. But, we’ll need a formula for this.

To represent the segment between the two points (not the entire infinite line), we have to subtract the first point from the second:

segment = point2 - point1 --order matters!

Now to get the proportion of the segment, you can simply multiply the segment by the “time”. So, putting 0.25 will return 1/4 of the segment.

number = 0.25
proportion = segment * number --1/4 of the segment

Now, to actually get an absolute position between the two points, we need to add the first point to this equation. That makes sense because 0.25 would be travelling 1/4 the distance from the first point.

point3 = point1 + proportion

--[[entire equation

B is in between A and C
t is the "time" value (the input)

]]
B = A + t * (C - A) 

But there is the conventional way to put this:

B = A + t * (C - A) 
B = A + tC - tA
B = A - tA + tC
B = (1 - t)A + tC --final; just remember the first point comes first with the (1 - t)

So, that’s the formula for linear interpolation. How do Bézier Curves use this, you may ask? Well, if you look at this diagram, all a Bézier Curve is is a set of points derived from three linear interpolations:

Bezier2
Image from the DevHub.

There is the left green point being interpolated between P0 and P1, then there’s the right green point going from P1 to P2. And then, there is the black point interpolating between the two green points, creating a curve.

P0 and P2 are endpoints of the curve, while P1 actually shapes the curve. If P1 was exactly between P0 and P2, then it’d just be a line.

See the “t” value increasing at the bottom? That’s the input. This is the value that drives the three interpolations. As the two green points reach the middle, so does the black point on the green line. Pretty interesting, but there is an equation you must know to use this as a function.

It may seem complicated at first, so I’m going to write it step by step.

--[[Bezier Curves are just three linear interpolations

I'll call the left and right green points L1 and L2, respectively; 
and the black point as L3.

]]

L1 = (1 - t)P0 + tP1 --from P0 to P1
L2 = (1 - t)P1+ tP2 --from P1 to P2
L3 = (1 - t)L1+ tL2 --from the Left Green Point to the Right Green Point

If you notice, we can actually shorten that down to one line by substituting for L1 and L2, and then simplifying:

It’s a little messy to put it into Lua, however. (I’m using extra parentheses just in case).

local function curve(t, p0, p1, p2)

    --skip the extra calculations if t is 0 or 1
    if t == 0 then return p0 elseif t == 1 then return p2 end

    return ((1 - t)^2 * p0)  + (2 * (1 - t) * t * p1) + (t^2 * p2)

end
Perlin Noise

The loudest mathematical function:

Perlin Noise

Perlin Noise is a way to get random numbers as an output, but still having a relation to the previous result. Take a totally random function (math.random(x, y)), which can be ridiculously inconsistent.

Say the number of uses of the function is the X-axis and the result is the Y-axis. Its graph can look very jagged and sharp:


There is no relation between one point and another point, they’re purely random.

But, Perlin Noise; huh, that puts random noise to shame! Because the output values are related to the previous one in some way, it’s partially random, but also not at the same time. Its graph is quite smooth:

image
Imagine a person walking in a 2D plane and the graph is their trail. Naturally, no one abruptly changes their path, so their new direction is usually close to the previous direction.

Perlin Noise is useful for procedural terrain generation that can have smooth, randomly generated terrain or it can be used to script an AI to walk in semi-random directions.

math.noise(x, y, z)

This is the Lua implementation of Perlin Noise; it takes in up to three input numbers and spits out a value. This value is just a single number “x” where -1 < x < 1. Only one argument is required (“X”), the others assumed to be zero if left out.

But, what does it mean exactly? How does this function spit out a value between - 1 and 1 by taking in up to three values? Well, I’ve tried to learn how Perlin Noise is calculated, and from what I understand, it uses vectors (“gradient vectors” and “distance vectors”) and takes their dot product (these are also restricted between -1 and 1). Depending on the number of arguments you put in, it either uses a 1D, 2D, or 3D grid to place the vectors in.

I won’t be going too much in-depth with how it calculates the result (as I’m still trying to get my head around it). However, you don’t have to know the inner workings of it to get its general behavior. In this tutorial, we’ll only provide one argument.

Several behaviors for 1D Perlin Noise:

  • The arguments aren’t like the traditional min and max of math.random(), but instead, just any value to get back a single result between -1 and 1.
  • Inputting an integer such as 9 will always result in 0.
  • A cool thing is that inputting the same number(s) will always, always result in the same output (thus, controlled randomness).
  • The greater the increment between the last “X” value and the current one, the more random the results can be; they’ll usually be farther apart as well. Go back up to the graph of Perlin Noise and you’ll see that moving horizontally just ever so slightly will result in a small change in the output value, and moving a lot will return a farther number.

So, how do we use this in Lua, you may ask? Well, to get different results, you’ll need to input different numbers. Remember that you should try to avoid using integers, but in the end, it’s inevitable. Here’s what I mean:

local inc = 0.05 --increment
local counter = 1 + inc --you can also just start at inc, but I prefer to do this

0.05, or any other consistent increment, will eventually make the counter be an integer, thus resulting in a 0. But, that’s fine as long as it’s not too frequent.

Now, to actually use the function over and over again, we’ll need to use a loop or a function that runs repeatedly like RenderStepped:

game.RunService.RenderStepped:Connect(function()

    print(math.noise(counter))
    counter = counter + inc

end)

That’s basically how we will use Perlin Noise in this tutorial, nothing too complicated.


Uses of Realistic Camera

There are perfect times when you can use camera bobbing, dynamic blur, and sprinting. For example:

  • horror games
  • adventure games
  • exploration games

Structure

Basically, this is what the explorer should look like if you want to follow the tutorial. The selected objects are required, and you can disregard the rest:


Because they look the same, I want to clarify: “Sine” is a NumberValue and “IsSprinting” is a BoolValue.

You can just ignore “run” if you want. It’s basically a controlled way of disabling the scripts while not actually setting their Disabled property to true; it’ll turn off various parts of the script. Why? Because of the settings UI I mentioned earlier. At the end of the tutorial, there’ll be a whole kit with the UI, so these “run” values will be there, too. Plus, there’s not much to learn about it and we’re not covering the UI aspect of it today, sadly.


Dynamic Blur

~38 LINES

Dynamic blur is the easiest to create and the shortest in terms of scripting, so this will be first on the list!

PLAN OF ACTION We’ll be tweening the blur based on how much the camera turns. To check in a loop, we’ll use RenderStepped as a reliable and performant method.

Start off with variables:

local rs, ts = game:GetService("RunService"), game:GetService("TweenService")

local blur, camera = game.Lighting:WaitForChild("Blur"), workspace.CurrentCamera

--we'll use these two to make the loop run less frequent (if counter % dampener == 0 then)
local counter, dampener = 1, 5

--[[the faster they turn, the more blurry it gets; so we'll be comparing the
previous look vector of the camera to the current one (dot product), and depending
on the result, the blur will be applied (you'll see later on)
]]
local prevLookVector = camera.CFrame.LookVector

--[[allowedRange is in degrees & maxBlur is in terms of the Blur object's Size property; 
allowedRange will be an angle in which the blur will not initiate, anything beyond
will, however
]]
local allowedRange, maxBlur = 10, 25

local ti = TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut) 
--we'll create the tweens later as their values will change

I just used degrees for allowedRange because’s it’s better to visualize and get exact than just a decimal value between -1 and 1 (dot product range).

Next, we’ll need a small function to return the dot product based on the allowedRange degrees. There is a formula for calculating the dot product this way:

A ⋅ B = |A| * |B| * cos(theta)

But, remember: look vectors are always unit vectors, and thus putting in 1 as A and B is redundant. So, it’ll simply be the cosine of theta:

--Lua trig only accepts radians, so picky geez!
local function degreesToDot() return math.cos(math.rad(allowedRange)) end

Now, onto the last part: the RenderStepped function:

rs.RenderStepped:Connect(function()
	
	if counter % dampener == 0 then --only run every so often
	
        --remember: order doesn't matter for the dot product
		local dot = prevLookVector:Dot(camera.CFrame.LookVector)
			
        --[[anything within 10 degrees of the previous one is not blur-worthy!
        also, 10 degrees will be ~0.98, the closer the dot is to 1, the closer
        the look vector is to the previous one; we want the dot product to be 
        higher than 0.98]]
		if dot >= degreesToDot() then
			
            --just fade out the blur; won't be noticeable if already 0 
			ts:Create(blur, ti, {Size = 0}):Play() 
				
        --outside of the allowedRange
		elseif dot < degreesToDot() then

			--[[you don't need to use this specific equation; just make the blur 
            related to the dot product in some reasonable way]]
			local equation = (1 - dot) * (maxBlur / 2)
			ts:Create(blur, ti, {Size = equation}):Play()
				
		end
			
	end

	prevLookVector = camera.CFrame.LookVector --update  for the next cycle
	counter = counter + 1 --keep the modulus working
	
end)

With the equation I have, the blur never reaches the maxBlur value of 25 I defined, I thought 25 was a decent amount and not too crazy. Remember: don’t overdo!

Dynamic blur is completed!


Sprint

~72 LINES

This one has a ton of variables, only because of the number of tweens there are!

PLAN OF ACTION Upon pressing the desired key (“LeftShift”), the player’s FOV and walk speed will tween up. However, to make it realistic, we won’t allow the player to sprint backwards, for instance (dot product again!). So, while sprinting, if you do try to run in such a way, their FOV and walk speed will reset (same goes for when the key is released). Just in case if they already have the key pressed before running, we’ll use RenderStepped again (as a loop) and :IsKeyPressed() to see if the key is being held down.

Variable time! (remember, I won’t repeat variables like the TweenService)

local player = game.Players.LocalPlayer
local uis = game:GetService("UserInputService")

local char = script.Parent
local hum = char:WaitForChild("Humanoid")
local hrp = hum.RootPart

local normalFOV, sprintFOV = 70, 100 --self-explanatory

--[[you can use absolute numbers if you want; I set default speed to 5 as slower
speeds are more suited for camera bobbing]]
local normalSpeed, sprintSpeed = hum.WalkSpeed, hum.WalkSpeed + 5 

--I'm obssed with Sine
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
local normalTween = ts:Create(camera, ti, {FieldOfView = normalFOV})
local sprintTween = ts:Create(camera, ti, {FieldOfView = sprintFOV})

local playing = Enum.PlaybackState.Playing --shorten it

--accelerate/decelerate humanoid walk speed (no one abruptly goes from 0 to 70mph)
local normalTweenHum = ts:Create(hum, ti, {WalkSpeed = normalSpeed})
local sprintTweenHum = ts:Create(hum, ti, {WalkSpeed = sprintSpeed})

local allowedRange = 45 --degrees; allow sprinting only inside this angle
local key = "LeftShift" --easy to change later on

--we'll need our friend again to convert degrees into the dot product
local function degreesToDot() return math.cos(math.rad(allowedRange)) end

I created a function for this simply to keep it from overflowing horizontally, but it’s not needed:

local function controlledTween(tween)

    --to keep the tween from playing again and interrupting itself over and over again
	if tween.PlaybackState ~= playing then tween:Play() end

end

Now, this isn’t very long at all, it just looks so massive because of all them tweens!

rs.RenderStepped:Connect(function()

	if uis:IsKeyDown(Enum.KeyCode[key]) then --stays true until released

        --[[check if within allowedRange; remember, when they walk sideways
        their move direction is going to be more different than their camera look vector
        (only for first-person)]]
		if hum.MoveDirection:Dot(hrp.CFrame.LookVector) >= degreesToDot() then
			
			isSprinting.Value = true --set this to true for the bobbing script to see
			controlledTween(sprintTweenHum) --walk speed
			controlledTween(sprintTween) --FOV
			
		end

	else --if it's released and not pressed down
		
		isSprinting.Value = false
		controlledTween(normalTweenHum) --walk speed
		controlledTween(normalTween) --FOV

	end

    --if players try to go beyond 45 degree angle, they won't be sprinting no more!
	if hum.MoveDirection:Dot(hrp.CFrame.LookVector) < degreesToDot() then
			
		isSprinting.Value = false
		controlledTween(normalTweenHum) --walk pseed
		controlledTween(normalTween) --FOV
			
	end

end)

And sprinting’s all done! Now, onto the final part of this:


Bob

~87 LINES

This will be the main script in the way that it actually creates something more noticeable and prominent: view bobbing.

PLAN OF ACTION Using Bézier Curves, we’ll create a U shaped path for the camera to travel back and forth on. To keep the motion from looking too linear, we’ll use TweenService to apply an EasingStyle to it. Also, to add a bit of randomness (no one’s perfect in real life), the middle point of the curve will use Perlin Noise and move to semi-random spots. Then, whenever the player stops running, the camera will tween back to the original position.

This one has the most variables, without a doubt!

--shortening purposes
local playing, completed = Enum.PlaybackState.Playing, Enum.PlaybackState.Completed
local style, dir = Enum.EasingStyle.Sine, Enum.EasingDirection.InOut

local sin = script:WaitForChild("Sine") --remember, the NumberValue; it will act as the "time" for our curve
local isSprinting = char.Sprint:WaitForChild("IsSprinting") --aah, finally it came in use

local dur, durSprint = 0.675, 0.5 --tween duration while walking & sprint time, respectively

--the tweens for walking and sprinting (I made them reverse, but running only once)
local ti, tiSprint = TweenInfo.new(dur, style, dir, 0, true), TweenInfo.new(durSprint, style, dir, 0, true)
local oneTween, oneSprintTween = ts:Create(sin, ti, {Value = 1}), ts:Create(sin, tiSprint, {Value = 1})

--offset = camera offset; maxRandomBounds = for clamping the Perlin Noise value for point0
local offset, maxRandomBounds = 0.25, 0.5

--point0 and point2 are on the right and left side, respectively
local point0, point2 = Vector3.new(offset, 0, 0), Vector3.new(-offset, 0, 0) 

--remember, point1 is in the middle; we'll be using Perlin Noise for the X position
local point1 = Vector3.new(0, -offset, 0)

local ti2 = TweenInfo.new(dur / 2, style, dir)

--tween back to the origin
local tweenBack = ts:Create(hum, ti2, {CameraOffset = Vector3.new(0, 0, 0)})

--tween to point0 so it can bob back and forth
local begTween = ts:Create(hum, ti2, {CameraOffset = point0}) --no, I'm not making the tween beg!

--this will be for our Perlin Noise, the higher the inc, the more random
local counter, counterInc = 1, 0.1

--this will be true once begTween starts playing (to make it not play again)
local running = false

Yeah, that wasn’t so bad when you break it down and explain each one. Alright, alright, let’s move onto the functions before getting to RenderStepped:

local function curve(t, p0, p1, p2) --curve creation
	
    --skip calculations when t is 0 or 1
	if t == 0 then return p0 elseif t == 1 then return p2 end

    --curve forumla
	return (((1 - t)^2) * p0) + (2 * (1 - t) * t * p1) + (t^2 * p2)

--[[do note: we're going to input vector3 values in p0, p1, and p2 
(point0, point1, and point2); but vector3 can be multiplied! so, the formula
still works]]
	
end

local function sprint() --run different tweens depending on if sprinting or not
	
    --just make sure the tween's not playing so we don't interrupt it
	if oneTween.PlaybackState ~= playing and isSprinting.Value == false then 
			
		oneTween:Play() --run the slower tween when sprinting is false
				
	end
			
	if oneSprintTween.PlaybackState ~= playing and isSprinting.Value == true then
			
        --i notice that we don't need to pause/cancel the tweens, it happens automatically
		oneSprintTween:Play() --run the faster tween when sprinting is true
				
	end
	
end

Lastly, we have the RenderStepped function. You can use the deltaTime parameter is you want, but I don’t think you should rely on something that unstable (frame rate is not always 60).

rs.RenderStepped:Connect(function(deltaTime)	
		
    --[[move direction's magnitude is either 0 (not running) or 1 (running), nothing else!
    use the dot product instead of magnitude to avoid unnecessary square root
    calculations (which are expensive). a vector's dot product w/ itself is
    its magnitude squared, so 0 squared is still 0]]

	if hum.MoveDirection:Dot(hum.MoveDirection) > 0 then
	
        --tween to point0, and run it only once
		if (not running) then begTween:Play() running = true end

		--after the tween reaches point0, continue
		if begTween.PlaybackState == completed then 
			
			if sin.Value == 0 then --aka when it's point0, set some things
				
				counter = counter + counterInc --for different Perlin Noise 

                --[[Perlin Noise is from -1 to 1, which can be too big of a range
                so clamp it down with the maxRandomBounds variable]]
				point1 = Vector3.new(math.clamp(math.noise(counter), -maxRandomBounds, maxRandomBounds), -offset, 0)
				
                --[[separate thread to not delay the function further
                I noticed that if it does delay, the tween becomes lopsided
                where it goes fast to point2 then slower towards point0;
                with coroutines, this will only happen at the beginning
                and it will balance out shortly after (IDK the reason!)]]
				local coro = coroutine.wrap(sprint) 
				coro()
				
			end

			--[[we actually need to set the cam offset w/ the number value
            remember: sin.Value will act as the "time" in the curve function]]
			hum.CameraOffset = curve(sin.Value, point0, point1, point2)
				
		end
			
	else --if the player is not running
					
		tweenBack:Play() --tween back to the beginning (if already there, it's not noticeable)
		oneTween:Cancel() --stop the walk/sprint tweens
		oneSprintTween:Cancel()
		sin.Value = 0 --reset the "time" value to 0
		running = false --make this false to allow begTween to play again
			
	end
			
end)

Woah, did that even seem like 87 lines? Them variables be hoggin’ up 27, so it was really 60 lines! Fun fact: this script was almost 156 lines before. Why? Because I had two tweens for going to point2 and coming back to point0, but then I realized, you can just make the tween reverse! That made things much more efficient.

Do remember that this is for first-person, so in third person, the bobbing may not sync up with the footsteps! If you want actual footsteps in first-person, disable the normal walking sound in the sound scripts, then add your own footsteps that match the bobbing.

Also, this is a simple script, so you may add more “jazz” in if you would like to. Plus, various variables like dur and durSprint need to be adjusted manually to somewhat match the walk speed. Like I said before, there is so much customization for this!


Full Kit

Recall how I mentioned that having a GUI that allows players to turn off these effects is recommended? Well, although not covered in this tutorial, that UI is in this kit below along with the scripts we just covered. To actually make the GUI work with the scripts, the code may look a teeny bit different, but it’s 99% the same (nothing’s removed from what we covered).

GetRealisticCamera

I couldn’t do the thumbnail properly on the model itself, so I made a custom link box. :stuck_out_tongue_winking_eye:

:warning: If you want to use the same UI I provided with the kit, please do not change the names of the scripts or anything within the UI itself! If you don’t want the UI, you can just delete it, then you can safely modify the objects. I’m saying this because my UI scripts are very name-based so, they can break easily.

Instructions

  • The ScreenGui – put it in StarterGui
  • And the three LocalScripts – parent them to StarterPlayer > StarterCharacterScripts

Closing Remarks & Feedback

This was one of those longer tutorials, so there may be some flaws that I have missed. Along with the polls, feel free to DM me with any mistakes in this tutorial, though I will reread after posting and edit if needed.

Rate this tutorial.
How well did you understand the topic? Are the diagrams/images clear? Etc.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

Did you learn anything new?
Any concepts as well (like Perlin Noise and Bézier Curves).

  • Yes
  • No

0 voters

On a scale of 1-10, how likely are you to use this (or just a part) in your games/projects?
Is this useful to you in any way? Are you going to implement this into your games? Etc.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

That’s it for today! I know it’s been a while since my last tutorial, but man I spent days trying to perfect the scripts and the UI.

Thank you all for your time and feedback,
And wear shades, but don’t be too shade-y! :sunglasses:

187 Likes

Hey, nice tutorial, thanks for your contribution, i would like to know if it is possible that you could show us a video example of how it works, or something like that, so we don’t need to install the model and test it for ourself, thanks for reading.

5 Likes

Oh yes, that is one of things I forgot! But, I swear, my computer is so bad that every video lags. Plus, I wasn’t sure if such subtle effects would show in the video, but I can amp them up a little if needed.

I’ll see what I can do. :slight_smile:

3 Likes

If you use Windows 10, press Win + G. It open the Xbox Game Bar, which has a really good screen recorder. My computer is pretty bad too and every other screen recorder lags, except for Game Bar.

Anyways, thanks for the tutorial!

7 Likes

I also would like to see images or videos. This helps us to visualize while following the tutorial if we aren’t doing it in Studio, especially mobile users.

4 Likes

@ArtFoundation @Fire540Games @Conejin_Alt

I have finally made a video. If you want to see it, just scroll up to the “video” section!

3 Likes

What I like to do is just check the characters magnitude then just offset it smoothly only on the Y though. I can maybe see swaying in third person but going on the x in first person just confuses the gamer.

Yes, that’s why I emphasized that you should have a turn off setting, because not everyone will be comfortable with these effects. Plus, you don’t have to copy the tutorial, change the parts that you need so that it best suits your preferences.

1 Like

Before developers rush to implement this, make sure to include option to disable these features. The sprint camera changes FOV which may be very disorienting on ultrawide displays or multi-display setups if the strength is not left at a reasonable value, while dynamic blur is generally disliked by many. View bobbing, while nice, can easily cause motion sickness after a while even if it is kept weak.

4 Likes

Yes, thank you for the emphasis. I have said this in the notes, and if people want to use the exact kit, it has it built-in with a splendid UI!

However, if you use your own system, then make sure to add a UI. I didn’t reflect on how I did this, but one way is using a bool value object inside of the scripts that are used to turn on/off certain parts of the script via if-statements. I’ve received feedback about this being a weird method, so using module scripts would also work perfectly.

I honestly don’t like dynamic blur that much either (comparatively and absolutely) because of how intense it is in games that use it. That’s why I made it almost impossible to notice most of the time in this tutorial and encouraged readers to do so as well.

So, keep the effects subtle and allow users to toggle them.

2 Likes

I like how you make it barely visible. In real life, it gets blurry if you spin fast, so thats pretty realistic. I think that everything is set realistically and reasonable

More about the blur, I feel like when you spin in real life it is that object moving leaving a ‘trail’ causing it to blur. Here is an example: Screen Shot 2020-09-01 at 5.32.38 pm

When you turn it causes the objects to appear to be ‘whizzing’ past you that our eyes cannot catch causing it to become blurry. I think the blurriness could maybe do some work.

I hope I could help and well done on this awesome creation!

Edit: I have a very fast moving gif and I took a screen shot, you can see the feet are slightly blurring
which is what you would need: Screen Shot 2020-09-01 at 5.35.06 pm

5 Likes

Yes, I tried my best to work with the one type of blur the engine supports, and I have thought of trying o creation that trail blur. But I guess I hit a limitation there.

Thanks for letting me know!

3 Likes

Yea, it would be almost impossible to create such a realistic thing, but well done!

3 Likes

Hey I am not being lazy or anything but can you put this is a game because I am low on time and I need the game file quick :slight_smile:

Sure…

RealisticCamera.rbxl (38.3 KB)

All you had to do was click on the model in the toolbox and put the items in the correct location.

3 Likes

nice tutorial! I would like to know if you could at a stamina system to the sprint. That would be awesome!

1 Like

Yeah, it’d be quite simple to implement. You can have a separate script check if the player has run out of stamina. Then, you can add a condition to the Sprinting script’s if-statement that evaluates to true if the player has stamina, false if not. Something like this, although not the entire code:

--this is in the part where the Shift key is down
--stamina can be a module that returns true if stamina > 0, and false if = 0
--condition 1 is that old dot product one, I just shortened it to focus more on the second condition
if condition1 and stamina:CanRun() then

    isSprinting.Value = true
    --among other things

end

DM me if you have any more questions.

1 Like

For the bobbing, the camera goes in and out. How could I change it so that it goes from left to write with still the up and down bobbing style? Thanks :]

You’re most likely manipulating the Z direction of the camera and not the X and Y. The camera’s front side is the negative Z vector and the back the positive.

1 Like

Also, how would I make a specific run animation or any animation in general play while sprinting, (if player is not out of stamina, of course).