Compare commits

..

No commits in common. "main" and "1.0.0" have entirely different histories.
main ... 1.0.0

91 changed files with 1620 additions and 7874 deletions

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f9c48f29655a4b8bb90520de5a7c2fa4
timeCreated: 1719552474

View File

@ -1,863 +0,0 @@
#if GURU_SDK_DEV
namespace Guru.Editor
{
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using System.Collections.Generic;
using System.Linq;
public class GuruServiceJsonBuilder: EditorWindow
{
const string GuruProjectSettingsName = "guru-project-settings.cfg";
// Tool Version
public const string Version = "0.2.0";
private const string K_APP_SETTINGS = "app_settings";
private const string K_ADJUST_SETTINGS = "adjust_settings";
private const string K_FB_SETTINGS = "fb_settings";
private const string K_AD_SETTINGS = "ad_settings";
private const string K_IAP_SETTINGS = "iap_settings";
private const char K_SPLITTER_TAB = '\t';
private const char K_SPLITTER_COMMA = ',';
private const string NoSelectionName = "------";
private const string STATE_IDLE = "s_idle";
private const string STATE_LOADING = "s_loading";
private const string STATE_SUCCESS = "s_success";
private static GuruServiceJsonBuilder _instance;
private List<string> _projectNames;
private int _selectedProjectIndex = 0;
private static Dictionary<string, string> _publishLinks;
public static Dictionary<string, string> PublishLinks
{
get
{
if (_publishLinks == null) _publishLinks = LoadProjectSettingsCfg();
return _publishLinks;
}
}
#region Link & Keys
private const string TSVLink = "https://docs.google.com/spreadsheets/d/e/{0}/pub?gid=0&single=true&output=tsv";
#endregion
#region Export JSON
/// <summary>
/// 从 TSV 文件进行转化
/// </summary>
/// <param name="tsv"></param>
/// <param name="savePath"></param>
public static void ConvertFromTSV(string tsv, string savePath = "")
{
if (string.IsNullOrEmpty(tsv))
{
EditorUtility.DisplayDialog("空文件!", $"文件格式错误!\n{tsv}", "OK");
return;
}
var guru_service = EditorGuruServiceIO.CreateEmpty();
var lines = tsv.Split('\n');
string line = "";
for (int index = 0; index < lines.Length; index++)
{
line = lines[index];
if (!IsInvalidLine(line))
{
//---------------- app_settings ----------------
if (line.StartsWith(K_APP_SETTINGS))
{
index++;
while (!line.StartsWith(K_ADJUST_SETTINGS))
{
line = lines[index];
FillAppSettings(guru_service, line);
index++;
line = lines[index];
}
}
//---------------- adjust_settings ----------------
if (line.StartsWith(K_ADJUST_SETTINGS))
{
index++;
FillAdjustSettings(guru_service, lines, ref index);
}
//---------------- fb_settings ----------------
if (line.StartsWith(K_FB_SETTINGS))
{
index++;
FillFacebookSettings(guru_service, lines, ref index);
}
//---------------- ad_settings ----------------
if (line.StartsWith(K_AD_SETTINGS))
{
index++;
FillAdSettings(guru_service, lines, ref index);
}
//---------------- iap_settings ----------------
if (line.StartsWith(K_IAP_SETTINGS))
{
index++;
FillProducts(guru_service, lines, ref index);
}
}
}
guru_service.version = GetFileVersionByDate();
if (string.IsNullOrEmpty(savePath))
{
var dir = Path.GetFullPath($"{Application.dataPath}/../output");
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
savePath = $"{dir}/guru-services-{guru_service.app_settings.app_id.ToLower()}.json";
}
var arr = savePath.Split('/');
var fileName = arr[arr.Length - 1];
EditorGuruServiceIO.SourceServiceFilePath = savePath;
EditorGuruServiceIO.SaveConfig(guru_service, savePath);
if (EditorUtility.DisplayDialog("CONVERT SUCCESS!", $"Export Json File\n{fileName}\nto:\n{savePath}", "OK"))
{
GuruEditorHelper.OpenPath(Directory.GetParent(savePath)?.FullName ?? Application.dataPath);
}
}
public static void ConvertFromTsvFile(string tsvPath, string savePath = "")
{
if (!File.Exists(tsvPath))
{
EditorUtility.DisplayDialog("FILE NOT FOUND!", $"File not exist:\n{tsvPath}", "OK");
return;
}
var tsvString = File.ReadAllText(tsvPath);
if (!string.IsNullOrEmpty(tsvString))
{
ConvertFromTSV(tsvString, savePath);
}
}
/// <summary>
/// AppSettings 填充
/// </summary>
/// <param name="settings"></param>
/// <param name="line"></param>
private static void FillAppSettings(GuruServicesConfig settings, string line)
{
// 对于空行和空值直接跳过
if (IsInvalidLine(line)) return;
string value = "";
if (settings.app_settings == null) settings.app_settings = new GuruAppSettings();
if (settings.parameters == null) settings.parameters = new GuruParameters();
//------------------- AppSettings -------------------------------
// 拾取值和注入
if (GetValue(line, "app_id", out value))
{
settings.app_settings.app_id = value;
}
else if (GetValue(line, "product_name", out value))
{
settings.app_settings.product_name = value;
}
else if (GetValue(line, "bundle_id", out value))
{
settings.app_settings.bundle_id = value;
}
else if (GetValue(line, "support_email", out value))
{
settings.app_settings.support_email = value;
}
else if (GetValue(line, "privacy_url", out value))
{
settings.app_settings.privacy_url = value;
}
else if (GetValue(line, "terms_url", out value))
{
settings.app_settings.terms_url = value;
}
else if (GetValue(line, "android_store", out value))
{
settings.app_settings.android_store = value;
}
else if (GetValue(line, "ios_store", out value))
{
settings.app_settings.ios_store = value;
}
else if (GetValue(line, "enable_firebase", out value))
{
settings.app_settings.enable_firebase = GetBool(value);
}
else if (GetValue(line, "enable_facebook", out value))
{
settings.app_settings.enable_facebook = GetBool(value);
}
else if (GetValue(line, "enable_adjust", out value))
{
settings.app_settings.enable_adjust = GetBool(value);
}
else if (GetValue(line, "enable_iap", out value))
{
settings.app_settings.enable_iap = GetBool(value);
}
else if (GetValue(line, "custom_keystore", out value))
{
settings.app_settings.custom_keystore = GetBool(value);
}
//------------------- Parameters -------------------------------
else if (GetValue(line, "tch_020", out value))
{
settings.parameters.tch_020 = GetDouble(value);
}
else if (GetValue(line, "using_uuid", out value))
{
settings.parameters.using_uuid = GetBool(value);
}
else if (GetValue(line, "cdn_host", out value))
{
settings.parameters.cdn_host = value;
}
else if (GetValue(line, "enable_errorlog", out value))
{
settings.parameters.enable_errorlog = GetBool(value);
}
else if (GetValue(line, "level_end_success_num", out value))
{
settings.parameters.level_end_success_num = GetInt(value);
}
}
/// <summary>
/// AdjustSettings 填充
/// </summary>
/// <param name="settings"></param>
/// <param name="line"></param>
private static void FillAdjustSettings(GuruServicesConfig settings, string[] lines, ref int index)
{
if(settings.adjust_settings == null) settings.adjust_settings = new GuruAdjustSettings();
string[] list = null;
string line = lines[index];
bool pass = false;
List<string> events = new List<string>(40);
while (!lines[index].StartsWith(K_FB_SETTINGS))
{
line = lines[index];
if (!IsInvalidLine(line))
{
if (line.StartsWith("app_token"))
{
list = GetStringArray(line, 1, 2);
settings.adjust_settings.app_token = list;
}
else
{
list = GetStringArray(line, 0, 3);
pass = list != null && !string.IsNullOrEmpty(list[0])
&& (!string.IsNullOrEmpty(list[1]) || !string.IsNullOrEmpty(list[2]));
if( pass) events.Add(string.Join(",", list));
}
}
index++;
}
settings.adjust_settings.events = events.ToArray();
index--;
}
private static long GetFileVersionByDate()
{
var startDt = new DateTime(1970,1,1,0,0,0);
return (long) (DateTime.UtcNow.Ticks - startDt.Ticks) / 10000;
}
private static void FillFacebookSettings(GuruServicesConfig settings, string[] lines, ref int index)
{
string value = "";
if(settings.fb_settings == null) settings.fb_settings = new GuruFbSettings();
var line = "";
while (!lines[index].StartsWith(K_AD_SETTINGS))
{
line = lines[index];
if (!IsInvalidLine(line))
{
// 拾取值和注入
if (GetValue(line, "fb_app_id", out value))
{
settings.fb_settings.fb_app_id = value;
}
else if (GetValue(line, "fb_client_token", out value))
{
settings.fb_settings.fb_client_token = value;
}
}
index++;
}
index--;
}
private static void FillAdSettings(GuruServicesConfig settings, string[] lines, ref int index)
{
string value = "";
if(settings.ad_settings == null) settings.ad_settings = new GuruAdSettings();
var line = lines[index];
// SDK Key
string[] max_ids_android = new string[3];
string[] max_ids_ios = new string[3];
string[] amazon_ids_android = new string[4];
string[] amazon_ids_ios = new string[4];
string[] pubmatic_ids_android = new string[3];
string[] pubmatic_ids_ios = new string[3];
string[] moloco_ids_android = new string[3];
string[] moloco_ids_ios = new string[3];
string[] tradplus_ids_android = new string[3];
string[] tradplus_ids_ios = new string[3];
//------- 开始记录广告配置;
string[] arr;
while (!lines[index].StartsWith(K_IAP_SETTINGS))
{
line = lines[index];
if (GetValue(line, "sdk_key", out value))
{
settings.ad_settings.sdk_key = value;
}
else if (line.StartsWith("admob_app_id"))
{
settings.ad_settings.admob_app_id = GetStringArray(line, 1, 2);
}
else if (line.StartsWith("max_bads"))
{
arr = GetStringArray(line, 1, 2);
max_ids_android[0] = arr[0];
max_ids_ios[0] = arr[1];
}
else if (line.StartsWith("max_iads"))
{
arr = GetStringArray(line, 1, 2);
max_ids_android[1] = arr[0];
max_ids_ios[1] = arr[1];
}
else if (line.StartsWith("max_rads"))
{
arr = GetStringArray(line, 1, 2);
max_ids_android[2] = arr[0];
max_ids_ios[2] = arr[1];
}
else if (line.StartsWith("amazon_app_id"))
{
arr = GetStringArray(line, 1, 2);
amazon_ids_android[0] = arr[0];
amazon_ids_ios[0] = arr[1];
}
else if (line.StartsWith("amazon_bads"))
{
arr = GetStringArray(line, 1, 2);
amazon_ids_android[1] = arr[0];
amazon_ids_ios[1] = arr[1];
}
else if (line.StartsWith("amazon_iads"))
{
arr = GetStringArray(line, 1, 2);
amazon_ids_android[2] = arr[0];
amazon_ids_ios[2] = arr[1];
}
else if (line.StartsWith("amazon_rads"))
{
arr = GetStringArray(line, 1, 2);
amazon_ids_android[3] = arr[0];
amazon_ids_ios[3] = arr[1];
}
else if (line.StartsWith("pubmatic_bads"))
{
arr = GetStringArray(line, 1, 2);
pubmatic_ids_android[0] = arr[0];
pubmatic_ids_ios[0] = arr[1];
}
else if (line.StartsWith("pubmatic_iads"))
{
arr = GetStringArray(line, 1, 2);
pubmatic_ids_android[1] = arr[0];
pubmatic_ids_ios[1] = arr[1];
}
else if (line.StartsWith("pubmatic_rads"))
{
arr = GetStringArray(line, 1, 2);
pubmatic_ids_android[2] = arr[0];
pubmatic_ids_ios[2] = arr[1];
}
else if (line.StartsWith("moloco_bads"))
{
arr = GetStringArray(line, 1, 2);
moloco_ids_android[0] = arr[0];
moloco_ids_ios[0] = arr[1];
}
else if (line.StartsWith("moloco_iads"))
{
arr = GetStringArray(line, 1, 2);
moloco_ids_android[1] = arr[0];
moloco_ids_ios[1] = arr[1];
}
else if (line.StartsWith("moloco_rads"))
{
arr = GetStringArray(line, 1, 2);
moloco_ids_android[2] = arr[0];
moloco_ids_ios[2] = arr[1];
}
else if (line.StartsWith("tradplus_bads"))
{
arr = GetStringArray(line, 1, 2);
tradplus_ids_android[0] = arr[0];
tradplus_ids_ios[0] = arr[1];
}
else if (line.StartsWith("tradplus_iads"))
{
arr = GetStringArray(line, 1, 2);
tradplus_ids_android[1] = arr[0];
tradplus_ids_ios[1] = arr[1];
}
else if (line.StartsWith("tradplus_rads"))
{
arr = GetStringArray(line, 1, 2);
tradplus_ids_android[2] = arr[0];
tradplus_ids_ios[2] = arr[1];
}
index++;
}
//-------- Fill all data -----------
settings.ad_settings.max_ids_android = max_ids_android;
settings.ad_settings.max_ids_ios = max_ids_ios;
settings.ad_settings.amazon_ids_android = amazon_ids_android;
settings.ad_settings.amazon_ids_ios = amazon_ids_ios;
settings.ad_settings.pubmatic_ids_android = pubmatic_ids_android;
settings.ad_settings.pubmatic_ids_ios = pubmatic_ids_ios;
settings.ad_settings.moloco_ids_android = moloco_ids_android;
settings.ad_settings.moloco_ids_ios = moloco_ids_ios;
settings.ad_settings.tradplus_ids_android = tradplus_ids_android;
settings.ad_settings.tradplus_ids_ios = tradplus_ids_ios;
index--;
}
private static void FillProducts(GuruServicesConfig settings, string[] lines, ref int index)
{
string line = "";
List<string> iaps = new List<string>(30);
string[] arr;
while (index < lines.Length)
{
line = lines[index];
if(IsInvalidLine(line)) continue;
arr = GetStringArray(line, 0, 7);
if(string.IsNullOrEmpty(arr[5])) arr[5] = "Store";
if(string.IsNullOrEmpty(arr[6])) arr[6] = "0";
iaps.Add(string.Join(",", arr).Replace("\r", ""));
index++;
}
settings.products = iaps.ToArray();
index--;
}
#endregion
#region Utils
private static bool GetBool(string value)
{
return value.ToLower() == "true" || value == "1";
}
private static int GetInt(string value)
{
int val = 0;
int.TryParse(value, out val);
return val;
}
private static double GetDouble(string value)
{
double val = 0;
double.TryParse(value, out val);
return val;
}
private static bool IsInvalidLine(string line)
{
return string.IsNullOrEmpty(line) || line.StartsWith(K_SPLITTER_TAB.ToString());
}
private static bool GetValue(string line, string key, out string value)
{
value = ""; // default
if (line.StartsWith(key))
{
value = line.Split(K_SPLITTER_TAB)[1];
if (string.IsNullOrEmpty(value)) value = "empty";
return true;
}
return false;
}
private static string[] GetStringArray(string line, int startIndex = 0, int length = 0, char spliter = K_SPLITTER_TAB)
{
if (IsInvalidLine(line)) return null;
var raw = line.Split(spliter);
if (length == 0) length = raw.Length;
var a = new List<string>(length);
for (int i = startIndex; i < length + startIndex; i++)
{
if (i < raw.Length)
{
a.Add(raw[i]);
}
else
{
a.Add("");
}
}
return a.ToArray();
}
#endregion
#region Menu Items
#if GURU_SDK_DEV
[MenuItem("Tools/Export Guru Service", false, 0 )]
#endif
private static void ExportJsonFile()
{
string saveDir = Path.GetFullPath($"{Application.dataPath}/../output");
string saveFile = Path.Combine(saveDir,$"guru-services____{DateTime.Now:yyyy-M-d-HH-mm}.json");
if(!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
string searchPath = "~/Downloads/";
string tsvPath = EditorUtility.OpenFilePanel("Load Guru Service TSV", searchPath, ".tsv");
if (!string.IsNullOrEmpty(tsvPath))
{
ConvertFromTsvFile(tsvPath, saveFile);
}
}
[MenuItem("Guru/Guru-Service Json Builder...", false, 0)]
private static void OpenWindow()
{
if(_instance != null ) _instance.Close();
_instance = GetWindow<GuruServiceJsonBuilder>();
_instance.Show();
}
#endregion
#region Settings
private static string GetRelativeDir()
{
var guids = AssetDatabase.FindAssets(nameof(GuruServiceJsonBuilder));
if (guids.Length > 0)
{
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
var rpath = Directory.GetParent(path).FullName;
return rpath;
}
return Path.GetFullPath($"Assets/../Packages/Editor/GuruJsonBuilder");
}
private static Dictionary<string, string> LoadProjectSettingsCfg()
{
var cfgPath = $"{GetRelativeDir()}/{GuruProjectSettingsName}";
if (File.Exists(cfgPath))
{
var lines = File.ReadAllLines(cfgPath);
int len = lines?.Length ?? -1;
if (len > 0)
{
Dictionary<string, string> dict = new Dictionary<string, string>(lines.Length);
int i = 0;
string[] raw;
string line, key, value;
while (i < len)
{
line = lines[i];
if(string.IsNullOrEmpty(line)) continue;
raw = lines[i].Split(',');
value = "";
key = raw[0];
if(string.IsNullOrEmpty(key)) continue;
if(raw.Length > 1) value = raw[1];
dict[key] = value;
i++;
}
return dict;
}
}
return null;
}
#endregion
#region Window
private void Awake()
{
Debug.Log($"------- Awake -------");
this.titleContent = new GUIContent("Json Builder");
_projectNames = new List<string>(20);
string[] names = PublishLinks.Keys.ToArray();
string name = "";
for(int i = 0; i < names.Length; i++)
{
name = names[i];
if (name == "Default")
{
_projectNames.Insert(0, NoSelectionName);
}
else
{
_projectNames.Add(name);
}
}
_selectedProjectIndex = 0;
_state = STATE_IDLE;
var json = LoadProjectSettingsCfg();
Debug.Log(json);
}
private void OnEnable()
{
// Debug.Log($"------- OnEnable -------");
}
private void OnGUI()
{
GUI_Title();
switch (_state)
{
case STATE_IDLE:
GUI_Projects();
break;
case STATE_LOADING:
GUI_Loading();
break;
}
}
#endregion
#region GUI
private string _state = "";
private void GUI_Title()
{
var s = new GUIStyle("box")
{
fontSize = 20,
fontStyle = FontStyle.Bold,
stretchWidth = true,
alignment = TextAnchor.MiddleCenter,
padding = new RectOffset(4, 4, 4, 4),
};
GUILayout.Label("Guru-Service Json Builder", s, GUILayout.Height(60));
s.fontSize = 14;
GUILayout.Label($"Version: {Version}", s);
GUILayout.Space(10);
}
private void GUI_Loading()
{
var s = new GUIStyle("box")
{
fontSize = 16,
alignment = TextAnchor.MiddleCenter,
stretchWidth = true,
padding = new RectOffset(4, 4, 4, 4),
};
GUILayout.Label("Loading...", s);
GUILayout.Space(10);
}
private void GUI_Projects()
{
_selectedProjectIndex = EditorGUILayout.Popup("选择生成项目", _selectedProjectIndex, _projectNames.ToArray());
GUILayout.Space(5);
if (GUILayout.Button("生成 guru-services.json", GUILayout.Height(40)))
{
var id = _projectNames[_selectedProjectIndex];
if (id == NoSelectionName)
{
ShowDialog("选择错误", "请选择一个存在的项目");
}
else
{
DownloadTsvAndBuild(_selectedProjectIndex, (success, txt) =>
{
if (success)
{
ConvertFromTSV(txt);
}
else
{
ShowDialog("网络错误", txt);
}
_state = STATE_IDLE;
});
_state = STATE_LOADING;
}
}
}
private void ShowDialog(string title, string content, string okName = "OK", Action onOKCallback = null,
string cancelName = "", Action onCancelCallback = null)
{
if (EditorUtility.DisplayDialog(title, content, okName, cancelName))
{
onOKCallback?.Invoke();
}
else
{
onCancelCallback?.Invoke();
}
}
#endregion
#region Networking
private static string GetProjectTSVUrl(string pid)
{
if (PublishLinks.TryGetValue(pid, out var id))
{
string url = string.Format(TSVLink, id);
return url;
}
return "";
}
private void DownloadTsvAndBuild(int projIndex, Action<bool, string> loadCompleted = null)
{
var pid = _projectNames[projIndex];
var id = GetProjectTSVUrl(pid);
string title, msg;
if(string.IsNullOrEmpty(id))
{
title = "参数错误";
msg = $"项目 {pid} 不正确, 请重新选择...";
ShowDialog(title, msg);
Debug.LogError($"{title}\n{msg}");
return;
}
var www = UnityEngine.Networking.UnityWebRequest.Get(id);
www.SendWebRequest().completed += ap =>
{
if (www.result == UnityWebRequest.Result.Success)
{
Debug.Log($"<color=#088ff00>--- Load Success ---</color>");
// Debug.Log(www.downloadHandler.text);
loadCompleted?.Invoke(true, www.downloadHandler.text);
}
else
{
msg = $"Result {www.result}: {www.responseCode}\n\r{www.error}";
Debug.LogError(msg);
loadCompleted?.Invoke(false, msg);
}
};
}
#endregion
#region TEST
#if GURU_SDK_DEV
// [MenuItem("Tools/Test/Fetch Config File", false, 1)]
#endif
private static void Test_FetchConfigFile()
{
var pid = "FindOut";
var url = GetProjectTSVUrl(pid);
if(string.IsNullOrEmpty(url))
{
Debug.LogError($"Wrong ProjectId: {pid}");
return;
}
var www = UnityEngine.Networking.UnityWebRequest.Get(url);
www.SendWebRequest().completed += ap =>
{
if (www.result == UnityWebRequest.Result.Success)
{
Debug.Log($"<color=#088ff00>--- Load Success ---</color>");
Debug.Log(www.downloadHandler.text);
}
else
{
Debug.LogError($"Loading Failed: {www.error} : {www.result} : {www.responseCode}");
}
};
}
#endregion
}
}
#endif

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8c7dc826ea0848f0a3bf05009fab6377
timeCreated: 1706856053

View File

@ -1,22 +0,0 @@
Default
BallSort,2PACX-1vSPus7415l66-zScY1B1JOgLfSkm0yRPnRDG-BW0JgtnVZJziGDzvtBZr8D9oEZL2x478SdCS2yh0S_
BallSort2,2PACX-1vSZIZYbFuURK_ZMeMHV9ruL0SWBGMPA13er_J_DLRPVw5HBnU8_6c4mvek5UBFo1Ebbk63TMr-rsY6y
WaterSort,2PACX-1vTPxWbpP7KnT-e2xg9Uz-nukbLmqYc8SjwNL9MgycIhInNgmAjOmRnmyH6PWm3-hsEugKTJHKDcoKdI
WaterSort2,2PACX-1vQnZNtE7ZpT6eYagQDt686Be9Jr0tg22sRFg5cGiqFIsCVhWLu6jxDUg7qmyfIrX3iL5awat8FFnN7B
BallSortPlus,2PACX-1vTa324nQIwNmXNtHC5sZZXYBdpUryKXdb8rFbTAwBa4KVlQ5ZXnErx2IvifkRxD8qKcNupfO1Kv5pJw
TileBurst,2PACX-1vS5wvSZ5eSlK8TmYwVvgScJstaqN8cAB7Uxlq_nsbQwQ4VeD1BkhQuDbRdcO9ncuOCtoKjzSnviHVmc
Parking3D,2PACX-1vQY18hIrYjYNOqQNE12BcPtF4gtFMhBbfDrakBcIVQKoStmfPBY7C4O_Efj1Y8sNbytbKZr-0lPmBml
TileConnect,2PACX-1vRfSd9UbrLilele8Fw1BafFjlvHG5EMnODfbppPiUaacr7wZ62IvgM65SYSjGEXJmU9g9AczollaFCf
DOF,2PACX-1vSDpYZEUrCioCBRSkoZLra5nWk53Ks1f180TD1g2dnKcm-MZtAYvFSXDieAF4xromZCmxIoZuIfGyCJ
D2,2PACX-1vS1V35WTTPx2Jdeu6sVjPbaqFpjVwFu1Rn9tZkvxm8aEHbuRWibxt2pxPkLdDzwmCmrGBtU-PilABJm
FindOut,2PACX-1vQA3R66yWRHmUn6sneeIUU1qXEiaXv6h9QdzYzVRTEOg-yZf7WCJ6tuvXMWzwcOgGihiSnr9shMX__d
FindIt,2PACX-1vQs75JLsmTv1PESzOP6ANRqVk4zA3Y7PD5OV0yQbA5MM3wmp-hu4qE9gztZy7ETCP2nHJgDziwVpcFE
AP,2PACX-1vSvXTqbTcNCuHZYbKGA-fPKQj8XuixYUB9UXWqTQXz5QLXNurCtmBlziMbUxte_eqsGO0tB2GXe-sEC
Arrow,2PACX-1vS5w8rNcJycK-VYgum7gkrLAx2Ln_7wykXLBX4EulI8XNGnlPsVlENQ8LupDdtiIu-JlngJaTw5dIzR
ApFindDifference,2PACX-1vSmodKOvKjxmiNoYbrXFI6n1XC0aqvlnbYkEGyabLKqS6-C3Yi3nstmC89Hc31cdIzCk1FAl54beW2P
GoodsSort,2PACX-1vQrg6Ss2bNYAI7A250t3_zp10UJtbbQXb8I4LtDJ2Q3uopUG_PUadoJy7T4w0jlf4xcTqot2NWsfp1m
CookingBlitz,2PACX-1vShqtSSpwYQ8CvK-BVr-wQ5ygGeKKLdpaaWysSN_QumoDZYATemayAQIOdnFRzMP69nEwFYSx51oey8
WoodNuts,2PACX-1vQhtRg354eThBpWGCEk5f_2cLVbz1clQXv0n6w4Cyip0Knl6EQ4XwWMlcCec-legZdHU3E0-_cqKipc
MahjongSolitaire,2PACX-1vQpcaj8CpO__K1KGl-mg_940WOOIXBVzi0lmcjYTt1sqBI2PtK37s29McLTGU2I6N3fWM0ZepChedq7
NutsSort,2PACX-1vShdEGGEeMp51fMhg0UnMRHA51xfP8OkCdaD3gu-GTZCcJ6NvveP9Q0ahWDwSi1HS4Okyabn3gnLoLU
FindMaster,2PACX-1vRXB42qVcqE0nhWxPScs_qtaUkkVz_BXmdMiEZm580VC4a3I6Av8MrhOEhixVzg2NcPhQh_8HIHt7wk

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 88694fb6aaa64ea192e0bffb4cb5b744
timeCreated: 1719552889

View File

@ -10,15 +10,12 @@ namespace Guru.Editor
public class EditorGuruServiceIO
{
internal static readonly string SourceConfigFileName = "guru-service";
internal const string LocalServicesConfigPath = "Guru/Resources";
internal const string SourceConfigExtension = ".json";
internal const string LocalConfigExtension = ".txt";
private static readonly string DefaultFileName = "guru-service";
internal static string DefaultFilePath =
Path.GetFullPath(Path.Combine(Application.dataPath, $"{SourceConfigFileName}{SourceConfigExtension}"));
private static string DefaultFilePath =
Path.GetFullPath(Path.Combine(Application.dataPath, $"{DefaultFileName}.json"));
internal static string SourceServiceFilePath = "";
internal static string EmbededServiceFilePath = "";
/// <summary>
/// 加载配置
@ -26,18 +23,18 @@ namespace Guru.Editor
/// <returns></returns>
public static GuruServicesConfig LoadConfig()
{
var a = AssetDatabase.FindAssets($"*{SourceConfigFileName}* t:TextAsset", new []{"Assets"});
var a = AssetDatabase.FindAssets($"*{DefaultFileName}* t:TextAsset");
if (a == null || a.Length == 0)
{
UnityEngine.Debug.Log($"<color=orange>--- Can't find guru-services file</color>");
Debug.Log($"<color=orange>--- Can't ind guru-services file</color>");
}
else
{
var p = AssetDatabase.GUIDToAssetPath(a[0]);
var fp = Path.GetFullPath(p);
if (File.Exists(fp)) SourceServiceFilePath = fp;
if (File.Exists(fp)) EmbededServiceFilePath = fp;
var t = AssetDatabase.LoadAssetAtPath<TextAsset>(p);
// UnityEngine.Debug.Log($"<color=#88ff00>--- find services file:{p} \n{t.text}</color>");
// Debug.Log($"<color=#88ff00>--- find services file:{p} \n{t.text}</color>");
return JsonMapper.ToObject<GuruServicesConfig>(t.text);
}
return null;
@ -47,7 +44,7 @@ namespace Guru.Editor
/// 保存配置
/// </summary>
/// <param name="config"></param>
internal static void SaveConfig(GuruServicesConfig config = null, string savePath = "")
internal static void SaveConfig(GuruServicesConfig config = null)
{
if (config == null)
{
@ -59,13 +56,12 @@ namespace Guru.Editor
PrettyPrint = true,
};
JsonMapper.ToJson(config, jw);
var json = jw.ToString();
if (string.IsNullOrEmpty(savePath)) savePath = SourceServiceFilePath;
if (string.IsNullOrEmpty(savePath)) savePath = DefaultFilePath;
File.WriteAllText(savePath, json);
UnityEngine.Debug.Log($"Save config to {savePath}");
if (string.IsNullOrEmpty(EmbededServiceFilePath)) EmbededServiceFilePath = DefaultFilePath;
File.WriteAllText(EmbededServiceFilePath, json);
Debug.Log($"Save config to {EmbededServiceFilePath}");
}
/// <summary>
@ -80,7 +76,6 @@ namespace Guru.Editor
cfg.ad_settings = new GuruAdSettings();
cfg.adjust_settings = new GuruAdjustSettings();
cfg.fb_settings = new GuruFbSettings();
cfg.parameters = new GuruParameters();
return cfg;
}
@ -93,14 +88,14 @@ namespace Guru.Editor
}
public static void DeployLocalServiceFile()
public static void DeployAppServiceFile()
{
var deployPath = Path.Combine(Application.dataPath, LocalServicesConfigPath);
if(!Directory.Exists(deployPath)) Directory.CreateDirectory(deployPath);
var path = Path.Combine(deployPath, $"{GuruSDK.ServicesConfigKey}{LocalConfigExtension}");
var streamingPath = Application.streamingAssetsPath;
if(!Directory.Exists(streamingPath)) Directory.CreateDirectory(streamingPath);
var path = Path.Combine(streamingPath, $"{GuruSDK.ServicesConfigKey}.{GuruSDK.ServicesConfigExtension}");
var config = LoadConfig();
var from = SourceServiceFilePath;
var from = EmbededServiceFilePath;
if (string.IsNullOrEmpty(from) || !File.Exists(from)) // 文件不存在
{
return;
@ -109,7 +104,7 @@ namespace Guru.Editor
if (null != config)
{
if (File.Exists(path)) File.Delete(path);
UnityEngine.Debug.Log($"<color=#88ff00> --- setup {GuruSDK.ServicesConfigKey} to local resources.</color>");
Debug.Log($"<color=#88ff00> --- setup {GuruSDK.ServicesConfigKey} file on streamingPath</color>");
File.Copy(from, path);
}
}

View File

@ -1,31 +0,0 @@
allprojects {
buildscript {
repositories {**ARTIFACTORYREPOSITORY**
google()
jcenter()
mavenCentral()
}
dependencies {
// If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity
// See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html
// See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
// To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version
// classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:7.4.2'
**BUILD_SCRIPT_DEPS**
}
}
repositories {**ARTIFACTORYREPOSITORY**
google()
jcenter()
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 210888b84fe449e88319129ae097514d
timeCreated: 1722245010

View File

@ -1,7 +1,6 @@
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.caching=true
android.enableR8=**MINIFY_WITH_R_EIGHT**
unityStreamingAssets=**STREAMING_ASSETS**
# Android Resolver Properties Start
android.useAndroidX=true

View File

@ -5,8 +5,7 @@ dependencies {
}
android {
namespace "**NAMESPACE**"
compileSdkVersion **TARGETSDKVERSION**
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
@ -23,7 +22,6 @@ android {
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
multiDexEnabled true
}
aaptOptions {
@ -35,11 +33,6 @@ android {
abortOnError false
checkReleaseBuilds false
}
dexOptions {
incremental true
javaMaxHeapSize "8g"
}
buildTypes {
debug {

View File

@ -1,41 +0,0 @@
apply plugin: 'com.android.library'
**APPLY_PLUGINS**
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
**DEPS**}
android {
namespace "com.unity3d.player"
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
}**PACKAGING_OPTIONS**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<!-- For Facebook(Meta) -->
<!-- In the Audience Network Android SDK,
we use 127.0.0.1 (localhost) as a caching
proxy to cache media files in the SDK.
Since Android P, cleartext traffic (unencrypted HTTP)
will be blocked by default, which will affect the
functionality of media caching of the SDK and could
affect user experience and ads revenue. -->
<domain includeSubdomains="true">127.0.0.1</domain>
<!-- For Amazon -->
<domain includeSubdomains="true">amazon-adsystem.com</domain>
<!-- For Unity -->
<domain includeSubdomains="true">cdn-creatives-akamai-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-creatives-akamaistls-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-creatives-akamaistls-re-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-creatives-geocdn-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-creatives-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-creatives-highwinds-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-creatives-tencent-prd.unityads.unitychina.cn</domain>
<domain includeSubdomains="true">cdn-store-icons-akamai-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-store-icons-highwinds-prd.unityads.unity3d.com</domain>
<domain includeSubdomains="true">cdn-store-icons-tencent-prd.unityads.unitychina.cn</domain>
<domain includeSubdomains="true">cdn-creatives-akamaistls-prd.acquire.unity3dusercontent.com</domain>
<domain includeSubdomains="true">unityads.unity3d.com</domain>
<domain includeSubdomains="true">unityads.unitychina.cn</domain>
</domain-config>
<debug-overrides>
<trust-anchors>
<!-- Trust user added CAs while debuggable only -->
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e92a383bcfa548e2bdf0eeb8b0264205
timeCreated: 1630417512

View File

@ -1,134 +0,0 @@
-keep class com.unity3d.plugin.* { *; }
#proguard-adjust.pro
-keep public class com.adjust.sdk.** { *; }
-keep class com.amazon.device.ads.** { *; }
-keep class com.amazon.aps.** { *; }
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
java.lang.String getId();
boolean isLimitAdTrackingEnabled();
}
-keep public class com.android.installreferrer.** { *; }
-keepclassmembers class com.ironsource.sdk.controller.IronSourceWebView$JSInterface {
public *;
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keep public class com.google.android.gms.ads.** {
public *;
}
-keep class com.ironsource.adapters.** { *; }
-dontwarn com.ironsource.mediationsdk.**
-dontwarn com.ironsource.adapters.**
-keepattributes JavascriptInterface
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
-keep class dalvik.system.VMRuntime {
java.lang.String getRuntime();
}
-keep class android.os.Build {
java.lang.String[] SUPPORTED_ABIS;
java.lang.String CPU_ABI;
}
-keep class android.content.res.Configuration {
android.os.LocaleList getLocales();
java.util.Locale locale;
}
-keep class android.os.LocaledList {
java.util.Locale get(int);
}
#proguard-facebook.pro
-keep class com.facebook.** { *; }
-keepattributes Signature
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
-keep @com.facebook.common.internal.DoNotStrip class *
-keep class com.facebook.stetho.** { *; }
-keepclassmembers class * {
@com.facebook.common.internal.DoNotStrip *;
}
-keepclassmembers class * {
native <methods>;
}
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn com.android.volley.toolbox.**
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**
-keep class com.bytedance.sdk.** { *; }
-keep class com.pgl.sys.ces.* { *; }
-keep class com.facebook.** { *; }
-keep class com.google.android.play.core.** { *; }
-keep class com.google.games.bridge.** { *; }
-keep class com.google.android.gms.** { *; }
-keep class com.google.android.gms.games.leaderboard.** { *; }
-keep class com.google.android.gms.games.snapshot.** { *; }
-keep class com.google.android.gms.games.achievement.** { *; }
-keep class com.google.android.gms.games.event.** { *; }
-keep class com.google.android.gms.games.stats.** { *; }
-keep class com.google.android.gms.games.video.** { *; }
-keep class com.google.android.gms.games.* { *; }
-keep class com.google.android.gms.common.api.ResultCallback { *; }
-keep class com.google.android.gms.signin.** { *; }
-keep class com.google.android.gms.dynamic.** { *; }
-keep class com.google.android.gms.dynamite.** { *; }
-keep class com.google.android.gms.tasks.** { *; }
-keep class com.google.android.gms.security.** { *; }
-keep class com.google.android.gms.base.** { *; }
-keep class com.google.android.gms.actions.** { *; }
-keep class com.google.android.gms.common.ConnectionResult { *; }
-keep class com.google.android.gms.common.GooglePlayServicesUtil { *; }
-keep class com.google.android.gms.common.api.** { *; }
-keep class com.google.android.gms.common.data.DataBufferUtils { *; }
-keep class com.google.android.gms.games.quest.** { *; }
-keep class com.google.android.gms.nearby.** { *; }
-keep class com.google.android.gms.ads.** { *; }
-keep class com.pubmatic.sdk.** { *; }
-keep class com.applovin.** { *; }
-keep class com.chartboost.** { *; }
-keep class com.guru.** { *; }
-keep class guru.core.** { *; }
-keep class com.onevcat.uniwebview.* { *; }
-keep class com.iab.omid.* { *; }
-keep public class com.tradplus.** { *; }
-keep class com.tradplus.ads.** { *; }
-keep class com.applovin.mediation.adapters.** { *; }
-keep public class com.applovin.sdk.AppLovinSdk{ *; }
-keep public class com.applovin.sdk.AppLovin* {
public protected *;
}
-keep public class com.applovin.nativeAds.AppLovin* {
public protected *;
}
-keep public class com.applovin.adview.* {
public protected *;
}
-keep public class com.applovin.mediation.* {
public protected *;
}
-keep public class com.applovin.mediation.ads.* {
public protected *;
}
-keep public class com.applovin.impl.*.AppLovin {
public protected *;
}
-keep public class com.applovin.impl.**.*Impl {
public protected *;
}
-keepclassmembers class com.applovin.sdk.AppLovinSdkSettings {
private java.util.Map localSettings;
}
-keep class com.applovin.mediation.adapters.** { *; }
-keep class com.applovin.mediation.adapter.** { *; }
-keep class com.applovin.mediation.unity.** { *; }

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a08608d11bf7483fa4c49df80ebab657
timeCreated: 1722245439

View File

@ -1,60 +0,0 @@
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher', ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
google()
mavenCentral()
// Android Resolver Repos Start
def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/")
maven {
url "https://verve.jfrog.io/artifactory/verve-gradle-release" // Packages/com.guru.unity.max/Mediation/Verve/Editor/Dependencies.xml:7
}
maven {
url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Packages/com.google.firebase.firestore/Firebase/Editor/FirestoreDependencies.xml:20, Packages/com.google.firebase.app/Firebase/Editor/AppDependencies.xml:22, Packages/com.google.firebase.auth/Firebase/Editor/AuthDependencies.xml:20, Packages/com.google.firebase.messaging/Firebase/Editor/MessagingDependencies.xml:24, Packages/com.google.firebase.crashlytics/Firebase/Editor/CrashlyticsDependencies.xml:20, Packages/com.google.firebase.dynamic-links/Firebase/Editor/DynamicLinksDependencies.xml:20, Packages/com.google.firebase.installations/Firebase/Editor/InstallationsDependencies.xml:20, Packages/com.google.firebase.remote-config/Firebase/Editor/RemoteConfigDependencies.xml:20, Packages/com.google.firebase.analytics/Firebase/Editor/AnalyticsDependencies.xml:18
}
maven {
url "https://aws.oss.sonatype.org/content/repositories/releases/" // Packages/com.guru.unity.max/Amazon/Scripts/Editor/AmazonDependencies.xml:10
}
maven {
url "https://artifactory.bidmachine.io/bidmachine" // Packages/com.guru.unity.max/Mediation/BidMachine/Editor/Dependencies.xml:8
}
maven {
url "https://artifact.bytedance.com/repository/pangle" // Packages/com.guru.unity.max/Mediation/ByteDance/Editor/Dependencies.xml:8
}
maven {
url "https://cboost.jfrog.io/artifactory/chartboost-ads/" // Packages/com.guru.unity.max/Mediation/Chartboost/Editor/Dependencies.xml:8
}
maven {
url "https://android-sdk.is.com/" // Packages/com.guru.unity.max/Mediation/IronSource/Editor/Dependencies.xml:8
}
maven {
url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea" // Packages/com.guru.unity.max/Mediation/Mintegral/Editor/Dependencies.xml:8
}
maven {
url "https://maven.ogury.co" // Packages/com.guru.unity.max/Mediation/OguryPresage/Editor/Dependencies.xml:8
}
maven {
url "https://s3.amazonaws.com/smaato-sdk-releases/" // Packages/com.guru.unity.max/Mediation/Smaato/Editor/Dependencies.xml:8
}
maven {
url "https://repo.pubmatic.com/artifactory/public-repos" // Packages/com.guru.unity.max/OpenWrapSDK/Editor/OpenWrapSDKDependencies.xml:18, Packages/com.guru.unity.max/OpenWrapSDK/Mediation/AppLovinMAX/Editor/ALOpenWrapMediationDependencies.xml:7
}
mavenLocal()
// Android Resolver Repos End
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4f7213bbbc8741bd89b3bd17a7bea43e
timeCreated: 1722240718

View File

@ -1,57 +0,0 @@
namespace Guru.Editor
{
using System;
using System.IO;
using UnityEngine;
/// <summary>
/// Create androidlib assets
/// </summary>
public class AndroidLibHelper
{
private static readonly string PluginsRoot = "Plugins/Android";
private static readonly string Extends = "androidlib";
private static readonly string ProjectPropertiesName = "project.properties";
private static readonly string ProjectPropertiesContent= "target=android-9\nandroid.library=true";
private static readonly string AndroidManifestName = "AndroidManifest.xml";
private static readonly string AndroidManifestContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"{0}\"\n android:versionCode=\"1\"\n android:versionName=\"1.0\">\n</manifest>";
public static bool IsEmbeddedAndroidLibExists(string fileName)
{
string dir = Path.GetFullPath($"{Application.dataPath}/{PluginsRoot}/{fileName}.{Extends}");
return Directory.Exists(dir);
}
public static string CreateLibRoot(string packageName, string fileName = "")
{
if (string.IsNullOrEmpty(packageName)) return "";
if(string.IsNullOrEmpty(fileName)) fileName = packageName;
string dir = Path.GetFullPath($"{Application.dataPath}/{PluginsRoot}/{fileName}.{Extends}");
if (Directory.Exists(dir))
{
return dir;
}
Directory.CreateDirectory(dir);
string path = "";
string content = "";
//------ Create project.properties ------
content = ProjectPropertiesContent;
path = $"{dir}/{ProjectPropertiesName}";
File.WriteAllText(path, content);
// ------ Create AndroidManifest.xml ------
content = AndroidManifestContent.Replace("{0}", packageName);
path = $"{dir}/{AndroidManifestName}";
File.WriteAllText(path, content);
return dir;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ee3d47ab5d544373ae6a43fdd36d96fb
timeCreated: 1711761343

View File

@ -1,389 +0,0 @@
namespace Guru.Editor
{
using System.Xml;
using System.IO;
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// Android 配置修改器
/// </summary>
public class AndroidManifestDoc
{
private const string TargetPath = "Plugins/Android/AndroidManifest.xml";
private const string XmlnsAndroid = "xmlns:android";
private const string NamespaceAndroid = "http://schemas.android.com/apk/res/android";
private const string XmlnsTools= "xmlns:tools";
private const string NamespaceTools = "http://schemas.android.com/tools";
private const string UserPermission = "uses-permission";
private const string MetaData = "meta-data";
private const string KName = "name";
private XmlDocument _doc;
public XmlDocument Doc => _doc;
private string _docPath;
private bool _isReady = false;
private XmlElement _manifestNode;
private XmlElement _applicationNode;
private XmlElement _queriesNode;
#region Initiallize
/// <summary>
/// 加载文件
/// </summary>
/// <param name="docPath"></param>
/// <returns></returns>
public static AndroidManifestDoc Load(string docPath = "")
{
if (string.IsNullOrEmpty(docPath))
{
docPath = Path.GetFullPath(Path.Combine(Application.dataPath, TargetPath));
}
if (!File.Exists(docPath))
{
Debug.LogError($"--- File not found: {docPath}");
return null;
}
var mod = new AndroidManifestDoc();
mod.ReadFromPath(docPath);
return mod;
}
public static AndroidManifestDoc Read(string xmlStr, string docPath = "")
{
var mod = new AndroidManifestDoc();
mod.ReadFromXml(xmlStr, docPath);
return mod;
}
/// <summary>
/// 从文件路径读取
/// </summary>
/// <param name="docPath"></param>
public void ReadFromPath(string docPath)
{
_isReady = false;
if (File.Exists(docPath))
{
var xmlStr = File.ReadAllText(docPath);
ReadFromXml(xmlStr, docPath);
}
else
{
Debug.LogError($"--- File not found: {docPath}");
}
}
public void ReadFromXml(string xmlStr, string docPath = "")
{
_doc = new XmlDocument();
_doc.LoadXml(xmlStr);
if(!string.IsNullOrEmpty(docPath)) _docPath = docPath;
Init();
}
/// <summary>
/// Initializes the Doc
/// </summary>
private void Init()
{
// --- Root Nodes ---
_manifestNode = _doc.SelectSingleNode("manifest") as XmlElement;
_applicationNode = _doc.SelectSingleNode("manifest/application") as XmlElement;
_queriesNode = _doc.SelectSingleNode("manifest/queries") as XmlElement;
AddXmlnsAndroid();
AddXmlnsTools();
_isReady = true;
}
/// <summary>
/// Save Doc
/// </summary>
public void Save(string docPath = "")
{
if (_isReady)
{
if (!string.IsNullOrEmpty(docPath)) _docPath = docPath;
if (!string.IsNullOrEmpty(_docPath))
{
var dir = Directory.GetParent(_docPath);
if(!dir.Exists) dir.Create();
_doc.Save(_docPath);
}
}
}
#endregion
#region Node Opreation
public static bool AddAttribute(XmlElement node, string key, string value)
{
if (node != null)
{
if (node.HasAttribute(key))
{
node.Attributes[key].Value = value;
}
else
{
node.SetAttribute(key, value);
}
return true;
}
return false;
}
#endregion
#region API
public bool AddXmlnsAndroid()
{
return AddAttribute(_manifestNode, XmlnsAndroid, NamespaceAndroid);
}
public bool AddXmlnsTools()
{
return AddAttribute(_manifestNode, XmlnsTools, NamespaceTools);
}
/// <summary>
/// Add Replace Item
/// </summary>
/// <param name="item"></param>
public void AddApplicationReplaceItem(string item)
{
if (_applicationNode != null)
{
List<string> items = new List<string>(5);
if (_applicationNode.HasAttribute("replace", NamespaceTools))
{
var arr = _applicationNode.GetAttribute("replace",NamespaceTools).Split(',');
if(arr != null && arr.Length > 0)
{
items.AddRange(arr);
}
}
if (!items.Contains(item)) items.Add(item);
_applicationNode.SetAttribute("replace", NamespaceTools, string.Join(",", items));
}
}
public void SetApplicationAttribute(string key, string value)
{
if (_applicationNode != null)
{
_applicationNode.SetAttribute(key, NamespaceAndroid, value);
}
}
/// <summary>
/// Set metadata
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="valueName"></param>
/// <param name="keyName"></param>
public void SetMetadata(string key, string value, string valueName = "value", string keyName = KName)
{
if (_doc == null || !_isReady) return;
XmlElement node = null;
if (!TryGetMetadata(key, out node, keyName))
{
node = _doc.CreateElement(MetaData);
_applicationNode?.AppendChild(node);
}
node.SetAttribute(keyName, NamespaceAndroid, key);
node.SetAttribute(valueName, NamespaceAndroid, value);
}
/// <summary>
/// 添加权限
/// </summary>
/// <param name="key"></param>
/// <param name="keyName"></param>
public void AddPermission(string key, string keyName = KName)
{
if (_doc == null || !_isReady) return;
XmlElement node = null;
if(!TryGetPermission(key, out node, keyName))
{
node = _doc.CreateElement(UserPermission);
_manifestNode?.AppendChild(node);
}
node.SetAttribute(keyName, NamespaceAndroid, key);
}
/// <summary>
/// 删除 Permission
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="keyName"></param>
/// <param name="valueName"></param>
public void RemovePermission(string key, string value = "remove", string keyName = "name", string valueName = "node")
{
if (_doc == null || !_isReady) return;
XmlElement node = null;
if(!TryGetPermission(key, out node, keyName))
{
node = _doc.CreateElement(UserPermission);
_manifestNode?.AppendChild(node);
}
node.SetAttribute(keyName, NamespaceAndroid, key);
node.SetAttribute(valueName, NamespaceTools, value);
}
public bool SetPackageName(string packageName)
{
if (_manifestNode != null)
{
_manifestNode.Attributes["package"].Value = packageName;
return true;
}
return false;
}
/// <summary>
/// 添加 Queries Intent
/// </summary>
/// <param name="value"></param>
/// <param name="keyName"></param>
/// <returns></returns>
public void AddQueriesIntent(string value, string keyName = "name")
{
if (_queriesNode == null)
{
_queriesNode = _doc.CreateElement("queries");
_manifestNode?.AppendChild(_queriesNode);
}
var intentList = _queriesNode.SelectNodes("intent");
if (intentList != null)
{
foreach (XmlElement intent in intentList)
{
var action = intent?.SelectSingleNode("action") as XmlElement;
if (action != null
&& action.GetAttribute(keyName, NamespaceAndroid) == value)
{
return; // Has injectedskip ...
}
}
}
// Inject new intent node
XmlElement intentNode = _doc.CreateElement("intent");
_queriesNode.AppendChild(intentNode);
XmlElement actionNode = _doc.CreateElement("action");
intentNode.AppendChild(actionNode);
actionNode.SetAttribute(keyName, NamespaceAndroid, value);
}
#endregion
#region Data Opration
public bool TryGetMetadata(string name, out XmlElement node, string keyName = KName)
{
node = null;
if(_applicationNode != null)
{
var list = _applicationNode.SelectNodes(MetaData);
if (list != null)
{
foreach (XmlElement e in list)
{
if (e.GetAttribute(keyName, NamespaceAndroid) == name)
{
node = e;
return true;
}
}
}
}
return false;
}
public bool TryGetPermission(string name, out XmlElement node, string keyName = KName)
{
node = null;
if(_manifestNode != null)
{
var list = _manifestNode.SelectNodes(UserPermission);
if (list != null)
{
foreach (XmlElement e in list)
{
if (e.GetAttribute(keyName, NamespaceAndroid) == name)
{
node = e;
return true;
}
}
}
}
return false;
}
public bool TryRootNode(string nodeName, out XmlElement node)
{
node = null;
if (_doc != null)
{
node = _doc.SelectSingleNode(nodeName) as XmlElement;
return node != null;
}
return false;
}
#endregion
#region Output
public override string ToString()
{
if (_doc != null) return _doc.InnerXml;
return this.ToString();
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 89a1c7f77fcf4982adbbaf6dc61bd62d
timeCreated: 1711505949

View File

@ -1,3 +1,5 @@
using System.Collections;
using Unity.EditorCoroutines.Editor;
namespace Guru.Editor
{
@ -14,35 +16,6 @@ namespace Guru.Editor
private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION";
private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING";
private const string PermissionReadPostNotifications = "android.permission.POST_NOTIFICATIONS";
private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE";
private const string PermissionAccessCoarseLocation = "android.permission.ACCESS_COARSE_LOCATION";
private const string PermissionAccessFineLocation = "android.permission.ACCESS_FINE_LOCATION";
private const string PermissionReadExternalStorage = "android.permission.READ_EXTERNAL_STORAGE";
private const string PermissionReadLogs = "android.permission.READ_LOGS";
private const string NetworkSecurityConfig = "networkSecurityConfig";
private const string NetworkSecurityConfigValue = "@xml/network_security_config";
private const string PermissionAdjustReadPermission = "com.adjust.preinstall.READ_PERMISSION"; // Adjust permission
private const string AdjustQueriesActionValue = "com.attribution.REFERRAL_PROVIDER"; // Adjust action
// Add Permissions
private static string[] addPermissions = new[]
{
PermissionReadPostNotifications,
PermissionAdjustReadPermission,
};
// Remove Permissions
private static string[] removePermissions = new[]
{
PermissionReadPhoneState,
PermissionAccessCoarseLocation,
PermissionAccessFineLocation,
PermissionReadExternalStorage,
PermissionReadLogs,
};
private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath);
public static bool IsManifestExist() => File.Exists(TargetFullPath);
@ -52,65 +25,80 @@ namespace Guru.Editor
if (!IsManifestExist())
{
CopyManifest();
return;
}
FixAndroidManifest();
var doc = new XmlDocument();
doc.Load(TargetFullPath);
var rootNode = doc.SelectSingleNode("manifest/application");
int item1 = 0;
int item2 = 0;
XmlNodeList metadatas = rootNode.SelectNodes("meta-data");
if (metadatas != null && metadatas.Count > 0)
{
bool isDirty = false;
foreach (XmlElement e in metadatas)
{
if (e != null)
{
if (e.HasAttribute("android:name"))
{
if (e.Attributes["android:name"].Value == ValOptimizeInitialization) item1 = 1;
if (e.Attributes["android:name"].Value == ValOptimizeAdLoading) item2 = 1;
}
}
}
}
string androidSP = "http://schemas.android.com/apk/res/android";
if (item1 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",androidSP, ValOptimizeInitialization);
e.SetAttribute("value",androidSP, "true");
rootNode.AppendChild(e);
}
if (item2 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",androidSP,ValOptimizeAdLoading);
e.SetAttribute("value",androidSP, "true");
rootNode.AppendChild(e);
}
var rootE = doc.SelectSingleNode("manifest") as XmlElement;
if (rootE != null)
{
rootE.Attributes["package"].Value = PlayerSettings.applicationIdentifier; // 写入包名
}
doc.Save(TargetFullPath);
}
/// <summary>
/// Fix Android Manifest
/// </summary>
private static void FixAndroidManifest()
{
var doc = AndroidManifestDoc.Load(TargetFullPath);
// --- network_security_config ---
doc.SetApplicationAttribute(NetworkSecurityConfig, NetworkSecurityConfigValue);
doc.AddApplicationReplaceItem($"android:{NetworkSecurityConfig}");
// ---- Metadata ---
doc.SetMetadata(ValOptimizeInitialization, "true");
doc.SetMetadata(ValOptimizeAdLoading, "true");
// ---- Permission ---
// Add needed permissions
foreach (var p in addPermissions)
{
doc.AddPermission(p);
}
// Remove sensitive permissions
foreach (var p in removePermissions)
{
doc.RemovePermission(p);
}
// --- Bundle Id ---
doc.SetPackageName(PlayerSettings.applicationIdentifier);
// --- Adjust Preinstall (Content provider) ---
doc.AddQueriesIntent(AdjustQueriesActionValue);
doc.Save();
}
/// <summary>
/// 拷贝 AndroidManifest
/// </summary>
private static void CopyManifest()
{
if (File.Exists(TargetFullPath)) return;
var path = GuruEditorHelper.GetAssetPath(nameof(AndroidManifestMod), "Script", true);
var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidManifestMod)}.cs t:Script");
if (!string.IsNullOrEmpty(path))
{
var from = Path.GetFullPath($"{path}/../../Files/AndroidManifest.txt");
var files = Path.GetFullPath($"{path}/../Files");
var from = $"{files}/AndroidManifest.txt";
if (File.Exists(from))
{
File.Copy(from, TargetFullPath);
}
}
}
#region Testing
[Test]

View File

@ -1,178 +0,0 @@
using System.Collections;
using Unity.EditorCoroutines.Editor;
namespace Guru.Editor
{
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using System.Xml;
public static class AndroidManifestMod
{
private const string TargetPath = "Plugins/Android/AndroidManifest.xml";
private const string ValOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION";
private const string ValOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING";
private const string NamespaceAndroid = "http://schemas.android.com/apk/res/android";
private const string NamespaceTools = "http://schemas.android.com/tools";
private const string PermissionReadPhoneState = "android.permission.READ_PHONE_STATE";
private static string TargetFullPath = Path.Combine(Application.dataPath, TargetPath);
public static bool IsManifestExist() => File.Exists(TargetFullPath);
public static void Apply()
{
if (!IsManifestExist())
{
CopyManifest();
}
var doc = new XmlDocument();
doc.Load(TargetFullPath);
SetApplicationMod(doc);
SetPermissionMod(doc);
doc.Save(TargetFullPath);
}
/// <summary>
/// Fix all Elements in <Applicaiton>
/// </summary>
/// <param name="doc"></param>
private static void SetApplicationMod(XmlDocument doc)
{
string rootName = "manifest/application";
var rootNode = doc.SelectSingleNode(rootName);
int item1 = 0;
int item2 = 0;
if (rootNode == null)
{
Debug.LogError($"Can't find root with name {rootName} ...");
return;
}
XmlNodeList metadatas = rootNode.SelectNodes("meta-data");
if (metadatas != null && metadatas.Count > 0)
{
bool isDirty = false;
foreach (XmlElement e in metadatas)
{
if (e != null)
{
if (e.HasAttribute("name", NamespaceAndroid))
{
if (e.Attributes["android:name"].Value == ValOptimizeInitialization) item1 = 1;
if (e.Attributes["android:name"].Value == ValOptimizeAdLoading) item2 = 1;
}
}
}
}
if (item1 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",NamespaceAndroid, ValOptimizeInitialization);
e.SetAttribute("value",NamespaceAndroid, "true");
rootNode.AppendChild(e);
}
if (item2 == 0)
{
var e = doc.CreateElement("meta-data");
e.SetAttribute("name",NamespaceAndroid,ValOptimizeAdLoading);
e.SetAttribute("value",NamespaceAndroid, "true");
rootNode.AppendChild(e);
}
var rootE = doc.SelectSingleNode("manifest") as XmlElement;
if (rootE != null)
{
rootE.Attributes["package"].Value = PlayerSettings.applicationIdentifier; // 写入包名
}
}
/// <summary>
/// Fix all permissions
/// </summary>
/// <param name="doc"></param>
private static void SetPermissionMod(XmlDocument doc)
{
string attName = "uses-permission";
string rootName = "manifest";
bool isBuild = false;
var rootNode = doc.SelectSingleNode(rootName);
XmlElement item1 = null;
if (rootNode == null)
{
Debug.LogError($"Can't find root with name {rootName} ...");
return;
}
XmlNodeList permissions = rootNode.SelectNodes(attName);
if (permissions != null && permissions.Count > 0)
{
foreach (XmlElement e in permissions)
{
if (e != null)
{
if (e.HasAttribute("android:name"))
{
if (e.Attributes["android:name"].Value == PermissionReadPhoneState) item1 = e;
}
}
}
}
isBuild = false;
if (item1 == null)
{
isBuild = true;
item1 = doc.CreateElement(attName);
}
item1.SetAttribute("name",NamespaceAndroid, PermissionReadPhoneState);
item1.SetAttribute("node",NamespaceTools, "remove");
if (isBuild) rootNode.AppendChild(item1);
}
private static void CopyManifest()
{
if (File.Exists(TargetFullPath)) return;
var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidManifestMod)} t:Script");
if (!string.IsNullOrEmpty(path))
{
var from = Path.GetFullPath($"{path}/../../Files/AndroidManifest.txt");
if (File.Exists(from))
{
File.Copy(from, TargetFullPath);
}
}
}
#region Testing
[Test]
public static void Test_Injection()
{
Apply();
}
#endregion
}
}

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: f4547abfcddf84bc6b61e884ebfb30e2
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,4 +1,4 @@
using System.Linq;
namespace Guru.Editor
{
@ -6,46 +6,29 @@ namespace Guru.Editor
using UnityEngine;
using System;
using System.IO;
using System.Linq;
public class AndroidProjectMod
{
private const int TargetSDKVersion = 34;
private const string K_ANDROID_PLUGINS_NAME = "Plugins/Android";
private static readonly int TargetSDKVersion = 33;
private const string LauncherName = "launcherTemplate";
private static readonly string LauncherFullPath = Path.Combine(Application.dataPath, $"{K_ANDROID_PLUGINS_NAME}/{LauncherName}.gradle");
private static readonly string LauncherName = "launcherTemplate";
private static string LauncherFullPath = Path.Combine(Application.dataPath, $"Plugins/Android/{LauncherName}.gradle");
private const string MainName = "mainTemplate";
private static readonly string MainFullPath = Path.Combine(Application.dataPath, $"{K_ANDROID_PLUGINS_NAME}/{MainName}.gradle");
private static readonly string MainName = "mainTemplate";
private static string MainFullPath = Path.Combine(Application.dataPath, $"Plugins/Android/{MainName}.gradle");
private const string BaseProjectName = "baseProjectTemplate";
private static readonly string BaseProjectFullPath = Path.Combine(Application.dataPath, $"{K_ANDROID_PLUGINS_NAME}/{BaseProjectName}.gradle");
private const string PropertiesName = "gradleTemplate";
private const string K_ENABLE_R8 = "android.enableR8";
private static readonly string PropertiesFullPath = Path.Combine(Application.dataPath, $"{K_ANDROID_PLUGINS_NAME}/{PropertiesName}.properties");
private const string SettingsName = "settingsTemplate";
private static readonly string SettingsFullPath = Path.Combine(Application.dataPath, $"Plugins/Android/{SettingsName}.gradle");
private const string K_LINE_UNITY_PROJECT = "def unityProjectPath";
private const string ProguardUserName = "proguard-user";
private static readonly string ProguardUserFullPath = Path.Combine(Application.dataPath, $"{K_ANDROID_PLUGINS_NAME}/{ProguardUserName}.txt");
private static readonly string PropertiesName = "gradleTemplate";
private static string PropertiesFullPath = Path.Combine(Application.dataPath, $"Plugins/Android/{PropertiesName}.properties");
public static void Apply()
{
ApplyLauncher();
ApplyBaseProjectTemplates();
ApplyMainTemplates();
ApplyGradleTemplate();
ApplySettings();
ApplyProguardUser();
CheckTargetSDKVersion(); // 强制修复构建版本号
FixLauncher();
FixMain();
FixProperties();
CheckTargetSDKVersion();
}
private static void ApplyLauncher()
private static void FixLauncher()
{
if (!File.Exists(LauncherFullPath))
{
@ -58,9 +41,10 @@ namespace Guru.Editor
var ptn2 = "abortOnError false";
var lines = File.ReadAllLines(LauncherFullPath);
string line = "";
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
line = lines[i];
if (line.Contains(ptn1))
{
lines[i] = line.Replace(ptn1, "\n\n\tpackagingOptions {\n\t\texclude(\"META-INF/*.kotlin_module\")\n\t}\n\n");
@ -78,7 +62,7 @@ namespace Guru.Editor
File.WriteAllLines(LauncherFullPath, lines);
}
private static void ApplyMainTemplates()
private static void FixMain()
{
if (!File.Exists(MainFullPath))
{
@ -86,100 +70,13 @@ namespace Guru.Editor
CopyFile($"{MainName}.txt", MainFullPath);
}
}
private static void ApplyBaseProjectTemplates()
{
if (!File.Exists(BaseProjectFullPath))
{
Debug.Log($"[MOD] --- Copy file to: {BaseProjectFullPath}");
CopyFile($"{BaseProjectName}.txt", BaseProjectFullPath);
}
}
private static void ApplyGradleTemplate()
private static void FixProperties()
{
if (!File.Exists(PropertiesFullPath))
{
Debug.Log($"[MOD] --- Copy file to: {PropertiesFullPath}");
CopyFile($"{PropertiesName}.txt", PropertiesFullPath);
}
if (TargetSDKVersion > 33)
{
FixGradleTemplate(PropertiesFullPath);
}
}
/// <summary>
/// 该版本中不再使用 R8
/// </summary>
/// <param name="filePath"></param>
private static void FixGradleTemplate(string filePath)
{
if (File.Exists(filePath))
{
bool isDirty = false;
var lines = File.ReadAllLines(filePath);
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(K_ENABLE_R8))
{
lines[i] = $"# {lines[i]}"; // 禁用R8
isDirty = true;
break;
}
}
if (isDirty) File.WriteAllLines(filePath, lines);
}
}
/// <summary>
/// 写入 settings.gradle 配置文件
/// </summary>
private static void ApplySettings()
{
if (!File.Exists(SettingsFullPath))
{
CopyFile($"{SettingsName}.txt", SettingsFullPath);
}
FixProjectPathInSettings(SettingsFullPath);
}
private static void FixProjectPathInSettings(string settingsPath)
{
bool isDirty = false;
if (File.Exists(settingsPath))
{
string projectPath = Path.GetFullPath($"{Application.dataPath}/../").Replace("\\", "/");
var lines = File.ReadAllLines(settingsPath);
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(K_LINE_UNITY_PROJECT))
{
lines[i] = $" def unityProjectPath = $/file:////{projectPath}/$.replace(\"\\\\\", \"/\")";
isDirty = true;
break;
}
}
if(isDirty)
File.WriteAllLines(settingsPath, lines);
}
}
/// <summary>
/// 写入所有的配置文件
/// </summary>
private static void ApplyProguardUser()
{
if (!File.Exists(ProguardUserFullPath))
{
CopyFile($"{ProguardUserName}.txt", ProguardUserFullPath);
}
}
private static void CheckTargetSDKVersion()
@ -196,8 +93,8 @@ namespace Guru.Editor
private static string GetMoveFilePath(string fileName)
{
var path = GuruEditorHelper.GetAssetPath(nameof(AndroidProjectMod), "Script", true);
var files = Path.GetFullPath($"{path}/../../Files");
var path = GuruEditorHelper.GetFilePath($"{nameof(AndroidProjectMod)}.cs t:Script");
var files = Path.GetFullPath($"{path}/../Files");
return $"{files}/{fileName}";
}
private static void CopyFile(string fileName, string toPath)

View File

@ -1,159 +0,0 @@
using System.Net;
using UnityEditor;
namespace Guru.Editor
{
using UnityEngine;
using System.IO;
public class AndroidPushIconHelper
{
public static readonly int targetWidth = 96; // 目标宽度
public static readonly int targetHeight = 96;
private static readonly string LibName = "SDKRes";
private static readonly string PackageName = "com.guru.unity.res";
private static readonly string IconName = "ic_notification.png";
private static readonly string ColorContent =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorAccent\">#{0}</color>\n</resources>";
private static readonly string ValueContent =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"default_notification_channel_id\" translatable=\"false\">{0}</string>\n</resources>";
private static readonly string[] iconNames = new string[]
{
"drawable-mdpi",
"drawable-hdpi",
"drawable-xhdpi",
"drawable-xxhdpi",
"drawable-xxxhdpi"
};
private static string FcmChannelId = "fcm_default_channel";
/// <summary>
/// 设置推送图标
/// </summary>
/// <param name="source"></param>
/// <param name="color"></param>
public static bool SetPushIconAssets(Texture2D source, Color color = default(Color))
{
if (source == null)
{
Debug.LogError($"=== No Texture2D found ===");
return false;
}
return DeployAllIcons(source, color);
}
private static Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight)
{
MakeTextureReadable(source);
// Texture2D result = new Texture2D(newWidth, newHeight);
// Color[] newColors = new Color[newWidth * newHeight];
//
// for (int y = 0; y < newHeight; y++)
// {
// for (int x = 0; x < newWidth; x++)
// {
// // 应用一些缩放逻辑来获取新的颜色值
// newColors[x + y * newWidth] = source.GetPixelBilinear((float)x / newWidth * source.width, (float)y / newHeight * source.height);
// }
// }
//
// result.SetPixels(newColors);
// result.Apply();
// return result;
RenderTexture rt=new RenderTexture(newWidth, newHeight,24);
RenderTexture.active = rt;
Graphics.Blit(source,rt);
Texture2D result=new Texture2D(newWidth,newHeight);
result.ReadPixels(new Rect(0,0,newWidth,newHeight),0,0);
result.Apply();
return result;
}
private static void MakeTextureReadable(Texture2D source)
{
if (source.isReadable) return;
var path = AssetDatabase.GetAssetPath(source);
TextureImporter ti = (TextureImporter)AssetImporter.GetAtPath(path);
if (!ti.isReadable)
{
ti.isReadable = true;
ti.SaveAndReimport();
}
}
private static string ColorToHex(Color color)
{
return string.Format("{0:X2}{1:X2}{2:X2}", (int)(color.r * 255), (int)(color.g * 255), (int)(color.b * 255));
}
private static bool DeployAllIcons(Texture2D source, Color color)
{
var dir = AndroidLibHelper.CreateLibRoot(PackageName, LibName);
string path = "";
string content = "";
var result = ResizeTexture(source, targetWidth, targetHeight);
byte[] bytes = result.EncodeToPNG();
var resPath = $"{dir}/res";
if (!Directory.Exists(resPath))
{
Directory.CreateDirectory(resPath);
}
File.WriteAllBytes($"{resPath}/{IconName}", bytes); // Base Icon;
// ----- Build all Icons ------
foreach (var iconName in iconNames)
{
var iconPath = $"{resPath}/{iconName}";
if (!Directory.Exists(iconPath))
{
Directory.CreateDirectory(iconPath);
}
File.WriteAllBytes($"{iconPath}/{IconName}", bytes);
}
var valuesPath = $"{resPath}/values";
if (!Directory.Exists(valuesPath)) Directory.CreateDirectory(valuesPath);
// ----- Build colors.xml ------
path = $"{valuesPath}/colors.xml";
content = ColorContent.Replace("{0}", ColorToHex(color));
File.WriteAllText(path, content);
// ----- Build strings.xml ------
path = $"{valuesPath}/strings.xml";
content = ValueContent.Replace("{0}", FcmChannelId);
File.WriteAllText(path, content);
// ----- Inject AndroidManifest.xml ------
var doc = AndroidManifestDoc.Load();
if (doc != null)
{
doc.SetMetadata("com.google.firebase.messaging.default_notification_icon", "@drawable/ic_notification", valueName:"resource");
doc.SetMetadata("com.google.firebase.messaging.default_notification_color", "@color/colorAccent", valueName:"resource");
doc.SetMetadata("com.google.firebase.messaging.default_notification_channel_id", "@string/default_notification_channel_id");
doc.Save();
Debug.Log("<color=#88ff00> --- Push Icon Build Success --- </color>");
AssetDatabase.Refresh();
return true;
}
Debug.LogError("AndroidManifest.xml not found ...");
return false;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 39d65f42b9694641b0c3509262aca1d4
timeCreated: 1711724404

View File

@ -1,51 +0,0 @@
namespace Guru.Editor
{
using System.IO;
using UnityEditor;
using UnityEngine;
public class AndroidResMod
{
private static readonly string NetworkSecurityXmlName = "network_security_config.xml";
private static readonly string LibNetworkSecurity = "GuruNetworkSecurity";
private static readonly string LibNetworkSecurityPackageName = "com.guru.unity.sdk.android.res.network.security";
private static string OldSecurityXml = Path.Combine(Application.dataPath, $"Plugins/Android/res/xml/{NetworkSecurityXmlName}");
/// <summary>
/// 应用补丁
/// </summary>
public static void Apply()
{
DeployNetworkSecurity();
}
/// <summary>
/// 部署网络安全配置
/// </summary>
private static void DeployNetworkSecurity()
{
if(File.Exists(OldSecurityXml)) File.Delete(OldSecurityXml); // 清理旧文件
if (!AndroidLibHelper.IsEmbeddedAndroidLibExists(LibNetworkSecurity))
{
string dir = AndroidLibHelper.CreateLibRoot(LibNetworkSecurityPackageName, LibNetworkSecurity);
var d = GuruEditorHelper.GetAssetPath(nameof(AndroidResMod), "Script", true);
if (!string.IsNullOrEmpty(d))
{
var from = $"{Directory.GetParent(d)?.FullName ?? ""}/../Files/{NetworkSecurityXmlName}";
if (File.Exists(from))
{
string toDir = $"{dir}/res/xml";
if(!Directory.Exists(toDir))Directory.CreateDirectory(toDir);
string to = $"{toDir}/{NetworkSecurityXmlName}";
FileUtil.CopyFileOrDirectory(from, to);
}
}
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: c06b1730aac144929ca73fba2e481508
timeCreated: 1706058636

View File

@ -1,46 +1,17 @@
namespace Guru.Editor
{
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
public class GuruEditorHelper
{
public static string GetAssetPath(string filter, bool useFullPath = false)
public static string GetFilePath(string filter)
{
var guids = AssetDatabase.FindAssets(filter);
string path = "";
string fullPath = "";
if (guids != null && guids.Length > 0)
{
path = AssetDatabase.GUIDToAssetPath(guids[0]);
fullPath = path.Replace("Assets", Application.dataPath);
if (File.Exists(fullPath))
{
return useFullPath? fullPath : path;
}
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
return path;
}
return "";
}
public static string GetAssetPath(string fileName, string typeName = "", bool useFullPath = false)
{
var filter = fileName;
if(!string.IsNullOrEmpty(typeName)) filter = $"{fileName} t:{typeName}";
return GetAssetPath(filter, useFullPath);
}
public static void OpenPath(string path)
{
#if UNITY_EDITOR_OSX
EditorUtility.RevealInFinder(path);
return;
#endif
Application.OpenURL($"file://{path}");
}
}
}

View File

@ -37,7 +37,7 @@ namespace Guru.Editor
{
static BoostOnLoad()
{
EditorGuruServiceIO.DeployLocalServiceFile();
EditorGuruServiceIO.DeployAppServiceFile();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 7f9d1111a9d94187bf583ae71d9192f0
timeCreated: 1711858346

View File

@ -1,116 +0,0 @@
namespace Guru.Editor
{
using System;
using System.Collections.Generic;
using System.IO;
public class EaseConfigFile
{
private Dictionary<string, string> _dataDict;
private string _filePath;
protected bool ReadFile(string path)
{
_filePath = path;
if (File.Exists(path))
{
var lines = File.ReadAllLines(path);
int len = lines.Length;
_dataDict = new Dictionary<string, string>(len);
string key = "";
string value = "";
for (int i=0; i< len; i++)
{
var line = lines[i];
if (line.Contains("="))
{
key = "";
value = "";
var kv = line.Split('=');
if(kv.Length > 0) key = kv[0];
if(kv.Length > 1) value = kv[1];
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
{
_dataDict[key] = value;
}
}
}
return true;
}
_dataDict = new Dictionary<string, string>(10);
var dir = Directory.GetParent(path);
if(dir is { Exists: false }) dir.Create();
return false;
}
public void Save()
{
if (_dataDict == null || _dataDict.Count < 1) return;
List<string> lines = new List<string>(_dataDict.Count);
foreach (var key in _dataDict.Keys)
{
lines.Add($"{key}={_dataDict[key].ToString()}");
}
if(!string.IsNullOrEmpty(_filePath))
File.WriteAllLines(_filePath, lines);
}
public void Set(string key, object value)
{
if (_dataDict == null) _dataDict = new Dictionary<string, string>(10);
_dataDict[key] = value.ToString();
Save();
}
public string Get(string key) => _dataDict.ContainsKey(key) ? _dataDict[key] : "";
public bool TryGet(string key, out string value)
{
value = "";
return _dataDict?.TryGetValue(key, out value) ?? false;
}
public bool GetBool(string key, bool defaultVal = false)
{
if (TryGet(key, out var str))
{
return (str.ToLower() == "true" || str == "1");
}
return defaultVal;
}
public int GetInt(string key, int defaultVal = 0)
{
if (TryGet(key, out var str))
{
var inVal = 0;
if (int.TryParse(str, out inVal))
{
return inVal;
}
}
return defaultVal;
}
public float GetFloat(string key, float defaultVal = 0)
{
if (TryGet(key, out var str))
{
float val = 0;
if (float.TryParse(str, out val))
{
return val;
}
}
return defaultVal;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 58ea378c89d742008a18f019afd54a27
timeCreated: 1711858357

View File

@ -6,9 +6,7 @@
"GuruSDK",
"Guru.LitJson",
"Guru.Runtime",
"MaxSdk.Scripts.IntegrationManager.Editor",
"Guru.Editor",
"GuruAdjust.Editor"
"MaxSdk.Scripts.IntegrationManager.Editor"
],
"includePlatforms": [
"Editor"

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 1d20da1a87484f37bc53360cc2414030
timeCreated: 1712622721

View File

@ -1,23 +0,0 @@
namespace Guru
{
/// <summary>
/// 更新器
/// </summary>
public interface IUpdater
{
UpdaterState State { get; }
void Start();
void OnUpdate();
void Pause(bool pause);
void Kill();
}
public enum UpdaterState
{
Prepare,
Running,
Pause,
Kill,
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6ab7dd209c3a494eab0dfbedce0700d8
timeCreated: 1712622736

View File

@ -1,82 +0,0 @@
namespace Guru
{
using System;
using System.Collections;
using System.Collections.Generic;
public class ThreadHandler: IUpdater
{
private Queue<Action> _actions;
public Queue<Action> Actions
{
get
{
if(_actions == null)
_actions = new Queue<Action>(10);
return _actions;
}
set
{
if (value != null) _actions = value;
}
}
private UpdaterState _state;
public UpdaterState State => _state;
/// <summary>
/// 启动 Updater
/// </summary>
public void Start()
{
_state = UpdaterState.Running;
}
/// <summary>
/// 执行方案
/// </summary>
public void OnUpdate()
{
if (Actions.Count > 0)
{
// 消耗对垒
while (Actions.Count > 0)
{
Actions.Dequeue()?.Invoke();
}
}
}
public void Pause(bool pause = true)
{
_state = pause ? UpdaterState.Pause : UpdaterState.Running;
}
/// <summary>
/// 删除对象
/// </summary>
public void Kill()
{
_state = UpdaterState.Kill;
}
public void Dispose()
{
_actions.Clear();
_state = UpdaterState.Kill;
}
public void AddAction(Action action)
{
if (action == null) return;
Actions.Enqueue(action);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: dc05f1b1ff5947c19ce6af1db301398d
timeCreated: 1712624042

View File

@ -1,213 +0,0 @@
namespace Guru
{
using System.Collections.Generic;
using System.Text;
using System;
/// <summary>
/// 启动参数配置
/// </summary>
public class GuruSDKInitConfig
{
#region Properties
/// <summary>
/// 使用自定义的ConsentFlow启动流程
/// </summary>
public bool UseCustomConsent = false;
/// <summary>
/// SDK初始化完成后自动加载广告
/// </summary>
public bool AutoLoadWhenAdsReady = true;
/// <summary>
/// 使用IAP支付插件功能
/// </summary>
public bool IAPEnabled = true;
/// <summary>
/// 自动申请推送授权信息
/// </summary>
public bool AutoNotificationPermission = true;
/// <summary>
/// 自动记录完成的关卡
/// </summary>
[Obsolete("Will be removed from InitConfig in next version. Use the <b_level> and <b_play> data from the GameUserData from game itself instead!")]
public bool AutoRecordFinishedLevels = false;
/// <summary>
/// 自定义 Service 云控 Key
/// </summary>
public string CustomServiceKey = "";
/// <summary>
/// Banner 背景颜色 Hex 值
/// </summary>
public string BannerBackgroundColor = "#00000040";
/// <summary>
/// 已购买去广告道具
/// </summary>
public bool IsBuyNoAds = false;
/// <summary>
/// Debug模式默认关闭
/// </summary>
public bool DebugMode = false;
/// <summary>
/// Debug模式下开启打点默认关闭
/// </summary>
public bool EnableDebugLogEvent = false;
private Dictionary<string, object> _defaultRemoteData = new Dictionary<string, object>();
/// <summary>
/// 云控参数的默认配置
/// </summary>
/// <returns></returns>
public Dictionary<string, object> DefaultRemoteData
{
set
{
if (value != null)
{
_defaultRemoteData = value;
}
}
get => _defaultRemoteData;
}
/// <summary>
/// 启用 AdjustDeeplink
/// </summary>
public Action<string> OnAdjustDeeplinkCallback = null;
/// <summary>
/// 支付初始化Keys
/// </summary>
public byte[] GoogleKeys; // 数据取自 GooglePlayTangle.Data();
public byte[] AppleRootCerts; // 数据取自 AppleTangle.Data();
#endregion
#region Print
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"------- Custom InitConfig -------");
sb.AppendLine($"\t UseCustomConsent: {UseCustomConsent}");
sb.AppendLine($"\t AutoLoadWhenAdsReady: {AutoLoadWhenAdsReady}");
sb.AppendLine($"\t IAPEnabled: {IAPEnabled}");
sb.AppendLine($"\t AutoNotificationPermission: {AutoNotificationPermission}");
sb.AppendLine($"\t AutoRecordFinishedLevels: {AutoRecordFinishedLevels}");
sb.AppendLine($"\t CustomServiceKey: {CustomServiceKey}");
sb.AppendLine($"\t BannerBackgroundColor: {BannerBackgroundColor}");
sb.AppendLine($"\t IsBuyNoAds: {IsBuyNoAds}");
sb.AppendLine($"\t DebugMode: {DebugMode}");
sb.AppendLine($"\t DefaultRemote: Count: {DefaultRemoteData.Count}");
sb.AppendLine($"------- Custom InitConfig -------");
return sb.ToString();
}
#endregion
#region Builder
/// <summary>
/// 构造器
/// </summary>
/// <returns></returns>
public static GuruSDKInitConfigBuilder Builder() => new GuruSDKInitConfigBuilder();
#endregion
}
/// <summary>
/// 构建器
/// </summary>
public class GuruSDKInitConfigBuilder
{
private GuruSDKInitConfig _config = new GuruSDKInitConfig();
/// <summary>
/// 构建配置
/// </summary>
/// <returns></returns>
public GuruSDKInitConfig Build()
{
return _config;
}
public GuruSDKInitConfigBuilder SetUseCustomConsent(bool value)
{
_config.UseCustomConsent = value;
return this;
}
public GuruSDKInitConfigBuilder SetAutoLoadWhenAdsReady(bool value)
{
_config.AutoLoadWhenAdsReady = value;
return this;
}
public GuruSDKInitConfigBuilder SetIAPEnabled(bool value)
{
_config.IAPEnabled = value;
return this;
}
public GuruSDKInitConfigBuilder SetAutoRecordFinishedLevels(bool value)
{
_config.AutoRecordFinishedLevels = value;
return this;
}
public GuruSDKInitConfigBuilder SetIsBuyNoAds(bool value)
{
_config.IsBuyNoAds = value;
return this;
}
public GuruSDKInitConfigBuilder SetBannerBackgroundColor(string value)
{
_config.BannerBackgroundColor = value;
return this;
}
public GuruSDKInitConfigBuilder SetDebugMode(bool value)
{
_config.DebugMode = value;
return this;
}
public GuruSDKInitConfigBuilder SetOnAdjustDeeplinkCallback(Action<string> callback)
{
_config.OnAdjustDeeplinkCallback = callback;
return this;
}
public GuruSDKInitConfigBuilder SetGoogleKeys(byte[] value)
{
_config.GoogleKeys = value;
return this;
}
public GuruSDKInitConfigBuilder SetAppleRootCerts(byte[] value)
{
_config.AppleRootCerts = value;
return this;
}
public GuruSDKInitConfigBuilder SetDefaultRemoteData(Dictionary<string, object> value)
{
_config.DefaultRemoteData = value;
return this;
}
public GuruSDKInitConfigBuilder SetEnableDebugLogEvent(bool value)
{
_config.EnableDebugLogEvent = value;
return this;
}
public GuruSDKInitConfigBuilder SetCustomServiceKey(string value)
{
_config.CustomServiceKey = value;
return this;
}
public GuruSDKInitConfigBuilder SetAutoNotificationPermission(bool value)
{
_config.AutoNotificationPermission = value;
return this;
}
}
}

View File

@ -1,16 +1,15 @@
using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace Guru
{
using System;
using UnityEngine.Serialization;
[Serializable]
public class GuruServicesConfig
{
public long version = 0;
public GuruAppSettings app_settings;
public GuruParameters parameters;
public GuruAdjustSettings adjust_settings;
public GuruFbSettings fb_settings;
public GuruAdSettings ad_settings;
@ -35,40 +34,10 @@ namespace Guru
public bool IsMolocoIOSEnabled() => ad_settings != null &&
ad_settings.moloco_ids_ios != null &&
ad_settings.moloco_ids_ios.Length > 0;
public bool IsTradplusAndroidEnabled() => ad_settings != null &&
ad_settings.tradplus_ids_android != null &&
ad_settings.tradplus_ids_android.Length > 0;
public bool IsTradplusIOSEnabled() => ad_settings != null &&
ad_settings.tradplus_ids_ios != null &&
ad_settings.tradplus_ids_ios.Length > 0;
public bool IsIAPEnabled() => app_settings != null && app_settings.enable_iap
&& products != null && products.Length > 0;
public bool UseCustomKeystore() => app_settings?.custom_keystore ?? false;
public bool IsFirebaseEnabled() => app_settings?.enable_firebase ?? true;
public bool IsFacebookEnabled() => app_settings?.enable_facebook ?? true;
public bool IsAdjustEnabled() => app_settings?.enable_adjust ?? true;
public bool IsKeywordsEnabled() => app_settings != null && app_settings.enable_keywords;
//-------------------------------- 配置检测 -------------------------------
//-------------------------------- Parameters --------------------------------
public double Tch02Value() => parameters?.tch_020 ?? 0;
public bool IsAppReview() => parameters?.apple_review ?? false;
public bool EnableAnaErrorLog() => parameters?.enable_errorlog ?? false;
public bool IsAdsCompliance() => parameters?.ads_compliance ?? false;
public bool DMACountryCheck() => parameters?.dma_country_check ?? false;
public string DMAMapRule() => parameters?.dma_map_rule ?? "";
public bool UseUUID() => parameters?.using_uuid ?? false;
public bool KeywordsEnabled() => parameters?.enable_keywords ?? false;
public int TokenValidTime() => parameters?.token_valid_time ?? 604800;
public int LevelEndSuccessNum() => parameters?.level_end_success_num ?? 50;
public string CdnHost() => parameters?.cdn_host ?? "";
public bool UsingUUID() => parameters?.using_uuid ?? true;
//-------------------------------- Parameters --------------------------------
}
[Serializable]
@ -82,44 +51,30 @@ namespace Guru
public string terms_url;
public string android_store;
public string ios_store;
public int token_vaild_time = 604800;
public int level_end_success_num = 50;
public bool enable_keywords = true;
public bool enable_firebase = true;
public bool enable_facebook = true;
public bool enable_adjust = true;
public bool enable_iap = false;
public bool custom_keystore = false;
}
[Serializable]
public class GuruParameters
{
public int token_valid_time = 604800;
public int level_end_success_num = 50;
public bool enable_keywords = false;
public double tch_020 = 0;
public bool using_uuid = false;
public string dma_map_rule = "";
public bool dma_country_check = false;
public bool apple_review = false; // 苹果审核标志位
public bool enable_errorlog = false;
public bool ads_compliance = false;
public string cdn_host = "";
}
[Serializable]
public class GuruAdjustSettings
{
public string[] app_token;
public string[] events;
public string AndroidToken() => app_token != null && app_token.Length > 0 ? app_token[0] : "";
public string iOSToken() => app_token != null && app_token.Length > 1 ? app_token[1] : "";
public string AndroidToken => app_token != null && app_token.Length > 0 ? app_token[0] : "";
public string iOSToken => app_token != null && app_token.Length > 1 ? app_token[1] : "";
}
[Serializable]
public class GuruFbSettings
{
public string fb_app_id;
public string fb_client_token;
public string app_id;
public string client_token;
}
[Serializable]
@ -135,8 +90,8 @@ namespace Guru
public string[] pubmatic_ids_ios;
public string[] moloco_ids_android;
public string[] moloco_ids_ios;
public string[] tradplus_ids_android;
public string[] tradplus_ids_ios;
}
}

View File

@ -0,0 +1,227 @@
namespace Guru
{
using UnityEngine;
using System;
public partial class GuruSDK
{
/// <summary>
/// 启动广告服务
/// </summary>
public static void StartAds()
{
if (InitConfig.UseCustomConsent)
{
Debug.Log($"{Tag} --- Call <color=orange>StartAdsWithCustomConsent</color> when you use custom consent, and pass the result (boolean) to the method.");
}
else
{
// 默认的启动顺序是先启动Consent后, 根据用户回调的结果来启动广告
Instance.StartConsentFlow();
}
}
/// <summary>
/// 使用自定义的Consent, 获取用户授权后, 调用此方法
/// </summary>
/// <param name="userAllow"></param>
public static void StartAdsWithCustomConsent(bool userAllow = true)
{
if (userAllow)
{
StartAdService();
}
else
{
Debug.Log($"{Tag} --- User refuse to provide ads Id, Ads Service will be cancelled");
}
}
#region Guru Consent
/// <summary>
/// 启动Consent流程
/// </summary>
private void StartConsentFlow()
{
LogI($"StartConsentFlow");
GuruConsent.StartConsent(OnConsentOver);
}
private void OnConsentOver(int code)
{
Callbacks.ConsentFlow._onConsentResult?.Invoke(code);
switch(code)
{
case GuruConsent.StatusCode.OBTAINED:
case GuruConsent.StatusCode.NOT_AVAILABLE:
// 已获取授权, 或者地区不可用
#if UNITY_IOS
CheckATTStatus();
#else
StartAdService();
#endif
break;
}
}
#if UNITY_IOS
/// <summary>
/// iOS 平台检查 ATT 状态
/// </summary>
private void CheckATTStatus()
{
AttManager.Instance.CheckATTStatus(OnATTStatus);
}
#endif
#endregion
#region Ad Services
private static bool _initAdsCompleted = false;
/// <summary>
/// 启动广告服务
/// </summary>
public static void StartAdService()
{
LogI($"StartAdService");
ADService.Instance.StartService(OnAdsInitComplete,
InitConfig.AutoLoadWhenAdsReady, IsDebugMode);
//--------- Callbacks -----------
ADService.OnInterstitialLoaded = OnInterstitialLoaded;
ADService.OnInterstitialFailed = OnInterstitialFailed;
ADService.OnRewardLoaded = OnRewardLoaded;
ADService.OnRewardFailed = OnRewardFailed;
}
private static void OnInterstitialLoaded()
=> Callbacks.Ads._onInterstitialADLoaded?.Invoke();
private static void OnInterstitialFailed()
=> Callbacks.Ads._onInterstitialADFailed?.Invoke();
private static void OnRewardLoaded()
=> Callbacks.Ads._onRewardedADLoaded?.Invoke();
private static void OnRewardFailed()
=> Callbacks.Ads._onRewardADFailed?.Invoke();
private static void OnAdsInitComplete()
{
_initAdsCompleted = true;
Callbacks.Ads._onAdsInitComplete?.Invoke();
}
private static bool CheckAdsReady()
{
if (!_initAdsCompleted)
{
LogE("Ads is not ready. Call <GuruSDk.StartAdService> first.");
return false;
}
return true;
}
/// <summary>
/// 显示Banner广告
/// </summary>
/// <param name="placement"></param>
public static void ShowBanner(string placement = "")
{
if (!CheckAdsReady()) return;
ADService.Instance.ShowBanner(placement);
}
/// <summary>
/// 隐藏Banner广告
/// </summary>
public static void HideBanner()
{
if (!CheckAdsReady()) return;
ADService.Instance.HideBanner();
}
public static void LoadInterstitialAd()
{
if (!CheckAdsReady()) return;
ADService.Instance.RequestInterstitialAD();
}
public static bool IsInterstitialAdReady => ADService.Instance.IsInterstitialADReady();
/// <summary>
/// 显示插屏广告
/// </summary>
/// <param name="placement"></param>
/// <param name="onDismissed"></param>
public static void ShowInterstitialAd(string placement = "", Action onDismissed = null)
{
if (!CheckAdsReady()) return;
if (!ADService.Instance.IsInterstitialADReady())
{
LogE("Interstitial is not ready. Call <GuruSDk.ShowInterstitialAd> again.");
LoadInterstitialAd();
return;
}
ADService.Instance.ShowInterstitialAD(placement, onDismissed);
}
public static void LoadRewardAd()
{
if (!CheckAdsReady()) return;
ADService.Instance.RequestRewardedAD();
}
public static bool IsRewardAdReady => ADService.Instance.IsRewardedADReady();
/// <summary>
/// 显示激励视频广告
/// </summary>
/// <param name="placement"></param>
/// <param name="onRewarded"></param>
/// <param name="onFailed"></param>
public static void ShowRewardAd(string placement = "", Action onRewarded = null, Action<string> onFailed = null)
{
if (!CheckAdsReady()) return;
if (!ADService.Instance.IsRewardedADReady())
{
LogE("RewardAd is not ready. Call <GuruSDk.LoadRewardAd> again.");
LoadRewardAd();
return;
}
ADService.Instance.ShowRewardedAD(placement, onRewarded, onFailed);
}
#endregion
#region MaxServices
/// <summary>
/// 显示Max调试菜单
/// </summary>
public static void ShowMaxDebugPanel()
{
#if UNITY_EDITOR
LogI($"Can not show Max Debug Panel in Editor, skipped.");
return;
#endif
if (!ADService.Instance.IsInitialized)
{
LogI($"ADService is not initialized, call <GuruSDK.StartAds> first.");
return;
}
ADService.Instance.ShowMaxDebugPanel();
}
#endregion
}
}

View File

@ -0,0 +1,216 @@
namespace Guru
{
using System.Collections.Generic;
/// <summary>
/// 打点管理
/// </summary>
public partial class GuruSDK
{
//----------------- 关卡开始类型 ---------------------
public const string EventLevelStartModePlay = "play";
public const string EventLevelStartModeReplay = "replay";
public const string EventLevelStartModeContinue= "continue";
//----------------- 关卡结束类型 ---------------------
public const string EventLevelEndSuccess = "success";
public const string EventLevelEndFail = "fail";
public const string EventLevelEndExit = "exit";
public const string EventLevelEndTimeout = "timeout";
#region 通用接口
/// <summary>
/// 自定义事件打点
/// </summary>
/// <param name="eventName"></param>
/// <param name="data"></param>
public static void LogEvent(string eventName, Dictionary<string, dynamic> data = null)
=> Analytics.Track(eventName, data);
public static void SetScreen(string screen, string extra = "")
=> Analytics.SetCurrentScreen(screen, extra);
#endregion
#region 游戏打点
/// <summary>
/// 游戏启动打点
/// </summary>
/// <param name="level"></param>
/// <param name="levelName"></param>
/// <param name="levelCategory"></param>
/// <param name="levelID"></param>
/// <param name="startType"></param>
/// <param name="isReplay"></param>
public static void LogLevelStart(int level, string startType = EventLevelStartModePlay,
string levelCategory = "main", string levelName = "", string levelID = "",
bool isReplay = false)
{
Analytics.LogLevelStart(level, levelName, levelCategory, levelID, startType, isReplay);
}
/// <summary>
/// 游戏点击 Continue 重开始游戏
/// </summary>
/// <param name="level"></param>
/// <param name="levelCategory"></param>
/// <param name="levelName"></param>
/// <param name="levelID"></param>
public static void LogLevelContinue(int level, string levelCategory = "main",
string levelName = "", string levelID = "")
{
LogLevelStart(level, EventLevelStartModeContinue, levelCategory, levelName, levelID, true);
}
/// <summary>
/// 游戏点击 Continue 重开始游戏
/// </summary>
/// <param name="level"></param>
/// <param name="levelCategory"></param>
/// <param name="levelName"></param>
/// <param name="levelID"></param>
public static void LogLevelReplay(int level, string levelCategory = "main",
string levelName = "", string levelID = "")
{
LogLevelStart(level, EventLevelStartModeReplay,levelCategory, levelName, levelID, true);
}
/// <summary>
/// 游戏胜利打点
/// </summary>
public static void LogLevelEnd(int level, string result = EventLevelEndSuccess,
string levelCategory = "main", string levelName = "", string levelID = "",
int? duration = null, int? step = null, int? score = null )
{
if (InitConfig.AutoRecordFinishedLevels)
{
if(result == EventLevelEndSuccess) Model.SuccessLevelCount++; // 自动记录关卡完成次数
Model.TotalPlayedCount++; // 自动记录关卡总次数
Analytics.BLevel = Model.SuccessLevelCount; // 记录 BLevel
Analytics.BPlay = Model.TotalPlayedCount; // 记录 BPlay
}
Analytics.LogLevelEnd(level, result,
levelName, levelCategory, levelCategory,
duration, step, score);
}
/// <summary>
/// 游戏失败打点
/// 需要为游戏记录详细的失败原因
/// </summary>
public static void LogLevelFail(int level,
string levelCategory = "main",string levelName = "", string levelID = "",
int? duration = null, int? step = null, int? score = null )
{
LogLevelEnd(level, EventLevelEndFail, levelCategory, levelName, levelID, duration, step, score);
}
/// <summary>
/// 因退出关卡导致游戏失败
/// </summary>
public static void LogLevelFailExit(int level,
string levelCategory = "main", string levelName = "", string levelID = "",
int? duration = null, int? step = null, int? score = null)
{
LogLevelEnd(level, EventLevelEndExit, levelCategory, levelName, levelID, duration, step, score);
}
/// <summary>
/// 因关卡超时导致游戏失败
/// </summary>
public static void LogLevelFailTimeout(int level,
string levelCategory = "main", string levelName = "", string levelID = "",
int? duration = null, int? step = null, int? score = null)
{
LogLevelEnd(level, EventLevelEndTimeout, levelCategory, levelName, levelID, duration, step, score);
}
/// <summary>
/// 玩家(角色)升级事件
/// </summary>
/// <param name="playerLevel"></param>
/// <param name="playerName"></param>
public static void LogLevelUp(int playerLevel, string playerName)
{
Analytics.LevelUp(playerLevel, playerName);
}
/// <summary>
/// 玩家解锁成就
/// </summary>
/// <param name="achievementName"></param>
public static void LogAchievement(string achievementName)
{
Analytics.UnlockAchievement(achievementName);
}
#endregion
#region 用户属性
/// <summary>
/// 设置用户属性
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public static void SetUserProperty(string key, string value)
=> Analytics.SetUserProperty(key, value);
public static void SetUID(string uid)
{
SetUserProperty(Analytics.PropertyUserID, uid);
}
public static void SetUserBLevel(int blevel)
{
SetUserProperty(Analytics.PropertyLevel, $"{blevel}");
}
public static void SetUserBPlay(int bplay)
{
SetUserProperty(Analytics.PropertyPlay, $"{bplay}");
}
public static void SetUserTotalCoins(int totalCoins)
{
SetUserProperty(Analytics.PropertyCoin, $"{totalCoins}");
}
public static void SetUserCoins(int coins)
{
SetUserProperty(Analytics.PropertyNonIAPCoin, $"{coins}");
}
public static void SetUserPaidCoins(int paidCoins)
{
SetUserProperty(Analytics.PropertyIAPCoin, $"{paidCoins}");
}
public static void SetUserExp(int exp)
{
SetUserProperty(Analytics.PropertyExp, $"{exp}");
}
public static void SetUserHp(int hp)
{
SetUserProperty(Analytics.PropertyHp, $"{hp}");
}
public static void SetUserGrade(int grade)
{
SetUserProperty(Analytics.PropertyGrade, $"{grade}");
}
#endregion
}
}

View File

@ -0,0 +1,157 @@
namespace Guru
{
using System;
using UnityEngine;
public partial class GuruSDK
{
/// <summary>
/// 回调参数类
/// </summary>
public class Callbacks
{
/// <summary>
/// APP 事件
/// </summary>
public static class App
{
internal static Action<bool> _onAppPaused;
public static event Action<bool> OnAppPaused
{
add => _onAppPaused += value;
remove => _onAppPaused -= value;
}
internal static Action _onAppQuit;
public static event Action OnAppQuit
{
add => _onAppQuit += value;
remove => _onAppQuit -= value;
}
}
/// <summary>
/// GDPR Consent
/// </summary>
public static class ConsentFlow
{
/// <summary>
/// 当Consent启动结束后返回状态码
/// </summary>
public static event Action<int> OnConsentResult
{
add => _onConsentResult += value;
remove => _onConsentResult -= value;
}
internal static Action<int> _onConsentResult;
/// <summary>
/// ATT 状态返回
/// </summary>
public static event Action<int> OnAttResult
{
add => _onAttResult += value;
remove => _onAttResult -= value;
}
internal static Action<int> _onAttResult;
}
/// <summary>
/// 广告回调
/// </summary>
public static class Ads
{
internal static Action _onAdsInitComplete;
public static event Action OnAdsInitComplete
{
add => _onAdsInitComplete += value;
remove => _onAdsInitComplete -= value;
}
internal static Action _onInterstitialADLoaded;
public static event Action OnInterstitialADLoaded
{
add => _onInterstitialADLoaded += value;
remove => _onInterstitialADLoaded -= value;
}
internal static Action _onInterstitialADFailed;
public static event Action OnInterstitialADFailed
{
add => _onInterstitialADFailed += value;
remove => _onInterstitialADFailed -= value;
}
internal static Action _onRewardedADLoaded;
public static event Action OnRewardedADLoaded
{
add => _onRewardedADLoaded += value;
remove => _onRewardedADLoaded -= value;
}
internal static Action _onRewardADFailed;
public static event Action OnRewardADFailed
{
add => _onRewardADFailed += value;
remove => _onRewardADFailed -= value;
}
}
/// <summary>
/// 云控参数
/// </summary>
public static class Remote
{
internal static Action _onRemoteInitComplete;
public static event Action OnRemoteInitComplete
{
add => _onRemoteInitComplete += value;
remove => _onRemoteInitComplete -= value;
}
internal static Action<bool> _onRemoteFetchComplete;
public static event Action<bool> OnRemoteFetchComplete
{
add => _onRemoteFetchComplete += value;
remove => _onRemoteFetchComplete -= value;
}
}
/// <summary>
/// 支付回调
/// </summary>
public static class IAP
{
internal static Action<bool> _onIAPInitComplete;
public static event Action<bool> OnIAPInitComplete
{
add => _onIAPInitComplete += value;
remove => _onIAPInitComplete -= value;
}
internal static Action<string> _onPurchaseStart;
public static event Action<string> OnPurchaseStart
{
add => _onPurchaseStart += value;
remove => _onPurchaseStart -= value;
}
internal static Action<string, string> _onPurchaseFailed;
public static event Action<string, string> OnPurchaseFailed
{
add => _onPurchaseFailed += value;
remove => _onPurchaseFailed -= value;
}
}
}
}
}

View File

@ -0,0 +1,171 @@
namespace Guru
{
using UnityEngine;
using System;
using System.Linq;
public partial class GuruSDK
{
public static bool IsIAPReady = false;
//---------- 支付失败原因 ----------
public const string BuyFail_PurchasingUnavailable = "PurchasingUnavailable";
public const string BuyFail_Pending = "ExistingPurchasePending";
public const string BuyFail_ProductUnavailable = "ProductUnavailable";
public const string BuyFail_SignatureInvalid = "SignatureInvalid";
public const string BuyFail_UserCancelled = "UserCancelled";
public const string BuyFail_PaymentDeclined = "PaymentDeclined";
public const string BuyFail_DuplicateTransaction = "DuplicateTransaction";
public const string BuyFail_Unknown = "Unknown";
/// <summary>
/// 初始化IAP 功能
/// </summary>
public static void InitIAP(byte[] googleKey, byte[] appleRootCerts)
{
GuruIAP.Instance.OnInitResult += OnIAPInitResult;
GuruIAP.Instance.OnRestored += OnRestored;
GuruIAP.Instance.OnBuyStart += OnBuyStart;
GuruIAP.Instance.OnBuyEnd += OnBuyEnd;
GuruIAP.Instance.OnBuyFailed += OnBuyFailed;
GuruIAP.Instance.InitWithKeys(googleKey, appleRootCerts, IsDebugMode);
}
/// <summary>
/// 初始化结果
/// </summary>
/// <param name="success"></param>
private static void OnIAPInitResult(bool success)
{
LogI($"IAP init result: {success}");
IsIAPReady = success;
Callbacks.IAP._onIAPInitComplete?.Invoke(success);
}
private static bool CheckIAPReady()
{
if (!IsIAPReady)
{
LogE("IAP is not ready, call <GuruSDK.InitIAP> first.");
return false;
}
return true;
}
#region Purchase
public static ProductSetting GetProductSettingById(string productId)
{
var products = GuruSettings.Instance.Products;
if (products != null && products.Length > 0)
{
return products.FirstOrDefault(p => p.ProductId == productId);
}
return null;
}
public static ProductSetting GetProductSetting(string productName)
{
var products = GuruSettings.Instance.Products;
if (products != null && products.Length > 0)
{
return products.FirstOrDefault(p => p.ProductName == productName);
}
return null;
}
private static Action<string, bool> _onPurchaseCallback;
/// <summary>
/// 购买商品, 通过商品Name
/// </summary>
/// <param name="productName"></param>
/// <param name="purchaseCallback"></param>
public static void Purchase(string productName, Action<string, bool> purchaseCallback = null)
{
if (CheckIAPReady())
{
_onPurchaseCallback = purchaseCallback;
GuruIAP.Instance.Buy(productName);
}
}
/// <summary>
/// 购买商品, 通过商品ID
/// </summary>
/// <param name="productId"></param>
/// <param name="purchaseCallback"></param>
public static bool PurchaseById(string productId, Action<string, bool> purchaseCallback = null)
{
var productName = GetProductSettingById(productId)?.ProductName ?? "";
if (CheckIAPReady() && !string.IsNullOrEmpty(productName))
{
Purchase(productName, purchaseCallback);
return true;
}
return false;
}
/// <summary>
/// 支付回调
/// </summary>
/// <param name="productId"></param>
/// <param name="success"></param>
private static void OnBuyEnd(string productName, bool success)
{
_onPurchaseCallback?.Invoke(productName, success);
}
/// <summary>
/// 支付开始
/// </summary>
/// <param name="productName"></param>
private static void OnBuyStart(string productName)
{
Callbacks.IAP._onPurchaseStart?.Invoke(productName);
}
/// <summary>
/// 支付失败
/// </summary>
/// <param name="productName"></param>
/// <param name="reason"></param>
private static void OnBuyFailed(string productName, string reason)
{
Callbacks.IAP._onPurchaseFailed?.Invoke(productName, reason);
}
#endregion
#region Restore Purchase
private static Action<bool> _onRestored;
/// <summary>
/// 恢复购买
/// </summary>
/// <param name="restoreCallback"></param>
public static void Restore(Action<bool> restoreCallback)
{
if (CheckIAPReady())
{
_onRestored = restoreCallback;
GuruIAP.Instance.Restore();
}
}
private static void OnRestored(bool success)
{
_onRestored?.Invoke(success);
}
#endregion
}
}

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using Firebase.RemoteConfig;
namespace Guru
{
using System;
using System.Collections.Generic;
using Firebase.RemoteConfig;
public partial class GuruSDK
{
@ -30,45 +30,27 @@ namespace Guru
{
return RemoteConfigManager.GetConfig<T>(key);
}
public static string GetRemoteString(string key, string defaultValue = "") => RemoteConfigManager.GetString(key, defaultValue);
public static int GetRemoteInt(string key, int defaultValue = 0) => RemoteConfigManager.GetInt(key, defaultValue);
public static long GetRemoteLong(string key, long defaultValue = 0 ) => RemoteConfigManager.GetLong(key, defaultValue);
public static double GetRemoteDouble(string key, double defaultValue = 0) => RemoteConfigManager.GetDouble(key, defaultValue);
public static float GetRemoteFloat(string key, float defaultValue = 0) => RemoteConfigManager.GetFloat(key, defaultValue);
public static bool GetRemoteBool(string key, bool defaultValue = false) => RemoteConfigManager.GetBool(key, defaultValue);
public static string GetRemoteString(string key) => RemoteConfigManager.GetString(key);
public static int GetRemoteInt(string key) => RemoteConfigManager.GetInt(key);
public static long GetRemoteLong(string key) => RemoteConfigManager.GetLong(key);
public static double GetRemoteDouble(string key) => RemoteConfigManager.GetDouble(key);
public static float GetRemoteFloat(string key) => RemoteConfigManager.GetFloat(key);
public static bool GetRemoteBool(string key) => RemoteConfigManager.GetBool(key);
/// <summary>
/// 注册监听某个 Key 的变化
/// </summary>
/// <param name="key"></param>
/// <param name="onValueChanged"></param>
public static void RegisterOnValueChanged(string key, Action<string,string> onValueChanged)
{
RemoteConfigManager.RegisterOnValueChanged(key, onValueChanged);
}
/// <summary>
/// 注销监听某个 Key 的变化
/// </summary>
/// <param name="key"></param>
/// <param name="onValueChanged"></param>
public static void UnRegisterOnValueChanged(string key, Action<string,string> onValueChanged)
{
RemoteConfigManager.UnRegisterOnValueChanged(key, onValueChanged);
}
/// <summary>
/// 获取所有云控配置
/// </summary>
/// <returns></returns>
public static Dictionary<string, ConfigValue> GetRemoteAllValues() => RemoteConfigManager.GetAllValues();
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string GetRemoteStaticValue(string key) => RemoteConfigManager.GetStaticValue(key);
}

View File

@ -0,0 +1,355 @@
namespace Guru
{
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public partial class GuruSDK: MonoBehaviour
{
public const string Version = "1.0.0";
public const string Tag = "[Guru]";
public const string ServicesConfigKey = "guru_services";
public const string ServicesConfigExtension = "cfg";
private static GuruSDK _instance;
/// <summary>
/// 单利引用
/// </summary>
public static GuruSDK Instance
{
get
{
if(null == _instance)
{
_instance = CreateInstance();
}
return _instance;
}
}
private GuruSDKInitConfig _initConfig;
private Action<bool> _onCompleteCallback;
private static GuruSDKModel _model;
internal static GuruSDKInitConfig InitConfig => Instance._initConfig;
internal static GuruSDKModel Model => GuruSDKModel.Instance;
private static GuruServicesConfig _appServicesConfig;
/// <summary>
/// Debug Mode
/// </summary>
public static bool IsDebugMode
{
get
{
#if UNITY_EDITOR || DEBUG
return true;
#endif
return false;
}
}
#region 初始化
private static GuruSDK CreateInstance()
{
var go = new GameObject(nameof(GuruSDK));
DontDestroyOnLoad(go);
_instance = go.AddComponent<GuruSDK>();
return _instance;
}
public static GuruSDKInitConfig BuildConfig(
bool useCustomConsent = false,
bool autoLoadAds = true,
bool iapEnabled = true,
bool autoRecordFinishedLevels = true,
bool showDebugLog = false,
Dictionary<string, object> defaultRemoteData = null,
byte[] googleKeys = null,
byte[] appleRootCerts = null)
{
var config = GuruSDKInitConfig.Build(useCustomConsent, autoLoadAds, iapEnabled,
autoRecordFinishedLevels, showDebugLog, defaultRemoteData, googleKeys, appleRootCerts);
return config;
}
public static void Init(Action<bool> onComplete)
{
Init(GuruSDKInitConfig.Build(), onComplete);
}
public static void Init(GuruSDKInitConfig config, Action<bool> onComplete)
{
LogI($"---- Guru SDK init ----");
LogI(config.ToString());
Instance.StartWithConfig(config, onComplete);
}
/// <summary>
/// 启动SDK
/// </summary>
/// <param name="config"></param>
/// <param name="onComplete"></param>
private void StartWithConfig(GuruSDKInitConfig config, Action<bool> onComplete)
{
Model.PropBLevel.OnValueChanged += OnBLevelChanged;
Model.PropBPlay.OnValueChanged += OnBPlayChanged;
_initConfig = config;
_onCompleteCallback = onComplete;
//---------- Start Firebase ------------
FirebaseUtil.InitFirebase(OnFirebaseReady);
// FirebaseUtil.OnFetchRemoteSuccess+= OnFetchRemoteCallback;
//---------- Start Facebook ------------
FBService.Instance.StartService();
}
/// <summary>
/// 开始各种组件初始化
/// </summary>
private void OnFirebaseReady()
{
if (!InitConfig.UseCustomConsent)
{
// LogI($"--- #3 Start Consent Flow ---");
StartConsentFlow();
}
if(!string.IsNullOrEmpty(IPMConfig.IPM_UID)) SetUID(IPMConfig.IPM_UID);
// 开始Remote Manager初始化
RemoteConfigManager.Init(BuildDefaultRemoteData(_initConfig.DefaultRemoteData));
RemoteConfigManager.OnFetchCompleted += OnFetchRemoteCallback;
// 根据上次的云控配置来初始化参数
SetupServicesConfig();
_onCompleteCallback?.Invoke(true);
}
/// <summary>
/// 注入云控参数基础数据
/// </summary>
/// <param name="dict"></param>
/// <returns></returns>
private Dictionary<string, object> BuildDefaultRemoteData(Dictionary<string, object> dict)
{
string json = Model.LoadAppServicesConfigJson(); // 注入默认的Services 配置值
if (!string.IsNullOrEmpty(json))
{
if (dict == null) dict = new Dictionary<string, object>(3);
dict[ServicesConfigKey] = json;
return dict;
}
return null;
}
/// <summary>
/// 拉取云控参数完成
/// </summary>
/// <param name="success"></param>
private void OnFetchRemoteCallback(bool success)
{
LogI($"--- Remote fetch complete: {success} ---");
ABTestManager.Init(); // 启动AB测试解析器
Callbacks.Remote._onRemoteFetchComplete?.Invoke(success);
}
#endregion
#region App Remote Update
private void SetupServicesConfig()
{
bool useKeywords = true;
bool useIAP = true;
var guruSettings = GuruSettings.Instance;
var services = GetRemoteServicesConfig();
if (services != null)
{
_appServicesConfig = services;
useKeywords = _appServicesConfig.IsKeywordsEnabled();
if (null != guruSettings)
{
if(_appServicesConfig.adjust_settings != null)
{
// 更新 Adjust Tokens
guruSettings.UpdateAdjustTokens(_appServicesConfig.adjust_settings.AndroidToken
,_appServicesConfig.adjust_settings.iOSToken);
// 更新 Adjust Events
guruSettings.UpdateAdjustEvents(_appServicesConfig.adjust_settings.events);
}
}
useIAP = _appServicesConfig.IsIAPEnabled();
}
AdjustService.StartService();
if(useIAP) {
InitIAP(_initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP
}
if(useKeywords) {
KeywordsManager.Install(Model.IsIAPUser, Model.SuccessLevelCount); // 启动Keyword管理器
}
}
private GuruServicesConfig GetRemoteServicesConfig()
{
var json = GetRemoteString(ServicesConfigKey);
if (!string.IsNullOrEmpty(json))
{
return JsonParser.ToObject<GuruServicesConfig>(json);
}
return null;
}
#endregion
#region 数据
private void OnBLevelChanged(int blevel)
{
SetUserBLevel(blevel);
}
private void OnBPlayChanged(int bplay)
{
SetUserBPlay(bplay);
}
#endregion
#region Misc
/// <summary>
/// 打开页面
/// </summary>
/// <param name="url"></param>
public static void OpenURL(string url)
{
GuruWebview.OpenPage(url);
}
#endregion
#region Logging
public static void LogI(object message)
{
Debug.Log($"{Tag} {message}");
}
public static void LogW(object message)
{
Debug.LogWarning($"{Tag} {message}");
}
public static void LogE(object message)
{
Debug.LogError($"{Tag} {message}");
}
public static void LogException(string message)
{
LogException( new Exception($"{Tag} {message}"));
}
public static void LogException(Exception e)
{
Debug.LogException(e);
}
#endregion
#region 生命周期
/// <summary>
/// 暂停时处理
/// </summary>
/// <param name="paused"></param>
private void OnAppPauseHandler(bool paused)
{
if(paused) Model.Save(true); // 强制保存数据
Callbacks.App._onAppPaused?.Invoke(paused);
}
private void OnApplicationPause(bool paused)
{
OnAppPauseHandler(paused);
}
private void OnApplicationFocus(bool hasFocus)
{
OnAppPauseHandler(!hasFocus);
}
private void OnApplicationQuit()
{
Model.Save(true);
Callbacks.App._onAppQuit?.Invoke();
}
#endregion
#region 延迟处理
/// <summary>
/// 启动协程
/// </summary>
/// <param name="enumerator"></param>
/// <returns></returns>
public static Coroutine DoCoroutine(IEnumerator enumerator)
{
return Instance != null ? Instance.StartCoroutine(enumerator) : null;
}
public static void KillCoroutine(Coroutine coroutine)
{
if(coroutine != null)
Instance.StopCoroutine(coroutine);
}
/// <summary>
/// 延时执行
/// </summary>
/// <param name="seconds"></param>
/// <param name="callback"></param>
public static void Delay(float seconds, Action callback)
{
DoCoroutine(Instance.OnDelayCall(seconds, callback));
}
private IEnumerator OnDelayCall(float delay, Action callback)
{
if (delay > 0)
{
yield return new WaitForSeconds(delay);
}
else
{
yield return null;
}
callback?.Invoke();
}
#endregion
}
}

View File

@ -0,0 +1,97 @@
namespace Guru
{
using System.Collections.Generic;
using System.Text;
/// <summary>
/// 启动参数配置
/// </summary>
public partial class GuruSDKInitConfig
{
/// <summary>
/// 使用自定义的ConsentFlow启动流程
/// </summary>
public bool UseCustomConsent = false;
/// <summary>
/// SDK初始化完成后自动加载广告
/// </summary>
public bool AutoLoadWhenAdsReady = true;
/// <summary>
/// 使用IAP支付插件功能
/// </summary>
public bool IAPEnabled = true;
/// <summary>
/// 自动记录完成的关卡
/// </summary>
public bool AutoRecordFinishedLevels = true;
/// <summary>
/// 显示Debug日志
/// </summary>
public bool ShowDebugLog = false;
/// <summary>
/// 云控参数的默认配置
/// </summary>
/// <returns></returns>
public Dictionary<string, object> DefaultRemoteData = new Dictionary<string, object>();
/// <summary>
/// 支付初始化Keys
/// </summary>
public byte[] GoogleKeys;
public byte[] AppleRootCerts;
#region Initialization
/// <summary>
/// 构建启动配置
/// </summary>
/// <returns></returns>
public static GuruSDKInitConfig Build(
bool useCustomConsent = false,
bool autoLoadAds = true,
bool iapEnabled = true,
bool autoRecordFinishedLevels = true,
bool showDebugLog = false,
Dictionary<string, object> defaultRemoteData = null,
byte[] googleKeys = null,
byte[] appleRootCerts = null)
{
// 创建启动用参数
GuruSDKInitConfig config = new GuruSDKInitConfig()
{
UseCustomConsent = useCustomConsent,
AutoLoadWhenAdsReady = autoLoadAds,
IAPEnabled = iapEnabled,
AutoRecordFinishedLevels = autoRecordFinishedLevels,
ShowDebugLog = showDebugLog,
GoogleKeys = googleKeys,
AppleRootCerts = appleRootCerts,
DefaultRemoteData = defaultRemoteData ?? new Dictionary<string, object>(),
};
#if UNITY_EDITOR
config.ShowDebugLog = true;
#endif
return config;
}
#endregion
#region Print
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"------- Custom init Config -------");
sb.AppendLine($"\tUseCustomConsent: {UseCustomConsent}");
sb.AppendLine($"\tAutoLoadWhenAdsReady: {AutoLoadWhenAdsReady}");
sb.AppendLine($"\tIAPEnabled: {IAPEnabled}");
sb.AppendLine($"\tShowDebugLog: {ShowDebugLog}");
sb.AppendLine($"------- Custom init Config -------");
return sb.ToString();
}
#endregion
}
}

View File

@ -3,21 +3,20 @@ namespace Guru
internal class GuruIAP: IAPServiceBase<GuruIAP>
{
/// <summary>
/// 获取BLevel
/// </summary>
/// <returns></returns>
protected override int GetBLevel() => Model.BLevel; // BLevel
protected override int GetBLevel() => GuruSDKModel.Instance.SuccessLevelCount; // BLevel
private GuruSDKModel Model => GuruSDKModel.Instance;
protected override void OnPurchaseOver(bool success, string productName)
{
}
public void ClearData()
{
_model.ClearData();
if (success)
{
GuruSDKModel.Instance.PurchasedCount++; // 记录成功购买次数
}
}
}
}

View File

@ -12,17 +12,27 @@ namespace Guru
get => _value;
set
{
if (_value.Equals(value)) return;
_value = value;
OnValueChanged?.Invoke(value);
}
}
public event Action<T> OnValueChanged;
public BindableProperty(T initValue)
public BindableProperty()
{
}
public BindableProperty(Action<T> onChanged)
{
OnValueChanged = onChanged;
}
public BindableProperty(T initValue, Action<T> onChanged)
{
_value = initValue;
OnValueChanged = onChanged;
}
}
}

View File

@ -1,46 +1,16 @@
namespace Guru
{
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System.IO;
[Serializable]
class PurchasedProduct
{
public string productName;
public string productId;
public string receipt;
public bool appleProductIsRestored;
}
[Serializable]
class GuruSDKSerializedModel
{
//-------------- data ---------------
public string uid = "";
public int b_level = 0;
public int b_play = 0;
public bool no_ads = false;
public List<PurchasedProduct> purchased = new List<PurchasedProduct>(10);
//-------------- data ---------------
}
[Serializable]
internal class GuruSDKModel
{
private const float SaveInterval = 3;
private const string SaveKey = "com.guru.sdk.model.save";
private DateTime _lastSavedTime = new DateTime(1970,1,1);
private bool _noAds = false;
private int _bLevel;
private int _bPlay;
private string _uid;
private List<PurchasedProduct> _purchased;
private static GuruSDKModel _instance;
@ -48,77 +18,67 @@ namespace Guru
{
get
{
if (null == _instance) _instance = new GuruSDKModel();
if (null == _instance) _instance = Load();
return _instance;
}
}
public GuruSDKModel()
{
// 读取内存值
GuruSDKSerializedModel model = LoadModel();
_uid = model.uid;
_noAds = model.no_ads;
_bLevel = model.b_level;
_bPlay = model.b_play;
_purchased = model.purchased;
}
//-------------- data ---------------
public int b_level = 0;
public int b_play = 0;
public int buy_count = 0;
//-------------- data ---------------
private float _lastSavedTime = 0;
public int BLevel
public int SuccessLevelCount
{
get => _bLevel;
set
get
{
if (value < _bLevel)
{
// b_level 必须比上一次的值大
Debug.LogWarning($"[SDK] :: Set b_level [{value}] should not be less than original value [{_bLevel}]");
return;
}
_bLevel = value;
Save();
if(_successLevel == null) InitProperties();
return _successLevel.Value;
}
set => _successLevel.Value = value;
}
public int BPlay
public int TotalPlayedCount
{
get => _bPlay;
set
get
{
_bPlay = value;
Save();
if(_totalPlayed == null) InitProperties();
return _totalPlayed.Value;
}
set => _totalPlayed.Value = value;
}
public string UserId
public int PurchasedCount
{
get => _uid;
set
get
{
_uid = value;
Save();
if(_purchasedCount == null) InitProperties();
return _purchasedCount.Value;
}
set => _purchasedCount.Value = value;
}
public bool IsIAPUser => PurchasedCount > 0;
public bool IsIapUser => _purchased.Count > 0;
public bool IsNoAds
{
get => _noAds;
set
{
_noAds = value;
Save();
}
}
private BindableProperty<int> _successLevel;
private BindableProperty<int> _totalPlayed;
private BindableProperty<int> _purchasedCount;
public BindableProperty<int> PropBLevel => _successLevel;
public BindableProperty<int> PropBPlay => _totalPlayed;
public BindableProperty<int> PropBuyCount => _purchasedCount;
#region 初始化
private GuruSDKSerializedModel LoadModel()
public static GuruSDKModel Load()
{
GuruSDKSerializedModel model = null;
GuruSDKModel model = null;
if (PlayerPrefs.HasKey(SaveKey))
{
var json = PlayerPrefs.GetString(SaveKey, "");
@ -126,7 +86,7 @@ namespace Guru
{
try
{
model = JsonUtility.FromJson<GuruSDKSerializedModel>(json);
model = JsonUtility.FromJson<GuruSDKModel>(json);
}
catch (Exception e)
{
@ -134,108 +94,98 @@ namespace Guru
}
}
}
if(model == null) model = new GuruSDKSerializedModel();
if(model == null) model = new GuruSDKModel();
model.InitProperties();
return model;
}
/// <summary>
/// 保存至 PlayerPrefs 数据
/// 保存至数据
/// </summary>
private void SetToMemory()
private void SaveToPlayerPrefs()
{
var model = new GuruSDKSerializedModel()
{
uid = _uid,
b_level = _bLevel,
b_play = _bPlay,
no_ads = _noAds,
purchased = _purchased,
};
var json = JsonUtility.ToJson(model);
if (!string.IsNullOrEmpty(json))
{
PlayerPrefs.SetString(SaveKey, json);
}
var json = JsonUtility.ToJson(this);
PlayerPrefs.SetString(SaveKey, json);
}
public void InitProperties()
{
_successLevel = new BindableProperty<int>(b_level, OnLevelChanged);
_totalPlayed = new BindableProperty<int>(b_play, OnPlayedChanged);
_purchasedCount = new BindableProperty<int>(buy_count, OnPurchasedNumChanged);
}
/// <summary>
/// 保存数据
/// </summary>
/// <param name="forceSave"></param>
public void Save(bool forceSave = false)
/// <param name="force"></param>
public void Save(bool force = false)
{
SetToMemory(); // 每次保存都要设置到 PlayerPrefs 内
bool shouldWriteToDisk = forceSave || (DateTime.Now - _lastSavedTime)>= TimeSpan.FromSeconds(SaveInterval);
if (!shouldWriteToDisk) return;
_lastSavedTime = DateTime.Now; // 更新最后保存时间
PlayerPrefs.Save(); // 写入到磁盘
}
#endregion
#region 订单记录
public bool HasPurchasedProduct(string receipt)
{
if(_purchased.Count == 0) return false;
return _purchased.Exists(p => p.receipt == receipt);
}
/// <summary>
/// 添加已支付订单
/// </summary>
/// <param name="receipt"></param>
/// <param name="productName"></param>
/// <param name="productId"></param>
/// <param name="appleProductIsRestored"></param>
public void AddReceipt(string receipt, string productName, string productId, bool appleProductIsRestored = false)
{
if (!HasPurchasedProduct(receipt))
bool save = force || (Time.realtimeSinceStartup - _lastSavedTime>= SaveInterval);
if (save)
{
_purchased.Add(new PurchasedProduct()
{
receipt = receipt,
productName = productName,
productId = productId,
appleProductIsRestored = appleProductIsRestored
});
Save();
_lastSavedTime = Time.realtimeSinceStartup;
SaveToPlayerPrefs();
}
}
public string[] GetReceipts(string productName)
{
var receipts = new List<string>();
receipts.AddRange(from purchasedProduct in _purchased where purchasedProduct.productName == productName select purchasedProduct.receipt);
return receipts.ToArray();
}
public string[] GetReceiptsById(string productId)
{
var receipts = new List<string>();
receipts.AddRange(from purchasedProduct in _purchased where purchasedProduct.productId == productId select purchasedProduct.receipt);
return receipts.ToArray();
}
#endregion
#region 清除数据
public void ClearData()
#region 数据绑定变化
private void OnLevelChanged(int value)
{
PlayerPrefs.DeleteKey(SaveKey);
b_level = value;
Save();
}
private void OnPlayedChanged(int value)
{
b_play = value;
Save();
}
private void OnPurchasedNumChanged(int value)
{
buy_count = value;
Save();
}
#endregion
#region 启动配置
/// <summary>
/// 从 Streaming 加载 AppServices 配置
/// </summary>
/// <returns></returns>
public string LoadAppServicesConfigJson()
{
try
{
string path = Path.Combine(Application.streamingAssetsPath, $"{GuruSDK.ServicesConfigKey}.{GuruSDK.ServicesConfigExtension}");
return File.ReadAllText(path);
}
catch (Exception e)
{
Log.Exception(e);
}
return "";
}
#endregion
}

View File

@ -1,556 +0,0 @@
namespace Guru
{
using UnityEngine;
using System;
using Guru.Notification;
public partial class GuruSDK
{
private const float CONSENT_FLOW_TIMEOUT = 10; // Consent Flow 超时时间(秒)
private static AdsInitSpec _adInitSpec;
/// <summary>
/// 启动广告服务
/// </summary>
public static void StartAds(AdsInitSpec spec = null)
{
_adInitSpec = spec;
if (InitConfig.UseCustomConsent)
{
Debug.Log($"{Tag} --- Call <color=orange>StartAdsWithCustomConsent</color> when you use custom consent, and pass the result (boolean) to the method.");
return;
}
// 默认的启动顺序是先启动Consent后, 根据用户回调的结果来启动广告
Instance.StartConsentFlow();
}
/// <summary>
/// 是否已经购买了去广告
/// </summary>
/// <param name="buyNoAds"></param>
public static void StartAds(bool buyNoAds = false)
{
StartAds(AdsInitSpec.BuildWithNoAds()); // 按照默认的去广告来生成广告启动配置
}
/// <summary>
/// 使用自定义的Consent, 获取用户授权后, 调用此方法
/// </summary>
/// <param name="userAllow"></param>
/// <param name="consentName">Consent 引导的类型, 如果使用了 MAX 的 consent 请填写 max </param>
/// <param name="spec">广告启动配置</param>
public static void StartAdsWithCustomConsent(bool userAllow = true,
string consentName = "custom", AdsInitSpec spec = null)
{
#if UNITY_IOS
_attType = consentName;
InitAttStatus();
#endif
if (userAllow)
{
#if UNITY_IOS
Instance.CheckAttStatus();
#else
StartAdService(spec);
#endif
}
else
{
Debug.Log($"{Tag} --- User refuse to provide ads Id, Ads Service will be cancelled");
}
}
/// <summary>
/// 使用自定义的Consent, 获取用户授权后, 调用此方法
/// </summary>
/// <param name="userAllow">自定义 Consent 的用户授权结果</param>
/// <param name="consentName">Consent引导名称</param>
/// <param name="buyNoAds">是否已经购买了去广告</param>
public static void StartAdsWithCustomConsent(bool userAllow = true, string consentName = "custom",
bool buyNoAds = false)
{
StartAdsWithCustomConsent(userAllow, consentName, AdsInitSpec.BuildWithNoAds());
}
#region Guru Consent
private bool _hasConsentCalled = false;
private bool _adServiceHasStarted = false;
private string _notiStatue = "";
private bool _hasNotiGranted = false;
/// <summary>
/// 启动Consent流程
/// 因为之后规划广告流程会放在 Consent 初始化之后, 因此请求广告的时候会需要先请求 Consent
/// </summary>
private void StartConsentFlow()
{
LogI($"#4.5 --- StartConsentFlow ---");
float time = 1;
if (!_adServiceHasStarted && _appServicesConfig != null)
{
time = _appServicesConfig.IsAdsCompliance() ? CONSENT_FLOW_TIMEOUT : 1f; // 启动合规判定后, 延迟最多 10 秒后启动广告
}
Delay(time, AdServiceHandler); // 广告延迟启动
if (_hasConsentCalled) return;
_hasConsentCalled = true;
bool enableCountryCheck = false;
string dmaMapRule = "";
if (_appServicesConfig != null && _appServicesConfig.parameters != null)
{
enableCountryCheck = _appServicesConfig.DMACountryCheck();
dmaMapRule = _appServicesConfig.DMAMapRule();
}
#if UNITY_IOS
InitAttStatus(); // Consent 启动前记录 ATT 初始值
#endif
UnityEngine.Debug.Log($"{Tag} --- Call:StartConsentFlow ---");
GuruConsent.StartConsent(OnGuruConsentOver, dmaMapRule:dmaMapRule, enableCountryCheck:enableCountryCheck);
}
/// <summary>
/// Guru Consent flow is Over
/// </summary>
/// <param name="code"></param>
private void OnGuruConsentOver(int code)
{
// 无论状态如何, 都在回调内启动广告初始化
AdServiceHandler();
// 调用回调
Callbacks.ConsentFlow.InvokeOnConsentResult(code);
#if UNITY_IOS
CheckAttStatus(); // [iOS] Consent 启动后检查 ATT 初始值
#elif UNITY_ANDROID
CheckNotiPermission(); // Consent 回调后检查 Notification 权限
#endif
// 内部处理后继逻辑
switch(code)
{
case GuruConsent.StatusCode.OBTAINED:
case GuruConsent.StatusCode.NOT_AVAILABLE:
// 已获取授权, 或者地区不可用, ATT 尚未启动
// TODO: 添加后继处理逻辑
break;
}
}
/// <summary>
/// 启动广告服务
/// </summary>
private void AdServiceHandler()
{
if (_adServiceHasStarted) return;
_adServiceHasStarted = true;
StartAdService(_adInitSpec);
}
#endregion
#region IOS ATT 广告授权流程
#if UNITY_IOS
private static string _initialAttStatus;
private static String _attType = "admob";
private static bool _autoReCallAtt = false;
/// <summary>
/// 显示系统的 ATT 弹窗
/// </summary>
public void RequestAttDialog()
{
LogI($"RequestATTDialog");
ATTManager.RequestATTDailog(ReportAttStatus);
}
/// <summary>
/// 初始化 ATT 状态
/// </summary>
public static void InitAttStatus()
{
_attType = InitConfig.UseCustomConsent ? ATTManager.GUIDE_TYPE_CUSTOM : ATTManager.GUIDE_TYPE_ADMOB; // 点位属性确定
_initialAttStatus = ATTManager.GetStatus();
SetATTStatus(_initialAttStatus); // 上报一个初始的状态
}
/// <summary>
/// iOS 平台检查 ATT 状态
/// </summary>
private void CheckAttStatus(bool autoReCall = false)
{
_autoReCallAtt = autoReCall;
// Delay 1s to get the user choice
Delay(1, () => ATTManager.CheckStatus(ReportAttStatus));
}
private void ReportAttStatus(string status)
{
LogI($"{Tag} --- Get Att status:{status} att Type:{_attType} recall:{_autoReCallAtt}");
SetATTStatus(_initialAttStatus); // 上报一个初始的状态
// SetUserProperty(Analytics.ParameterATTStatus, status); // 当前的状态
if (!string.IsNullOrEmpty(status)
&& status != _initialAttStatus
&& status != ATTManager.ATT_STATUS_NOT_DETERMINED)
{
// 上报点位:
SetATTStatus(_initialAttStatus);
}
switch(status)
{
case ATTManager.ATT_STATUS_NOT_DETERMINED:
// ATT 状态未知, 请求弹窗
if(_autoReCallAtt) RequestAttDialog();
break;
case ATTManager.ATT_STATUS_RESTRICTED:
case ATTManager.ATT_STATUS_DENIED:
// ATT 状态受限, 或者被拒绝
break;
case ATTManager.ATT_STATUS_AUTHORIZED:
// ATT 状态已授权
break;
}
CheckNotiPermission(); // Consent 回调后检查 Notification 权限
}
#endif
#endregion
#region Notification Permission Check
/// <summary>
/// 初始化 Noti Service
/// </summary>
private void InitNotiPermission()
{
// bool hasNotiGranted = false;
_notiStatue = "no_determined";
NotificationService.Initialize(); // 初始化 Noti 服务
Analytics.SetNotiPerm(NotificationService.GetStatus());
}
/// <summary>
/// 检查 Noti 状态
/// </summary>
private void CheckNotiPermission()
{
var status = NotificationService.GetStatus();
// 如果未启用自动 Noti 授权,则直接上报状态
if (!_initConfig.AutoNotificationPermission)
{
Debug.LogWarning($"[SDK] ---- AutoNotificationPermission is OFF, Project should request permission own.");
return;
}
bool isGranted = NotificationService.IsPermissionGranted();
Debug.Log($"[SDK] ---- Check Noti Permission: {isGranted}");
if (isGranted)
{
Debug.Log($"[SDK] ---- Set Notification Permission: {status}");
Analytics.SetNotiPerm(status);
return;
}
RequestNotificationPermission(); // 请求授权
}
/// <summary>
/// 请求推送授权
/// </summary>
/// <param name="callback"></param>
public static void RequestNotificationPermission(Action<string> callback = null)
{
FirebaseUtil.StartFetchFcmToken();
Debug.Log($"[SDK] ---- RequestNotificationPermission");
NotificationService.RequestPermission(status =>
{
Debug.Log($"[SDK] ---- Set Notification Permission: {status}");
if(!string.IsNullOrEmpty(status)) Analytics.SetNotiPerm(status);
callback?.Invoke(status);
});
}
/// <summary>
/// 获取 Notification 状态值
/// </summary>
/// <returns></returns>
public static string GetNotificationStatus()
{
return NotificationService.GetStatus();
}
/// <summary>
/// 用户是否已经获取了 Notification 授权了
/// </summary>
/// <returns></returns>
public static bool IsNotificationPermissionGranted()
{
return NotificationService.IsPermissionGranted();
}
#endregion
#region Ad Services
private static bool _initAdsCompleted = false;
public static bool IsAdsReady => _initAdsCompleted;
private static int _preBannerAction = 0;
public static AdsInitSpec GetDefaultAdsSpec()
{
return AdsInitSpec.BuildDefault(InitConfig.AutoLoadWhenAdsReady, IsDebugMode);
}
/// <summary>
/// 启动广告服务
/// </summary>
private static void StartAdService(AdsInitSpec spec = null)
{
//---------- Using InitConfig ----------
if (InitConfig is { IsBuyNoAds: true }) SetBuyNoAds(true);
LogI($"StartAdService");
if (spec == null)
{
spec = AdsInitSpec.BuildDefault(InitConfig.AutoLoadWhenAdsReady, IsDebugMode);
if (ADService.Instance.IsBuyNoAds)
{
spec = AdsInitSpec.BuildWithNoAds(InitConfig.AutoLoadWhenAdsReady, IsDebugMode);
}
}
if(InitConfig != null && !string.IsNullOrEmpty(InitConfig.BannerBackgroundColor))
spec.bannerColorHex = InitConfig.BannerBackgroundColor;
//--------- Add Callbacks -----------
// BADS
ADService.Instance.OnBannerStartLoad = OnBannerStartLoad;
ADService.Instance.OnBannerLoaded = OnBannerLoaded;
// IADS
ADService.Instance.OnInterstitialStartLoad = OnInterstitialStartLoad;
ADService.Instance.OnInterstitialLoaded = OnInterstitialLoaded;
ADService.Instance.OnInterstitialFailed = OnInterstitialFailed;
ADService.Instance.OnInterstitialClosed = OnInterstitialClosed;
// RADS
ADService.Instance.OnRewardedStartLoad = OnRewardStartLoad;
ADService.Instance.OnRewardLoaded = OnRewardLoaded;
ADService.Instance.OnRewardFailed = OnRewardFailed;
ADService.Instance.OnRewardClosed = OnRewardClosed;
// ---------- Start Services ----------
ADService.Instance.StartService(OnAdsInitComplete, spec);
// ---------- Life Cycle ----------
Callbacks.App.OnAppPaused += OnAppPaused;
}
/// <summary>
/// 生命周期回调
/// </summary>
/// <param name="paused"></param>
private static void OnAppPaused(bool paused)
{
if(ADService.Instance.IsInitialized)
ADService.Instance.OnAppPaused(paused);
}
private static void OnBannerStartLoad(string adUnitId)
=> Callbacks.Ads.InvokeOnBannerADStartLoad(adUnitId);
private static void OnBannerLoaded()
=> Callbacks.Ads.InvokeOnBannerADLoaded();
private static void OnInterstitialStartLoad(string adUnitId)
=> Callbacks.Ads.InvokeOnInterstitialADStartLoad(adUnitId);
private static void OnInterstitialLoaded()
=> Callbacks.Ads.InvokeOnInterstitialADLoaded();
private static void OnInterstitialFailed()
=> Callbacks.Ads.InvokeOnInterstitialADFailed();
private static void OnInterstitialClosed()
=> Callbacks.Ads.InvokeOnInterstitialADClosed();
private static void OnRewardStartLoad(string adUnitId)
=> Callbacks.Ads.InvokeOnRewardedADStartLoad(adUnitId);
private static void OnRewardLoaded()
=> Callbacks.Ads.InvokeOnRewardedADLoaded();
private static void OnRewardFailed()
=> Callbacks.Ads.InvokeOnRewardADFailed();
private static void OnRewardClosed()
=> Callbacks.Ads.InvokeOnRewardADClosed();
private static void OnAdsInitComplete()
{
_initAdsCompleted = true;
if (_adInitSpec != null && _adInitSpec.loadBanner)
{
// 预制动作处理
if (_preBannerAction == 1)
{
_preBannerAction = 0;
ShowBanner();
}
else if (_preBannerAction == -1)
{
_preBannerAction = 0;
HideBanner();
}
}
Callbacks.Ads.InvokeOnAdsInitComplete();
}
private static bool CheckAdsReady()
{
if (!IsAdsReady)
{
LogW("[SDK] Ads is not ready. Call <GuruSDk.StartAdService> first.");
return false;
}
return true;
}
/// <summary>
/// 显示Banner广告
/// </summary>
/// <param name="placement"></param>
public static void ShowBanner(string placement = "")
{
if (!CheckAdsReady())
{
_preBannerAction = 1;
return;
}
ADService.Instance.ShowBanner(placement);
}
/// <summary>
/// 设置 Banner 背景颜色
/// </summary>
/// <param name="color"></param>
public static void SetBannerBackgroundColor(Color color)
{
// if (!CheckAdsReady()) return;
ADService.Instance.SetBannerBackgroundColor(color);
}
public static void SetBannerAutoRefresh(bool value = true)
{
if (!CheckAdsReady()) return;
ADService.Instance.SetBannerAutoRefresh(value);
}
/// <summary>
/// 隐藏Banner广告
/// </summary>
public static void HideBanner()
{
if (!CheckAdsReady())
{
_preBannerAction = -1;
return;
}
ADService.Instance.HideBanner();
}
public static void LoadInterstitialAd()
{
if (!CheckAdsReady()) return;
ADService.Instance.RequestInterstitialAD();
}
public static bool IsInterstitialAdReady => ADService.Instance.IsInterstitialADReady();
/// <summary>
/// 显示插屏广告
/// </summary>
/// <param name="placement"></param>
/// <param name="onDismissed"></param>
public static void ShowInterstitialAd(string placement = "", Action onDismissed = null)
{
if (!CheckAdsReady()) return;
if (!ADService.Instance.IsInterstitialADReady())
{
LogE("Interstitial is not ready. Call <GuruSDk.ShowInterstitialAd> again.");
LoadInterstitialAd();
return;
}
ADService.Instance.ShowInterstitialAD(placement, onDismissed);
}
public static void LoadRewardAd()
{
if (!CheckAdsReady()) return;
ADService.Instance.RequestRewardedAD();
}
public static bool IsRewardAdReady => ADService.Instance.IsRewardedADReady();
/// <summary>
/// 显示激励视频广告
/// </summary>
/// <param name="placement"></param>
/// <param name="onRewarded"></param>
/// <param name="onFailed"></param>
public static void ShowRewardAd(string placement = "", Action onRewarded = null, Action<string> onFailed = null)
{
if (!CheckAdsReady()) return;
if (!ADService.Instance.IsRewardedADReady())
{
LogE("RewardAd is not ready. Call <GuruSDk.LoadRewardAd> again.");
LoadRewardAd();
return;
}
ADService.Instance.ShowRewardedAD(placement, onRewarded, onFailed);
}
#endregion
#region MaxServices
/// <summary>
/// 显示Max调试菜单
/// </summary>
public static void ShowMaxDebugPanel()
{
#if UNITY_EDITOR
LogI($"Can not show Max Debug Panel in Editor, skipped.");
return;
#endif
if (!ADService.Instance.IsInitialized)
{
LogI($"ADService is not initialized, call <GuruSDK.StartAds> first.");
return;
}
ADService.Instance.ShowMaxDebugPanel();
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,378 +0,0 @@
namespace Guru
{
using System;
public partial class GuruSDK
{
/// <summary>
/// 回调参数类
/// </summary>
public class Callbacks
{
/// <summary>
/// APP 事件
/// </summary>
public static class App
{
private static Action<bool> _onAppPaused;
public static event Action<bool> OnAppPaused
{
add => _onAppPaused += value;
remove => _onAppPaused -= value;
}
internal static void InvokeOnAppPaused(bool isPaused)
{
_onAppPaused?.Invoke(isPaused);
}
private static Action _onAppQuit;
public static event Action OnAppQuit
{
add => _onAppQuit += value;
remove => _onAppQuit -= value;
}
internal static void InvokeOnAppQuit()
{
_onAppQuit?.Invoke();
}
}
/// <summary>
/// GDPR Consent
/// </summary>
public static class ConsentFlow
{
/// <summary>
/// 当Consent启动结束后返回状态码
/// </summary>
public static event Action<int> OnConsentResult
{
add => _onConsentResult += value;
remove => _onConsentResult -= value;
}
private static Action<int> _onConsentResult;
internal static void InvokeOnConsentResult(int code)
{
_onConsentResult?.Invoke(code);
}
/// <summary>
/// ATT 状态返回
/// </summary>
public static event Action<int> OnAttResult
{
add => _onAttResult += value;
remove => _onAttResult -= value;
}
private static Action<int> _onAttResult;
internal static void InvokeOnAttResultCallback(int code)
{
_onAttResult?.Invoke(code);
}
}
/// <summary>
/// 广告回调
/// </summary>
public static class Ads
{
private static Action _onAdsInitComplete;
public static event Action OnAdsInitComplete
{
add => _onAdsInitComplete += value;
remove => _onAdsInitComplete -= value;
}
internal static void InvokeOnAdsInitComplete()
{
_onAdsInitComplete?.Invoke();
}
//------------ BANNER -----------------
private static Action<string> _onBannerADStartLoad;
public static event Action<string> OnBannerADStartLoad
{
add => _onBannerADStartLoad += value;
remove => _onBannerADStartLoad -= value;
}
internal static void InvokeOnBannerADStartLoad(string adUnitId)
{
_onBannerADStartLoad?.Invoke(adUnitId);
}
private static Action _onBannerADLoaded;
public static event Action OnBannerADLoaded
{
add => _onBannerADLoaded += value;
remove => _onBannerADLoaded -= value;
}
internal static void InvokeOnBannerADLoaded()
{
_onBannerADLoaded?.Invoke();
}
//------------ INTER -----------------
private static Action<string> _onInterstitialADStartLoad;
public static event Action<string> OnInterstitialADStartLoad
{
add => _onInterstitialADStartLoad += value;
remove => _onInterstitialADStartLoad -= value;
}
internal static void InvokeOnInterstitialADStartLoad(string adUnitId)
{
_onInterstitialADStartLoad?.Invoke(adUnitId);
}
private static Action _onInterstitialADLoaded;
public static event Action OnInterstitialADLoaded
{
add => _onInterstitialADLoaded += value;
remove => _onInterstitialADLoaded -= value;
}
internal static void InvokeOnInterstitialADLoaded()
{
_onInterstitialADLoaded?.Invoke();
}
private static Action _onInterstitialADFailed;
public static event Action OnInterstitialADFailed
{
add => _onInterstitialADFailed += value;
remove => _onInterstitialADFailed -= value;
}
internal static void InvokeOnInterstitialADFailed()
{
_onInterstitialADFailed?.Invoke();
}
private static Action _onInterstitialADClosed;
public static event Action OnInterstitialADClosed
{
add => _onInterstitialADClosed += value;
remove => _onInterstitialADClosed -= value;
}
internal static void InvokeOnInterstitialADClosed()
{
_onInterstitialADClosed?.Invoke();
}
//------------ REWARD -----------------
private static Action<string> _onRewardedADStartLoad;
public static event Action<string> OnRewardedADStartLoad
{
add => _onRewardedADStartLoad += value;
remove => _onRewardedADStartLoad -= value;
}
internal static void InvokeOnRewardedADStartLoad(string adUnitId)
{
_onRewardedADStartLoad?.Invoke(adUnitId);
}
private static Action _onRewardedADLoaded;
public static event Action OnRewardedADLoaded
{
add => _onRewardedADLoaded += value;
remove => _onRewardedADLoaded -= value;
}
internal static void InvokeOnRewardedADLoaded()
{
_onRewardedADLoaded?.Invoke();
}
private static Action _onRewardADClosed;
public static event Action OnRewardedADClosed
{
add => _onRewardADClosed += value;
remove => _onRewardADClosed -= value;
}
internal static void InvokeOnRewardADClosed()
{
_onRewardADClosed?.Invoke();
}
private static Action _onRewardADFailed;
public static event Action OnRewardADFailed
{
add => _onRewardADFailed += value;
remove => _onRewardADFailed -= value;
}
internal static void InvokeOnRewardADFailed()
{
_onRewardADFailed?.Invoke();
}
}
/// <summary>
/// 云控参数
/// </summary>
public static class Remote
{
private static Action<bool> _onRemoteFetchComplete;
public static event Action<bool> OnRemoteFetchComplete
{
add => _onRemoteFetchComplete += value;
remove => _onRemoteFetchComplete -= value;
}
internal static void InvokeOnRemoteFetchComplete(bool success)
{
_onRemoteFetchComplete?.Invoke(success);
}
}
/// <summary>
/// 支付回调
/// </summary>
public static class IAP
{
private static Action _onIAPInitStart;
public static event Action OnIAPInitStart
{
add => _onIAPInitStart += value;
remove => _onIAPInitStart -= value;
}
internal static void InvokeOnIAPInitStart()
{
_onIAPInitStart?.Invoke();
}
private static Action<bool> _onIAPInitComplete;
public static event Action<bool> OnIAPInitComplete
{
add => _onIAPInitComplete += value;
remove => _onIAPInitComplete -= value;
}
internal static void InvokeOnIAPInitComplete(bool success)
{
_onIAPInitComplete?.Invoke(success);
}
private static Action<string> _onPurchaseStart;
public static event Action<string> OnPurchaseStart
{
add => _onPurchaseStart += value;
remove => _onPurchaseStart -= value;
}
internal static void InvokeOnPurchaseStart(string productId)
{
_onPurchaseStart?.Invoke(productId);
}
private static Action<string, bool> _onPurchaseEnd;
public static event Action<string, bool> OnPurchaseEnd
{
add => _onPurchaseEnd += value;
remove => _onPurchaseEnd -= value;
}
internal static void InvokeOnPurchaseEnd(string productId, bool success)
{
_onPurchaseEnd?.Invoke(productId, success);
}
private static Action<string, string> _onPurchaseFailed;
public static event Action<string, string> OnPurchaseFailed
{
add => _onPurchaseFailed += value;
remove => _onPurchaseFailed -= value;
}
internal static void InvokeOnPurchaseFailed(string productId, string error)
{
_onPurchaseFailed?.Invoke(productId, error);
}
private static Action<bool, string> _onIAPRestored;
public static event Action<bool, string> OnIAPRestored
{
add => _onIAPRestored += value;
remove => _onIAPRestored -= value;
}
internal static void InvokeOnIAPRestored(bool success, string productId)
{
_onIAPRestored?.Invoke(success, productId);
}
}
public static class SDK
{
private static Action<bool> _onFirebaseReady;
public static event Action<bool> OnFirebaseReady
{
add => _onFirebaseReady += value;
remove => _onFirebaseReady -= value;
}
internal static void InvokeOnFirebaseReady(bool success)
{
_onFirebaseReady?.Invoke(success);
}
private static Action _onGuruServiceReady;
public static event Action OnGuruServiceReady
{
add => _onGuruServiceReady += value;
remove => _onGuruServiceReady -= value;
}
internal static void InvokeOnGuruServiceReady()
{
_onGuruServiceReady?.Invoke();
}
private static Action<bool> _onDebuggerDisplayed;
public static event Action<bool> OnDisplayDebugger
{
add => _onDebuggerDisplayed += value;
remove => _onDebuggerDisplayed -= value;
}
internal static void InvokeOnDebuggerDisplayed(bool success)
{
_onDebuggerDisplayed?.Invoke(success);
}
private static Action<bool> _onUserAuthResult;
public static event Action<bool> OnGuruUserAuthResult
{
add => _onUserAuthResult += value;
remove => _onUserAuthResult -= value;
}
internal static void InvokeOnGuruUserAuthResult(bool success)
{
_onUserAuthResult?.Invoke(success);
}
// DeepLink 回调
private static Action<string> _onDeeplinkCallback;
public static event Action<string> OnDeeplinkCallback
{
add => _onDeeplinkCallback += value;
remove => _onDeeplinkCallback -= value;
}
internal static void InvokeDeeplinkCallback(string deeplink)
{
_onDeeplinkCallback?.Invoke(deeplink);
}
// TODO: 之后需要添加 define 宏来控制是否可用
// Firebase Auth 回调
private static Action<bool, Firebase.Auth.FirebaseUser> _onFirebaseUserAuthResult;
public static event Action<bool, Firebase.Auth.FirebaseUser> OnFirebaseUserAuthResult
{
add => _onFirebaseUserAuthResult += value;
remove => _onFirebaseUserAuthResult -= value;
}
internal static void InvokeOnFirebaseAuthResult(bool success, Firebase.Auth.FirebaseUser firebaseUser = null)
{
_onFirebaseUserAuthResult?.Invoke(success, firebaseUser);
}
}
}
}
}

View File

@ -1,282 +0,0 @@
namespace Guru
{
public partial class GuruSDK
{
/// <summary>
/// Consts values
/// </summary>
public static class Consts
{
#region Firebase Defines
public const string EventAdImpression = "ad_impression";
public const string EventAddPaymentInfo = "add_payment_info";
public const string EventAddShippingInfo = "add_shipping_info";
public const string EventAddToCart = "add_to_cart";
public const string EventAddToWishlist = "add_to_wishlist";
public const string EventAppOpen = "app_open";
public const string EventBeginCheckout = "begin_checkout";
public const string EventCampaignDetails = "campaign_details";
public const string EventEarnVirtualCurrency = "earn_virtual_currency";
public const string EventGenerateLead = "generate_lead";
public const string EventJoinGroup = "join_group";
public const string EventLevelEnd = "level_end";
public const string EventLevelStart = "level_start";
public const string EventLevelEndSuccessPrefix = "level_end_success_";
public const string EventLevelUp = "level_up";
public const string EventLogin = "login";
public const string EventPostScore = "post_score";
public const string EventPurchase = "purchase";
public const string EventRefund = "refund";
public const string EventRemoveFromCart = "remove_from_cart";
public const string EventScreenView = "screen_view";
public const string EventSearch = "search";
public const string EventSelectContent = "select_content";
public const string EventSelectItem = "select_item";
public const string EventSelectPromotion = "select_promotion";
public const string EventShare = "share";
public const string EventSignUp = "sign_up";
public const string EventSpendVirtualCurrency = "spend_virtual_currency";
public const string EventUnlockAchievement = "unlock_achievement";
public const string EventHpPoints = "hp_points";
public const string EventAttReguideImp = "att_reguide_imp";
public const string EventAttReguideClk = "att_reguide_clk";
public const string EventAttReguideResult = "att_reguide_result";
public const string EventTutorialBegin = "tutorial_begin";
public const string EventTutorialImp= "tutorial_{0}_imp";
public const string EventTutorialNextClick= "tutorial_{0}_next_clk";
public const string EventTutorialComplete= "tutorial_complete";
public const string EventTutorialClose = "tutorial_close";
public const string EventNotiPermImp = "noti_perm_imp";
public const string EventNotiPermResult = "noti_perm_result";
public const string EventNotiPermRationaleImp = "noti_perm_rationale_imp";
public const string EventNotiPermRationaleResult = "noti_perm_rationale_result";
public const string EventDevAudit = "dev_audit";
public const string EventViewCart = "view_cart";
public const string EventViewItem = "view_item";
public const string EventViewItemList = "view_item_list";
public const string EventViewPromotion = "view_promotion";
public const string EventViewSearchResults = "view_search_results";
public const string ParameterAchievementId = "achievement_id";
public const string ParameterAdFormat = "ad_format";
public const string ParameterAdNetworkClickID = "aclid";
public const string ParameterAdPlatform = "ad_platform";
public const string ParameterAdSource = "ad_source";
public const string ParameterAdUnitName = "ad_unit_name";
public const string ParameterAffiliation = "affiliation";
public const string ParameterCP1 = "cp1";
public const string ParameterCampaign = "campaign";
public const string ParameterCharacter = "character";
public const string ParameterContent = "content";
public const string ParameterContentType = "content_type";
public const string ParameterCoupon = "coupon";
public const string ParameterCreativeName = "creative_name";
public const string ParameterCreativeSlot = "creative_slot";
public const string ParameterCurrency = "currency";
public const string ParameterDestination = "destination";
public const string ParameterDiscount = "discount";
public const string ParameterEndDate = "end_date";
public const string ParameterExtendSession = "extend_session";
public const string ParameterFlightNumber = "flight_number";
public const string ParameterGroupId = "group_id";
public const string ParameterIndex = "index";
public const string ParameterItemBrand = "item_brand";
public const string ParameterItemCategory = "item_category";
public const string ParameterItemCategory2 = "item_category2";
public const string ParameterItemCategory3 = "item_category3";
public const string ParameterItemCategory4 = "item_category4";
public const string ParameterItemCategory5 = "item_category5";
public const string ParameterItemId = "item_id";
public const string ParameterItemList = "item_list";
public const string ParameterItemListID = "item_list_id";
public const string ParameterItemListName = "item_list_name";
public const string ParameterItemName = "item_name";
public const string ParameterLevel = "level";
public const string ParameterLevelName = "level_name";
public const string ParameterLocation = "location";
public const string ParameterLocationID = "location_id";
public const string ParameterMedium = "medium";
public const string ParameterMethod = "method";
public const string ParameterNumberOfNights = "number_of_nights";
public const string ParameterNumberOfPassengers = "number_of_passengers";
public const string ParameterNumberOfRooms = "number_of_rooms";
public const string ParameterOrigin = "origin";
public const string ParameterPaymentType = "payment_type";
public const string ParameterPrice = "price";
public const string ParameterPromotionID = "promotion_id";
public const string ParameterPromotionName = "promotion_name";
public const string ParameterQuantity = "quantity";
public const string ParameterScore = "score";
public const string ParameterScreenClass = "screen_class";
public const string ParameterScreenName = "screen_name";
public const string ParameterSearchTerm = "search_term";
public const string ParameterShipping = "shipping";
public const string ParameterShippingTier = "shipping_tier";
public const string ParameterSignUpMethod = "sign_up_method";
public const string ParameterSource = "source";
public const string ParameterStartDate = "start_date";
public const string ParameterSuccess = "success";
public const string ParameterTax = "tax";
public const string ParameterTerm = "term";
public const string ParameterTransactionId = "transaction_id";
public const string ParameterTravelClass = "travel_class";
public const string ParameterValue = "value";
#endregion
#region Guru BI Events & Parameters
public const string TAG = "Analytics";
// 美元符号
public const string USD = "USD";
// 广告平台
public const string AdMAX = "MAX";
//IAP打点事件
public const string EventIAPFirst = "first_iap";
public const string EventIAPImp = "iap_imp";
public const string EventIAPClose = "iap_close";
public const string EventIAPClick = "iap_clk";
public const string EventIAPReturnTrue = "iap_ret_true";
public const string EventIAPReturnFalse = "iap_ret_false";
//横幅广告打点事件
public const string EventBadsLoad = "bads_load";
public const string EventBadsLoaded = "bads_loaded";
public const string EventBadsFailed = "bads_failed";
public const string EventBadsClick = "bads_clk";
public const string EventBadsImp = "bads_imp";
//插屏广告打点事件
public const string EventIadsLoad = "iads_load";
public const string EventIadsLoaded = "iads_loaded";
public const string EventIadsFailed = "iads_failed";
public const string EventIadsImp = "iads_imp";
public const string EventIadsClick = "iads_clk";
public const string EventIadsClose = "iads_close";
//激励视频广告打点事件
public const string EventRadsLoad = "rads_load";
public const string EventRadsLoaded = "rads_loaded";
public const string EventRadsFailed = "rads_failed";
public const string EventRadsImp = "rads_imp";
public const string EventRadsRewarded = "rads_rewarded";
public const string EventRadsClick = "rads_clk";
public const string EventRadsClose = "rads_close";
public const string EventFirstRadsRewarded = "first_rads_rewarded";
//广告收益打点事件
public const string EventTchAdRev001Impression = "tch_ad_rev_roas_001";
public const string EventTchAdRev02Impression = "tch_ad_rev_roas_02";
public const string EventTchAdRevAbnormal = "tch_ad_rev_value_abnormal";
//内购成功事件上报
public const string EventIAPPurchase = "iap_purchase";
public const string EventSubPurchase = "sub_purchase";
public const string IAPStoreCategory = "Store";
public const string IAPTypeProduct = "product";
public const string IAPTypeSubscription = "subscription";
//打点参数名
public const string ParameterResult = "result";
public const string ParameterStep = "step";
public const string ParameterDuration = "duration";
public const string ParameterErrorCode = "error_code";
public const string ParameterProductId = "product_id";
public const string ParameterPlatform = "platform";
public const string ParameterStartType = "start_type"; // 游戏启动类型
public const string ParameterReplay = "replay"; // 游戏重玩
public const string ParameterContinue = "continue"; // 游戏继续
// 评价参数
public const string EventRateImp = "rate_imp"; // 评价弹窗展示
public const string EventRateNow = "rate_now"; // 点击评分引导弹窗中的评分
//打点内部执行错误
public static string ParameterEventError => "event_error";
//ios ATT打点
public const string ATTGuideShow = "att_guide_show";
public const string ATTGuideOK = "att_guide_ok";
public const string ATTWindowShow = "att_window_show";
public const string ATTOptIn = "att_opt_in";
public const string ATTOpOut = "att_opt_out";
public const string ParameterATTStatus = "att_status";
public const string EventAttResult = "att_result";
// 用户属性
public const string PropertyFirstOpenTime = "first_open_time"; //用户第一次first_open的时间
public const string PropertyDeviceID = "device_id"; //用户的设备ID
public const string PropertyUserID = "user_id";
public const string PropertyLevel = "b_level"; //"每次完成通关上升一次显示用户完成的最大关卡数。只针对主关卡和主玩法的局数做累加初始值为0。"
public const string PropertyPlay = "b_play"; //每完成一局或者游戏触发,
public const string PropertyLastPlayedLevel = "last_played_level";
public const string PropertyGrade = "grade"; //当游戏玩家角色升级时触发
public const string PropertyIsIAPUser = "is_iap_user"; //付费成功后设置属性参数为true如果没有发生付费可以不用设置该属性
public const string PropertyIAPCoin = "iap_coin"; //付费所得的总金币数(iap获取累计值)\
public const string PropertyNonIAPCoin = "noniap_coin"; //非付费iap获取累计值
public const string PropertyCoin = "coin"; //当前金币数
public const string PropertyExp = "exp"; // 经验值
public const string PropertyHp = "hp"; // 生命值/体力
public const string PropertyNetwork = "network"; // 网络状态
public const string PropertyAndroidID = "android_id"; // Android 平台 AndroidID
public const string PropertyIDFV = "idfv"; // iOS 平台 IDFV
public const string PropertyPicture = "picture"; // 玩家在主线的mapid
public const string PropertyNoAds = "no_ads"; // 玩家是否去广告
public const string PropertyATTStatus = "att_status"; // ATT 状态
public const string PropertyNotiPerm = "noti_perm"; // Notification Permission 状态
public const string PropertyAdjustId = "adjust_id"; // AdjustId
public const string PropertyGDPR = "gdpr"; // GDPR状态
// 经济相关
public const string ParameterBalance = "balance"; // 用于余额
public const string ParameterDefaultScene = "in_game"; // 货币消费默认场景
public const string ParameterVirtualCurrencyName = "virtual_currency_name"; // 虚拟货币名称
public const string CurrencyNameProps = "props"; // props
public const string CurrencyCategoryReward = "reward"; // common, ads
public const string CurrencyCategoryIAP = "iap_buy"; // In app purchase
public const string CurrencyCategoryBonus = "bonus"; // ads+items, gift box, item group
public const string CurrencyCategoryIGC = "igc"; // In game currency
public const string CurrencyCategoryIGB = "igb"; // In game barter
public const string CurrencyCategoryProp = "prop"; // prop
public const string CurrencyCategoryProps = "props"; // props
public const string CurrencyCategoryBundle = "bundle"; // prop groups
public const string CurrencyCategoryBoost = "boost"; // boost
// SDK
public const string EventSDKInfo = "sdk_info";
//----------------- 关卡开始类型 ---------------------
public const string EventLevelStartModePlay = "play";
public const string EventLevelStartModeReplay = "replay";
public const string EventLevelStartModeContinue= "continue";
//----------------- 关卡结束类型 ---------------------
public const string EventLevelEndSuccess = "success";
public const string EventLevelEndFail = "fail";
public const string EventLevelEndExit = "exit";
public const string EventLevelEndTimeout = "timeout";
/// <summary>
/// 主线关卡类型
/// 只有传入此类型时才会进行 Blevel 的累加
/// </summary>
public const string LevelTypeMain = "main";
#endregion
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 35b764b09fe74efe8e2ebead9716279a
timeCreated: 1709802191

View File

@ -1,60 +0,0 @@
namespace Guru
{
public partial class GuruSDK
{
private const string K_CMD_NAME_DEBUGGER = "gurusdk.unity.dbg";
private const string K_CMD_NAME_WATERMARK = "gurusdk.unity.wm";
private const string K_CMD_NAME_CONSOLE = "gurusdk.unity.con";
#region Android 测试入口
/// <summary>
/// 启动 Android 测试配置
/// </summary>
private void StartAndroidDebugCmds()
{
if (string.IsNullOrEmpty(AppBundleId))
{
UnityEngine.Debug.LogError("--- App Bundle Id is empty, please set it first. ---");
return;
}
string val;
string key;
key = K_CMD_NAME_DEBUGGER;
val = AndroidSystemPropertiesHelper.Get(key);
if (val == AppBundleId)
{
// 显示应用调试状态栏
Debugger.ShowAdStatus();
}
key = K_CMD_NAME_WATERMARK;
val = AndroidSystemPropertiesHelper.Get(key);
if (val == AppBundleId)
{
// 显示应用水印
// TODO
}
key = K_CMD_NAME_CONSOLE;
val = AndroidSystemPropertiesHelper.Get(key);
if (val == AppBundleId)
{
// 显示控制台
}
}
private bool IsCmdAvailable(string value)
{
return value == "1" || value == "true";
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 36f2d28a30ff46568d6adc0b728a3268
timeCreated: 1714354376

View File

@ -1,129 +0,0 @@
namespace Guru
{
using UnityEngine;
using System;
public partial class GuruSDK
{
private static readonly bool _useBaseOptions = true;
private static GuruDebugger _debugger;
public static GuruDebugger Debugger
{
get
{
if (_debugger == null)
{
_debugger = GuruDebugger.Instance;
if (_useBaseOptions)
{
InitDebuggerLayout();
}
}
return _debugger;
}
}
/// <summary>
/// 显示广告状态
/// </summary>
public static bool ShowAdStatus()
{
if (!IsServiceReady) return false;
Debugger.ShowAdStatus();
return true;
}
/// <summary>
/// 显示 Debugger
/// </summary>
/// <returns></returns>
public static bool ShowDebugger()
{
if (!IsServiceReady) return false;
Debugger.ShowPage(); // 显示 Debugger 界面
return true;
}
private static void InitDebuggerLayout()
{
var settings = GuruSettings.Instance;
var v = GuruAppVersion.Load();
var app_version = (v == null ? $"{Application.version} (unknown)" : $"{v.version} ({v.code})");
var uid = (string.IsNullOrEmpty(UID) ? "NULL" : UID);
var device_id = (string.IsNullOrEmpty(DeviceId) ? "NULL" : DeviceId);
var push_token = (string.IsNullOrEmpty(PushToken) ? "NULL" : PushToken);
var auth_token = (string.IsNullOrEmpty(AuthToken) ? "NULL" : AuthToken);
var fid = (string.IsNullOrEmpty(FirebaseId) ? "NULL" : FirebaseId);
var adjust_id = (string.IsNullOrEmpty(AdjustId) ? "NULL" : AdjustId);
var idfa = (string.IsNullOrEmpty(IDFA) ? "NULL" : IDFA);
var gsid = (string.IsNullOrEmpty(GSADID) ? "NULL" : GSADID);
// ------------ Info Page --------------------
Debugger.AddOption("Info/Guru SDK", GuruSDK.Version);
Debugger.AddOption("Info/Unity Version", Application.unityVersion);
Debugger.AddOption("Info/Name", settings.ProductName);
Debugger.AddOption("Info/Bundle Id", settings.GameIdentifier);
Debugger.AddOption("Info/Version", app_version);
Debugger.AddOption("Info/Uid", uid).AddCopyButton();
Debugger.AddOption("Info/Device ID", device_id).AddCopyButton();
Debugger.AddOption("Info/Push Token", push_token).AddCopyButton();
Debugger.AddOption("Info/Auth Token", auth_token).AddCopyButton();
Debugger.AddOption("Info/Firebase Id", fid).AddCopyButton();
Debugger.AddOption("Info/Adjust Id", adjust_id).AddCopyButton();
Debugger.AddOption("Info/IDFA", idfa).AddCopyButton();
Debugger.AddOption("Info/GSADID", gsid).AddCopyButton();
Debugger.AddOption("Info/Debug Mode", GuruSDK.IsDebugMode? "true" : "false");
Debugger.AddOption("Info/Screen size", $"{Screen.width} x {Screen.height}");
// ------------ Ads Page --------------------
Debugger.AddOption("Ads/Show Ads Debug Panel", "", ShowMaxDebugPanel);
var badsId = settings.ADSetting.GetBannerID();
var iadsId = settings.ADSetting.GetInterstitialID();
var radsId = settings.ADSetting.GetRewardedVideoID();
Debugger.AddOption("Ads/Banner Id", badsId);
Debugger.AddOption("Ads/Interstitial Id", iadsId);
Debugger.AddOption("Ads/Rewarded Id", radsId);
GuruDebugger.OnClosed -= OnDebuggerClosed;
GuruDebugger.OnClosed += OnDebuggerClosed;
Callbacks.SDK.InvokeOnDebuggerDisplayed(true);
}
private static void OnDebuggerClosed()
{
GuruDebugger.OnClosed -= OnDebuggerClosed;
Callbacks.SDK.InvokeOnDebuggerDisplayed(false);
}
/// <summary>
/// 显示 Debugger
/// </summary>
/// <param name="debugger"></param>
/// <returns></returns>
public static bool ShowDebuggerWithData(out GuruDebugger debugger)
{
debugger = null;
bool res = ShowDebugger();
if (res)
{
debugger = GuruDebugger.Instance;
}
return res;
}
public static GuruDebugger.OptionLayout AddOption(string uri, string content = "", Action clickHandler = null)
{
return Debugger.AddOption(uri, content, clickHandler);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 002605b9e408487bb69d27d07dda016c
timeCreated: 1711078536

View File

@ -1,335 +0,0 @@
namespace Guru
{
using System;
using System.Linq;
public partial class GuruSDK
{
public static bool IsIAPReady = false;
//---------- 支付失败原因 ----------
public const string BuyFail_PurchasingUnavailable = "PurchasingUnavailable";
public const string BuyFail_Pending = "ExistingPurchasePending";
public const string BuyFail_ProductUnavailable = "ProductUnavailable";
public const string BuyFail_SignatureInvalid = "SignatureInvalid";
public const string BuyFail_UserCancelled = "UserCancelled";
public const string BuyFail_PaymentDeclined = "PaymentDeclined";
public const string BuyFail_DuplicateTransaction = "DuplicateTransaction";
public const string BuyFail_Unknown = "Unknown";
#region Start
/// <summary>
/// 初始化IAP 功能
/// </summary>
public static void InitIAP(string uid, byte[] googleKey, byte[] appleRootCerts)
{
GuruIAP.Instance.OnInitResult += OnIAPInitResult;
GuruIAP.Instance.OnRestored += OnRestored;
GuruIAP.Instance.OnBuyStart += OnBuyStart;
GuruIAP.Instance.OnBuyEnd += OnBuyEnd;
GuruIAP.Instance.OnBuyFailed += OnBuyFailed;
GuruIAP.Instance.OnGetProductReceipt += OnGetReceipt;
Callbacks.IAP.InvokeOnIAPInitStart(); // 初始化之前进行调用
GuruIAP.Instance.InitWithKeys(uid, googleKey, appleRootCerts, IsDebugMode);
}
/// <summary>
/// 初始化结果
/// </summary>
/// <param name="success"></param>
private static void OnIAPInitResult(bool success)
{
LogI($"IAP init result: {success}");
IsIAPReady = success;
Callbacks.IAP.InvokeOnIAPInitComplete(success);
}
private static bool CheckIAPReady()
{
if (!IsIAPReady)
{
LogE("IAP is not ready, call <GuruSDK.InitIAP> first.");
return false;
}
return true;
}
#endregion
#region Data
/// <summary>
/// 获取商品信息
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public static ProductInfo GetProductInfo(string productName)
{
return GuruIAP.Instance?.GetInfo(productName);
}
/// <summary>
/// 获取商品信息 (提供 ProductId)
/// </summary>
/// <param name="productId"></param>
/// <returns></returns>
public static ProductInfo GetProductInfoById(string productId)
{
return GuruIAP.Instance?.GetInfoById(productId);
}
[Obsolete("Will be discarded in next version. Using GetProductInfoById(string productId) instead.")]
public static ProductSetting GetProductSettingById(string productId)
{
var products = GuruSettings.Instance.Products;
if (products != null && products.Length > 0)
{
return products.FirstOrDefault(p => p.ProductId == productId);
}
return null;
}
[Obsolete("Will be discarded in next version. Using GetProductInfo(string productName) instead.")]
public static ProductSetting GetProductSetting(string productName)
{
var products = GuruSettings.Instance.Products;
if (products != null && products.Length > 0)
{
return products.FirstOrDefault(p => p.ProductName == productName);
}
return null;
}
/// <summary>
/// 查询某个商品是否已经包含订单了
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public static bool IsProductHasReceipt(string productName)
{
return GuruIAP.Instance.IsProductHasReceipt(productName);
}
public static string GetProductLocalizedPriceString(string productName)
{
return GuruIAP.Instance.GetLocalizedPriceString(productName);
}
#endregion
#region Purchase
private static Action<string, bool> InvokeOnPurchaseCallback;
/// <summary>
/// 老接口, 将会被废弃
/// </summary>
/// <param name="productName"></param>
/// <param name="purchaseCallback"></param>
[Obsolete("Will be discarded in next version. Using Purchase(string productName, string category, Action<string, bool> purchaseCallback) instead.")]
internal static void Purchase(string productName, Action<string, bool> purchaseCallback = null)
{
Purchase(productName, "", purchaseCallback);
}
/// <summary>
/// 购买商品, 通过商品Name
/// </summary>
/// <param name="productName"></param>
/// <param name="category"></param>
/// <param name="purchaseCallback"></param>
public static void Purchase(string productName, string category = "", Action<string, bool> purchaseCallback = null)
{
if (CheckIAPReady())
{
InvokeOnPurchaseCallback = purchaseCallback;
GuruIAP.Instance.Buy(productName, category);
}
}
/// <summary>
/// 购买商品, 通过商品ID
/// </summary>
/// <param name="productId"></param>
/// <param name="category"></param>
/// <param name="purchaseCallback"></param>
public static bool PurchaseById(string productId, string category = "", Action<string, bool> purchaseCallback = null)
{
var productName = GetProductInfoById(productId)?.Name ?? "";
if (CheckIAPReady() && !string.IsNullOrEmpty(productName))
{
Purchase(productName, category, purchaseCallback);
return true;
}
return false;
}
/// <summary>
/// 支付回调
/// </summary>
/// <param name="productName"></param>
/// <param name="success"></param>
private static void OnBuyEnd(string productName, bool success)
{
InvokeOnPurchaseCallback?.Invoke(productName, success);
Callbacks.IAP.InvokeOnPurchaseEnd(productName, success);
}
/// <summary>
/// 支付开始
/// </summary>
/// <param name="productName"></param>
private static void OnBuyStart(string productName)
{
Callbacks.IAP.InvokeOnPurchaseStart(productName);
}
/// <summary>
/// 支付失败
/// </summary>
/// <param name="productName"></param>
/// <param name="reason"></param>
private static void OnBuyFailed(string productName, string reason)
{
Callbacks.IAP.InvokeOnPurchaseFailed(productName, reason);
}
#endregion
#region Restore Purchase
/// <summary>
/// 恢复购买
/// </summary>
/// <param name="restoreCallback"></param>
public static void Restore(Action<bool, string> restoreCallback = null)
{
if( restoreCallback != null) Callbacks.IAP.OnIAPRestored += restoreCallback;
if (CheckIAPReady())
{
GuruIAP.Instance.Restore();
}
}
private static void OnRestored(bool success, string msg)
{
Callbacks.IAP.InvokeOnIAPRestored(success, msg); // 更新回复购买回调
}
#endregion
#region Receipt
/// <summary>
/// 获取订单收据
/// </summary>
/// <param name="productId"></param>
/// <param name="receipt"></param>
/// <param name="appleProductIsRestored"></param>
private static void OnGetReceipt(string productId, string receipt, bool appleProductIsRestored = false)
{
var productName = GetProductInfoById(productId)?.Name ?? "";
if(!string.IsNullOrEmpty(productName))
Model.AddReceipt(receipt, productName, productId, appleProductIsRestored);
}
#endregion
#region Subscriptions
/// <summary>
/// 订阅是否被取消
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public static bool IsSubscriptionCancelled(string productName)
{
return GuruIAP.Instance.IsSubscriptionCancelled(productName);
}
/// <summary>
/// 订阅是否可用
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public static bool IsSubscriptionAvailable(string productName)
{
return GuruIAP.Instance.IsSubscriptionAvailable(productName);
}
/// <summary>
/// 订阅是否过期
/// </summary>
/// <param name="productName"></param>
/// <returns></returns>
public static bool IsSubscriptionExpired(string productName)
{
return GuruIAP.Instance.IsSubscriptionExpired(productName);
}
public static bool IsSubscriptionFreeTrail(string productName)
{
return GuruIAP.Instance.IsSubscriptionFreeTrail(productName);
}
public static bool IsSubscriptionAutoRenewing(string productName)
{
return GuruIAP.Instance.IsSubscriptionAutoRenewing(productName);
}
public static bool IsSubscriptionIntroductoryPricePeriod(string productName)
{
return GuruIAP.Instance.IsSubscriptionIntroductoryPricePeriod(productName);
}
public DateTime GetSubscriptionExpireDate(string productName)
{
return GuruIAP.Instance.GetSubscriptionExpireDate(productName);
}
public DateTime GetSubscriptionPurchaseDate(string productName)
{
return GuruIAP.Instance.GetSubscriptionPurchaseDate(productName);
}
public DateTime GetSubscriptionCancelDate(string productName)
{
return GuruIAP.Instance.GetSubscriptionCancelDate(productName);
}
public TimeSpan GetSubscriptionRemainingTime(string productName)
{
return GuruIAP.Instance.GetSubscriptionRemainingTime(productName);
}
public TimeSpan GetSubscriptionIntroductoryPricePeriod(string productName)
{
return GuruIAP.Instance.GetSubscriptionIntroductoryPricePeriod(productName);
}
public TimeSpan GetSubscriptionFreeTrialPeriod(string productName)
{
return GuruIAP.Instance.GetSubscriptionFreeTrialPeriod(productName);
}
public string GetSubscriptionInfoJsonString(string productName)
{
return GuruIAP.Instance.GetSubscriptionInfoJsonString(productName);
}
#endregion
}
}

View File

@ -1,108 +0,0 @@
namespace Guru
{
public partial class GuruSDK
{
// UID
public static string UID
{
get
{
if(Model != null && !string.IsNullOrEmpty(Model.UserId))
return Model.UserId;
return IPMConfig.IPM_UID;
}
}
public static string UUID => IPMConfig.IPM_UUID ?? "";
public static string DeviceId => IPMConfig.IPM_DEVICE_ID ?? ""; // TODO: change it to _model member later.
public static string PushToken => IPMConfig.IPM_PUSH_TOKEN ?? ""; // TODO: change it to _model member later.
public static string AuthToken => IPMConfig.IPM_TOKEN ?? ""; // TODO: change it to _model member later.
public static string SupportEmail => GuruSettings.SupportEmail ?? "";
public static string StoreUrl
{
get
{
string url = "";
#if UNITY_EDITOR
url = "https://test@com.guru.ai";
#elif UNITY_ANDROID
url = GuruSettings?.AndroidStoreUrl ?? "";
#elif UNITY_IOS
url = GuruSettings?.IOSStoreUrl ?? "";
#endif
return url;
}
}
public static string PrivacyUrl => GuruSettings.PriacyUrl ?? "";
public static string TermsUrl => GuruSettings.TermsUrl ?? "";
public static string AppVersion => GuruAppVersion.version;
public static string AppVersionCode => GuruAppVersion.code;
public static string AppVersionString => GuruAppVersion.ToString();
public static bool IsNewUser => IPMConfig.IPM_NEWUSER;
public static string FirebaseId => IPMConfig.FIREBASE_ID;
public static string IDFA => IPMConfig.ADJUST_IDFA;
public static string AdjustId => IPMConfig.ADJUST_ID;
public static string GSADID => IPMConfig.ADJUST_ADID;
public static string CdnHost => _appServicesConfig?.CdnHost() ?? "";
private static GuruAppVersion _appVersion;
private static GuruAppVersion GuruAppVersion
{
get
{
if(_appVersion == null) _appVersion = GuruAppVersion.Load();
return _appVersion;
}
}
private static string _appBundleId;
public static string AppBundleId => _appBundleId;
/// <summary>
/// 设置购买去广告道具的标志位
/// </summary>
/// <param name="value"></param>
public static void SetBuyNoAds(bool value = true)
{
Model.IsNoAds = value;
ADService.Instance.IsBuyNoAds = value;
if (value)
{
Analytics.SetIsIapUser(true);
}
}
/// <summary>
/// 所有成功的主线关卡数量 (b_level)
/// </summary>
public static int BLevel
{
get => GuruSDKModel.Instance.BLevel;
set => GuruSDKModel.Instance.BLevel = value;
}
/// <summary>
/// 成功关卡总计数量 (b_play)
/// </summary>
public static int BPlay
{
get => GuruSDKModel.Instance.BPlay;
set => GuruSDKModel.Instance.BPlay = value;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ab7d65b5c8774a12924f3fe72a6e4a38
timeCreated: 1710154459

View File

@ -1,97 +0,0 @@
using System;
using UnityEngine;
namespace Guru
{
public partial class GuruSDK
{
#region System
/// <summary>
/// 打开页面
/// </summary>
/// <param name="url"></param>
public static void OpenURL(string url)
{
GuruWebview.OpenPage(url);
}
/// <summary>
/// 打开隐私协议页面
/// </summary>
public static void OpenPrivacyPage()
{
if (string.IsNullOrEmpty(PrivacyUrl))
{
LogE("PrivacyUrl is null");
return;
}
OpenURL(PrivacyUrl);
}
/// <summary>
/// 打开服务条款页面
/// </summary>
public static void OpenTermsPage()
{
if (string.IsNullOrEmpty(TermsUrl))
{
LogE("TermsUrl is null");
return;
}
OpenURL(TermsUrl);
}
#endregion
#region Android System
#if UNITY_ANDROID
/// <summary>
/// 获取 AndroidSDK 的系统版本号
/// </summary>
/// <returns></returns>
public static int GetAndroidSystemVersion()
{
try
{
// sdkInt 是 Android SDK 的整数版本号,例如 Android 10 对应 29。
// release 是 Android 版本的字符串表示,例如 "10"。
using (AndroidJavaClass jc = new AndroidJavaClass("android.os.Build$VERSION"))
{
int sdkInt = jc.GetStatic<int>("SDK_INT");
Debug.LogWarning($"[SDK] --- Android SDK Version:{sdkInt}");
return sdkInt;
}
}
catch (Exception ex)
{
Debug.LogError(ex);
}
return 0;
}
#endif
#endregion
#region Clear Data Cache
/// <summary>
/// 清除数据缓存
/// </summary>
public static void ClearData()
{
Model.ClearData();
GuruIAP.Instance.ClearData();
PlayerPrefs.DeleteAll();
PlayerPrefs.Save();
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 80d35b4c8f0f4d23a7228fddfb06dd03
timeCreated: 1713238972

View File

@ -1,29 +0,0 @@
namespace Guru
{
using System;
using UnityEngine;
public partial class GuruSDK
{
private ThreadHandler _threadHandler;
private void InitThreadHandler()
{
_threadHandler = new ThreadHandler();
RegisterUpdater(_threadHandler);
}
private void AddActionToMainThread(Action action)
{
_threadHandler?.AddAction(action);
}
public static void RunOnMainThread(Action action)
{
Instance.AddActionToMainThread(action);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8a0605c6b4b64b37a0036b34cbadb9ca
timeCreated: 1712622534

View File

@ -1,843 +0,0 @@
namespace Guru
{
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Debug = UnityEngine.Debug;
using Guru.Network;
using System.Linq;
public partial class GuruSDK: MonoBehaviour
{
// SDK_VERSION
public const string Version = "1.1.0";
// Const
private const string Tag = "[Guru]";
public const string ServicesConfigKey = "guru_services";
private static GuruSDK _instance;
/// <summary>
/// 单利引用
/// </summary>
public static GuruSDK Instance
{
get
{
if(null == _instance)
{
_instance = CreateInstance();
}
return _instance;
}
}
private GuruSDKInitConfig _initConfig;
private static GuruSDKInitConfig InitConfig => Instance._initConfig;
private static GuruSDKModel Model => GuruSDKModel.Instance;
private static GuruServicesConfig _appServicesConfig;
private static GuruSettings _guruSettings;
private static GuruSettings GuruSettings
{
get
{
if (_guruSettings == null) _guruSettings = GuruSettings.Instance;
return _guruSettings;
}
}
private static DateTime _initTime;
private static bool _isDebugEnabled = false;
/// <summary>
/// Debug Mode
/// </summary>
public static bool IsDebugMode
{
get
{
#if UNITY_EDITOR || DEBUG
return true;
#endif
return _isDebugEnabled;
}
}
/// <summary>
/// 初始化成功标志位
/// </summary>
public static bool IsInitialSuccess { get; private set; } = false;
/// <summary>
/// Firebase 就绪标志位
/// </summary>
public static bool IsFirebaseReady { get; private set; } = false;
/// <summary>
/// 服务就绪标志位
/// </summary>
public static bool IsServiceReady { get; private set; } = false;
private Firebase.Auth.FirebaseUser _firebaseUser;
[Obsolete("获取 FirebaseUser 的属性接口即将废弃,请改用 <GuruSDK.Callbacks.SDK.OnFirebaseUserAuthResult += OnMyGetFirebaseUserCallback> 来异步获取该属性")]
public static Firebase.Auth.FirebaseUser FirebaseUser => Instance?._firebaseUser ?? null;
#region 初始化
private static GuruSDK CreateInstance()
{
var go = new GameObject(nameof(GuruSDK));
DontDestroyOnLoad(go);
_instance = go.AddComponent<GuruSDK>();
return _instance;
}
// TODO : 下个版本需要将 整个 GuruSDK 做功能性的拆分
public static void Init(Action<bool> onComplete)
{
Init(GuruSDKInitConfig.Builder().Build(), onComplete);
}
public static void Init(GuruSDKInitConfig config, Action<bool> onComplete)
{
_initTime = DateTime.UtcNow;
// ----- First Open Time -----
// SetFirstOpenTime(GetFirstOpenTime()); // FirstOpenTime
LogI($"#1 ---- Guru SDK [{Version}] ----\n{config}");
Instance.StartWithConfig(config, onComplete);
}
/// <summary>
/// 启动SDK
/// </summary>
/// <param name="config"></param>
/// <param name="onComplete"></param>
private void StartWithConfig(GuruSDKInitConfig config, Action<bool> onComplete)
{
IsInitialSuccess = false;
_initConfig = config;
_isDebugEnabled = config.DebugMode;
if (config.EnableDebugLogEvent) Analytics.EnableDebugAnalytics = true; // 允许 Debug 模式下打点
if (!config.AutoNotificationPermission) FirebaseUtil.SetAutoFetchFcmToken(false); // 不允许自动启动获取 FCM Token
InitUpdaters(); // Updaters
InitThreadHandler(); // 初始化线程处理器
InitServices(); // 初始化所有的服务
InitNetworkMonitor(); // 网络状态
onComplete?.Invoke(true);
}
private void InitServices()
{
//---------- Start Analytics ------------
LogI($"#1.1 ---- Init Analytics ----");
// 初始化打点类
Analytics.Init();
// 从 Model 中注入打点属性初始值
Analytics.SetFirstOpenTime(IPMConfig.FIRST_OPEN_TIME);
Analytics.SetIsIapUser(Model.IsIapUser);
// Analytics.SetBLevel(Model.BLevel);
// Analytics.SetBPlay(Model.BPlay);
//---- Start All tools ----
LogI($"#2 --- InitFirebase ---");
//---------- Start Firebase ------------
StartFirebaseService();
LogI($"#2.1 --- InitFacebook ---");
//---------- Start Facebook ------------
FBService.Instance.StartService(Analytics.OnFBInitComplete);
IsInitialSuccess = true;
}
/// <summary>
/// 注入云控参数基础数据
/// </summary>
/// <returns></returns>
private string LoadDefaultGuruServiceJson()
{
// 加载本地 Services 配置值
var txtAsset = Resources.Load<TextAsset>(ServicesConfigKey);
if (txtAsset != null)
{
return txtAsset.text;
}
return "";
}
/// <summary>
/// 拉取云控参数完成
/// </summary>
/// <param name="success"></param>
private void OnFetchRemoteCallback(bool success)
{
LogI($"#6 --- Remote fetch complete: {success} ---");
ABTestManager.Init(); // 启动AB测试解析器
Callbacks.Remote.InvokeOnRemoteFetchComplete(success);
}
private void Update()
{
UpdateAllUpdates(); // 驱动所有的更新器
}
#endregion
#region App Remote Update
/// <summary>
/// Apply Cloud guru-service configs for sdk assets
/// </summary>
private void InitAllGuruServices()
{
// -------- Init Analytics ---------
SetSDKEventPriority();
// -------- Init Notification -----------
InitNotiPermission();
bool useKeywords = false;
bool useIAP = _initConfig.IAPEnabled;
bool appleReview = false;
// bool enableAnaErrorLog = false;
//----------- Set GuruServices ----------------
var services = GetRemoteServicesConfig();
if (services != null)
{
_appServicesConfig = services;
useKeywords = _appServicesConfig.KeywordsEnabled();
// 使用初始化配置中的 IAPEnable来联合限定, 如果本地写死关闭则不走云控开启
useIAP = _initConfig.IAPEnabled && _appServicesConfig.IsIAPEnabled();
// enableAnaErrorLog = _appServicesConfig.EnableAnaErrorLog();
Try(() =>
{
LogI($"#4.1 --- Start apply services ---");
//----------------------------------------------------------------
// 自打点设置错误上报
// if(enableAnaErrorLog) GuruAnalytics.EnableErrorLog = true;
// adjust 事件设置
if (null != _appServicesConfig.adjust_settings && null != GuruSettings)
{
// 更新 Adjust Tokens
GuruSettings.UpdateAdjustTokens(
_appServicesConfig.adjust_settings.AndroidToken(),
_appServicesConfig.adjust_settings.iOSToken());
// 更新 Adjust Events
GuruSettings.UpdateAdjustEvents(_appServicesConfig.adjust_settings.events);
}
LogI($"#4.2 --- Start GuruSettings ---");
// GuruSettings 设置
if (null != _appServicesConfig.app_settings)
{
if (_appServicesConfig.Tch02Value() > 0)
{
Analytics.EnableTch02Event = true;
Analytics.SetTch02TargetValue(_appServicesConfig.Tch02Value());
}
// 设置获取设备 UUID 的方法
if (_appServicesConfig.UseUUID())
{
IPMConfig.UsingUUID = true; // 开始使用 UUID 作为 DeviceID 标识
}
#if UNITY_IOS
// 苹果审核标志位
appleReview = _appServicesConfig.IsAppReview();
#endif
if (null != GuruSettings)
{
// 更新和升级 GuruSettings 对应的值
GuruSettings.UpdateAppSettings(
_appServicesConfig.app_settings.bundle_id,
_appServicesConfig.fb_settings?.fb_app_id ?? "",
_appServicesConfig.app_settings.support_email,
_appServicesConfig.app_settings.privacy_url,
_appServicesConfig.app_settings.terms_url,
_appServicesConfig.app_settings.android_store,
_appServicesConfig.app_settings.ios_store,
_appServicesConfig.parameters?.using_uuid ?? false,
_appServicesConfig.parameters?.cdn_host ?? "");
_appBundleId = _appServicesConfig.app_settings.bundle_id; // 配置预设的 BundleId
}
}
//---------------------------------
}, ex =>
{
Debug.LogError($"--- ERROR on apply services: {ex.Message}");
});
}
//----------- Set IAP ----------------
if (useIAP)
{
// InitIAP(_initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP
Try(() =>
{
LogI($"#4.3 --- Start IAP ---");
if (_initConfig.GoogleKeys == null || _initConfig.AppleRootCerts == null)
{
LogEx("[IAP] GoogleKeys is null when using IAPService! Integration failed. App will Exit");
}
InitIAP(UID, _initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP
}, ex =>
{
Debug.LogError($"--- ERROR on useIAP: {ex.Message}");
});
}
//----------- Set Keywords ----------------
if (useKeywords)
{
// KeywordsManager.Install(Model.IsIAPUser, Model.SuccessLevelId); // 启动Keyword管理器
Try(() =>
{
LogI($"#4.4 --- Start Keywords ---");
KeywordsManager.Install(Model.IsIapUser, Model.BLevel); // 启动Keyword管理器
}, ex =>
{
Debug.LogError($"--- ERROR on Keywords: {ex.Message}");
});
}
#if UNITY_IOS
if (appleReview)
{
// StartAppleReviewFlow(); // 直接显示 ATT 弹窗, 跳过 Consent 流程
Try(() =>
{
LogI($"#4.5 --- StartAppleReviewFlow ---");
StartAppleReviewFlow(); // 直接显示 ATT 弹窗, 跳过 Consent 流程
}, ex =>
{
Debug.LogError($"--- ERROR on StartAppleReviewFlow: {ex.Message}");
});
return;
}
#endif
//----------- Set Consent ----------------
if (!InitConfig.UseCustomConsent && !appleReview)
{
LogI($"#4.6 --- Start Consent Flow ---");
Try(StartConsentFlow, ex =>
{
Debug.LogError($"--- ERROR on StartConsentFlow: {ex.Message}");
});
}
#if UNITY_ANDROID
LogI($"#5.1 --- Android StartAndroidDebug Cmd lines---");
// Android 命令行调试
StartAndroidDebugCmds();
#endif
IsServiceReady = true;
// 中台服务初始化结束
Callbacks.SDK.InvokeOnGuruServiceReady();
}
/// <summary>
/// Get the guru-service cloud config value;
/// User can fetch the cloud guru-service config by using Custom Service Key
/// </summary>
/// <returns></returns>
private GuruServicesConfig GetRemoteServicesConfig()
{
string defaultJson = GetRemoteString(ServicesConfigKey);
bool useCustomKey = false;
string key = ServicesConfigKey;
if (!string.IsNullOrEmpty(_initConfig.CustomServiceKey))
{
key = _initConfig.CustomServiceKey;
useCustomKey = true;
}
var json = GetRemoteString(key); // Cloud cached data
if (string.IsNullOrEmpty(json) && useCustomKey && !string.IsNullOrEmpty(defaultJson))
{
// No remote data fetched from cloud, should use default values
json = defaultJson;
Debug.Log($"{Tag} --- No remote data found with: {key} -> Using default key {ServicesConfigKey} and local data!!!");
}
if (!string.IsNullOrEmpty(json))
{
return JsonParser.ToObject<GuruServicesConfig>(json);
}
return null;
}
private void Try(Action method, Action<Exception> onException = null, Action onFinal = null)
{
if (method == null) return;
try
{
method.Invoke();
}
catch (Exception ex)
{
LogEx(ex);
// ignored
onException?.Invoke(ex);
}
finally
{
// Finally
onFinal?.Invoke();
}
}
#endregion
#region Apple 审核流程逻辑
#if UNITY_IOS
private void StartAppleReviewFlow()
{
CheckAttStatus();
}
#endif
#endregion
#region Logging
private static void LogI(object message)
{
Debug.Log($"{Tag} {message}");
}
private static void LogW(object message)
{
Debug.LogWarning($"{Tag} {message}");
}
private static void LogE(object message)
{
Debug.LogError($"{Tag} {message}");
}
private static void LogEx(string message)
{
LogEx( new Exception($"{Tag} {message}"));
}
private static void LogEx(Exception e)
{
Debug.LogException(e);
}
/// <summary>
/// 上报崩溃信息
/// </summary>
/// <param name="message"></param>
public static void Report(string message)
{
Analytics.LogCrashlytics(message, false);
}
/// <summary>
/// 上报异常
/// </summary>
/// <param name="message"></param>
public static void ReportException(string message)
{
Analytics.LogCrashlytics(message);
}
/// <summary>
/// 上报异常 Exception
/// </summary>
/// <param name="ex"></param>
public static void ReportException(Exception ex)
{
Analytics.LogCrashlytics(ex);
}
#endregion
#region 生命周期
/// <summary>
/// 暂停时处理
/// </summary>
/// <param name="paused"></param>
private void OnAppPauseHandler(bool paused)
{
if(paused) Model.Save(true); // 强制保存数据
Callbacks.App.InvokeOnAppPaused(paused);
}
private void OnApplicationPause(bool paused)
{
OnAppPauseHandler(paused);
}
private void OnApplicationFocus(bool hasFocus)
{
OnAppPauseHandler(!hasFocus);
}
private void OnApplicationQuit()
{
Model.Save(true);
Callbacks.App.InvokeOnAppQuit();
}
#endregion
#region 延迟处理
/// <summary>
/// 启动协程
/// </summary>
/// <param name="enumerator"></param>
/// <returns></returns>
public static Coroutine DoCoroutine(IEnumerator enumerator)
{
return Instance != null ? Instance.StartCoroutine(enumerator) : null;
}
public static void KillCoroutine(Coroutine coroutine)
{
if(coroutine != null)
Instance.StopCoroutine(coroutine);
}
/// <summary>
/// 延时执行
/// </summary>
/// <param name="seconds"></param>
/// <param name="callback"></param>
public static Coroutine Delay(float seconds, Action callback)
{
return DoCoroutine(Instance.OnDelayCall(seconds, callback));
}
private IEnumerator OnDelayCall(float delay, Action callback)
{
if (delay > 0)
{
yield return new WaitForSeconds(delay);
}
else
{
yield return null;
}
callback?.Invoke();
}
#endregion
#region 帧更新 Updater
private List<IUpdater> _updaterRunningList;
private List<IUpdater> _updaterRemoveList;
private void InitUpdaters()
{
_updaterRunningList = new List<IUpdater>(20);
_updaterRemoveList = new List<IUpdater>(20);
}
private void UpdateAllUpdates()
{
int i = 0;
// ---- Updater Trigger ----
if (_updaterRunningList.Count > 0)
{
i = 0;
while (i < _updaterRunningList.Count)
{
var updater = _updaterRunningList[i];
if (updater != null)
{
if (updater.State == UpdaterState.Running)
{
updater.OnUpdate();
}
else if(updater.State == UpdaterState.Kill)
{
_updaterRemoveList.Add(updater);
}
}
else
{
_updaterRunningList.RemoveAt(i);
i--;
}
i++;
}
}
if (_updaterRemoveList.Count > 0)
{
i = 0;
while (i < _updaterRemoveList.Count)
{
RemoveUpdater(_updaterRemoveList[i]);
i++;
}
_updaterRemoveList.Clear();
}
}
/// <summary>
/// 注册帧更新器
/// </summary>
/// <param name="updater"></param>
public static void RegisterUpdater(IUpdater updater)
{
Instance.AddUpdater(updater);
updater.Start();
}
private void AddUpdater(IUpdater updater)
{
if (_updaterRunningList == null) _updaterRunningList = new List<IUpdater>(20);
_updaterRunningList.Add(updater);
}
private void RemoveUpdater(IUpdater updater)
{
if (_updaterRunningList != null && updater != null)
{
_updaterRunningList.Remove(updater);
}
}
#endregion
#region 中台推送管理
private static int _messageRetry = 0;
public static void SetPushNotificationEnabled(bool enabled)
{
DeviceInfoUploadRequest request = (DeviceInfoUploadRequest) new DeviceInfoUploadRequest()
.SetRetryTimes(1)
.SetSuccessCallBack(() =>
{
_messageRetry = 0;
Debug.Log($"[SDK] --- Set Push Enabled: {enabled} success");
})
.SetFailCallBack(() =>
{
double retryDelay = Math.Pow(2, _messageRetry);
_messageRetry++;
CoroutineHelper.Instance.StartDelayed((float)retryDelay, ()=> SetPushNotificationEnabled(enabled));
});
if (request == null) return;
request.SetPushEnabled(enabled);
request.Send();
}
#endregion
#region Deeplink
/// <summary>
/// 添加回调链接
/// </summary>
/// <param name="deeplink"></param>
private void OnDeeplinkCallback(string deeplink)
{
Callbacks.SDK.InvokeDeeplinkCallback(deeplink); // 尝试调用回调
}
#endregion
#region 网络状态上报
private NetworkStatusMonitor _networkStatusMonitor;
private string _lastNetworkStatus;
private void InitNetworkMonitor()
{
_networkStatusMonitor = new NetworkStatusMonitor(Analytics.SetNetworkStatus,
lastStatus =>
{
LogEvent("guru_offline", new Dictionary<string, dynamic>()
{
["from"] = lastStatus
});
});
}
/// <summary>
/// 获取当前的网络状态
/// </summary>
/// <returns></returns>
private string GetNetworkStatus() => _networkStatusMonitor.GetNetworkStatus();
#endregion
#region Firebase 服务
/// <summary>
/// 启动 Firebase 服务
/// </summary>
private void StartFirebaseService()
{
FirebaseUtil.Init(OnFirebaseDepsCheckResult,
OnGetFirebaseId,
OnGetGuruUID,
OnFirebaseLoginResult); // 确保所有的逻辑提前被调用到
}
private void OnGetGuruUID(bool success)
{
if (success)
{
Model.UserId = IPMConfig.IPM_UID;
if (GuruIAP.Instance != null)
{
GuruIAP.Instance.SetUID(UID);
GuruIAP.Instance.SetUUID(UUID);
}
// 自打点设置用户 ID
Analytics.SetUid(UID);
// Crashlytics 设置 uid
CrashlyticsAgent.SetUserId(UID);
// 上报所有的事件
Analytics.ShouldFlushGuruEvents();
}
Callbacks.SDK.InvokeOnGuruUserAuthResult(success);
}
private void OnGetFirebaseId(string fid)
{
// 初始化 Adjust 服务
InitAdjustService(fid, InitConfig.OnAdjustDeeplinkCallback);
// 初始化自打点
Analytics.InitGuruAnalyticService(fid);
//---------- Event SDK Info ------------
LogI($"#6.0 --- SDK is ready, report Info ---");
LogSDKInfo((DateTime.UtcNow - _initTime).TotalSeconds);
}
// TODO: 需要之后用宏隔离应用和实现
// Auth 登录认证
private void OnFirebaseLoginResult(bool success, Firebase.Auth.FirebaseUser firebaseUser)
{
_firebaseUser = firebaseUser;
Callbacks.SDK.InvokeOnFirebaseAuthResult(success, firebaseUser);
}
/// <summary>
/// 开始各种组件初始化
/// </summary>
private void OnFirebaseDepsCheckResult(bool success)
{
LogI($"#3 --- On FirebaseDeps: {success} ---");
IsFirebaseReady = success;
Callbacks.SDK.InvokeOnFirebaseReady(success);
Analytics.OnFirebaseInitCompleted();
LogI($"#3.5 --- Call InitRemoteConfig ---");
// 开始Remote Manager初始化
var defaultGuruServiceJson = LoadDefaultGuruServiceJson();
var dict = _initConfig.DefaultRemoteData.ToDictionary(
entry => entry.Key,
entry => entry.Value);
if (!string.IsNullOrEmpty(defaultGuruServiceJson))
{
dict[ServicesConfigKey] = defaultGuruServiceJson;
}
RemoteConfigManager.Init(dict);
RemoteConfigManager.OnFetchCompleted += OnFetchRemoteCallback;
LogI($"#4 --- Apply remote services config ---");
// 根据缓存的云控配置来初始化参数
InitAllGuruServices();
}
#endregion
#region Adjust服务
/// <summary>
/// 启动 Adjust 服务
/// </summary>
private static void InitAdjustService(string firebaseId, Action<string> onDeeplinkCallback = null)
{
// 启动 AdjustService
string appToken = GuruSettings.Instance.AdjustSetting?.GetAppToken() ?? "";
string fbAppId = GuruSettings.Instance.IPMSetting.FacebookAppId;
// if (!string.IsNullOrEmpty(IPMConfig.ADJUST_ID))
// Analytics.SetAdjustId(IPMConfig.ADJUST_ID); // 二次启动后,若有值则立即上报属性
AdjustService.Instance.Start(appToken, fbAppId, firebaseId, DeviceId,
OnAdjustInitComplete, onDeeplinkCallback ,OnGetGoogleAdId );
}
/// <summary>
/// Adjust 初始化结束
/// </summary>
/// <param name="adjustId"></param>
/// <param name="idfv"></param>
/// <param name="idfa"></param>
private static void OnAdjustInitComplete(string adjustId, string idfv, string idfa)
{
Debug.Log($"{Tag} --- OnAdjustInitComplete: adjustId:{adjustId} idfv:{idfv} idfa:{idfa}");
// 获取 ADID
if (string.IsNullOrEmpty(adjustId)) adjustId = "not_set";
if (string.IsNullOrEmpty(idfv)) idfv = "not_set";
if (string.IsNullOrEmpty(idfa)) idfa = "not_set";
IPMConfig.ADJUST_ID = adjustId;
IPMConfig.ADJUST_IDFV = idfv;
IPMConfig.ADJUST_IDFA = idfa;
Analytics.SetAdjustId(adjustId);
Analytics.SetIDFV(idfv);
Analytics.SetIDFA(idfa);
Analytics.OnAdjustInitComplete();
}
private static void OnGetGoogleAdId(string googleAdId)
{
Debug.Log($"{Tag} --- OnGetGoogleAdId: {googleAdId}");
// IPMConfig.ADJUST_GOOGLE_ADID = googleAdId;
Analytics.SetGoogleAdId(googleAdId);
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 45ff6fac409a4698ada70e0d8d5e8269
timeCreated: 1714353187

View File

@ -1,67 +0,0 @@
namespace Guru
{
using System;
using UnityEngine;
/// <summary>
/// Android 系统属性获取器
/// </summary>
public class AndroidSystemPropertiesHelper
{
private static string _appBundleId;
public static string AppBundleId
{
get => _appBundleId;
set => _appBundleId = value;
}
/// <summary>
/// Get the system property value by key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string Get(string key)
{
#if UNITY_ANDROID
return GetPropValue(key);
#endif
return "";
}
#if UNITY_ANDROID
private static AndroidJavaClass _systemPropsCls;
private const string SYSTEM_PROPS_CLASS = "android.os.SystemProperties";
private static string GetPropValue(string key)
{
try
{
if (_systemPropsCls == null)
{
_systemPropsCls = new AndroidJavaClass(SYSTEM_PROPS_CLASS);
}
if (_systemPropsCls != null)
{
return _systemPropsCls.CallStatic<string>("get", key);
}
}
catch (Exception e)
{
Debug.LogException(e);
}
return "";
}
#endif
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 41272ed6104b4d80be5322135a811186
timeCreated: 1714353340

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 75760985db114e44bfcd901ed50af96c
timeCreated: 1712580213

View File

@ -1,23 +0,0 @@
{
"name": "GuruSDK.Tests",
"rootNamespace": "Guru.Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"Guru.Runtime",
"GuruSDK"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 9eaf5abb3054d452ba2776169a31aa91
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,51 +0,0 @@
namespace Guru.Tests
{
using UnityEditor;
using NUnit.Framework;
using UnityEngine;
using System;
public class Test_IAP
{
[Test]
public void Test__AppleOrders()
{
var model = IAPModel.Load();
int level = 1;
int orderType = 0;
for (int i = 0; i < 5; i++)
{
model.AddAppleOrder(new AppleOrderData(orderType,
$"i.iap.test.icon_{i}",
$"receipt_{i}",
$"order_id_{i}",
DateTime.Now.ToString("g"),
level,
"RMB", 6.99d, "Store"));
level++;
}
if (model.HasUnreportedAppleOrder)
{
int i = 0;
while (model.appleOrders.Count > 0
&& i < model.appleOrders.Count)
{
var o = model.appleOrders[i];
model.RemoveAppleOrder(o);
i++;
}
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 7181a5508c604c439d3f4585ff49a5e0
timeCreated: 1712580254

View File

@ -1,49 +0,0 @@
namespace Guru.Tests
{
using NUnit.Framework;
using System.Threading;
using UnityEngine;
public class Test_Threading
{
private int TestCount
{
get => PlayerPrefs.GetInt(nameof(TestCount), 0);
set => PlayerPrefs.SetInt(nameof(TestCount), value);
}
[Test]
public void Test_ThreadingCall()
{
GuruSDK.Init(success =>
{
GuruSDK.Delay(0.1f, () =>
{
CallThreading();
});
});
}
private void CallThreading()
{
Debug.Log($"--------- CallThreading -------------");
var t = new Thread(() =>
{
Debug.Log($"--------- Thread Start -------------");
Thread.Sleep(2000);
GuruSDK.RunOnMainThread(() =>
{
TestCount++;
Debug.Log($">>>>> CallThreading: {TestCount}");
});
});
t.Start();
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 67d9cfd9949c48f3a8f6e6821b733ff1
timeCreated: 1712649738

View File

@ -1,11 +1,11 @@
{
"name": "com.guru.unity.sdk",
"displayName": "Guru SDK",
"version": "1.1.0",
"version": "1.0.0",
"description": "Guru SDK for unity project",
"unity": "2020.3",
"author":{
"name": "Guru Game"
"name": "Guru Fungames Studio"
},
"license": "MIT",
"category": "Game,Tool,Development",