User Interface

We wanted to add an interactive map UI to let users consume information in the space station that looked and felt like it lived in this world. We decided to build the map inside the 3D space instead of on a screen that overlays the experience. This type of diegetic visualization allows for more immersion with the world as opposed to feeling like it is a completely separate experience.

Designing the Map

To design the map:

  1. We mocked the UI in an external application and came up with a rough idea of how we wanted it to look.

    UI Mock
  2. We exported the individual pieces of the map as .png and imported them into Studio.

    UI Elements Exported

Building the map

Building the map inside Studio involved using Parts and SurfaceGuis.

  1. For non-interactive elements, all we needed to do is add a SurfaceGui object to the part.

  2. For interactive elements, the SurfaceGui also needs to be inside the StarterGui container, with the Adornee property linked to the appropriate part in the 3D workspace. Doing so allows you to add button events.

  3. To achieve a parallax effect, we used three separate ScreenGui instances assigned to three unique Parts with different X values.

    Parallax Example
  4. We then added a glow effect with the SurfaceGui.LightInfluence property. If you set the property value to anything less than 1, it enables the SurfaceGui.Brightness property. By adjusting the brightness, you can increase the glow emitting from the image.

  5. To let users toggle the display of the map, we used a ProximityPrompt that we attached to a 3D model. This is an easy way to allow user interaction with world elements.

    Proximity Prompt in Explorer
  6. Finally, using a UITweenModule ModuleScript inside ReplicatedStorage, we animated hiding and showing the UI with TweenService and a bit of logic for determining the state. By tracking what the user clicked, we could hide and show elements by tweening various properties like alpha, position, and size.

    PUI Tween Module in Explorer
    UITweenModule ModuleScript

    1local tweenService = game:GetService("TweenService")
    2local UITween = {}
    3
    4-- for fading images
    5function UITween.fadePart(object, amount, time, delay)
    6
    7 local tweenAlpha = TweenInfo.new(
    8 time, --Time
    9 Enum.EasingStyle.Quad, --EasingStyle
    10 Enum.EasingDirection.Out, --EasingDirection
    11 0, --Repeat count
    12 false, --Reverses if true
    13 delay --Delay time
    14 )
    15
    16 local tween = tweenService:Create(object, tweenAlpha, {Transparency = amount})
    17 tween:Play()
    18end
    19
    20function UITween.fade(object, amount, time, delay)
    21
    22 local tweenAlpha = TweenInfo.new(
    23 time, --Time
    24 Enum.EasingStyle.Quad, --EasingStyle
    25 Enum.EasingDirection.Out, --EasingDirection
    26 0, --Repeat count
    27 false, --Reverses if true
    28 delay --Delay time
    29 )
    30
    31 local tween = tweenService:Create(object, tweenAlpha, {ImageTransparency = amount})
    32 tween:Play()
    33end
    34
    35-- for fading images
    36function UITween.fadeBackground(object, amount, time, delay)
    37
    38 local tweenAlpha = TweenInfo.new(
    39 time, --Time
    40 Enum.EasingStyle.Quad, --EasingStyle
    41 Enum.EasingDirection.Out, --EasingDirection
    42 0, --Repeat count
    43 false, --Reverses if true
    44 delay --Delay time
    45 )
    46
    47 local tween = tweenService:Create(object, tweenAlpha, {BackgroundTransparency = amount})
    48 tween:Play()
    49end
    50
    51-- for fading text
    52function UITween.fadeText(object, amount, time, delay)
    53
    54 local tweenAlpha = TweenInfo.new(
    55 time, --Time
    56 Enum.EasingStyle.Quad, --EasingStyle
    57 Enum.EasingDirection.Out, --EasingDirection
    58 0, --Repeat count
    59 false, --Reverses if true
    60 delay --Delay time
    61 )
    62
    63 local tween1 = tweenService:Create(object, tweenAlpha, {TextTransparency = amount})
    64 tween1:Play()
    65end
    66
    67-- for moving text and images
    68function UITween.move(object, position, time, delay)
    69
    70 wait(delay)
    71 object:TweenPosition(position, Enum.EasingDirection.Out, Enum.EasingStyle.Quint, time)
    72end
    73
    74-- for changing size
    75function UITween.size(object, size, time, delay, override, callback)
    76
    77 local tweenSize = TweenInfo.new(
    78 time, --Time
    79 Enum.EasingStyle.Quint, --EasingStyle
    80 Enum.EasingDirection.Out, --EasingDirection
    81 0, --Repeat count
    82 false, --Reverses if true
    83 delay, --Delay time
    84 override,
    85 callback
    86 )
    87
    88 local tween = tweenService:Create(object, tweenSize, {Size = size})
    89 tween:Play()
    90end
    91
    92function UITween.rotate(object, rotation, time, delay, override, callback)
    93
    94 local tweenSize = TweenInfo.new(
    95 time, --Time
    96 Enum.EasingStyle.Quint, --EasingStyle
    97 Enum.EasingDirection.Out, --EasingDirection
    98 0, --Repeat count
    99 false, --Reverses if true
    100 delay, --Delay time
    101 override,
    102 callback
    103 )
    104
    105 local tween = tweenService:Create(object, tweenSize, {Rotation = rotation})
    106 tween:Play()
    107end
    108
    109-- for blurring the game camera
    110function UITween.blur(object, amount, time)
    111
    112 local tweenInfo = TweenInfo.new(time, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, false, 0)
    113 local tween = tweenService:Create(object, tweenInfo, {Size = amount})
    114 tween:Play()
    115end
    116
    117-- for blurring the game camera
    118function UITween.turnOn(object, amount, time)
    119
    120 local tweenInfo = TweenInfo.new(time, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, false, 0)
    121 local tween = tweenService:Create(object, tweenInfo, {Brightness = amount})
    122 tween:Play()
    123end
    124
    125return UITween
    126
    Applying UI Tween to Objects

    1-- Add UITween Module
    2local UITween = require(game.ReplicatedStorage.UITweenModule)
    3
    4-- Find player Guis and UI objects
    5local playerGui = game:GetService('Players').LocalPlayer:WaitForChild('PlayerGui')
    6local screenGuiMapUIFrame = playerGui:WaitForChild("ScreenGuiMapUIFrame").SurfaceGui
    7local mapUIFrameStroke = screenGuiMapUIFrame.FrameStroke
    8local mapUIFrameFill = screenGuiMapUIFrame.FrameFill
    9
    10-- Sizes used for tweening
    11local frameSizeStart = UDim2.new(0, 0, 0, 0)
    12local frameSizeMid = UDim2.new(1, 0, 0.05, 0)
    13local frameSizeEnd = UDim2.new(1, 0, 1, 0)
    14
    15-- Example Tweening
    16UITween.fade(mapUIFrameStroke, 0, 2, 0)
    17UITween.size(mapUIFrameStroke, frameSizeMid, 0.4, 0)
    18UITween.fade(mapUIFrameFill, 0, 2, 0.5)
    19UITween.size(mapUIFrameFill, frameSizeEnd, 0.4, 0.25)
    20wait(0.25)
    21UITween.size(mapUIFrameStroke, frameSizeMid, 0.4, 0)
    22UITween.size(mapUIFrameFill, frameSizeMid, 0.4, 0.25)
    23wait(0.25)
    24UITween.size(mapUIFrameStroke, frameSizeEnd, 0.4, 0)
    25UITween.size(mapUIFrameFill, frameSizeEnd, 0.4, 0.25)
    26

Here's the final result of the interactive map: