Files
zeling_v2/Assets/_Game/Scripts/Support/Analytics/AnalyticsManager.cs

135 lines
4.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using BaseGames.Core;
namespace BaseGames.Support.Analytics
{
/// <summary>
/// 游戏玩法数据分析收集器(架构 16_SupportingModules §7
/// 在本地记录结构化事件日志,供后续分析或上传。
/// 注不收集任何个人身份信息PII
/// </summary>
public class AnalyticsManager : MonoBehaviour, IAnalyticsService
{
[Header("配置")]
[Tooltip("是否启用分析收集")]
[SerializeField] private bool _enabled = true;
[Tooltip("超过此条数时自动将缓存刷写到磁盘")]
[SerializeField] private int _flushThreshold = 50;
private readonly List<AnalyticsEvent> _buffer = new();
private string _logPath;
private void Awake()
{
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
if (!_enabled) { enabled = false; return; }
#endif
if (ServiceLocator.GetOrDefault<IAnalyticsService>() != null) { Destroy(gameObject); return; }
ServiceLocator.Register<IAnalyticsService>(this);
_logPath = Path.Combine(Application.persistentDataPath, "analytics.json");
}
private void OnDestroy()
{
Flush();
ServiceLocator.Unregister<IAnalyticsService>(this);
}
private void OnApplicationQuit() => Flush();
// ── 实例方法IAnalyticsService 实现)────────────────────────────────────
/// <summary>记录一个自定义分析事件。</summary>
public void Track(string eventName, Dictionary<string, object> parameters = null)
{
if (!_enabled) return;
Enqueue(eventName, parameters);
}
// ── 预定义事件 ────────────────────────────────────────────────────────────
public void TrackBossKill(string bossId, float duration, int deathCount)
=> Track("boss_kill", new Dictionary<string, object>
{
{ "boss_id", bossId },
{ "duration", duration },
{ "death_count", deathCount },
});
public void TrackDeath(string cause, string sceneId, Vector2 position)
=> Track("player_death", new Dictionary<string, object>
{
{ "cause", cause },
{ "scene", sceneId },
{ "pos_x", Mathf.RoundToInt(position.x) },
{ "pos_y", Mathf.RoundToInt(position.y) },
});
public void TrackAbilityUnlock(string abilityId)
=> Track("ability_unlock", new Dictionary<string, object>
{
{ "ability", abilityId }
});
public void TrackSessionStart(int slotIndex)
=> Track("session_start", new Dictionary<string, object>
{
{ "slot", slotIndex },
});
public void TrackSessionEnd(float playtimeSeconds)
=> Track("session_end", new Dictionary<string, object>
{
{ "playtime", playtimeSeconds },
});
// ── 内部实现 ──────────────────────────────────────────────────────────────
private void Enqueue(string name, Dictionary<string, object> parms)
{
_buffer.Add(new AnalyticsEvent
{
EventName = name,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
Parameters = parms ?? new Dictionary<string, object>(),
});
if (_buffer.Count >= _flushThreshold)
Flush();
}
private void Flush()
{
if (_buffer.Count == 0) return;
try
{
var existing = new List<AnalyticsEvent>();
if (File.Exists(_logPath))
{
string json = File.ReadAllText(_logPath);
var loaded = JsonConvert.DeserializeObject<List<AnalyticsEvent>>(json);
if (loaded != null) existing.AddRange(loaded);
}
existing.AddRange(_buffer);
File.WriteAllText(_logPath, JsonConvert.SerializeObject(existing, Formatting.Indented));
_buffer.Clear();
}
catch (Exception ex)
{
Debug.LogWarning($"[Analytics] 写入失败: {ex.Message}");
}
}
[Serializable]
private class AnalyticsEvent
{
public string EventName;
public long Timestamp;
public Dictionary<string, object> Parameters;
}
}
} // namespace BaseGames.Support.Analytics