多轮审查和修复

This commit is contained in:
2026-05-12 15:34:08 +08:00
parent f55d2a57c3
commit ebbbb7332e
805 changed files with 838724 additions and 1905 deletions

View File

@@ -0,0 +1,134 @@
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