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.

alt text

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.

alt text

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 ID en 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:

AmbienteCuándo se usa
developBuilds de debug, pruebas locales en editor
stagingBuilds de QA internos. Este es el ambiente donde interactúan los usuarios del Playtest
productionBuilds 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.

alt text

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"

alt text

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.

alt text

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

alt text

  • 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étricaQué mide
DAU / WAU / MAUUsuarios activos por día, semana y mes
New vs Returning UsersCuántos jugadores son nuevos cada día
Average Session LengthDuración promedio de cada sesión
Sessions per DAUCuántas sesiones hace en promedio cada usuario activo
CountriesDesde qué países se juega
Operating SystemsDistribució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.

alt text

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