using System; using System.Collections; using System.Collections.Generic; using DataEyeAnalytics; using Newtonsoft.Json; using UnityEngine; namespace BallKingdomCrush { public class BIManager : MonoBehaviour { private static BIManager _instance; private static readonly object _initLock = new object(); // 状态标志 private bool _initialized = false; // 仅在初始化成功后为 true private bool _initializing = false; // 防止并发多次尝试初始化 private string ntpServer = "time.windows.com"; private string defaultChannel = "gp"; private int maxInitRetries = 6; private float retryIntervalSeconds = 0.5f; public static BIManager Instance { get { if (_instance == null) { var go = new GameObject("BIManager"); DontDestroyOnLoad(go); _instance = go.AddComponent(); } return _instance; } } private void Awake() { // 单例保护:如果场景里已有实例,销毁重复的 if (_instance == null) { _instance = this; DontDestroyOnLoad(gameObject); } else if (_instance != this) { Destroy(gameObject); return; } } private void Start() { // Start 调用 InitBI 是安全的:InitBI 本身是幂等且线程安全的 InitBI(); } /// /// 对外统一初始化入口(幂等、线程安全) /// public void InitBI() { // 快速检查 if (_initialized) return; lock (_initLock) { if (_initialized || _initializing) { // 已经初始化或正在初始化,直接返回 return; } _initializing = true; } // 使用协程执行初始化(必要时可以做重试) StartCoroutine(DoInitCoroutine()); } private IEnumerator DoInitCoroutine() { try { int attempt = 0; Exception lastException = null; while (attempt < maxInitRetries) { attempt++; try { // 这里不主动调用 DataEyeAnalyticsAPI.Init(...) 假设你使用的是场景里的 DataEyeAnalytics 预制体, // SDK 会在预制体 Awake/Start 里初始化。如果你的流程是手动 Init,请在这里调用 Init(appId)。 // 1) 校准时间(默认实例) DataEyeAnalyticsAPI.CalibrateTimeWithNtp(ntpServer); // 2) 设置超参数 var superProperties = new Dictionary { { "channel", defaultChannel } }; DataEyeAnalyticsAPI.SetSuperProperties(superProperties); // 3) 开启自动采集 DataEyeAnalyticsAPI.EnableAutoTrack(AUTO_TRACK_EVENTS.ALL); // 如果上面没有抛异常,认为初始化成功 _initialized = true; Debug.Log($"[BIManager] InitBI succeeded on attempt {attempt}"); yield break; } catch (ArgumentNullException ane) { // 典型:DataEye 内部 instance 还没创建(appId 为 null) -> retry lastException = ane; Debug.LogWarning( $"[BIManager] DataEye not ready (attempt {attempt}), will retry. msg={ane.Message}"); } catch (Exception ex) { // 其他异常,记录并决定是否重试 lastException = ex; Debug.LogWarning($"[BIManager] InitBI attempt {attempt} threw: {ex.Message}"); } // 等待后重试 yield return new WaitForSeconds(retryIntervalSeconds); } // 重试用尽仍失败 —— 打日志(不再抛异常以免影响游戏流程) Debug.LogError("[BIManager] InitBI failed after retries. Last exception: " + (lastException?.ToString() ?? "null")); } finally { lock (_initLock) { _initializing = false; } } } // ================= 通用埋点 ================= public void TrackEvent(string eventName, Dictionary properties = null) { if (properties == null) properties = new Dictionary(); DataEyeAnalyticsAPI.Track(eventName, properties); DataEyeAnalyticsAPI.Flush(); Debug.Log($"[BI] 事件上报: {eventName}, 属性={JsonConvert.SerializeObject(properties)}"); } // ================= 预制事件封装 ================= /// /// 上报广告事件 /// /// /// Native,RewardedVideo,Banner,Interstitial,Splash /// 聚合广告位id /// 广告网络ID public void TrackAdEvent(string eventName, string adType, string placementId = null, string networkFirmId = null, double revenue = 0, string scene = null, string currency = "USD") { var props = new Dictionary { { "ad_type", adType } }; if (!string.IsNullOrEmpty(placementId)) props["PlacementId"] = placementId; if (!string.IsNullOrEmpty(networkFirmId)) props["NetworkFirmId"] = networkFirmId; if (revenue > 0) { props["Revenue"] = revenue; props["Ecpm"] = revenue * 1000; } if (!string.IsNullOrEmpty(scene)) props["scene"] = scene; if (!string.IsNullOrEmpty(currency)) props["Currency"] = currency; TrackEvent(eventName, props); } /// /// 上报内购事件 /// /// 收入 /// 币种 /// 支付方式 0=商店支付,1=三方支付 /// sku /// order:下单 paid:付费成功 paid_err:扣款失败 /// 商品名称 /// 如果遇到异常或相关情况,将err_msg进行上报 public void TrackPurchase(double revenue, string currency, string payType, string itemId, string status, string itemName = null, string msg = null) { var props = new Dictionary { { "Revenue", revenue }, { "Currency", currency }, { "type", payType }, { "item_id", itemId }, { "purchase_status", status } }; if (!string.IsNullOrEmpty(itemName)) props["item_name"] = itemName; if (!string.IsNullOrEmpty(msg)) props["msg"] = msg; TrackEvent(BIEvent.PURCHASE, props); } /// /// 上报页面浏览 /// public void TrackPageView(string pageName) { var props = new Dictionary { { "page_name", pageName } // 例如 "loading"、"game" }; TrackEvent(BIEvent.PAGE_VIEW, props); } /// /// AB 分组埋点 /// public void TrackABConfig(int responseTime) { var props = new Dictionary { { "response_time", responseTime } }; TrackEvent(BIEvent.AB_CONFIG, props); } } } public static class BIEvent { // 自动采集(SDK 开启自动采集即可,不需要手动埋点) public const string APP_INSTALL = "app_install"; public const string APP_START = "app_start"; public const string APP_END = "app_end"; public const string APP_VIEW = "app_view"; public const string APP_CRASH = "app_crash"; // 内部预制事件 public const string AD_REQUEST = "ad_request"; // 广告请求 public const string AD_INVENTORY = "ad_inventory"; // 广告填充的时候 public const string AD_IMP = "ad_imp"; // 广告展示 public const string AD_CLICK = "ad_click"; // 广告点击 public const string PURCHASE = "purchase"; // 内购 // 页面相关 public const string PAGE_VIEW = "page_view_customize"; // 其他业务事件(AB 测试等) public const string AB_CONFIG = "config"; // AB }