The Fastest Camera System You'll Probably See Written For Roblox!

A function for using a controllers right stick to change direction would make this module 10/10

4 Likes

This looks great! I recommend removing the “probably” from the topic title, it makes it more appealing! I also would recommend moving this to #resources:community-resources until the tutorial is complete!

3 Likes

Seems a bit silly to worry so much about how fast your camera is. Are there any cases where the performance of your camera is a major concern?

1 Like

On the contrary, camera calculations have to be ran usually at the beginning of every RenderStepped (Priority 200 to be exact), and while most camera systems usually don’t show any bottlenecking on most modern cpus, older mobile devices can struggle especially with hardware specific limitations

This resource accounts for small optimizations with SMID computing and generally quicker alternatives to bloaty math calculations (i.e. inverse multiplying vs normal division), which alleviates stress on user’s devices and overall lead to a more smoother experience

7 Likes

In terms of where performance for cameras can be a major concern is coding some expensive mechanic for a game that can be pretty tolling on FPS. A good example of this from experience was writing up some custom water. The calculations for the water were as optimized as they can get. The problem came down to some latency with userdata creation. Even when it comes to optimizing out userdatas, it was noted just how poor Roblox optimized their own scripts which turned out to be the true bottleneck. It’s actually pretty common for professional developers to rewrite custom game mechanics for Roblox. For example, it’s also common for some games to have their own physics engine specifically optimized for their games because the actual Roblox physics engine was that much of a bottleneck for them or even custom humanoids. Regardless, it doesn’t hurt to use a custom Camera that is more faster than Roblox’s camera as it just gives you more freedom of choice for mechanics you may want to add to your game. It’s the idea of compensating for resources that may be added in the future.

On a technical breakdown, a big problem relating to performance that was mentioned in the post under the design section was:

  • GC footprint: Luau’s GC (garbage collector) is notorious when it comes to cleaning up userdata creation or even table creations that may result in GC Assist instructions which will halt Luau scripts for this extra work needing to be done by the GC.
  • Consideration of Luau->C/C++ bridge invocations: Biggest bottleneck Roblox games face. It is extremely expensive to bridge data between C++ and Luau often making calculations in Luau much faster than they could be in C++.

As mentioned, userdata is somewhat often difficult for the GC to clean up which can have some performance impacts. On a frame-per-frame breakdown, most of your userdata creation will most likely stem from the default Roblox camera scripts. The math isn’t the best and many people actually despise the modules itself. There are plenty of calls to the C++ side to bridge back data such as CFrames for needed math or thereof. In the context of CFrame specifically, some people may argue it’s sort of a lazy way of working with matrix math which is true considering doing matrix math out by hand takes some effort, however, you can actually simplify most matrix calculations and only use a single CFrame object when needed to update an object’s coordinate frame. For example:

local _ = CFrame.new(...) * CFrame.Angles(...) -- Common practice

Alternatively, this can be simplified into just one CFrame. This was pretty much the basis of optimizations that were considered for the camera. In terms of raw-computing speed, some of these calculations that Roblox may do for their matrix calculations can sometimes be further simplified given certain cases so you can compute some math faster than what they could be in C++ but it is important to mention that Roblox does have some benefits from hardware-related optimizations such as inverting a CFrame so it’s important to benchmark when it comes to uncertainty.

It’s always good to consider expensive operations in your main loops and how you may optimize them. @maycoleee2231 Does a nice job explaining the rest.

5 Likes

I see. I’m guessing the reason why it’s difficult for me to track this performance penalty of using the default camera script is because most of the price is paid in GC, which is as opaque as it gets for performance tracking. If the proposed change to making CFrame a native type goes through eventually, this difference will become negligible if I’m not mistaken.

I suppose until then, your camera system is indeed a big boost. I don’t think most people are that concerned about performance, but as a proof of concept, it is well done. Nice!

I have no idea what any of this code means, but it looks awesome! Thanks for your contribution <3

I think this is something I’d look into for my own games.

1 Like

:raised_hand:
me

This is an awesome resource and really nicely written too.

Could you provide a little more clarification as to the difference between the CameraSystem and FastCameraSystem? Is it just that FastCameraSystem is more optimized, but less readable (or is it something more)?

1 Like

What function does the normal camera system use that is not present in Luau? I’m curious.

Also, you mention it’s faster, but you don’t exactly tell us the difference.

YEAH, this looks good, it’s certainly very useful. After activating your system can you move the camera to some object?

Yes just as you assumed, I probably forgot to explain the other file but it’s most likely explained in the GitHub. The faster version is considerably faster but less readable but the original version already achieves reasonable speeds which is why I decided to still keep a saner version.

Wow this module is really nice

Is there a way for you to implement head follow cursor much like head follow camera?

Very awesome resource, although is R6 compatibility planned anytime soon?

The normal camera system uses the C++ library that isn’t native to Luau, such as the CFrame library or needing to index C++ instances. This all requires bridging data between Lua and C++.

It’s hard to present accurate benchmarks with Roblox’s camera system and mine because of how constrained Roblox’s player module is but I can give you an analysis of the math they use to put the perspective of how much faster my module is.

Here is a somewhat rough yet most basic example: say I wanted to build a rotation matrix from only the x-axis and probably want to multiply some existing transformation by this rotation. Traditionally, we would do something like CFrame.angles(x, 0, 0) or equivalently fromEulerAnglesXYZ(x, 0, 0). However, given the case where we only need rotation off the X-Axis, we can simplify the actual calculations for the rotation matrix given that we have to take the cosine and sine of the other axes like so:

local function AnglesXYZ(x, y, z)
	local m11 = cos(y) * cos(z)
	local m12 = -cos(y) * sin(z)
	local m13 = sin(y)
	local m21 = cos(z) * sin(x) * sin(y) + cos(x) * sin(z)
	local m22 = cos(x) * cos(z) - sin(x) * sin(y) * sin(z) 
	local m23 = -cos(y) * sin(x)
	local m31 = sin(x) * sin(z) - cos(x) * cos(z) * sin(y)
	local m32 = cos(z) * sin(x) + cos(x) * sin(y) * sin(z)
	local m33 = cos(x) * cos(y)

	return CFrame.new(0, 0, 0, m11, m12, m13, m21, m22, m23, m31, m32, m33)
end

Because of our known axes (x, 0, 0) we don’t have to waste precious computations calculating their sin and cosign since we know sin(1) = 0 and cos(1) = 0 now we can start to simplify:

Simplifying
--local function AnglesX(x)
--	local m11 = cos(0) * cos(0)
--	local m12 = -cos(0) * sin(0)
--	local m13 = sin(0)
--	local m21 = cos(0) * sin(x) * sin(0) + cos(x) * sin(0)
--	local m22 = cos(x) * cos(0) - sin(x) * sin(0) * sin(0) 
--	local m23 = -cos(0) * sin(x)
--	local m31 = sin(x) * sin(0) - cos(x) * cos(0) * sin(0)
--	local m32 = cos(0) * sin(x) + cos(x) * sin(0) * sin(0)
--	local m33 = cos(x) * cos(0)

--	return CFrame.new(0, 0, 0, m11, m12, m13, m21, m22, m23, m31, m32, m33)
--end

--local function AnglesX(x)
--	local m11 = 1 * 1
--	local m12 = -1 * 0
--	local m13 = 0
--	local m21 = 1 * 0 * 0 + cos(x) * 0
--	local m22 = cos(x) * 1 - sin(x) * 0 * 0
--	local m23 = -1 * sin(x)
--	local m31 = sin(x) * 0 - cos(x) * 1 * 0
--	local m32 = 1 * sin(x) + cos(x) * 0 *0
--	local m33 = cos(x) * 1

--	return CFrame.new(0, 0, 0, m11, m12, m13, m21, m22, m23, m31, m32, m33)
--end

--local function AnglesX(x)
--	local m11 = 1
--	local m12 = 0
--	local m13 = 0
--	local m21 = 0
--	local m22 = cos(x)
--	local m23 = -sin(x)
--	local m31 = 0
--	local m32 = sin(x)
--	local m33 = cos(x)

--	return CFrame.new(0, 0, 0, m11, m12, m13, m21, m22, m23, m31, m32, m33)
--end

Yields us:

local function AnglesX(x)
	local Cosx = math.cos(x)
	local Sinx = math.sin(x)

	return CFrame.new(0, 0, 0, 1, 0, 0, 0, Cosx, -Sinx, 0 ,Sinx, Cosx)
end

In all it’s glory, we are left with just this however if you are a mathy person you could probably have derived this on your own without even trying to think of the rotation matrix by working in a 2D perspective. Building a full CFrame however isn’t as useful to us and barely gives us a speed up. As the example given:

want to multiply some existing transformations by this rotation

We are actually more interested in just Cosx and Sinx which we can actually directly multiply now with a transformation. The instinctive minds however here will notice that just a X-Axis rotation matrix is mostly composed of 0s and a 1 component meaning we have more cool shortcuts to do, the fun never ends!

Now, from all of this rotation matrix fun, we have eliminated a CFrame creation since we don’t have to do something like: Transformation*CFrame.Angles(...) we can just multiply only 4 components and cancel other components out. In just the rotation matrix alternative, we have actually just speed up our math by roughly 7467.8%!

See for yourself

Rotation Math Example Benchmark
local Clock = os.clock()
for i = 1, 100  do
	local Cosx = math.cos(i)
	local Sinx = math.sin(i)

	local m11 = 1
	local m12 = 0
	local m13 = 0
	local m21 = 0
	local m22 = Cosx
	local m23 = -Sinx
	local m31 = 0
	local m32 = Sinx
	local m33 = Cosx
	
end
print(os.clock()-Clock, "Faster Rotation")


local Clock2 = os.clock()
for i = 1, 100  do
	local _ = CFrame.Angles(i, 0, 0)
end
print(os.clock()-Clock2, "CFrame Rotation")
3 Likes

what one is over the shoulders?

2 Likes

I can’t find as well. @4thAxis Am I looking at the wrong places or has it been removed?

1 Like

The ShiftLockCamera function is the over-the-shoulder camera. Should probably rename this.

Call these functions from the CameraSystem module:

Module.EnableShiftLockCamera
Module.DisableShiftLockCamera
1 Like

yes please rename that ---------

1 Like

Hey man, is there anyway I’d be able to disable the camera bobbing? I’m using the isometric view and it’s really obnoxious, love what you’ve done though :slight_smile:

Hi, how do I use this with R6?

1 Like