LetterBoxer! | A Cool Letterboxing module for Cinematic Scenes

Hey! Have you ever wanted to create custom cinematic shots with cool black frames on the top or bottom of the screen? Well… look no further! LetterBoxer is a custom Letterboxing module that creates Letterboxing for you! It takes no time at all to set up, it’s mostly customizable and you can learn it quickly!

Here’s a quick example on how I used it in my upcoming game:

Constructor

local LetterBoxer = require(path.To.LetterBoxer)

LetterBoxer.New(John123, 1) 
--Creates and returns a new Letterbox object

This function takes a Player and an optional DisplayOrder for specification of UI overlap. Taking a player parameter makes the Letterbox versatile for the Client and Server!

Methods

:Descend(...)

local LetterBoxer = require(path.To.LetterBoxer)
local Letterbox = LetterBoxer.New(Joe123, 1) --Setting up an example Letterbox

Letterbox:Descend(
  {Intensity, Lifetime},  --The intensity and lifetime (covered below)
  {Top = {Intensity, Lifetime}}, --Specific intensity and lifetime for a specific bar
  TweenInfo.new(1.75, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), --Base tween info
  {Bottom = TweenInfo.new(0.5, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out)} --Specific tween info for certain bars
)
--This function brings the letterbox frame(s) into view (or not) depending on your choice of arguments

This function takes a type which includes:

Intensity - How much you letterbox you want to cover the screen. This is not limited but you’ll only see effect from a range >0 and <= 0.5.

Lifetime - How long the Letterbox will descend before going back up AKA Ascending (covered next). Set to 0 or nil to specify not to go back up automatically.

Here, you can see the custom usage of tweening in my game to achieve different playful effects with Descent.

:Ascend(...)

local LetterBoxer = require(path.To.LetterBoxer)
local Letterbox = LetterBoxer.New(Joe123, 1) --Setting up an example Letterbox (again)

Letterbox:Ascend(
  {"Top"},  --Ignore List: Ignore certain bar's when moving offscreen
  TweenInfo.new(1.75, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), --The default tween data
  {Top = TweenInfo.new(1.75, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out)}, --The tween data for a specific bar
)
--This function moves letterbox frame(s) offscreen, away from view (or not) depending on arguments

This function takes an ignore list to ignore bars, default tween data, and specific tween data and moves specified/unspecified bars offscreen depending on the arguments you input.

:Rotate(...)

local LetterBoxer = require(path.To.LetterBoxer)
local Letterbox = LetterBoxer.New(Joe123, 1) --Setting up an example Letterbox (hey again)

Letterbox:Rotate(
  {"Top"},  --Ignore List: Ignore certain bar's when rotating
  {Angle = 5, Lifetime = 3}, -- Rotate Properties: Angle Rotation and Lifetime
  {Bottom = {Angle = -7.3, Lifetime = 1.5}}, --Specified Properties for a specific bar
  TweenInfo.new(1.75, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), --The default tween data
  {Bottom =  TweenInfo.new(1.2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)} --Specific tween info for certain bars
)
--This function rotates letterbox frame(s) around the screen center/origin

This function takes a type which includes:

Angle - How much the letterbox rotates

Lifetime - How long the Letterbox will rotate before returning to a rotation of zero. Set to 0 or nil to cancel this action.

Here, you can see the usage of rotation, along with the usage of “Property Specification” to achieve a cool rotation effect!

:Destroy(boolean)

local LetterBoxer = require(path.To.LetterBoxer)
local Letterbox = LetterBoxer.New(Joe123, 1) --Setting up an example Letterbox (hey again)

Letterbox:Destroy(
  true --Smooth Destruction: When destroying, rotate and tween bars back off-screen or destroy blatantly
)
--This function destroys the Letterbox making it useless :(

Disclaimers!

  • This module was rushed and may lack some intended features!

  • I’m not that an experienced of a scripter. I have a little bit over 2 years of experience so don’t flame me if you see unorganized or code considered improper

  • This is my first Community-Resource related post so my documentation skills aren’t the best to start off with

  • The code WILL error for nil or missing values that you defined but have no true value. Remember to put define your variables with a value or leave them blank!

Notes!

  • Have fun with this module. I’d like to see some the things you guys create. Show them and I could post them on an Examples section!

  • This project is completely open-sourced and requires no such proper citation or credit to the author, me.

  • Post suggestions!

Other Stuff!

  • You may have wondered why I’ve been using the words “Top” and “Bottom.” These actually refer to the specific letterboxing bars. Top is the bar at the top and bottom is the bar at the bottom. You can get the bars using a semi-private method :_GetUI. The first return item is a table of the center frames, the frames the letterboxes rotate around the second return item is actual table of the letterboxes!

*** ↓ DOWNLOAD BELOW! ↓ ***
LetterBoxer | Creator Store

or For external IDE users, copy the direct source code below:

local TweenService = game:GetService("TweenService")

local LetterboxModule = {}
LetterboxModule.__index = LetterboxModule

export type boxingProperties = {
	Intensity: number?,
	Lifetime: number?
}

export type rotateProperties = {
	Angle: number?, 
	Lifetime: number?
}

export type rotateOverride = {
	[string]: rotateProperties
}

export type boxOverride = {
	[string]: boxingProperties
}

export type boxTweenOverride = {
	[string]: TweenInfo
}

local Defaults: boxingProperties = {
	Intensity = 0.2,
	Lifetime = 0,
}

local RotateDefaults: rotateProperties = {
	Angle = 5,
	Lifetime = 0
}

local DefaultTweenTime = 1.75

local boxingTweenDefault = TweenInfo.new(DefaultTweenTime, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out)
local rotateTweenDefault = TweenInfo.new(DefaultTweenTime, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out)

-- Shared tween config for bars
local BAR_TWEEN_DATA = { Enum.EasingStyle.Circular, Enum.EasingDirection.In }
-- Shared tween config for rotation
local ROTATE_TWEEN_DATA = { Enum.EasingStyle.Sine, Enum.EasingDirection.InOut }

local Names = {
	"Top",
	"Bottom"
}

local letterboxData = {
	Top = {
		AnchorPoint = Vector2.new(0.5, 0),
		Position = UDim2.fromScale(0.5, -1)
	},
	Bottom = {
		AnchorPoint = Vector2.new(0.5, 1),
		Position = UDim2.fromScale(0.5, 2)
	}
}

export type LetterObject = typeof(LetterboxModule.New())

--------------------
--HELPER FUNCTIONS
--------------------

local function HandleDeadTween(letterObject: LetterObject, tween: Tween)
	local foundTweenIndex = table.find(letterObject.ActiveTweens, tween)

	if foundTweenIndex then
		table.remove(letterObject.ActiveTweens, foundTweenIndex)
	end

	if tween then 
		tween:Destroy()
	end
end

local function HandleTweenDemolishment(letterObject: LetterObject, tween: Tween)
	
	tween.Completed:Once(function()
		HandleDeadTween(letterObject, tween)
	end)

	tween.Destroying:Once(function()
		HandleDeadTween(letterObject, tween)
	end)
end

local function PlayTableOfTweens(Tweens: {Tween})

	for key, tween in pairs(Tweens) do

		tween:Play()

	end

end


function LetterboxModule.New(player: Player, DisplayOrder: number?)
	local self = setmetatable({}, LetterboxModule)
	
	local plrGui: PlayerGui = player:WaitForChild("PlayerGui")
	
	local letterboxFolder: Folder = plrGui:FindFirstChild("LetterboxGuis")
	
	if not letterboxFolder then
		letterboxFolder = Instance.new("Folder")
		letterboxFolder.Name = "LetterboxGuis"
		letterboxFolder.Parent = plrGui
	end
	
	local newLetterboxGui = Instance.new("ScreenGui")
	newLetterboxGui.Name = `LetterboxGui_{#letterboxFolder:GetChildren() + 1}`
	newLetterboxGui.IgnoreGuiInset = true
	newLetterboxGui.ClipToDeviceSafeArea = false
	newLetterboxGui.Parent = letterboxFolder
	newLetterboxGui.DisplayOrder = DisplayOrder ~= nil and DisplayOrder or -1
	
	
	self.LetterboxGui = newLetterboxGui
	self.Player = player
	self.UI = {}
	self.CenterFrames = {}
	self.LetterboxFrames = {}
	self.ActiveTweens = {}
	
	self:_CreateUI()
	
	return self
end


--------------------
--METHODS
--------------------

function LetterboxModule:Descend(
	PropertyDefault: boxingProperties?, 
	FrameOverride: boxOverride?, 
	TweenDefault: TweenInfo?, 
	TweenOverride: boxTweenOverride?
)

	
	local function constructTween(tweenData: TweenInfo, Name: string)
		
		local tData = tweenData or TweenDefault or boxingTweenDefault
		
		local LetterboxFrames = self.LetterboxFrames
		local foundFrame: Frame = self:_GetLetterboxFrameFromName(Name)
		
		local boxPropData = letterboxData[Name]
		local intensity = (FrameOverride and FrameOverride[Name] and FrameOverride[Name].Intensity) 
			or (PropertyDefault and PropertyDefault.Intensity)
			or Defaults.Intensity
		intensity += 1			
		
		local newTween = TweenService:Create(foundFrame, tData, {
			Size = UDim2.fromScale(2, intensity)
		})
		
		table.insert(self.ActiveTweens, newTween)
		
		HandleTweenDemolishment(self, newTween)
		
		return newTween
		
	end
	
	local newTweens = {}
	
	for index, Name in ipairs(Names) do
		
		local foundTweenData = TweenOverride and TweenOverride[Name]
		
		local newTween = constructTween(foundTweenData, Name)
		
		newTweens[Name] = newTween
		
		local foundFrame: Frame = self:_GetLetterboxFrameFromName(Name)
		
		local OverrideProperties: boxingProperties = FrameOverride and FrameOverride[Name] or PropertyDefault
		local Lifetime = OverrideProperties.Lifetime
		
		if Lifetime then
			task.delay(Lifetime, function()
				
				self:Ascend()
				
			end)
		end
		
	end
	
	PlayTableOfTweens(newTweens)
	
end

function LetterboxModule:Ascend(
	StopOverride: {string}?, 
	TweenDefault: TweenInfo?, 
	TweenOverride: boxTweenOverride?
)
	
	local letterboxes = {}
	local letterboxNames: {string} = {}
	
	for index, name in ipairs(Names) do
		
		if StopOverride and table.find(StopOverride, name) then continue end
		
		local foundLetterbox: Frame = self:_GetLetterboxFrameFromName(name)
		
		letterboxes[name] = foundLetterbox
		
		table.insert(letterboxNames, name)
		
	end
	
	local function constructTween(tweenData: TweenInfo, Name: string)
		
		local tData = tweenData or TweenDefault or boxingTweenDefault
		
		local LetterboxFrames = self.LetterboxFrames
		local foundFrame: Frame = self:_GetLetterboxFrameFromName(Name)
		
		local newTween = TweenService:Create(foundFrame, tData, {
			Size = UDim2.fromScale(2, 1)
		})
		
		HandleTweenDemolishment(self, newTween)
		
		table.insert(self.ActiveTweens, newTween)
		
		return newTween
		
	end
	
	local newTweens = {}
	
	for index, Name in ipairs(letterboxNames) do
		
		local foundTweenData = TweenOverride and TweenOverride[Name]
		
		local newTween = constructTween(foundTweenData, Name)

		newTweens[Name] = newTween
		
		
	end
	
	PlayTableOfTweens(newTweens)
	 
end

function LetterboxModule:Rotate(
	StopOverride: {string}?,
	RotateDefault: rotateProperties?, 
	RotateOverride: rotateOverride?,
	RotTweenDefault: TweenInfo,
	RotateTweenOverride: boxTweenOverride
)
	
	local NamesToRotate: {string} = {}
	
	for index, Name in ipairs(Names) do
		
		if StopOverride and table.find(StopOverride, Name) then continue end
		
		table.insert(NamesToRotate, Name)
		
	end
	
	local function constuctTween(tweenData: TweenInfo, Name: string)
		
		local tData = tweenData or RotTweenDefault or rotateTweenDefault
		
		local foundFrame: Frame = self:_GetLetterboxFrameFromName(Name)
		local centerFrame = foundFrame.Parent
		
		local rotateProperties: rotateProperties = RotateOverride and RotateOverride[Name] or RotateDefault or RotateDefaults
		local rot = rotateProperties.Angle
		local Lifetime = rotateProperties.Lifetime
		
		local rotTween = TweenService:Create(centerFrame, tData, {
			Rotation = rot
		})
		
		local backTween = TweenService:Create(centerFrame, tData, {
			Rotation = 0
		})
		
		HandleTweenDemolishment(self, rotTween)
		
		if Lifetime and Lifetime > 0 then
			
			HandleTweenDemolishment(self, backTween)
			
			task.delay(Lifetime, function()
				backTween:Play()
			end)
		end
		
		return rotTween
		
	end
	
	local myTweens = {}
	
	for index, Name in ipairs(NamesToRotate) do
		
		local tweenData = RotateTweenOverride and RotateTweenOverride[Name]
		
		local newTween = constuctTween(tweenData, Name)
		
		myTweens[Name] = newTween
		
	end
	
	PlayTableOfTweens(myTweens)
	
end

function LetterboxModule:Destroy(smoothDestruction: boolean?)
	
	if smoothDestruction == nil then
		smoothDestruction = false
	end
	
	for index, tween in ipairs(self.ActiveTweens :: {Tween}) do
		tween:Destroy()
		tween = nil
	end
	
	if smoothDestruction then
		self:Ascend()
		self:Rotate(nil, {
			Angle = 0
		})
		
		task.delay(DefaultTweenTime + 0.5, function()
			self.LetterboxGui:Destroy()
			self = nil
		end)
	else
		
		self.LetterboxGui:Destroy()
		self = nil
		
	end
	
	
end

-----------------------
--SEMI-PRIVATE METHODS
-----------------------

function LetterboxModule:_CreateUI()
	
	local PlayerGui: PlayerGui = self.Player:WaitForChild("PlayerGui")
	
	local LetterboxUI: ScreenGui = self.LetterboxGui
	
	if not LetterboxUI then
		LetterboxUI = Instance.new("ScreenGui")
		LetterboxUI.Name = "LetterboxGui"
		LetterboxUI.IgnoreGuiInset = true
		LetterboxUI.ClipToDeviceSafeArea = false
		LetterboxUI.Parent = PlayerGui
	end
	
	
	local function handleNewCentral(name: string)
		local NewCentralFrame = Instance.new("Frame")
		NewCentralFrame.Name = `Central_{name}`
		NewCentralFrame.Size = UDim2.fromScale(1, 1)
		NewCentralFrame.BackgroundTransparency = 1
		NewCentralFrame.AnchorPoint = Vector2.new(0.5, 0.5)
		NewCentralFrame.Position = UDim2.fromScale(0.5, 0.5)
		NewCentralFrame.Parent = LetterboxUI

		table.insert(self.CenterFrames, NewCentralFrame)
	end
	
	local function handleNewLetterbox(name: string)
		
		local centralFrame: Frame = LetterboxUI:FindFirstChild(`Central_{name}`)
		
		local newLetterbox = Instance.new("Frame")
		newLetterbox.Name = name
		newLetterbox.Size = UDim2.fromScale(2, 1)
		newLetterbox.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
		newLetterbox.Parent = centralFrame
		
		local foundBoxData = letterboxData[name]
		
		for propKey, value in pairs(foundBoxData) do
			
			newLetterbox[propKey] = value
			
		end
		
		table.insert(self.LetterboxFrames, newLetterbox)
		
	end
	
	for index, name in ipairs(Names) do
		handleNewCentral(name)
		handleNewLetterbox(name)
	end
	
end

function LetterboxModule:_GetUI()
	return self.CenterFrames, self.LetterboxFrames
end

function LetterboxModule:_GetLetterboxFrameFromName(Name: string)
	
	local letterboxFrames: {Frame} = self.LetterboxFrames
	local foundLetterbox
	
	for index, letterbox in ipairs(letterboxFrames) do
		
		if letterbox.Name ~= Name then continue end
		
		foundLetterbox = letterbox
		
	end
	
	return foundLetterbox
	
end

return LetterboxModule

Thanks for checking this page out :smiling_face_with_sunglasses: :+1:

4 Likes

Btw, any feedback is helpful, encouraged, and greatly supports the development of future modules or the improvement of existing modules like this. Thanks! :smiley: :heart:

ow.

1 Like

Let me look into this. I’ve seen errors like this on other Community Resources but I haven’t seen throughly delve into this error. Give me a sec :+1:

Oh it seems that I forgot to make it public and distribute it :sweat_smile: May you try again after when you can to see if the issue persists?

1 Like

its fixed, thx :smiley:

1 Like

Oh Gosh. I almost had a heart attack! This has been published for the past four days and I didn’t even know about this. Thanks hope you enjoy it!