UNPKG

com.didabu.core.unity

Version:

Didabu Unity Core

523 lines (491 loc) 23.8 kB
# Didabu开发指南 Didabu采用面向接口的开发模式,通过实现指定的接口可实现不同功能的不同实现。 ## 埋点事件 通过实现ILogEventReporter(EventReporterAbstract),Didabu可以将埋点事件同时上报到不同的事件分析平台,埋点事件上报器接口定义如下: ```csharp public interface ILogEventReporter { /// <summary> /// 事件上报器名称 /// </summary> string Name { get; } /// <summary> /// 初始化事件上报器 /// </summary> /// <param name="commonEventParameterNames">通用事件参数名称</param> /// <param name="eventNameMap">事件名称映射</param> /// <param name="eventParameterNameMap">事件参数名称映射</param> /// <returns></returns> Task Initialize(List<string> commonEventParameterNames, Dictionary<string, string> eventNameMap, Dictionary<string, string> eventParameterNameMap); /// <summary> /// 添加通用事件参数 /// </summary> /// <param name="eventParameterName">事件参数名称</param> /// <param name="value">事件参数值</param> void AddCommonEventParameter(string eventParameterName, string value); /// <summary> /// 上报事件 /// </summary> /// <param name="name">事件名称</param> /// <param name="eventParameters">事件参数</param> /// <returns></returns> Task LogEvent(string name, Dictionary<string, string> eventParameters); } public abstract class EventReporterAbstract : MogafaBase, ILogEventReporter { /// <summary> /// 名称 /// </summary> public abstract string Name { get; } protected List<string> commonEventParameterNames; /// <summary> /// 通用事件参数, 每次上报事件时都需要的事件参数 比如用户ID等 /// 这个配置是和不同log event Provider不想关 /// </summary> protected Dictionary<string, string> commonEventParameters; /// <summary> /// 事件名称映射表, /// 每个不同的log event Provider配置不一样 /// </summary> protected Dictionary<string, string> eventNameMap; /// <summary> /// 时间参数名称映射表 /// 每个不同的log event Provider配置不一样 /// </summary> protected Dictionary<string, string> eventParameterNameMap; protected EventReporterAbstract() { commonEventParameters = new Dictionary<string, string>(); } public Task Initialize(List<string> commonEventParameterNames, Dictionary<string, string> eventNameMap, Dictionary<string, string> eventParameterNameMap) { Logger.LogInformation($"Log event reporter {Name} init start."); this.eventNameMap = eventNameMap; if(this.eventNameMap == null) { this.eventNameMap = new Dictionary<string, string>(); } this.eventParameterNameMap = eventParameterNameMap; if(this.eventParameterNameMap == null) { this.eventParameterNameMap = new Dictionary<string, string>(); } this.commonEventParameterNames = commonEventParameterNames; if(this.commonEventParameterNames == null) { this.commonEventParameterNames = new List<string>(); } Logger.LogInformation($"Log event reporter {Name} init finished."); return Task.CompletedTask; } public void AddCommonEventParameter(string eventParameterName, string value) { if (!commonEventParameterNames.Contains(eventParameterName)) { return; } if (commonEventParameters.ContainsKey(eventParameterName)) { commonEventParameters.Remove(eventParameterName); } commonEventParameters.Add(eventParameterName, value); } public Task LogEvent(string name, Dictionary<string, string> eventParameters) { Logger.LogInformation($"Log event to: {Name}, event name: {name}"); var cloneEventParameters = new Dictionary<string, string>(commonEventParameters); foreach (var parameter in eventParameters) { cloneEventParameters.Add(parameter.Key, parameter.Value); } var mapName = MapName(name, eventNameMap); var mapEventParameters = new Dictionary<string, string>(); foreach (var parameter in cloneEventParameters) { var mapParameterName = MapName(parameter.Key, eventParameterNameMap); mapEventParameters.Add(mapParameterName, parameter.Value); } return LogEventInternal(mapName, mapEventParameters); } private string MapName(string eventName, Dictionary<string, string> nameMap) { if(nameMap == null || nameMap.Count == 0) { return eventName; } var needMapName = nameMap.Keys.FirstOrDefault(nm => (nm.EndsWith("_") && (eventName.StartsWith(nm) || eventName == nm.Substring(0, nm.Length - 1)) || (!nm.EndsWith("_") && eventName == nm))); if (needMapName == null) { return eventName; } var mapName = nameMap[needMapName]; if (!needMapName.EndsWith("_")) { return mapName; } mapName = eventName.Replace(needMapName, $"{mapName}_"); if (mapName.EndsWith("_")) { mapName = mapName.Substring(0, mapName.Length - 1); } return mapName; } protected abstract Task LogEventInternal(string name, Dictionary<string, string> eventParameters); } ``` 针对不同的事件平台,比如(Firebase Analytics或AppsFlyer)实现ILogEventReporter(EventReporterAbstract)接口,并通过LogEventReporter管理器添加,然后就可以使用通用的埋点事件API向不同的事件平台发送,比如: ``` csharp public class FirebaseAnalyticsLogEventReporter : EventReporterAbstract { public override string Name => "FirebaseAnalytics"; protected override Task LogEventInternal(string name, Dictionary<string, string> eventParameters) { Firebase.Analytics.FirebaseAnalytics.LogEvent(name, ToParameters(eventParameters)); return Task.CompletedTask; } private Parameter[] ToParameters(Dictionary<string, string> eventParameters) { var parameters = new List<Parameter>(); foreach(var key in eventParameters.Keys) { parameters.Add(new Parameter(key, eventParameters[key])); } return parameters.ToArray(); } } ``` Firebase Analytics初始化代码(Didabu扩展方法): ```csharp public static class DidabuFirebaseAnalyticsExtension { public static void InitFirebase(this Didabu didabu) { FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => { var dependencyStatus = task.Result; if (dependencyStatus == DependencyStatus.Available) { FirebaseAnalytics.SetAnalyticsCollectionEnabled(true); // Set the user's sign up method. FirebaseAnalytics.SetUserProperty( FirebaseAnalytics.UserPropertySignUpMethod, "Google"); // Set the user ID. if (!string.IsNullOrEmpty(didabu.User.DidabuId)) { FirebaseAnalytics.SetUserId(didabu.User.DidabuId); } else { didabu.OnInitialSuccessed += OnDidabuInitialSuccessed; if (!string.IsNullOrEmpty(didabu.User.DidabuId)) { FirebaseAnalytics.SetUserId(didabu.User.DidabuId); } } // Set default session duration values. FirebaseAnalytics.SetSessionTimeoutDuration(new TimeSpan(0, 30, 0)); } else { Debug.LogError( "Could not resolve all Firebase dependencies: " + dependencyStatus); } }); } private static void OnDidabuInitialSuccessed(string obj) { FirebaseAnalytics.SetUserId(obj); } public static void UseFirebaseAnalyticsLogEvent(this Didabu didabu, List<string> commonEventParameterNames = null, Dictionary<string, string> eventNameMap = null, Dictionary<string, string> eventParameterNameMap = null) { didabu.LogEventReporter.AddEventReporter(new FirebaseAnalyticsLogEventReporter(), commonEventParameterNames, eventNameMap, eventParameterNameMap); } } ``` 然后如下在App中使用Firebase Analytics: ```csharp //初始化Firebase Didabu.Application.InitFirebase(); //使用Firebase Analytics上报埋点事件 Didabu.Application.UseFirebaseAnalyticsLogEvent(); Didabu.Application.LogEvent("test_event"); ``` ### LogEvent事件名称及事件参数名称映射 因为各Event平台会对特殊的事件名称和时间参数的名称会做汇总展示,比如AppsFlyer用af_purchase事件来记录App的购买事件,同时用af_revenue事件参数来记录购买金额,而Firebase Analytics用in_app_purchase事件来记录App的购买事件,同时用value事件参数来记录购买金额,而我们用Didabu来上报购买事件时,我们并不知道事件会上报到哪些平台,所以不能使用各平台特定的事件名和事件参数名来上报,这时就需要使用事件名称映射及时间参数名称映射,比如Didabu用ddb_purchase来记录App的购买事件,用purchase_value事件参数来记录购买金额,那么使用FirebaseAnalytics时可以传递如下参数来告诉Didabu如何映射事件名称和事件参数名称: ``` csharp //初始化时 Didabu.Application.UseFirebaseAnalyticsLogEvent(eventNameMap:new Dictionary<string, string> { {"ddb_purchase","in_app_purchase" } }, eventParameterNameMap:new Dictionary<string, string> { {"purchase_value", "value" } }); //记录购买(广告收益)事件 Didabu.Application.LogEvent("ddb_purchase", new Dictionary<string, string>{ {"ddb_purchase", "0.00856"} }); //同样对于AppsFlyer,初始化时映射好事件名称和事件参数名称,这样不需要修改记录购买事件的代码也能保证上报到AppsFlyer中的事件也能正确在AppsFlyer的控制台网页中正确展示出来 Didabu.Application.UseFirebaseAnalyticsLogEvent(eventNameMap:new Dictionary<string, string> { {"ddb_purchase","af_purchase" } }, eventParameterNameMap:new Dictionary<string, string> { {"purchase_value", "af_revenue" } }); ``` #### 参考: https://support.appsflyer.com/hc/zh-cn/articles/115005544169-Rich-in-app-events-guide#%E4%BA%8B%E4%BB%B6%E7%BB%93%E6%9E%84-%E9%A2%84%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%8F%82%E6%95%B0 https://support.google.com/firebase/answer/9234069?hl=zh-Hans ### 事件上报拦截器 有时候我们需要对上报事件进行处理,比如要对click事件计数处理:第一次触发click事件时同时触发一个click_1的事件,第二次触发click事件时同时触发一个click_2的事件,这是就需要实现事件上报拦截器的接口,该接口定义如下: ```csharp public interface ILogEventIntercept { /// <summary> /// 拦截器名称 /// </summary> string Name { get; } /// <summary> /// 拦截器排序 /// </summary> int Order { get; } /// <summary> /// 拦截器执行逻辑 /// </summary> /// <param name="eventName">需要拦截的事件名称</param> /// <param name="parameters">事件参数列表</param> /// <returns>返回新的事件列表(某一个事件最终可能会生成多个事件)</returns> Task<Dictionary<string, Dictionary<string, string>>> Execute( string eventName, Dictionary<string, string> parameters); } ``` 目前didabu提供三种事件拦截器: 1. 上面提到的事件计数拦截器,代码参见:https://github.com/mogafa/csharp-app/blob/main/src/LogEvents/Intercepts/EventCounterIntercept.cs 2. AB分组事件拦截器,比如当前分为A、B两组,需要分别记录每组的click事件,那么A组上报的click事件就应该为click_A,B组上报的click事件就应该为click_b,该拦截器就是实现该功能的,代码参见:https://github.com/mogafa/csharp-app/blob/main/src/LogEvents/Intercepts/AbGroupEventIntercept.cs 3. 资产上报拦截器,每次上报事件的时候都把用户当前的资产作为事件参数上报,代码参见:https://github.com/mogafa/csharp-app/blob/main/src/Assets/EventIntercepts/AssetEventIntercept.cs 另外还有一个事件生成器不是实现事件拦截器接口来实现的,而是通过监听用户资产变化来生成新事件以达到如下需求:当用户的某项资产达到指定值时需要上报相应的事件,比如美元达到100200500时分别需要上报dollar_100, dollar_200, dollar_500事件,代码参见:https://github.com/mogafa/csharp-app/blob/main/src/Assets/EventIntercepts/AssetChangedEventGenerator.cs ### LogEvent相关代码仓库 https://github.com/mogafa/csharp-common https://github.com/mogafa/csharp-app https://github.com/mogafa/unity-firebase-analytics https://github.com/mogafa/unity-appsflyer https://github.com/didabu/unity-firebase-analytics https://github.com/didabu/unity-appsflyer ## 广告组件 目前Didabu支持三种广告模式: 1. 激励视频(Rewarded Video) 2. 插屏(Interstitial Video) 3. Banner ### 接口定义 这里以激励视频插件开发介绍一下开发流程,激励视频有各种回调数据,我们先把回调事件定义好: ```csharp /// <summary> /// 激励视频点击回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoClickedHandler(string adUnitId, string placement); /// <summary> /// 激励视频关闭回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoClosedHandler(string adUnitId, string placement); /// <summary> /// 激励视频完成回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoCompletedHandler(string adUnitId, string placement); /// <summary> /// 激励视频展示回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoShownHandler(string adUnitId, string placement); /// <summary> /// 激励视频展示失败回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="error">错误信息</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoShowFailedHandler(string adUnitId, string error, string placement); /// <summary> /// 激励视频加载成功回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoLoadedHandler(string adUnitId, string placement); /// <summary> /// 激励视频加载失败回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="error">错误信息</param> public delegate void RewardedVideoLoadFailedHandler(string adUnitId, string error); /// <summary> /// 激励视频收益回调 /// </summary> /// <param name="adUnitId">广告单元ID</param> /// <param name="label">广告收益标签</param> /// <param name="value">广告收益</param> /// <param name="placement">广告位置</param> public delegate void RewardedVideoRevenuePaidHandler(string adUnitId, string label, float value, string placement); ``` 然后是激励视频播放接口定义: ```csharp /// <summary> /// Interface of rewarded video player /// </summary> public interface IRewardedVideoPlayer { /// <summary> /// Occurs when [on clicked]. /// </summary> event RewardedVideoClickedHandler OnClicked; /// <summary> /// Occurs when [on closed]. /// </summary> event RewardedVideoClosedHandler OnClosed; /// <summary> /// Occurs when [on shown]. /// </summary> event RewardedVideoShownHandler OnShown; /// <summary> /// Occurs when [on loaded]. /// </summary> event RewardedVideoLoadedHandler OnLoaded; /// <summary> /// Occurs when [on failed]. /// </summary> event RewardedVideoLoadFailedHandler OnLoadFailed; /// <summary> /// Occurs when [on failed to play]. /// </summary> event RewardedVideoShowFailedHandler OnShowFailed; /// <summary> /// Occurs when [on revenue to pay]. /// </summary> event RewardedVideoRevenuePaidHandler OnRevenuePaid; event RewardedVideoCompletedHandler OnCompleted; /// <summary> /// Requests the rewarded video. /// </summary> /// <param name="adUnitId">The ad unit identifier.</param> /// <returns></returns> void RequestRewardedVideo(string adUnitId); /// <summary> /// Determines whether [has rewarded video] [the specified ad unit identifier]. /// </summary> /// <param name="adUnitId">The ad unit identifier.</param> /// <returns></returns> bool HasRewardedVideo(string adUnitId); /// <summary> /// Shows the rewarded video. /// </summary> /// <param name="adUnitId">The ad unit identifier.</param> /// <param name="placement">The placement.</param> void ShowRewardedVideo(string adUnitId, string placement); } ``` 这里的接口设计尽量简单,不涉及广告请求策略等,只需要提供广告单元ID去请求广告,判断广告是否加载成功以及展示广告。 各广告平台实现这个接口就可以了,我们来看看Applovin Max平台: ```csharp public class MaxRewardedVideoPlayer : MogafaBase, IRewardedVideoPlayer { public MaxRewardedVideoPlayer() { MaxSdkCallbacks.Rewarded.OnAdLoadedEvent += OnRewardedAdLoadedEvent; MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent += OnRewardedAdFailedEvent; MaxSdkCallbacks.Rewarded.OnAdDisplayFailedEvent += OnRewardedAdFailedToDisplayEvent; MaxSdkCallbacks.Rewarded.OnAdDisplayedEvent += OnRewardedAdDisplayedEvent; MaxSdkCallbacks.Rewarded.OnAdClickedEvent += OnRewardedAdClickedEvent; MaxSdkCallbacks.Rewarded.OnAdHiddenEvent += OnRewardedAdDismissedEvent; MaxSdkCallbacks.Rewarded.OnAdRevenuePaidEvent += OnRewardedAdRevenuePaidEvent; } public event RewardedVideoClickedHandler OnClicked; public event RewardedVideoClosedHandler OnClosed; public event RewardedVideoShownHandler OnShown; public event RewardedVideoShowFailedHandler OnShowFailed; public event RewardedVideoLoadedHandler OnLoaded; public event RewardedVideoLoadFailedHandler OnLoadFailed; public event RewardedVideoRevenuePaidHandler OnRevenuePaid; public event RewardedVideoCompletedHandler OnCompleted; public void OnRewardedAdLoadedEvent(string adUnitId, AdInfo adInfo) { Logger.LogDebug($"Rewarded video loaded, ad info:{JsonConvert.SerializeObject(adInfo)}"); OnLoaded?.Invoke(adInfo.AdUnitIdentifier, adInfo.Placement); } public void OnRewardedAdFailedEvent(string adUnitId, ErrorInfo errorInfo) { var errorMessage = $"Rewarded video load failed. ErrorCode:{errorInfo.Code},Message:{errorInfo.Message}, FailureInfo:{errorInfo.AdLoadFailureInfo}"; Logger.LogError(errorMessage); OnLoadFailed?.Invoke(adUnitId, errorMessage); } public void OnRewardedAdFailedToDisplayEvent(string adUnitId, ErrorInfo errorInfo, AdInfo adInfo) { var errorMessage = $"Rewarded video show failed. ErrorCode:{errorInfo.Code},Message:{errorInfo.Message}, FailureInfo:{errorInfo.AdLoadFailureInfo}"; Logger.LogError(errorMessage); OnShowFailed?.Invoke(adUnitId, errorMessage, adInfo.Placement); } public void OnRewardedAdDisplayedEvent(string adUnitId, AdInfo adInfo) { Logger.LogDebug($"Rewarded shown, ad info:{JsonConvert.SerializeObject(adInfo)}"); OnShown?.Invoke(adUnitId, adInfo.Placement); } public void OnRewardedAdClickedEvent(string adUnitId, AdInfo adInfo) { Logger.LogDebug($"Rewarded clicked, ad info:{JsonConvert.SerializeObject(adInfo)}"); OnClicked?.Invoke(adUnitId, adInfo.Placement); } public void OnRewardedAdDismissedEvent(string adUnitId, AdInfo adInfo) { Logger.LogDebug($"Rewarded hidden, ad info:{JsonConvert.SerializeObject(adInfo)}"); OnClosed?.Invoke(adUnitId, adInfo.Placement); } public void OnRewardedAdRevenuePaidEvent(string adUnitId, AdInfo adInfo) { Logger.LogDebug($"Rewarded revenue paid, ad info:{JsonConvert.SerializeObject(adInfo)}"); OnRevenuePaid?.Invoke(adUnitId, adInfo.NetworkName, (float)adInfo.Revenue, adInfo.Placement); OnCompleted?.Invoke(adUnitId, adInfo.Placement); } public bool HasRewardedVideo(string adUnitId) { return MaxSdk.IsRewardedAdReady(adUnitId); } public void RequestRewardedVideo(string adUnitId) { MaxSdk.LoadRewardedAd(adUnitId); } public void ShowRewardedVideo(string adUnitId, string placement) { MaxSdk.ShowRewardedAd(adUnitId, placement); } } ``` ### 广告策略 ```csharp public interface IAdPlayingStrategy { string StrategyName { get; } string GetRewardedVideoAdUnitId(string placement); string GetInterstitialVideoAdUnitId(string placement); string GetBannerAdUnitId(string placement); void SetRewardedVideoPlayer(IRewardedVideoPlayer rewardedVideoPlayer); void SetInterstitialVideoPlayer(IInterstitialVideoPlayer interstitialVideoPlayer); void SetBannerPlayer(IBannerPlayer bannerVideoPlayer); } ```