Skip to content

UnioGame/unigame.analytics

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UniGame Game Analytics

Comprehensive analytics system for Unity games with support for multiple analytics providers and flexible event tracking.

Overview

The UniGame Game Analytics module provides a comprehensive analytics solution for Unity games with the following features:

  • Multi-Provider Support: Integrate with GameAnalytics, Firebase Analytics, Unity Analytics, MyTracker, and more
  • Event-Driven Architecture: Flexible event system with custom message types
  • Async Operations: Full async/await support for all analytics operations
  • Message Pipeline: Configurable message handlers for data preprocessing
  • Performance Monitoring: Built-in FPS tracking and performance analytics
  • Debug Support: Comprehensive logging and debug analytics adapter
  • Type Safety: Strongly typed event messages with parameter validation
  • Lifetime Management: Automatic resource cleanup and memory management

Installation

Add the package to your project via Package Manager:

{
  "dependencies": {
    "com.unigame.analytics": "https://github.com/UnioGame/game.analytics.git"
  }
}

Dependencies

The module requires these packages:

  • com.unity.addressables: Asset management
  • com.cysharp.unitask: Async operations
  • com.cysharp.r3: Reactive programming
  • com.unigame.unicore: Core utilities
  • com.unigame.addressablestools: Addressable extensions

Core Architecture

System Components

// Core service interface
public interface IAnalyticsService : IGameService
{
    void TrackEvent(IAnalyticsMessage message);
    IDisposable RegisterAdapter(IAnalyticsAdapter adapter);
    IDisposable RegisterMessageHandler(IAnalyticsMessageHandler handler);
}

// Analytics adapter interface
public interface IAnalyticsAdapter : IDisposable
{
    UniTask InitializeAsync();
    void TrackEvent(IAnalyticsMessage message);
}

// Message interface
public interface IAnalyticsMessage
{
    string GroupId { get; }
    string Name { get; }
    Dictionary<string, string> Parameters { get; }
    string this[string key] { get; set; }
}

Analytics Flow

Event Creation → Message Handlers → Analytics Service → Adapters → External Services
  1. Event Creation: Create analytics messages using built-in or custom types
  2. Message Handlers: Process and enrich messages with additional data
  3. Analytics Service: Distribute messages to registered adapters
  4. Adapters: Send events to external analytics providers
  5. External Services: GameAnalytics, Firebase, Unity Analytics, etc.

Quick Start

Basic Setup

  1. Create Analytics Configuration:
// Create configuration asset
[CreateAssetMenu(menuName = "Game/Services/Analytics/Analytics Configuration")]
public class AnalyticsConfigurationAsset : ScriptableObject
{
    public AnalyticsConfiguration configuration;
}
  1. Register Service:
// In your game context
public class GameBootstrap : MonoBehaviour
{
    [SerializeField] private AnalyticsServiceSource analyticsSource;
    
    private async void Start()
    {
        var context = new GameContext();
        var analyticsService = await analyticsSource.CreateAsync(context);
        
        // Service is now available
        context.Get<IAnalyticsService>();
    }
}

Configuration

// Configure analytics in inspector
[Serializable]
public class AnalyticsConfiguration
{
    public bool isEnabled = true;
    public string platformIdOverride = string.Empty;
    public List<string> enabledPlatforms = new();
    public List<string> disabledPlatforms = new();
    public List<AnalyticsAdapterData> analytics = new();
    public List<IAnalyticsMessageHandler> messageHandlers = new();
}

enabledPlatforms and disabledPlatforms are applied before SDK initialization. The same platform allow/deny fields are also available on every AnalyticsAdapterData, so a build can globally disable analytics on platforms like YouTube Playables or disable only selected providers.

Analytics Service

Service Interface

public interface IAnalyticsService : IGameService, IAnalyticsHandlers
{
    // Track analytics event
    void TrackEvent(IAnalyticsMessage message);
    
    // Register analytics adapter
    IDisposable RegisterAdapter(IAnalyticsAdapter adapter);
}

public interface IAnalyticsHandlers
{
    // Register message handler
    IDisposable RegisterMessageHandler(IAnalyticsMessageHandler handler);
    
    // Update handlers collection
    void UpdateHandlers(IEnumerable<IAnalyticsMessageHandler> messageHandlers);
}

Service Registration

// Register service in context
public class AnalyticsServiceSource : DataSourceAsset<IAnalyticsService>
{
    public AddressableValue<AnalyticsConfigurationAsset> configurationReference;

    protected override async UniTask<IAnalyticsService> CreateInternalAsync(IContext context)
    {
        var configuration = await configurationReference.LoadAssetInstanceTaskAsync(context.LifeTime);
        var service = new GameAnalyticsService();
        
        // Register adapters
        foreach (var adapterData in configuration.analytics)
        {
            if (adapterData.isEnabled)
            {
                await adapterData.adapter.InitializeAsync();
                service.RegisterAdapter(adapterData.adapter);
            }
        }
        
        return service;
    }
}

Event Tracking

// Basic event tracking
public class GameController : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    private void Start()
    {
        _analyticsService = Context.Get<IAnalyticsService>();
    }
    
    public void TrackLevelComplete(int level, int score)
    {
        var message = new AnalyticsEventMessage("level_complete", "gameplay")
        {
            ["level"] = level.ToString(),
            ["score"] = score.ToString(),
            ["time"] = Time.time.ToString()
        };
        
        _analyticsService.TrackEvent(message);
    }
}

Analytics Adapters

Supported Providers

The module includes adapters for:

  • GameAnalytics: Full GameAnalytics SDK integration
  • Firebase Analytics: Google Firebase Analytics
  • Unity Analytics: Unity's built-in analytics
  • MyTracker: VK MyTracker analytics
  • AppMetrica: AppMetrica event reporting
  • Yandex Analytics: Yandex Games metrica bridge
  • MTT Analytics: Custom mtt.analytics ingestion adapter with HMAC signing
  • Debug Adapter: Console logging for development

Provider adapter assemblies are compile-gated. Use explicit project defines such as UNITY_ANALYTICS_ENABLED, FIREBASE_ANALYTICS_ENABLED, GAME_ANALYTICS_ENABLED, MYTRACKER_ANALYTICS_ENABLED, APP_METRICA_ENABLED, YANDEX_ANALYTICS_ENABLED, and UNIGAME_ANALYTICS_ENABLED together with the corresponding SDK packages.

GameAnalytics Integration

[Serializable]
public class GameAnalyticsProvider : IAnalyticsAdapter, IGameAnalyticsATTListener
{
    public AssetReferenceGameObject gameAnalyticsPrefab;
    public bool enableUnderEditor = false;
    
    public async UniTask InitializeAsync()
    {
        if (Application.isEditor && !enableUnderEditor)
            return;
            
        // Initialize GameAnalytics
        var gameAnalytics = await gameAnalyticsPrefab.InstantiateTaskAsync();
        
        if (Application.platform == RuntimePlatform.IPhonePlayer)
            GameAnalytics.RequestTrackingAuthorization(this);
        else
            GameAnalytics.Initialize();
    }
    
    public void TrackEvent(IAnalyticsMessage message)
    {
        // Handle different message types
        if (message is PaymentEventMessage purchaseMessage)
        {
            TrackPurchase(purchaseMessage);
            return;
        }
        
        if (message is AdsEventMessage adsMessage)
        {
            TrackAdsEvent(adsMessage);
            return;
        }
        
        // Default design event
        GameAnalytics.NewDesignEvent(message.Name, message.Parameters);
    }
}

Firebase Analytics

[Serializable]
public class FirebaseAnalyticsHandler : IAnalyticsAdapter
{
    public async UniTask InitializeAsync()
    {
        var status = await FirebaseApp.CheckDependenciesAsync();
        if (status == DependencyStatus.Available)
        {
            // Firebase is ready
        }
    }
    
    public void TrackEvent(IAnalyticsMessage message)
    {
        var parameters = message.Parameters
            .Select(p => new Parameter(p.Key, p.Value))
            .ToArray();
            
        FirebaseAnalytics.LogEvent(message.Name, parameters);
    }
}

Unity Analytics

[Serializable]
public class UnityAnalyticsHandler : IAnalyticsAdapter
{
    public async UniTask InitializeAsync()
    {
        await UnityServices.InitializeAsync();
        AnalyticsService.Instance.StartDataCollection();
    }
    
    public void TrackEvent(IAnalyticsMessage message)
    {
        var unityEvent = new UnityEventMessage(message.Name);
        foreach (var parameter in message.Parameters)
            unityEvent[parameter.Key] = parameter.Value;
            
        AnalyticsService.Instance.RecordEvent(unityEvent);
        AnalyticsService.Instance.Flush();
    }
}

MyTracker

[Serializable]
public class MyTrackerProvider : IAnalyticsAdapter
{
    public string iOSKey;
    public string AndroidKey;
    public bool IsDebug;
    
    public UniTask InitializeAsync()
    {
        MyTracker.IsDebugMode = IsDebug;
        
#if UNITY_IOS
        MyTracker.Init(iOSKey);
#elif UNITY_ANDROID
        MyTracker.Init(AndroidKey);
#endif
        
        return UniTask.CompletedTask;
    }
    
    public void TrackEvent(IAnalyticsMessage message)
    {
        MyTracker.TrackEvent(message.Name, message.Parameters);
    }
}

Debug Adapter

[Serializable]
public class DebugAnalyticsAdapter : IAnalyticsAdapter
{
    public UniTask InitializeAsync() => UniTask.CompletedTask;
    
    public void TrackEvent(IAnalyticsMessage message)
    {
#if GAME_DEBUG || UNITY_EDITOR
        GameLog.LogRuntime($"ANALYTICS EVENT: {message}", Color.yellow);
#endif
    }
}

Event System

Analytics Messages

// Base analytics message
[Serializable]
public class AnalyticsEventMessage : IAnalyticsMessage
{
    public Dictionary<string, string> parameters = new();
    
    public AnalyticsEventMessage(string name, string groupId)
    {
        Name = name;
        GroupId = groupId;
        DeviceModel = SystemInfo.deviceModel;
    }
    
    public string Name { get; protected set; }
    public string GroupId { get; set; }
    public string UserId { get; set; }
    public string EventSource { get; set; }
    public string SceneName { get; set; }
    
    public string this[string key]
    {
        get => parameters.TryGetValue(key, out var value) ? value : string.Empty;
        set => parameters[key] = value;
    }
}

Built-in Events

Session Events

// Login event
public class LoginMessage : AnalyticsEventMessage
{
    public LoginMessage() : base("login", "session_group") { }
}

// Session start
public class SessionStartMessage : AnalyticsEventMessage
{
    public SessionStartMessage(string name) : base(name, name) { }
}

// First app open
public class FirstOpenMessage : AnalyticsEventMessage
{
    public FirstOpenMessage(string name) : base(name, name) { }
}

Purchase Events

[Serializable]
public class PaymentEventMessage : AnalyticsEventMessage
{
    public PaymentEventMessage(string eventName) : base(eventName, "shop_group") { }
    
    public float Price { get; set; }
    public string Currency { get; set; }
    public string ItemType { get; set; }
    public string ItemId { get; set; }
    public string Receipt { get; set; }
    public string Signature { get; set; }
    public string EntryPoint { get; set; }
}

// Specific purchase events
public class PaymentAttemptingEventMessage : PaymentEventMessage
{
    public PaymentAttemptingEventMessage() : base("payment_attempting") { }
}

public class PurchaseCompleteEventMessage : PaymentEventMessage
{
    public PurchaseCompleteEventMessage() : base("payment_complete") { }
}

public class PurchaseErrorEventMessage : PaymentEventMessage
{
    public PurchaseErrorEventMessage() : base("payment_error") { }
}

Ads Events

[Serializable]
public class AdsEventMessage : AnalyticsEventMessage
{
    public AdsEventMessage(string name) : base(name, "shop_group") { }
    
    public string Placement { get; set; }
    public string SdkName { get; set; }
    public string AdsType { get; set; }
    public string ActionType { get; set; }
    public bool FirstTimeAds { get; set; }
    public float AdsPrice { get; set; }
    public int AdsDuration { get; set; }
    public string Message { get; set; }
    public string ErrorCode { get; set; }
}

Game Resource Events

[Serializable]
public class GameResourceEventMessage : AnalyticsEventMessage
{
    public GameResourceEventMessage(string eventName) : base(eventName, "game_resource_group") { }
    
    public string ResourceCurrency { get; set; }
    public string ResourceType { get; set; }
    public int ResourceValue { get; set; }
    public string ResourceSource { get; set; }
}

public class GameResourceFlowEventMessage : GameResourceEventMessage
{
    public GameResourceFlowEventMessage() : base("game_resource_flow") { }
    
    public string FlowType { get; set; } // "resource_flow_added" or "resource_flow_spend"
    public string ItemType { get; set; }
    public string ItemId { get; set; }
}

Progression Events

[Serializable]
public class GAProgressionEventMessage : AnalyticsEventMessage
{
    public GAProgressionEventMessage() : base("ga_progression", "ga_group") { }
    
    public string ProgressionStatus { get; set; } // "start", "complete", "fail"
    public string Progression01 { get; set; }
    public string Progression02 { get; set; }
    public string Progression03 { get; set; }
    public int Score { get; set; }
}

Custom Events

// Create custom event message
[Serializable]
public class CustomGameEvent : AnalyticsEventMessage
{
    public CustomGameEvent() : base("custom_event", "custom_group") { }
    
    public string CustomParameter
    {
        get => this["custom_param"];
        set => this["custom_param"] = value;
    }
    
    public int CustomValue
    {
        get => int.TryParse(this["custom_value"], out var value) ? value : 0;
        set => this["custom_value"] = value.ToString();
    }
}

// Usage
var customEvent = new CustomGameEvent
{
    CustomParameter = "test_value",
    CustomValue = 42,
    UserId = "user123",
    SceneName = "MainMenu"
};

analyticsService.TrackEvent(customEvent);

Event Parameters

// Typed parameter values
[Serializable]
public class AnalyticsIntValue
{
    private int _value;
    private AnalyticsEventMessage _message;
    private string _name;
    
    public int Value
    {
        get => _value;
        set
        {
            _value = value;
            _message[_name] = value.ToStringFromCache();
        }
    }
}

[Serializable]
public class AnalyticsFloatValue
{
    private float _value;
    private AnalyticsEventMessage _message;
    private string _name;
    
    public float Value
    {
        get => _value;
        set
        {
            _value = value;
            _message[_name] = value.ToString(CultureInfo.InvariantCulture);
        }
    }
}

[Serializable]
public class AnalyticsBoolValue
{
    public const string TrueValue = "true";
    public const string FalseValue = "false";
    
    private bool _value;
    private AnalyticsEventMessage _message;
    private string _name;
    
    public bool Value
    {
        get => _value;
        set
        {
            _value = value;
            _message[_name] = value ? TrueValue : FalseValue;
        }
    }
}

Message Handlers

Handler Interface

public interface IAnalyticsMessageHandler
{
    UniTask<IAnalyticsMessage> UpdateMessageAsync(IAnalyticsMessage message);
}

Custom Handlers

// Example: Add timestamp to all events
[Serializable]
public class TimestampHandler : IAnalyticsMessageHandler
{
    public async UniTask<IAnalyticsMessage> UpdateMessageAsync(IAnalyticsMessage message)
    {
        message["timestamp"] = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
        message["time_from_start"] = Time.time.ToString("F2");
        return message;
    }
}

// Example: Add user data
[Serializable]
public class UserDataHandler : IAnalyticsMessageHandler
{
    private readonly IUserService _userService;
    
    public async UniTask<IAnalyticsMessage> UpdateMessageAsync(IAnalyticsMessage message)
    {
        var user = await _userService.GetCurrentUserAsync();
        if (user != null)
        {
            message["user_id"] = user.Id;
            message["user_level"] = user.Level.ToString();
            message["user_coins"] = user.Coins.ToString();
        }
        
        return message;
    }
}

Handler Registration

// Register handlers in configuration
public class AnalyticsConfiguration
{
    [SerializeReference]
    public List<IAnalyticsMessageHandler> messageHandlers = new()
    {
        new TimestampHandler(),
        new UserDataHandler(),
        new FpsHandler()
    };
}

// Or register at runtime
analyticsService.RegisterMessageHandler(new CustomHandler());

Configuration System

Analytics Configuration

[Serializable]
public class AnalyticsConfiguration
{
    public bool isEnabled = true;
    
    [Header("Analytics Adapters")]
    [ListDrawerSettings(ListElementLabelName = "@name")]
    public List<AnalyticsAdapterData> analytics = new();
    
    [Header("Analytics Handlers")]
    public List<IAnalyticsMessageHandler> messageHandlers = new();
}

Adapter Configuration

[Serializable]
public class AnalyticsAdapterData
{
    public string name;
    public bool isEnabled = true;
    
    [SerializeReference]
    public IAnalyticsAdapter adapter;
}

Runtime Configuration

// Configure adapters at runtime
public void ConfigureAnalytics()
{
    var configuration = new AnalyticsConfiguration
    {
        isEnabled = true,
        analytics = new List<AnalyticsAdapterData>
        {
            new()
            {
                name = "GameAnalytics",
                isEnabled = true,
                adapter = new GameAnalyticsProvider()
            },
            new()
            {
                name = "Firebase",
                isEnabled = true,
                adapter = new FirebaseAnalyticsHandler()
            }
        }
    };
}

FPS Service

FPS Monitoring

public interface IFpsService : IGameService
{
    ReadOnlyReactiveProperty<float> CurrentFps { get; }
}

[Serializable]
public class FpsService : GameService, IFpsService
{
    public ReadOnlyReactiveProperty<float> CurrentFps { get; }
    
    public FpsService()
    {
        CurrentFps = Observable.EveryUpdate()
            .Select(_ => Time.unscaledDeltaTime)
            .Chunk(bufferSize, skipFrame)
            .Select(x => 1 / x.Average())
            .ToReadOnlyReactiveProperty();
    }
}

Performance Tracking

// Track FPS in analytics
public class FpsHandler : IAnalyticsMessageHandler
{
    public async UniTask<IAnalyticsMessage> UpdateMessageAsync(IAnalyticsMessage message)
    {
        message["fps"] = FpsService.Fps.ToString("F1");
        return message;
    }
}

// Monitor performance
public class PerformanceMonitor : MonoBehaviour
{
    private IFpsService _fpsService;
    private IAnalyticsService _analyticsService;
    
    private void Start()
    {
        _fpsService = Context.Get<IFpsService>();
        _analyticsService = Context.Get<IAnalyticsService>();
        
        // Track low FPS events
        _fpsService.CurrentFps
            .Where(fps => fps < 30f)
            .Throttle(TimeSpan.FromSeconds(5))
            .Subscribe(fps => TrackLowFps(fps))
            .AddTo(this);
    }
    
    private void TrackLowFps(float fps)
    {
        var message = new AnalyticsEventMessage("low_fps", "performance")
        {
            ["fps"] = fps.ToString("F1"),
            ["scene"] = SceneManager.GetActiveScene().name
        };
        
        _analyticsService.TrackEvent(message);
    }
}

Examples

Basic Event Tracking

public class BasicAnalyticsExample : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    private void Start()
    {
        _analyticsService = Context.Get<IAnalyticsService>();
        
        // Track simple event
        TrackSimpleEvent();
        
        // Track event with parameters
        TrackEventWithParameters();
    }
    
    private void TrackSimpleEvent()
    {
        var message = new AnalyticsEventMessage("game_start", "session");
        _analyticsService.TrackEvent(message);
    }
    
    private void TrackEventWithParameters()
    {
        var message = new AnalyticsEventMessage("level_start", "gameplay")
        {
            ["level"] = "1",
            ["difficulty"] = "normal",
            ["character"] = "warrior"
        };
        
        _analyticsService.TrackEvent(message);
    }
}

Custom Event Messages

// Define custom event
[Serializable]
public class PlayerDeathEvent : AnalyticsEventMessage
{
    public PlayerDeathEvent() : base("player_death", "gameplay") { }
    
    public string DeathCause
    {
        get => this["death_cause"];
        set => this["death_cause"] = value;
    }
    
    public int Level
    {
        get => int.TryParse(this["level"], out var level) ? level : 0;
        set => this["level"] = value.ToString();
    }
    
    public float SurvivalTime
    {
        get => float.TryParse(this["survival_time"], out var time) ? time : 0f;
        set => this["survival_time"] = value.ToString("F2");
    }
}

// Usage
public class GameplayAnalytics : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    public void OnPlayerDeath(string cause, int level, float survivalTime)
    {
        var deathEvent = new PlayerDeathEvent
        {
            DeathCause = cause,
            Level = level,
            SurvivalTime = survivalTime,
            UserId = PlayerData.UserId,
            SceneName = SceneManager.GetActiveScene().name
        };
        
        _analyticsService.TrackEvent(deathEvent);
    }
}

Purchase Events

public class PurchaseAnalytics : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    public void OnPurchaseAttempt(string productId, float price, string currency)
    {
        var attemptEvent = new PaymentAttemptingEventMessage
        {
            ItemId = productId,
            Price = price,
            Currency = currency,
            EntryPoint = "shop_main"
        };
        
        _analyticsService.TrackEvent(attemptEvent);
    }
    
    public void OnPurchaseComplete(string productId, float price, string currency, string receipt)
    {
        var completeEvent = new PurchaseCompleteEventMessage
        {
            ItemId = productId,
            ItemType = "consumable",
            Price = price,
            Currency = currency,
            Receipt = receipt,
            EntryPoint = "shop_main"
        };
        
        _analyticsService.TrackEvent(completeEvent);
    }
    
    public void OnPurchaseError(string productId, string error)
    {
        var errorEvent = new PurchaseErrorEventMessage
        {
            ItemId = productId,
            EntryPoint = "shop_main"
        };
        errorEvent["error_message"] = error;
        
        _analyticsService.TrackEvent(errorEvent);
    }
}

Ads Events

public class AdsAnalytics : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    public void OnRewardedAdShown(string placement, string sdkName)
    {
        var adsEvent = new AdsEventMessage("ads_rewarded_shown")
        {
            Placement = placement,
            SdkName = sdkName,
            AdsType = "rewarded_video",
            ActionType = "opened",
            FirstTimeAds = IsFirstTimeAd(placement)
        };
        
        _analyticsService.TrackEvent(adsEvent);
    }
    
    public void OnRewardedAdCompleted(string placement, string reward, int rewardAmount)
    {
        var adsEvent = new AdsEventMessage("ads_rewarded_completed")
        {
            Placement = placement,
            AdsType = "rewarded_video",
            ActionType = "rewarded"
        };
        adsEvent["reward_type"] = reward;
        adsEvent["reward_amount"] = rewardAmount.ToString();
        
        _analyticsService.TrackEvent(adsEvent);
    }
    
    public void OnAdsFailed(string placement, string error)
    {
        var adsEvent = new AdsEventMessage("ads_failed")
        {
            Placement = placement,
            ActionType = "failed",
            Message = error
        };
        
        _analyticsService.TrackEvent(adsEvent);
    }
    
    private bool IsFirstTimeAd(string placement)
    {
        // Check if this is the first time showing ad for this placement
        return !PlayerPrefs.HasKey($"ads_shown_{placement}");
    }
}

Game Resource Events

public class ResourceAnalytics : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    public void OnResourceEarned(string resourceType, int amount, string source)
    {
        var resourceEvent = new GameResourceFlowEventMessage
        {
            ResourceCurrency = resourceType,
            ResourceValue = amount,
            ResourceSource = source,
            FlowType = "resource_flow_added",
            ItemType = "currency",
            ItemId = resourceType
        };
        
        _analyticsService.TrackEvent(resourceEvent);
    }
    
    public void OnResourceSpent(string resourceType, int amount, string itemType, string itemId)
    {
        var resourceEvent = new GameResourceFlowEventMessage
        {
            ResourceCurrency = resourceType,
            ResourceValue = amount,
            FlowType = "resource_flow_spend",
            ItemType = itemType,
            ItemId = itemId
        };
        
        _analyticsService.TrackEvent(resourceEvent);
    }
    
    public void OnResourceChanged(string resourceType, int newAmount, int change)
    {
        var changeEvent = new GameResourceChangeEventMessage
        {
            ResourceCurrency = resourceType,
            ResourceValue = newAmount
        };
        changeEvent["change_amount"] = change.ToString();
        changeEvent["change_type"] = change > 0 ? "increase" : "decrease";
        
        _analyticsService.TrackEvent(changeEvent);
    }
}

Progression Events

public class ProgressionAnalytics : MonoBehaviour
{
    private IAnalyticsService _analyticsService;
    
    public void OnLevelStart(string world, string level)
    {
        var progressionEvent = new GAProgressionEventMessage
        {
            ProgressionStatus = "start",
            Progression01 = world,
            Progression02 = level,
            Score = 0
        };
        
        _analyticsService.TrackEvent(progressionEvent);
    }
    
    public void OnLevelComplete(string world, string level, int score)
    {
        var progressionEvent = new GAProgressionEventMessage
        {
            ProgressionStatus = "complete",
            Progression01 = world,
            Progression02 = level,
            Score = score
        };
        
        _analyticsService.TrackEvent(progressionEvent);
    }
    
    public void OnLevelFailed(string world, string level, int score, string failReason)
    {
        var progressionEvent = new GAProgressionEventMessage
        {
            ProgressionStatus = "fail",
            Progression01 = world,
            Progression02 = level,
            Score = score
        };
        progressionEvent["fail_reason"] = failReason;
        
        _analyticsService.TrackEvent(progressionEvent);
    }
}

Best Practices

Event Design

  1. Use Consistent Naming: Follow consistent naming conventions for events and parameters
  2. Group Related Events: Use meaningful group IDs to organize related events
  3. Limit Parameter Count: Keep parameter count reasonable (< 25 parameters per event)
  4. Validate Data: Always validate parameter values before sending events

Performance

  1. Async Operations: Use async/await for all analytics operations
  2. Batch Events: Consider batching events for better performance
  3. Error Handling: Implement proper error handling in adapters
  4. Memory Management: Use lifetime management for proper cleanup

Testing

  1. Debug Adapter: Use debug adapter during development
  2. Conditional Compilation: Use conditional compilation for debug code
  3. Test Events: Create test events to verify integration
  4. Monitor Logs: Monitor analytics logs for errors

Configuration

  1. Environment-Specific: Use different configurations for dev/staging/production
  2. Feature Flags: Use feature flags to enable/disable analytics
  3. Adapter Selection: Choose appropriate adapters for your needs
  4. Handler Pipeline: Design message handler pipeline carefully

Troubleshooting

Common Issues

Events Not Sending

// Check if service is registered
var analyticsService = Context.Get<IAnalyticsService>();
if (analyticsService == null)
{
    Debug.LogError("Analytics service not registered!");
    return;
}

// Check if adapters are initialized
foreach (var adapter in configuration.analytics)
{
    if (!adapter.isEnabled)
    {
        Debug.LogWarning($"Adapter {adapter.name} is disabled");
    }
}

Adapter Initialization Failures

// Add error handling in adapter initialization
public async UniTask InitializeAsync()
{
    try
    {
        await SomeAsyncInitialization();
    }
    catch (Exception ex)
    {
        Debug.LogError($"Analytics adapter initialization failed: {ex.Message}");
        throw;
    }
}

Missing Parameters

// Validate required parameters
public class ValidationHandler : IAnalyticsMessageHandler
{
    public async UniTask<IAnalyticsMessage> UpdateMessageAsync(IAnalyticsMessage message)
    {
        // Ensure required parameters are present
        if (string.IsNullOrEmpty(message["user_id"]))
        {
            message["user_id"] = "unknown";
        }
        
        if (string.IsNullOrEmpty(message["session_id"]))
        {
            message["session_id"] = SystemInfo.deviceUniqueIdentifier;
        }
        
        return message;
    }
}

Debug Tips

Enable Debug Logging

// Use debug adapter for development
var debugAdapter = new DebugAnalyticsAdapter();
analyticsService.RegisterAdapter(debugAdapter);

// Enable debug mode in GameAnalytics
var gameAnalyticsProvider = new GameAnalyticsProvider
{
    enableUnderEditor = true
};

Event Validation

// Create validation utility
public static class AnalyticsValidator
{
    public static bool ValidateEvent(IAnalyticsMessage message)
    {
        if (string.IsNullOrEmpty(message.Name))
        {
            Debug.LogError("Event name is required");
            return false;
        }
        
        if (message.Parameters.Count > 25)
        {
            Debug.LogWarning($"Event {message.Name} has {message.Parameters.Count} parameters (recommended: < 25)");
        }
        
        return true;
    }
}

Performance Monitoring

// Monitor analytics performance
public class AnalyticsPerformanceMonitor : IAnalyticsMessageHandler
{
    public async UniTask<IAnalyticsMessage> UpdateMessageAsync(IAnalyticsMessage message)
    {
        var stopwatch = Stopwatch.StartNew();
        
        // Process message
        var result = await ProcessMessage(message);
        
        stopwatch.Stop();
        
        if (stopwatch.ElapsedMilliseconds > 100)
        {
            Debug.LogWarning($"Analytics message processing took {stopwatch.ElapsedMilliseconds}ms");
        }
        
        return result;
    }
}

The UniGame Game Analytics module provides a comprehensive, flexible, and performant solution for analytics integration in Unity games. With its multi-provider support, event-driven architecture, and extensive customization options, it can handle analytics needs for games of any scale and complexity.

About

Analytics service for Unity with custom adapters support

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages