An example of a Parallax UI system

I recently wrote a simple system that allows to create UI layers, utilizing SurfaceGuis and Lerp.
Example of a fully finished script using the system:

local permanentCharacterDetails = workspace:WaitForChild('PermanentCharacterDetails')
local camera = workspace:FindFirstChildOfClass('Camera'); local FOV = camera.FieldOfView; local viewportSize = camera.ViewportSize

local cameraOverlayPart = Instance.new('Part'); cameraOverlayPart.Name = 'CameraOverlayPart'
cameraOverlayPart.CanCollide = false; cameraOverlayPart.Anchored = true; cameraOverlayPart.Massless = true
cameraOverlayPart.Transparency = 1; cameraOverlayPart.Material = Enum.Material.Neon; cameraOverlayPart.Size = Vector3.new(1,1,1)
cameraOverlayPart.CFrame = camera.CFrame
local verticalSize = math.tan(math.rad(FOV/2)) * 2 * 1
local horizontalSize = (viewportSize.X / viewportSize.Y) * verticalSize
cameraOverlayPart.Size = Vector3.new(horizontalSize, verticalSize, 1); cameraOverlayPart.CFrame = camera.CFrame * CFrame.new(0,0,-1) 
cameraOverlayPart.Parent = permanentCharacterDetails
local attachment0 = Instance.new('Attachment'); attachment0.Name = 'CameraAttachment0'
attachment0.Parent = cameraOverlayPart

local cameraUiPart = Instance.new('Part'); cameraUiPart.Name = 'CameraUiPart'
cameraUiPart.CanCollide = false; cameraUiPart.Anchored = true; cameraUiPart.Massless = true
cameraUiPart.Transparency = 1; cameraUiPart.Material = Enum.Material.Neon; cameraUiPart.Size = Vector3.new(1,1,1)
cameraUiPart.CFrame = cameraOverlayPart.CFrame
cameraUiPart.Parent = permanentCharacterDetails
local attachment1 = Instance.new('Attachment'); attachment0.Name = 'CameraAttachment0'
attachment1.Parent = cameraUiPart

local function onInstanceAdded(cameraSurfaceGui : SurfaceGui)
    local surfaceGui = cameraSurfaceGui:Clone()
    surfaceGui:AddTag('CameraAppliedSurfaceGui'); surfaceGui:RemoveTag('CameraSurfaceGuis')
    surfaceGui.Adornee = cameraUiPart; surfaceGui.AlwaysOnTop = true; surfaceGui.Parent = cameraUiPart
end
cs:GetInstanceAddedSignal('CameraSurfaceGuis'):Connect(onInstanceAdded)
for _, cameraSurfaceGui in ipairs(cs:GetTagged('CameraSurfaceGuis')) do onInstanceAdded(cameraSurfaceGui) end

rs.RenderStepped:Connect(function(dt)
    viewportSize = camera.ViewportSize; FOV = camera.FieldOfView
    verticalSize = math.tan(math.rad(FOV/2)) * 2 * 1; horizontalSize = (viewportSize.X / viewportSize.Y) * verticalSize
    cameraOverlayPart.Size = Vector3.new(horizontalSize, verticalSize, 1); cameraUiPart.Size = cameraOverlayPart.Size
    for _, surfaceGui : SurfaceGui in ipairs(cs:GetTagged('CameraAppliedSurfaceGui')) do surfaceGui.CanvasSize = Vector2.new(viewportSize.X, viewportSize.Y) end
    cameraOverlayPart.CFrame = camera.CFrame * CFrame.new(0,0,-1.5); cameraUiPart.CFrame = cameraUiPart.CFrame:Lerp(cameraOverlayPart.CFrame, math.clamp(1-math.exp(-50*dt), 0.985, 1))
end)

Breakdown of the code:
-Create a part that follows the camera’s CFrame and calculates the viewport’s size in studs using tan and camera’s FOV, with a given displacement from the camera
-Create an additional part, with its size linked to the original part
-Using RunService, we use the RenderStepped event to constantly Lerp the CFrame of the second part to the first one
-Using a frame-independent calculation of Lerp with deltatime and a well adjusted math.clamp(), we smooth out the movement of the second camera part
-Bind the SurfaceGui to the second camera part, make sure it faces a proper direction, has its AlwaysOnTop property enabled (to avoid possible Z-issues) and its LightInfluence property is set to 0 (to display the UI the same way a ScreenGui would), make sure the CanvasSize property of the SurfaceGui follows the size of the viewport (it’s up to you whether you’ll check the player’s viewport size every frame).

Further addons:
-One might add custom “weight” values to each additional camera part, making them follow the camera either slower or faster
-It’s possible to have more than one “original” camera part, but for what? Most games, often have their UI with a parallax effect at an angle, for example, simulating a combat mech (image attached demonstrating the use). One might solve this through having angled original camera parts, making them additional targets for the secondary camera parts

All in all, this is how the effect looks in game:


I hope this post will be useful for further UI work.

11 Likes