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.