Forcing a certain aspect ratio for the camera on all clients

Hi, I have a problem.

First, let me start off with this image.

This is from a game with a fixed camera angle, and I’d like it to look like this for all clients.
However, due to the way the camera is handled on Roblox, it will look differently for any windows that are not displaying in a 16:9 aspect ratio.
For example, a device displaying the game in a square aspect ratio would look a little something like this.

What would be the optimal way to force the camera to display in a 16:9 format regardless of the actual aspect ratio?
(If someone was playing in the square aspect ratio, I’d expect it to look something like this.)

I understand that this is possible to do with a viewport frame in the same way someone would force GUIs to adhere to a certain aspect ratio, however viewport frames don’t have the full range of rendering capabilities that the main window does, so it would be a compromise to do it this way.

Curious to see what kind of solutions the developer community has come up with to combat this.

3 Likes

Dealing with field of view

First of all, since your play field is mostly horizontal, you’ll probably want the horizontal FoV to be the same for all players, rather than the vertical FoV. The Camera.FieldOfView property sets the vertical FoV, so you’ll have to do some calculations to figure out which vertical FoV results in the desired horizontal FoV with the current screen aspect ratio.

Here are functions for *calculating* the vertical and horizontal FoV, given a screen and a camera:

Note that the getVerticalFov function is pretty redundant because you can just get that number with CurrentCamera.FieldOfView. But it gives a good idea of how the calculation works.

function getVerticalFov(camera)
	return camera.FieldOfView
	
	--Equivalent calculation:
	--local z = camera.NearPlaneZ
	--local viewSize = camera.ViewportSize
	--
	--local r0, r1 = 
	--	camera:ViewportPointToRay(viewSize.X/2, viewSize.Y*0, z), 
	--	camera:ViewportPointToRay(viewSize.X/2, viewSize.Y*1, z)
	--	
	--return math.deg(math.acos(r0.Direction.Unit:Dot(r1.Direction.Unit)))
end

function getHorizontalFov(camera)
	local z = camera.NearPlaneZ
	local viewSize = camera.ViewportSize
	
	local r0, r1 = 
		camera:ViewportPointToRay(viewSize.X*0, viewSize.Y/2, z), 
		camera:ViewportPointToRay(viewSize.X*1, viewSize.Y/2, z)
		
	return math.deg(math.acos(r0.Direction.Unit:Dot(r1.Direction.Unit)))
end
Here's a function for calculating the camera aspect ratio (apparently not the same as screen FoV?)
function getAspectRatio(camera)
	--Apparently this is not the same as
	--	screen.AbsoluteSize.X / screen.AbsoluteSize.Y
	--In my tests, only the current version gave the correct result
	return camera.ViewportSize.X / camera.ViewportSize.Y
end
Here are functions for setting either the vertical for horizontal FoV
function setVerticalFov(camera, fov)
	camera.FieldOfView = fov
end

function setHorizontalFov(camera, fov)
	local aspectRatio = getHorizontalFov(camera)/getVerticalFov(camera)
	
	camera.FieldOfView = fov / aspectRatio
end
Here are some screenshots showing exactly what happens when setting the horizontal FoV instead of the vertical FoV:

At aspect- ratio near 1 to 1:

At aspect- ratio closer to 2 to 1:

Notice how the horizontal FoV (in the output window) stays at 80 (because that’s what I set it to) between the two aspect- ratios. Also notice how the two red balls are always near the left and right edges of the screen, no matter how it’s stretched. The opposite is the case if you keep the vertical FoV constant. In that case, balls near the top and bottom edges of the screen will remain so when the screen is stretched.

Here's an example script showing how you can make sure the camera always has a fixed horizontal FoV, even if the player resizes the game window:
-- Keep a constant horizontal FoV. Put in game.Players.StarterPlayerScripts

-- Set the horizontal FoV at the start of the came
setHorizontalFov(camera, 80)

-- Set it every time the camera viewport changes size
camera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
	setHorizontalFov(camera, 80)
end)

Dealing with aspect ratio

As for making sure the player can only every see enough of the screen to force a certain aspect ratio, you’ll have to adjust the black bars. At aspect rations less than 16:9 you’ll need horizontal bars like in your example picture, and for aspect ratios greater than 16:9 you’ll need vertical bars at the sides of the screen. You don’t actually need a script for this, since the GuiElement.AnchorPoint property was released. Just create a setup like this:

image

  • Check BlackBars.IgnoreGuiInset.
  • Set UiAspectRatioConstraint.AspectRatio to 1.778 (which is approximately 16/9).
  • Set UiAspectRatioConstraint.AspectType to ScaleWithParentSize.
  • UiAspectRatioConstraint.DominantAxis = Height.
  • The AnchorPoint of BarLeft should be (1, 0).
  • BarRight.AnchorPoint = (0, 0).
  • BarTop.AnchorPoint = (0, 1).
  • BarBottom.AnchorPoint = (0, 0).
  • Set the Size of each bar Frame to (10, 0, 10, 0)

You can change the 10's to even bigger numbers, if you expect any player to ever have an aspect ratio greater than 10:1 or less than 1:10. If those numbers aren’t big enough, the black bars might not fill the entire space that they should.

Screenshots of aspect ration- forcing black bars

If screen is wider than 16:9 (e.g. phones, widescreen PCs)

Notice the gap in the upper left corner. That’s because I forgot to check BlackBars.IgnoreGuiInset.

If screen is narrower than 16:9 (e.g. older monitors)

image

You can add textures to make it look a bit prettier/less jarring. Or just pick a less contrasty- color than black.

: D

So I hope that’s helpful. It was pretty interesting figuring out how to calculate/set the horizontal FoV, and the black bars were surprisingly easy with Roblox’s UI constraint tools. Let me know if you have any questions, if anything is unclear or you don’t understand parts of the code. In case you can’t get stuff to work, here’s a place file Horizontal FOV.rbxl (21.0 KB)

19 Likes

Thank you! This is just what I needed.

I’ve compiled together a package so anyone else interested in applying a fixed aspect ratio to their place can do so as well.
I plan to make a separate thread for it, so I will edit a link to it here once it’s up.

EDIT: Here it is!

2 Likes

I know this topic is old, but @ThanksRoBama gave such a great response - I just feel that it could be slightly improved upon so as to make this thread an ever so slightly more valuable resource for future searchers (such as myself).

The getHorizontalFov function is overly complicated. It’s actually possible to calculate more directly, without getting into creating Ray instances. The math is explained better in this Wikipedia article. Here’s my version:

function getHorizontalFov(camera)
    local verticalFov = math.rad(camera.FieldOfView)
    local aspectRatio = camera.ViewportSize.X / camera.ViewportSize.Y
    return math.deg(2 * math.atan(math.tan(verticalFov / 2) * aspectRatio))
end

Everything else is fantastic :slight_smile: Thanks so much for this thread!

4 Likes