Analytics pt 2: Implementando Unity Analytics
Foto de cabecera por Lucas Pezeta
En el artículo anterior planteamos el por qué de los analytics y el funnel que definimos para el Playtest de Dinopirates. Ahora viene el cómo: desde la configuración en Unity Dashboard hasta el código de inicialización.
El objetivo de este artículo es que puedas tener Unity Analytics funcionando en tu proyecto con buenas prácticas desde el inicio.
Configuración en Unity Dashboard
Antes de escribir una línea de código, el proyecto tiene que estar configurado en dashboard.unity3d.com.
Vinculando el proyecto
En el dashboard de Unity, una vez inicializado el proyecto, el servicio de Analytics deberá activarse automáticamente. Para validarlo, podemos ir a la pestaña Services y scrollear al final de la página hasta encontrarnos con el servicio de Analytics.

En Unity Editor, podemos validar si el proyecto está vinculado correctamente yendo a Edit → Project Settings → Services. Ahí debería aparecer el Project ID del proyecto, que es el identificador único que Unity usa para asociar los eventos que envía tu juego con el dashboard.

NOTA: En tiempos prehistóricos del desarrollo de Dinopirates, evaluamos utilizar Github o Plastic SCM como gestor de versionamiento de código. Preferimos trabajar con Plastic dado que al ser propio de Unity, resuelve de mejor manera los conflictos y la gestión de los archivos .meta. Una herencia de esta decisión es la integración inmediata con Unity Services, incluyendo Analytics. Si usas otro sistema de versionamiento, asegúrate de configurar correctamente el
Project IDen tu proyecto.
Gestión de ambientes
Una buena práctica que implementamos desde el inicio en Dinopirates es separar los datos por ambiente. Unity Dashboard permite crear múltiples entornos por proyecto. Nosotros usamos tres:
| Ambiente | Cuándo se usa |
|---|---|
| develop | Builds de debug, pruebas locales en editor |
| staging | Builds de QA internos. Este es el ambiente donde interactúan los usuarios del Playtest |
| production | Builds públicos, eventualmente el Demo o lanzamiento final |
Esto evita que el tráfico de pruebas contamine los datos reales. Ver 500 muertes en develop cuando en realidad son runs del equipo probando balance es exactamente el tipo de ruido que arruina un dataset.
En el Dashboard, en el home del proyecto, hay que buscar la pestaña Environments antes de empezar a integrar.

Instalación del paquete
En Unity 6, instala el paquete desde Window → Package Manager → Unity Registry → Analytics, o agrégalo directamente en Packages/manifest.json:
"com.unity.services.analytics": "6.3.0"

Verifica la versión más reciente en el Package Manager. La API cambió significativamente entre la versión 4.x y la 6.x. Este artículo cubre la 6.x.
Inicialización con selección de ambiente
La inicialización va en el primer sistema que cargue en tu juego. El punto clave es pasarle el nombre del ambiente correcto a InitializationOptions, de modo que los eventos lleguen al entorno que corresponde según el tipo de build.
async void InitAnalytics()
{
var options = new InitializationOptions();
options.SetEnvironmentName(GetCurrentEnvironment());
await UnityServices.InitializeAsync(options);
if (consentAlreadyGranted)
OptInAnalytics();
}
string GetCurrentEnvironment()
{
if (Debug.isDebugBuild)
return "develop";
#if TANGARA_QA_ENABLED
return "staging";
#else
return "production";
#endif
}
La separación de ambientes por Debug.isDebugBuild y scripting symbols te asegura que ningún dato de prueba llega a producción, sin tener que recordar cambiar nada manualmente entre builds.
La variable TANGARA_QA_ENABLED es una variable de entorno que configuramos especial para la build del playstest. Esta se configura en los profiles de build.

Consentimiento del usuario
Unity Analytics no recolecta datos por defecto. El SDK espera que explícitamente llames al opt-in después de que el usuario acepta. Si omites este paso, no llega nada al dashboard.
Esto no es una restricción arbitraria: es cumplimiento de GDPR y regulaciones de privacidad similares. Hay que pedirle permiso al jugador antes de recolectar cualquier dato. Así mismo, como Tangara Studio también hacemos un declarativo a nuestros usuarios sobre el uso de datos y para qué los usamos. Es importante tener una declaración de políticas de privacidad.
El flujo correcto
Juego inicia
↓
¿Ya tenemos respuesta guardada?
├── Sí, Granted → OptIn directo, sin mostrar alerta
├── Sí, Denied → No recolectar, continuar
└── No → Mostrar alerta de consentimiento
├── Acepta → guardar Granted, OptIn
└── Rechaza → guardar Denied
Implementación
En Dinopirates, poseemos una clase llamada TSAnalyticsManager que maneja esto leyendo el estado guardado en las settings del jugador antes de inicializar los servicios. A continuación compartimos esta clase, que puedes adaptar a tu proyecto:
// TSAnalyticsManager.cs
using System;
using UnityEngine;
using UnityEngine.UnityConsent;
using Unity.Services.Core;
using Unity.Services.Analytics;
using Unity.Services.Core.Environments;
using MoreMountains.Tools;
[Serializable]
public enum TSAnalyticConsent
{
None,
Granted,
Denied,
}
public enum DPEnvironment
{
Develop,
Staging,
Production
}
public class TSAnalyticsManager : MMPersistentSingleton<TSAnalyticsManager>
{
[MMReadOnly] public TSAnalyticConsent CurrentAnalyticConsent = TSAnalyticConsent.None;
public static DPEnvironment CurrentEnvironment() {
if (Debug.isDebugBuild == true)
{
return DPEnvironment.Develop;
}
else
{
#if TANGARA_QA_ENABLED
return DPEnvironment.Staging;
#else
return DPEnvironment.Production;
#endif
}
}
public void ValidateConsent(Action<TSAnalyticConsent> callback)
{
LoadSettings();
InitAnalytics(); // Async: inicializa los servicios y ejecuta OptIn si el consentimiento ya fue otorgado
callback(CurrentAnalyticConsent);
}
async void InitAnalytics()
{
var options = new InitializationOptions();
options.SetEnvironmentName(CurrentEnvironment().ToString().ToLower());
await UnityServices.InitializeAsync(options);
if (CurrentAnalyticConsent == TSAnalyticConsent.Granted)
{
OptInAnalytics();
}
}
void LoadSettings()
{
// Load Settings
// TSSaveLoadManager es una clase que utilizamos para cargar y guardar las preferencias del jugador. En este caso, el consentimiento analítico se guarda en las settings para no tener que preguntarle al jugador cada vez que inicia el juego.
var settings = TSSaveLoadManager.LoadSettings();
if (settings != null)
{
CurrentAnalyticConsent = settings.AnalyticConsent;
}
}
// Opt In/Out & Delete request
public void ChangeAndSaveNewConsentState(TSAnalyticConsent newState)
{
CurrentAnalyticConsent = newState;
TSSaveLoadManager.SaveSettings(new() {
AnalyticConsent = CurrentAnalyticConsent,
});
}
public void OptOutAnalytics()
{
ChangeAndSaveNewConsentState(TSAnalyticConsent.Denied);
var consentState = EndUserConsent.GetConsentState();
consentState.AnalyticsIntent = ConsentStatus.Denied;
EndUserConsent.SetConsentState(consentState);
}
public void OptInAnalytics()
{
var consentState = EndUserConsent.GetConsentState();
consentState.AnalyticsIntent = ConsentStatus.Granted;
EndUserConsent.SetConsentState(consentState);
}
public void RequestDataDeletion()
{
var consentState = EndUserConsent.GetConsentState();
consentState.AnalyticsIntent = ConsentStatus.Denied;
EndUserConsent.SetConsentState(consentState);
AnalyticsService.Instance.RequestDataDeletion();
}
}
En la práctica, la primera vez que el jugador abre el juego, se muestra una alerta simple antes del menú principal. Si acepta, se guarda Granted y se hace OptIn. En los inicios siguientes, el estado ya está guardado y no vuelve a preguntar.
Buenas prácticas para la alerta

- Texto claro y breve. Algo como: “¿Nos ayudas a mejorar el juego compartiendo datos anónimos de sesión?” es suficiente.
- Ambos botones (aceptar y rechazar) igualmente visibles. No escondas el rechazo.
- Guarda la preferencia. Nadie quiere ver esa pantalla cada vez que abre el juego.
- Respeta la decisión. Si rechaza, no degrades la experiencia de ninguna manera.
Lo que obtienes con la integración base
Con esto funcionando —sin haber definido ningún evento custom todavía— Unity Dashboard ya empieza a mostrar datos. Los más útiles para un indie:
| Métrica | Qué mide |
|---|---|
| DAU / WAU / MAU | Usuarios activos por día, semana y mes |
| New vs Returning Users | Cuántos jugadores son nuevos cada día |
| Average Session Length | Duración promedio de cada sesión |
| Sessions per DAU | Cuántas sesiones hace en promedio cada usuario activo |
| Countries | Desde qué países se juega |
| Operating Systems | Distribución de Windows, macOS, Linux |
| Hardware (CPU / GPU) | Qué hardware tienen los jugadores |
Para un Playtest, esto ya responde preguntas importantes: ¿la gente vuelve a jugar? ¿Cuánto tiempo le dedican por sesión? ¿Desde dónde juegan? ¿El hardware de tu audiencia corresponde con tus targets de performance?
En el dashboard, la información se verá similar a esta.

Para el caso de los datos de Countries, Operating Systems o Hardware, hay que generar reportes custom.

Dejamos a modo de ejemplo todos los parámetros que se guardan en el evento clientDevice. Si notarán, hay mucha otra información que puede ser de valor, como por ejemplo estado de la RAM, resolución de pantalla, o zona horaria. Es importante revisar qué datos ya entrega el SDK antes de agregar parámetros custom que podrían ser redundantes.
{
"batteryLoad": 1,
"buildGUUID": "2c82e431b0ec48d6807d4XXXXXXXXX",
"clientVersion": "0.1",
"collectInsertedTimestamp": "2026-05-28 20:45:45.309",
"cpuCores": 16,
"cpuType": "AMD Ryzen Z1 Extreme",
"eventDate": "2026-05-28 00:00:00.000",
"eventID": 3526528638864123123,
"eventLevel": 0,
"eventName": "clientDevice",
"eventTimestamp": "2026-05-28 20:45:44.966",
"eventUUID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"eventVersion": 1,
"gaUserAcquisitionChannel": "None",
"gaUserAgeGroup": "UNKNOWN",
"gaUserCountry": "CL",
"gaUserGender": "UNKNOWN",
"gaUserStartDate": "2026-05-22 00:00:00.000",
"gpuType": "AMD Ryzen Z1 Extreme (RADV PHOENIX)",
"idfv": "de824a99e67b1d937c83a0c1ce68b87XXXXXXXXX",
"mainEventID": 352652863886123123,
"msSinceLastEvent": 28,
"platform": "PC_CLIENT",
"projectID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"ramTotal": 11645,
"screenHeight": 1080,
"screenResolution": 96,
"screenWidth": 1920,
"sdkMethod": "com.unity.services.analytics.Events.Startup",
"sessionID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"timezoneOffset": "-0400",
"unityInstallationID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"userCountry": "CL",
"userID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}
Con la base funcionando, el siguiente paso es definir los eventos que importan para tu juego específicamente. Eso es lo que cubre el próximo artículo: eventos custom, cómo estructurarlos en código, y cómo construir el funnel en el Dashboard.
—
Serie: Analytics, puedes revisar las otras partes acá pt1 y pt3