com.didabu.core.unity
Version:
Didabu Unity Core
523 lines (491 loc) • 23.8 kB
Markdown
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");
```
因为各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
另外还有一个事件生成器不是实现事件拦截器接口来实现的,而是通过监听用户资产变化来生成新事件以达到如下需求:当用户的某项资产达到指定值时需要上报相应的事件,比如美元达到100,200,500时分别需要上报dollar_100, dollar_200, dollar_500事件,代码参见:https://github.com/mogafa/csharp-app/blob/main/src/Assets/EventIntercepts/AssetChangedEventGenerator.cs
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);
}
```