DevEx e negócio

Eventos de funil: descobrir em qual passo o jogador desiste, e não só que ele desistiu

O painel diz que a retenção está baixa. O funil diz onde. São dois tipos de evento — único por jogador e recorrente — e eles só funcionam no servidor, em jogo publicado.

por Gustavo Cantino9 min de leituraIntermediário

Produzido com IA a partir das fontes citadas abaixo, e conferido por checagens automáticas contra essas mesmas fontes. Nenhuma pessoa leu este texto antes da publicação. Política Editorial

Placa tipográfica com a categoria DevEx e negócio em destaque, o nível intermediário abaixo, e um campo de barras verticais verde-limão sobre fundo escuro.

O painel de retenção diz que o D1 está baixo. Isso é um diagnóstico e não é uma direção — dá para passar semanas mexendo no jogo sem saber se você está consertando a coisa certa.

O funil responde a pergunta seguinte: em qual passo as pessoas param. E a diferença entre saber que elas param e saber onde é a diferença entre adivinhar e trabalhar.

O problema

Eventos de funil acompanham o progresso do jogador pelas etapas importantes do jogo. A documentação lista três perguntas que eles respondem, e cada uma corresponde a um tipo de funil:

  • Primeira sessão: onde o jogador desiste ao começar no jogo?
  • Progressão: onde ele para de avançar?
  • Loja: onde ele abandona a compra?

Com o jogo registrando esses eventos, destrava-se a página de Funil do painel de analytics no Creator Hub. E dá para acrescentar abas para até dez funis — o que é bastante, e ainda assim um limite que vale ter em mente antes de instrumentar tudo.

Há uma restrição de plataforma que muda o fluxo de trabalho, e ela precisa ser dita cedo: os eventos só podem ser enviados do servidor e em jogo publicado.

No Studio, nada é enviado. Isso não é erro nem configuração faltando — é o comportamento. Quem testa a instrumentação no Studio e vai procurar o dado no painel perde uma tarde antes de descobrir.

Passo a passo

1. Escolha os funis. O primeiro passo é identificar os funis mais importantes do jogo e dividi-los em etapas. E a documentação dá uma recomendação de prioridade: o fluxo de primeira sessão é um ótimo lugar para começar, porque é provavelmente onde se perde a maioria dos jogadores.

2. Divida em etapas. As etapas costumam corresponder a uma ação ou a uma conquista. O exemplo oficial é de um RPG: uma etapa para entrar no jogo, outra para escolher a classe, outra para sair da sala inicial, e assim por diante.

Repare no formato: são coisas que o jogador faz, não tempo decorrido nem tela aberta. Etapa que não corresponde a uma ação não diz nada quando o funil mostrar a queda ali.

3. Registre depois da etapa. A chamada acontece depois de o jogador completar o passo. E aqui está a regra que a documentação destaca acima de qualquer detalhe técnico: o mais importante é ser consistente sobre quando chamar os métodos e usar nomes fáceis de interpretar.

As duas metades dessa frase importam. Consistência de momento — sempre depois, nunca antes — porque um passo registrado na intenção e outro na conclusão produzem um funil que mistura duas coisas. E nomes legíveis porque o painel mostra o nome, e "step3" não diz a ninguém, daqui a três meses, o que estava sendo medido.

4. Escolha o tipo. São dois, e a diferença é sobre repetição:

  • Funil de uma vez só acompanha conversão que acontece uma única vez por jogador. O exemplo comum é o da primeira sessão.
  • Funil recorrente acompanha conversão que acontece várias vezes por jogador. O exemplo comum é o da loja.

5. Use identificador de sessão onde for preciso. No funil recorrente, o identificador distingue sessões diferentes do mesmo jogador — o caso citado é o jogador abrir a loja várias vezes na mesma sessão de jogo.

No funil de uma vez só, ele não é necessário, justamente porque o funil ocorre uma vez por jogador.

E para o caso da loja, a recomendação é específica: usar um GUID como identificador.

Um detalhe sobre o número da etapa. Ele não é só um rótulo: é a ordem no gráfico. Mudar a numeração de um funil já publicado reescreve como o painel monta a sequência, e o histórico anterior passa a conviver com a numeração nova.

A consequência prática é que vale acertar a lista antes de publicar a instrumentação, e tratá-la como algo estável depois. Acrescentar uma etapa no fim é barato; inserir uma no meio renumera tudo o que vem depois e embaralha a comparação com os dados que você já tem.

Artefato

O módulo abaixo implementa os dois tipos num lugar só, e a decisão de desenho mais importante dele não é técnica: as etapas são declaradas numa tabela, com número e nome juntos.

O motivo é a regra de ouro da documentação. Nome digitado à mão em cinco pontos de chamada vira cinco nomes diferentes — um com acento, outro no plural, outro com um espaço a mais. E o painel, que agrupa por nome, mostra cinco funis de um passo cada em vez de um funil de cinco passos.

Além disso, ele faz duas coisas que a documentação não pede e que salvam o dado: ignora etapa repetida e recusa etapa fora de ordem. Funil com passo pulado é ilegível, e passo pulado quase sempre é bug de chamada, não comportamento de jogador.

E ele sai cedo no Studio, sem tentar enviar — o que evita ruído no output durante o desenvolvimento.

Vale notar a diferença de propósito entre os dois tipos, porque ela decide qual usar quando o caso não é óbvio. A pergunta não é "quantas vezes o jogador faz isso?", e sim "quantas vezes isso pode CONVERTER?". Escolher classe acontece uma vez e converte uma vez: funil de uma vez só. Abrir a loja acontece muitas vezes e pode converter em cada uma: funil recorrente. Um tutorial que o jogador pode refazer continua sendo de uma vez só, porque a conversão que interessa é a primeira.

Erro comum

Testar no Studio. O primeiro e o mais frustrante. Os eventos só saem do servidor e em jogo publicado. A instrumentação parece funcionar — o código roda, não dá erro — e o painel fica vazio. A causa não é o seu código.

Registrar antes da ação. Chamar o evento quando o jogador abre a tela de escolha de classe, em vez de quando ele escolhe, transforma a etapa em "viu a tela". O funil continua fazendo sentido visualmente e mede outra coisa — e a queda que você vai ver não é a queda que existe.

Nome inconsistente. Já descrito, e vale repetir porque é silencioso: nada quebra, o painel simplesmente mostra funis fragmentados que ninguém entende.

Esquecer o identificador de sessão no funil recorrente. Sem ele, duas visitas à loja do mesmo jogador colapsam numa só. O resultado é um funil que parece bom demais: a taxa de abandono desaparece, porque a segunda visita "completa" o que a primeira deixou pela metade.

Usar identificador de sessão no funil de uma vez só. O erro inverso, e a documentação é explícita sobre não ser necessário. Ele não quebra nada e acrescenta uma variável a um funil que não tem essa dimensão.

Instrumentar demais. Com o limite de dez funis no painel, a tentação de medir tudo produz dez abas que ninguém abre. Um funil bem feito do primeiro minuto vale mais que dez funis de sistemas que quase ninguém usa.

Etapas demais. Um funil de quinze passos é um gráfico que desce e não diz onde. A regra prática: cada etapa precisa ser uma coisa que você saberia como consertar se a queda estivesse ali. Se não sabe, ela não é uma etapa — é ruído entre duas etapas de verdade.

Esperar o dado aparecer na hora. Painel de analytics não é tempo real, e o primeiro dia de instrumentação costuma render um gráfico vazio ou raso. Isso leva gente a mexer na implementação que estava certa. Publique, espere o volume normal de um dia inteiro, e só então julgue.

Próximo passo

O exercício de hoje não exige código: escreva, numa linha cada, os passos do primeiro minuto do seu jogo. Do clique em jogar até o momento em que o jogador faz, pela primeira vez, a coisa que o jogo existe para ele fazer.

Se a lista tiver mais de seis passos, o seu início é longo — e isso já é um achado, antes de qualquer instrumentação. Se tiver menos de três, provavelmente há passos implícitos que você conhece bem demais para enxergar.

Essa lista vira, diretamente, a tabela do artefato. E quando os dados começarem a chegar, ela é o que permite ler o gráfico: a maior queda entre dois passos consecutivos é o lugar onde trabalhar, e você já sabe o que acontece ali porque escreveu.

Depois do funil de primeira sessão funcionando, o próximo com melhor retorno costuma ser o da loja — porque ele é recorrente, mede dinheiro, e a queda nele costuma estar num passo que não tem nada a ver com preço.

Fontes

Como corrigimos erros

Artefato pronto pra usar

Os dois tipos de funil num módulo só, com os nomes declarados em tabela (ServerScriptService)
lua
-- ServerScriptService: os dois tipos de funil, num lugar só.
--
-- REGRA DE OURO da documentação: ser CONSISTENTE sobre quando chamar, e
-- usar nomes FÁCEIS DE INTERPRETAR. Por isso as etapas são declaradas
-- numa tabela, e não escritas soltas em cada ponto de chamada -- nome
-- digitado à mão em cinco lugares vira cinco nomes diferentes, e o
-- painel mostra cinco funis de um passo cada.
--
-- LIMITE DA PLATAFORMA: os eventos só saem DO SERVIDOR e em jogo
-- PUBLICADO. No Studio, nada é enviado -- e isso não é erro, é o
-- comportamento. Não gaste tarde procurando o dado no painel depois de
-- testar no Studio.

local AnalyticsService = game:GetService("AnalyticsService")
local HttpService = game:GetService("HttpService")
local RunService = game:GetService("RunService")

local Funil = {}

-- ── FUNIL DE PRIMEIRA SESSÃO (uma vez por jogador) ──────────────────
-- Número e nome juntos, na ordem. Mudar a ordem aqui reescreve o funil
-- inteiro no painel, então vale acertar antes de publicar.
local PRIMEIRA_SESSAO = {
	{ n = 1, nome = "Entrou no jogo" },
	{ n = 2, nome = "Escolheu a classe" },
	{ n = 3, nome = "Saiu da sala inicial" },
	{ n = 4, nome = "Primeiro combate" },
}

local porNome = {}
for _, p in PRIMEIRA_SESSAO do
	porNome[p.nome] = p.n
end

-- idempotente: repetir a mesma etapa não conta duas vezes, e pular
-- etapa não é aceito. Funil com passo fora de ordem é ilegível.
local alcancado = {}

function Funil.primeiraSessao(player, nomeDaEtapa)
	local n = porNome[nomeDaEtapa]
	if not n then
		warn("etapa desconhecida no funil: " .. tostring(nomeDaEtapa))
		return
	end

	alcancado[player] = alcancado[player] or 0
	if n <= alcancado[player] then
		return
	end
	if n ~= alcancado[player] + 1 then
		warn("funil fora de ordem: " .. nomeDaEtapa .. " depois de " .. alcancado[player])
		return
	end
	alcancado[player] = n

	if RunService:IsStudio() then
		return -- no Studio nada é enviado; evita ruído no output
	end

	pcall(function()
		AnalyticsService:LogOnboardingFunnelStepEvent(player, n, nomeDaEtapa)
	end)
end

-- ── FUNIL RECORRENTE (várias vezes por jogador) ─────────────────────
-- Aqui o identificador de sessão é obrigatório: sem ele, duas visitas à
-- loja do mesmo jogador viram uma só, e a taxa de abandono some.
local LOJA = "ArmoryCheckout"
local ETAPAS_LOJA = {
	{ n = 1, nome = "Abriu a loja" },
	{ n = 2, nome = "Viu o item" },
	{ n = 3, nome = "Adicionou ao carrinho" },
	{ n = 4, nome = "Confirmou a compra" },
}

local lojaPorNome = {}
for _, p in ETAPAS_LOJA do
	lojaPorNome[p.nome] = p.n
end

-- Chame ao ABRIR a loja: cada abertura é uma sessão nova do funil.
function Funil.novaSessaoDeLoja()
	return HttpService:GenerateGUID()
end

function Funil.loja(player, sessaoId, nomeDaEtapa)
	local n = lojaPorNome[nomeDaEtapa]
	if not n or not sessaoId then
		warn("etapa ou sessao invalida no funil da loja")
		return
	end
	if RunService:IsStudio() then
		return
	end
	pcall(function()
		AnalyticsService:LogFunnelStepEvent(player, LOJA, sessaoId, n, nomeDaEtapa)
	end)
end

game:GetService("Players").PlayerRemoving:Connect(function(player)
	alcancado[player] = nil
end)

return Funil
Neste artigo

Continue por aqui