Capitulo 4

Se o código é o cérebro e as DataStores são a memória, a Interface do Usuário (UI) e a Experiência do Usuário (UX) são a alma do seu jogo. No Roblox, onde o público varia de dispositivos móveis de entrada a PCs de altíssimo desempenho, criar uma interface que seja simultaneamente bela, funcional e leve é um dos maiores desafios da engenharia de software.

Neste capítulo denso, exploraremos como transcender o básico do “clicar e arrastar” no Studio para construir sistemas de interface modulares, responsivos e otimizados, utilizando os padrões de design mais modernos da indústria.

1. A Hierarquia de Renderização: ScreenGui, Frames e ZIndex

A renderização de UI no Roblox acontece em uma camada separada da física do mundo, mas consome recursos preciosos de CPU e GPU. Entender a hierarquia é o primeiro passo para o controle total.

ScreenGui e DisplayOrder

O objeto ScreenGui é o container principal. Desenvolvedores seniores utilizam a propriedade DisplayOrder para gerenciar quais interfaces aparecem sobre as outras, evitando a confusão de ter dezenas de instâncias lutando pelo espaço visual.

ZIndexBehavior: Global vs Sibling

Um erro comum é manter o ZIndexBehavior como Global. No padrão de engenharia profissional, utilizamos Sibling. Isso garante que o ZIndex de um objeto seja relativo apenas aos seus irmãos diretos dentro de um Frame, permitindo uma organização modular onde você pode mover janelas inteiras sem quebrar a ordem de renderização dos botões internos.

2. Design Responsivo: A Matemática do Scale vs Offset

O Roblox é jogado em telas que variam de $480p$ a $4K$. Se você dimensiona sua UI usando apenas pixels (Offset), ela aparecerá gigantesca em um celular e minúscula em um monitor ultra-wide.

A Fórmula do Dimensionamento

O tamanho final de um elemento é dado pela fórmula: $$Tamanho_{final} = (Tela_{dimensão} \times Scale) + Offset$$

Para um design verdadeiramente responsivo:

  1. Use Scale para Proporção: Defina larguras e alturas em porcentagem da tela (ex: $0.5$ para 50%).
  2. Use Offset para Pequenos Ajustes: Use pixels apenas para margens fixas ou contornos.
  3. UIAspectRatioConstraint: Este é o segredo da perfeição. Ele força um elemento (como um ícone de habilidade) a manter sua proporção (ex: $1:1$), independentemente de quão esticada a tela do jogador esteja.

3. Otimização de Performance: O Uso de CanvasGroup

Até recentemente, animar a transparência de um grupo de objetos causava o efeito de “raio-x”, onde os elementos internos apareciam através uns dos outros. A introdução do CanvasGroup resolveu isso, mas trouxe novas responsabilidades técnicas.

Quando usar CanvasGroup?

O CanvasGroup renderiza todos os seus filhos em uma única textura antes de exibi-los.

  • Vantagem: Permite animar a transparência de um menu inteiro de forma suave e performática.
  • Custo: Consome memória de vídeo (VRAM). Usar muitos CanvasGroups simultaneamente em um jogo pesado pode causar travamentos em dispositivos móveis com pouca RAM.

A regra sênior é: use CanvasGroup para menus que abrem e fecham com animação, mas evite usá-lo como container para toda a HUD do jogo.

4. TweenService: A Física da Animação e o “Game Juice”

Um botão que simplesmente aparece é sem vida. Um botão que “pulsa” ou desliza para a tela cria satisfação. Isso é o que chamamos de Game Juice.

EasingStyles e EasingDirections

O TweenService permite interpolar propriedades de forma matemática. Em vez de uma transição linear ($f(x) = x$), utilizamos curvas de aceleração:

  • Elastic e Bounce: Criam uma sensação de “mola”, ideal para notificações e botões de impacto.
  • Quart e Quint: Oferecem uma transição suave e elegante para menus de configurações.

Exemplo Técnico de Interpolação:

local TweenService = game:GetService("TweenService")
local info = TweenInfo.new(0.5, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)

local function animarEntrada(frame)
    frame.Position = UDim2.fromScale(0.5, -0.5) -- Começa fora da tela
    local tween = TweenService:Create(frame, info, {Position = UDim2.fromScale(0.5, 0.5)})
    tween:Play()
end

5. Arquitetura Orientada a Eventos na UI

Nunca utilize loops while true do para atualizar barras de vida ou contadores de moedas. Isso é um desperdício massivo de processamento.

Sinais Natos vs. BindableEvents

A UI deve ser “reativa”. Utilize o evento GetPropertyChangedSignal ou conecte-se diretamente às mudanças de atributos do jogador.

  • HealthBar: Conecte-se ao humanoid.HealthChanged.
  • Inventário: Conecte-se a um BindableEvent disparado pelo seu sistema de itens sempre que o inventário mudar.

Isso garante que o código da interface permaneça em “estado de repouso” (idle) 99% do tempo, disparando apenas quando há uma atualização real para mostrar ao usuário.

6. UX (Experiência do Usuário): O Fluxo de Feedback

A UX foca no que o jogador sente. Um erro de UX comum é a falta de feedback para ações.

  1. Feedback Auditivo: Todo clique deve ter um som sutil. Todo erro (como “saldo insuficiente”) deve ter um som distinto.
  2. Feedback Visual: Botões devem mudar de cor ou tamanho ao passar o mouse (MouseEnter) e ao serem pressionados (MouseButton1Down).
  3. Tempo de Reação: Se uma ação demora (como carregar dados), mostre um Loading Spinner. Nunca deixe o jogador em dúvida se o jogo travou ou se está processando.

7. Prática Aplicada: Sistema de HUD Responsiva e Animada

Abaixo, um exemplo de como estruturar um controlador de UI modular que gerencia a abertura de um inventário de forma profissional.

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local menuFrame = playerGui:WaitForChild("MainHUD"):WaitForChild("InventoryFrame")

local isOpen = false
local tweenInfo = TweenInfo.new(0.3, Enum.EasingStyle.Back, Enum.EasingDirection.Out)

local function toggleMenu()
    isOpen = not isOpen
    
    local targetPosition = isOpen and UDim2.fromScale(0.5, 0.5) or UDim2.fromScale(0.5, 1.5)
    local tween = TweenService:Create(menuFrame, tweenInfo, {Position = targetPosition})
    
    tween:Play()
    
    if isOpen then
        menuFrame.Visible = true
    else
        tween.Completed:Connect(function()
            if not isOpen then menuFrame.Visible = false end
        end)
    end
end

-- Gatilho por tecla (Ex: 'E')
game:GetService("UserInputService").InputBegan:Connect(function(input, gpe)
    if gpe then return end -- Ignora se o jogador estiver digitando no chat
    if input.KeyCode == Enum.KeyCode.E then
        toggleMenu()
    end
end)

8. Acessibilidade e Localização

Para atingir o “Padrão de Perfeição”, seu jogo deve ser acessível.

  • Contraste: Garanta que o texto seja legível sobre qualquer fundo. Use UIStroke para criar bordas pretas em textos brancos.
  • LocalizationService: O Roblox traduz automaticamente partes da UI se você configurar o LocalizationTable. Jogadores que jogam em sua língua nativa têm uma probabilidade significativamente maior de gastar e permanecer no jogo.

Conclusão: A Interface como Extensão do Gameplay

Dominar a UI/UX é entender que você está projetando para seres humanos, não para máquinas. Uma interface limpa, rápida e responsiva reduz a fricção e permite que o jogador foque no que realmente importa: a diversão. Ao aplicar os conceitos de Scale, Tweening e Reatividade, você eleva seu projeto ao patamar de um produto profissional pronto para o mercado global.

No nosso quinto e último capítulo, abordaremos o Ciclo de Lançamento, Polimento Final e Estratégias de Crescimento, onde aprenderemos como transformar seu código e sua interface em um sucesso comercial de público e crítica.