3D GUI for all screen size

I want to know how to make something like the video as follow:

I do know that they use SurfaceGui and Rotations in 3D Space, however, how do I ensure that all screen types and devices are able to see the full GUI?

You need to compute the width / height of the frustum plane for either (a) a given desired size or (b) a desired distance, e.g.

-- i.e. get the size of the frustum plane given the radius of an object
--      that you're trying to encapsulate
--
--      can be used to approximate dist required to fit some arbitrary AABB
--      inside of a frustum where radius = math.max(AABB.Size.X, AABB.Size.Y)
--
local function getFrustumRectForRadius(transform, screenSize, fieldOfView, radius)
  local aspectRatio = screenSize.X / screenSize.Y

  local hfov = math.rad(fieldOfView * 0.5)
  local distance = radius / math.sin(hfov)
  local htanfov = math.tan(hfov)

  local height = 2 * htanfov * distance
  local width = height * aspectRatio

  local origin = transform * CFrame.new(0, 0, -distance)
  local normal = transform.ZVector
  return {
    width = width,
    height = height,
    normal = normal,
    distance = -origin.Position:Dot(normal),
    origin = CFrame.fromMatrix(origin.Position, -origin.XVector, origin.YVector, -normal),
  }
end

-- i.e. get the size of the frustum plane at a given distance
local function getFrustumRectAtDistance(transform, screenSize, fieldOfView, distance)
  local aspectRatio = screenSize.X / screenSize.Y

  local hfov = math.rad(fieldOfView * 0.5)
  local htanfov = math.tan(hfov)

  local height = 2 * htanfov * distance
  local width = height * aspectRatio

  local origin = transform * CFrame.new(0, 0, -distance)
  local normal = transform.ZVector
  return {
    width = width,
    height = height,
    normal = normal,
    distance = -origin.Position:Dot(normal),
    origin = CFrame.fromMatrix(origin.Position, -origin.XVector, origin.YVector, -normal),
  }
end

Learn more about the viewing/camera frustum here, but an example usage would be:

Example usage
local camera = game.Workspace.CurrentCamera

local part = Instance.new('Part')
part.Anchored = true
part.Transparency = 1
part.Parent = workspace

local surfaceGui = Instance.new('SurfaceGui')
surfaceGui.Face = Enum.NormalId.Front
surfaceGui.Adornee = part
surfaceGui.AlwaysOnTop = true
surfaceGui.Parent = part

local label = Instance.new('TextLabel')
label.Size = UDim2.fromScale(1, 1)
label.Text = 'Hello, world!'
label.TextScaled = true
label.BackgroundTransparency = 1
label.Parent = surfaceGui

local connection
connection = RunService.RenderStepped:Connect(function (dt)
  local now = os.clock()
  local spin = CFrame.Angles(math.cos(now)*0.25, math.sin(now)*0.5, 0) -- just to demo it moving around

  -- get our camera properties
  local transform = camera.CFrame
  local fieldOfView = camera.FieldOfView
  local viewportSize = camera.ViewportSize

  -- compute the frustum plane rect or you could of course use the distance
  -- i.e. ...
  --    local rect = getFrustumRectAtDistance(transform, viewportSize, fieldOfView, 10)
  --
  -- where 10 = some arbitrary distance
  --
  local rect = getFrustumRectForRadius(transform, viewportSize, fieldOfView, 1) -- where 1 = some arbitrary size you're trying to fit

  -- put the surface gui part into position
  part.CFrame = rect.origin * spin
  part.Size = Vector3.new(rect.width, rect.height, 0.01)
end)
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.