commit b37bfe58c4e565018712d4dea535ad7e137f27b7 Author: barry Date: Mon Jul 6 09:56:40 2026 +0800 fix:1、h5 sdk创建项目 diff --git a/.idea/.idea.Uni2SDK/.idea/.gitignore b/.idea/.idea.Uni2SDK/.idea/.gitignore new file mode 100644 index 0000000..5252128 --- /dev/null +++ b/.idea/.idea.Uni2SDK/.idea/.gitignore @@ -0,0 +1,13 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# Rider 忽略的文件 +/.idea.WV2SDK.iml +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.Uni2SDK/.idea/.name b/.idea/.idea.Uni2SDK/.idea/.name new file mode 100644 index 0000000..3d8027e --- /dev/null +++ b/.idea/.idea.Uni2SDK/.idea/.name @@ -0,0 +1 @@ +Uni2SDK \ No newline at end of file diff --git a/.idea/.idea.Uni2SDK/.idea/MarsCodeWorkspaceAppSettings.xml b/.idea/.idea.Uni2SDK/.idea/MarsCodeWorkspaceAppSettings.xml new file mode 100644 index 0000000..c4cdb72 --- /dev/null +++ b/.idea/.idea.Uni2SDK/.idea/MarsCodeWorkspaceAppSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Uni2SDK/.idea/indexLayout.xml b/.idea/.idea.Uni2SDK/.idea/indexLayout.xml new file mode 100644 index 0000000..f5a863a --- /dev/null +++ b/.idea/.idea.Uni2SDK/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Uni2SDK/.idea/vcs.xml b/.idea/.idea.Uni2SDK/.idea/vcs.xml new file mode 100644 index 0000000..012064f --- /dev/null +++ b/.idea/.idea.Uni2SDK/.idea/vcs.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.WV2SDK/.idea/workspace.xml b/.idea/.idea.WV2SDK/.idea/workspace.xml new file mode 100644 index 0000000..042f151 --- /dev/null +++ b/.idea/.idea.WV2SDK/.idea/workspace.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + { + "associatedIndex": 5 +} + + + + { + "keyToString": { + "RunOnceActivity.ShowReadmeOnStart": "true", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "preferences.lookFeel", + "vue.rearranger.settings.migration": "true" + } +} + + + + + + + + + + + + + + + + 1746611058126 + + + + + + + + + + + \ No newline at end of file diff --git a/Uni2SDK.sln b/Uni2SDK.sln new file mode 100644 index 0000000..e5763bf --- /dev/null +++ b/Uni2SDK.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WV2SDK", "WV2SDK\WV2SDK.csproj", "{1ABFEB96-089E-4743-8A22-0A34FE6327D4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1ABFEB96-089E-4743-8A22-0A34FE6327D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ABFEB96-089E-4743-8A22-0A34FE6327D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ABFEB96-089E-4743-8A22-0A34FE6327D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ABFEB96-089E-4743-8A22-0A34FE6327D4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Uni2SDK.sln.DotSettings.user b/Uni2SDK.sln.DotSettings.user new file mode 100644 index 0000000..1281a2f --- /dev/null +++ b/Uni2SDK.sln.DotSettings.user @@ -0,0 +1,33 @@ + + True + True + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/WV2SDK.sln.DotSettings.user b/WV2SDK.sln.DotSettings.user new file mode 100644 index 0000000..0f6f5db --- /dev/null +++ b/WV2SDK.sln.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/WV2SDK/Adnroid/AndroidKit.cs b/WV2SDK/Adnroid/AndroidKit.cs new file mode 100644 index 0000000..01c260c --- /dev/null +++ b/WV2SDK/Adnroid/AndroidKit.cs @@ -0,0 +1,229 @@ +using UnityEngine; +using System; +using Newtonsoft.Json; + +namespace Uni2SDK +{ + /// + /// Android交互工具(v3.0) + /// 核心优化:统一调用模板 + 智能参数处理 + /// + public static class AndroidKit + { + #region 核心字段 + + private static readonly AndroidJavaObject UnityPlayer; + + private static readonly AndroidJavaObject Bridge; + private const string UnityPlayerPack = "com.unity3d.player"; + private const string UniPluginPack = "com.sglib.uniplugin"; + + #endregion + + #region 初始化 + + static AndroidKit() + { + if (Application.platform != RuntimePlatform.Android) return; + + try + { + // 获取 UnityPlayer 和 Activity 实例 + using (AndroidJavaClass unityPlayerClass = new AndroidJavaClass(UnityPlayerPack + ".UnityPlayer")) + { + UnityPlayer = unityPlayerClass.GetStatic("currentActivity"); + } + + // 获取 MyActivity 的单例对象 + using (AndroidJavaClass myActivityClass = new AndroidJavaClass(UniPluginPack + ".UniPluginClass")) + { + Bridge = myActivityClass.CallStatic("getInstance"); + } + } + catch (Exception e) + { + LogKit.LogError($"Android初始化失败: {e.Message}"); + } + } + + #endregion + + #region 通用调用模板 + + /// + /// 统一调用方法(无返回值) + /// + private static void Call(string method, params object[] args) + { + if (Bridge == null) return; + + try + { + Bridge.Call(method, args); + } + catch (Exception e) + { + LogKit.LogError($"Android.{method}调用异常: {e.Message}"); + } + } + + /// + /// 统一调用方法(带返回值) + /// + private static T Call(string method, params object[] args) + { + if (Bridge == null) return default; + + try + { + return Bridge.Call(method, args); + } + catch (Exception e) + { + LogKit.LogError($"Android.{method}调用异常: {e.Message}"); + return default; + } + } + + #endregion + + #region WebView控制 + + // 异步初始化 WebView, 通过回调控制其他的流程 + public static void InitUniPlugin(string param, InitCallbackProxy callback) => + Call("initUniPlugin", UnityPlayer, param, callback); + + // 设置有感WebView是否可见 + public static void SetWvVisible(bool visible) => Call("SetWvVisible", UnityPlayer, visible); + + // 设置 当前模式的指定层级 WebView 画幅的内边距, layer为0的时候设置全部层级, 每个模式设置一次即可 + public static void SetPadding(int layer, int l, int t, int r, int b) => Call("SetPadding", layer, l, t, r, b); + + // 加载 当前模式的指定层级链接和ct概率(0~100), layer为0的时候设置全部层级 + public static void LoadLink(int layer, string url, int tr) => Call("LoadLink", layer, url, tr); + + // 设置 当前模式 WebView 是否可见 + public static void SetVisible(bool visible) => Call("SetVisible", visible); + + // 全局设置一个区域能直接点到Unity的元素 + public static void SetShield(int id, int w, int h, int x, int y) => Call("SetShield", id, w, h, x, y); + + #endregion + + #region 交互控制 + + // 全局设置 WebView 是否可交互, 默认不可交互 + public static void SetInteroperable(bool enable) => Call("SetInteroperable", enable); + + // 全局设置 WebView 是否可触摸, 默认可触摸 + public static void SetTouchable(bool enable) => Call("SetTouchable", enable); + + #endregion + + #region 回调系统 + + // 当点击广告时, 触发回调 + public static void OnClickAD(CallbackProxy callback) => Call("OnClickAD", callback); + + public class CallbackProxy : AndroidJavaProxy + { + private readonly Action _onCallback; + + public CallbackProxy(Action callback) : base(UniPluginPack + ".UnityCallback") + { + _onCallback = callback; + } + + // 实现接口中的方法(方法名和参数类型必须与 Java 端保持一致) + public void OnCallback(int id, string url) + { + MainThreadDispatcher.RunOnMainThread(() => + { + _onCallback?.Invoke(id, url); // ✅ 现在在主线程中执行 + }); + } + } + + public class InitCallbackProxy : AndroidJavaProxy + { + private readonly Action _onCallback; + + public InitCallbackProxy(Action callback) : base(UniPluginPack + ".UnityCallback") + { + _onCallback = callback; + } + + // 实现接口中的方法(方法名和参数类型必须与 Java 端保持一致) + public void OnCallback(int code, string msg) + { + MainThreadDispatcher.RunOnMainThread(() => + { + _onCallback?.Invoke(code, msg); // ✅ 现在在主线程中执行 + }); + } + } + + #endregion + + #region 参数model + + [Serializable] + public class ColorInfo + { + [JsonProperty("alpha")] public int alpha; + [JsonProperty("red")] public int red; + [JsonProperty("green")] public int green; + [JsonProperty("blue")] public int blue; + + public ColorInfo(int alpha, int red, int green, int blue) + { + this.alpha = alpha; + this.red = red; + this.green = green; + this.blue = blue; + } + } + + [Serializable] + public class AuthInfo + { + [JsonProperty("isRelease")] public bool isRelease; + [JsonProperty("secret")] public string secret; + [JsonProperty("url")] public string url; + [JsonProperty("token")] public string token; + + public AuthInfo(bool isRelease, string secret, string url, string token) + { + this.isRelease = isRelease; + this.secret = secret; + this.url = url; + this.token = token; + } + } + + [Serializable] + public class ADCondition + { + [JsonProperty("linkLen")] public int linkLen; + [JsonProperty("keys")] public string[] keys; + + public ADCondition(int linkLen, params string[] keys) + { + this.linkLen = linkLen; + this.keys = keys; + } + } + + [Serializable] + public class InitConfig + { + [JsonProperty("layers")] public int layers; + [JsonProperty("printLog")] public bool printLog; + [JsonProperty("color")] public ColorInfo color; + [JsonProperty("auth")] public AuthInfo auth; + [JsonProperty("adCondition")] public ADCondition adCondition; + } + + #endregion + } +} \ No newline at end of file diff --git a/WV2SDK/Adnroid/MainThreadDispatcher.cs b/WV2SDK/Adnroid/MainThreadDispatcher.cs new file mode 100644 index 0000000..3b65d0a --- /dev/null +++ b/WV2SDK/Adnroid/MainThreadDispatcher.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Uni2SDK; + +public class MainThreadDispatcher : MonoBehaviour +{ + private static readonly Queue _actions = new Queue(); + private static MainThreadDispatcher _instance; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void Init() + { + if (_instance == null) + { + var obj = new GameObject("[MainThreadDispatcher]"); + _instance = obj.AddComponent(); + DontDestroyOnLoad(obj); + } + } + + public static void RunOnMainThread(Action action) + { + lock (_actions) + { + _actions.Enqueue(action); + } + } + + void Update() + { + lock (_actions) + { + while (_actions.Count > 0) + { + _actions.Dequeue()?.Invoke(); + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/Async/AsyncDealData.cs b/WV2SDK/Async/AsyncDealData.cs new file mode 100644 index 0000000..d9766a9 --- /dev/null +++ b/WV2SDK/Async/AsyncDealData.cs @@ -0,0 +1,52 @@ +namespace Uni2SDK +{ + internal class AsyncDealData + { + public delegate void DealMethod(); + + + private float _currentTime; + + + public float DelayTime = 1f; + + + public DealMethod dealMethod; + + + public int RepeatCount = 1; + + + public bool IsComplete => RepeatCount == 0; + + + public void PassTime(float time) + { + _currentTime += time; + if (!(_currentTime >= DelayTime)) + { + return; + } + + _currentTime -= DelayTime; + Run(); + } + + + private void Run() + { + if (RepeatCount == -1) + { + dealMethod?.Invoke(); + } + else + { + if (RepeatCount > 0) + { + dealMethod?.Invoke(); + RepeatCount--; + } + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/Async/AsyncKitUnityBehaviour.cs b/WV2SDK/Async/AsyncKitUnityBehaviour.cs new file mode 100644 index 0000000..5bad6c4 --- /dev/null +++ b/WV2SDK/Async/AsyncKitUnityBehaviour.cs @@ -0,0 +1,92 @@ +namespace Uni2SDK +{ + using System; + using UnityEngine; + using System.Collections.Generic; + + internal class AsyncKitUnityBehaviour : SingletonUnity + { + private readonly Dictionary _asyncDataDict = new Dictionary(); + + + private readonly Dictionary _updateActionDict = new Dictionary(); + + private readonly List _keyClean = new List(); + + private void Update() + { + foreach (var temp in _asyncDataDict.Values) + { + temp.PassTime(Time.deltaTime); + } + + try + { + foreach (var temp in _updateActionDict.Values) + { + temp?.dealMethod?.Invoke(); + } + } + catch (Exception) + { + } + + + ClearFinished(); + + + ClearUpdateActionFinished(); + } + + + private void ClearFinished() + { + _keyClean.Clear(); + foreach (var dealData in _asyncDataDict) + { + if (dealData.Value.IsComplete) + { + _keyClean.Add(dealData.Key); + } + } + + foreach (var key in _keyClean) + { + _asyncDataDict.Remove(key); + } + } + + + public void AddAsyncData(string key, AsyncDealData asyncDealData) + { + RemoveAsyncData(key); + _asyncDataDict.Add(key, asyncDealData); + } + + + public void RemoveAsyncData(string key) + { + if (_asyncDataDict.ContainsKey(key)) + { + _asyncDataDict.Remove(key); + } + } + + public void ClearUpdateActionFinished() + { + _keyClean.Clear(); + foreach (var dealData in _updateActionDict) + { + if (dealData.Value.IsComplete) + { + _keyClean.Add(dealData.Key); + } + } + + foreach (var key in _keyClean) + { + _updateActionDict.Remove(key); + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/Async/SugarAsyKit.cs b/WV2SDK/Async/SugarAsyKit.cs new file mode 100644 index 0000000..c6f541a --- /dev/null +++ b/WV2SDK/Async/SugarAsyKit.cs @@ -0,0 +1,63 @@ +namespace Uni2SDK +{ + using UnityEngine; + using System.Collections; + + internal static class SugarAsyKit + { + private static AsyncKitUnityBehaviour sAsyncKitUnityBehaviour; + + + private static readonly object SynClock = new object(); + + private static AsyncKitUnityBehaviour mAsyncKitUnityBehaviour + { + get + { + if (sAsyncKitUnityBehaviour != null) + { + return sAsyncKitUnityBehaviour; + } + + lock (SynClock) + { + if (sAsyncKitUnityBehaviour != null) + { + return sAsyncKitUnityBehaviour; + } + + + sAsyncKitUnityBehaviour = AsyncKitUnityBehaviour.Instance; + if (sAsyncKitUnityBehaviour != null) + { + sAsyncKitUnityBehaviour.name = "[ SugarAsyKit ]"; + } + } + + return sAsyncKitUnityBehaviour; + } + } + + public static Coroutine StartCoroutine(IEnumerator enumerator) + { + return mAsyncKitUnityBehaviour.StartCoroutine(enumerator); + } + + public static void StopCoroutine(Coroutine enumerator) + { + mAsyncKitUnityBehaviour.StopCoroutine(enumerator); + } + + public static void StartAction(string key, AsyncDealData.DealMethod method, float delayTime, int repeat = 1) + { + var temp = new AsyncDealData { dealMethod = method, DelayTime = delayTime, RepeatCount = repeat }; + mAsyncKitUnityBehaviour.AddAsyncData(key, temp); + } + + + public static void StopAction(string key) + { + mAsyncKitUnityBehaviour.RemoveAsyncData(key); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Base/BaseCtrl.cs b/WV2SDK/Base/BaseCtrl.cs new file mode 100644 index 0000000..d5d25d9 --- /dev/null +++ b/WV2SDK/Base/BaseCtrl.cs @@ -0,0 +1,115 @@ +namespace Uni2SDK +{ + internal abstract class BaseCtrl + { + public string ctrlName; + public bool isEnable = true; + public bool IsNew { get; private set; } + + protected ModuleManager moduleManager; + + // protected ModelDispatcher modelDispatcher; + protected CtrlDispatcher ctrlDispatcher; + + protected UICtrlDispatcher uiCtrlDispatcher; + // protected DataDispatcher dataDispatcher; + // protected GameDispatcher gameDispatcher; + + public void New() + { + if (!isEnable) return; + + OnNew(); + IsNew = true; + } + + public virtual void Init() + { + if (!isEnable) return; + + Assignment(); + AddListener(); + AddServerListener(); + OnInit(); + } + + public virtual void StartUp() + { + if (!isEnable) return; + + OnStartUp(); + } + + public virtual void GameStart() + { + if (!isEnable) return; + + OnGameStart(); + } + + public virtual void Dispose() + { + if (!isEnable) return; + + RemoveListener(); + RemoveServerListener(); + OnDispose(); + UnAssignment(); + IsNew = false; + } + + protected virtual void Assignment() + { + moduleManager = ModuleManager.Instance; + + // modelDispatcher = ModelDispatcher.Instance; + ctrlDispatcher = CtrlDispatcher.Instance; + uiCtrlDispatcher = UICtrlDispatcher.Instance; + // dataDispatcher = DataDispatcher.Instance; + // gameDispatcher = GameDispatcher.Instance; + } + + protected virtual void UnAssignment() + { + moduleManager = null; + + // modelDispatcher = null; + ctrlDispatcher = null; + uiCtrlDispatcher = null; + // dataDispatcher = null; + // gameDispatcher = null; + } + + protected virtual void OnNew() + { + } + + protected abstract void OnInit(); + + protected virtual void OnStartUp() + { + } + + protected virtual void OnGameStart() + { + } + + protected abstract void OnDispose(); + + protected virtual void AddListener() + { + } + + protected virtual void RemoveListener() + { + } + + protected virtual void AddServerListener() + { + } + + protected virtual void RemoveServerListener() + { + } + } +} \ No newline at end of file diff --git a/WV2SDK/Base/BaseInterfaceManager.cs b/WV2SDK/Base/BaseInterfaceManager.cs new file mode 100644 index 0000000..7ed63a6 --- /dev/null +++ b/WV2SDK/Base/BaseInterfaceManager.cs @@ -0,0 +1,55 @@ +using System; + +namespace Uni2SDK +{ + internal abstract class BaseInterfaceManager : IDisposable, InterfaceManager + where T : BaseInterfaceManager, new() + { + public bool IsInit { get; private set; } + public bool IsStartUp { get; private set; } + public bool IsDispose { get; private set; } + + private static T m_instance; + + public static T Instance + { + get + { + if (m_instance == null) + { + m_instance = new T(); + m_instance.New(); + } + + return m_instance; + } + } + + protected virtual void New() + { + IsDispose = false; + } + + public virtual void Init() + { + IsInit = true; + } + + public virtual void StartUp() + { + IsStartUp = true; + } + + public virtual void DisposeBefore() + { + IsDispose = true; + IsInit = false; + IsStartUp = false; + } + + public virtual void Dispose() + { + m_instance = null; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Base/BaseModel.cs b/WV2SDK/Base/BaseModel.cs new file mode 100644 index 0000000..5814392 --- /dev/null +++ b/WV2SDK/Base/BaseModel.cs @@ -0,0 +1,108 @@ +namespace Uni2SDK +{ + internal abstract class BaseModel + { + public string modelName; + private string localStorageKey; + + protected ModuleManager mOduleManager; + + // protected ModelDispatcher modelDispatcher; + protected CtrlDispatcher ctrlDispatcher; + + protected UICtrlDispatcher uiCtrlDispatcher; + // protected DataDispatcher dataDispatcher; + // protected GameDispatcher gameDispatcher; + + public void New() + { + OnNew(); + } + + public void Init() + { + Assignment(); + AddListener(); + AddServerListener(); + OnInit(); + } + + public void StartUp() + { + OnStartUp(); + } + + public void GameStart() + { + OnGameStart(); + } + + public void Reset() + { + Dispose(); + Init(); + } + + public void Dispose() + { + RemoveListener(); + RemoveServerListener(); + OnDispose(); + UnAssignment(); + } + + protected virtual void Assignment() + { + mOduleManager = ModuleManager.Instance; + + // modelDispatcher = ModelDispatcher.Instance; + ctrlDispatcher = CtrlDispatcher.Instance; + uiCtrlDispatcher = UICtrlDispatcher.Instance; + // dataDispatcher = DataDispatcher.Instance; + // gameDispatcher = GameDispatcher.Instance; + } + + protected virtual void UnAssignment() + { + mOduleManager = null; + + // modelDispatcher = null; + ctrlDispatcher = null; + uiCtrlDispatcher = null; + // dataDispatcher = null; + // gameDispatcher = null; + } + + protected virtual void OnNew() + { + } + + protected abstract void OnInit(); + + protected virtual void OnStartUp() + { + } + + protected virtual void OnGameStart() + { + } + + protected abstract void OnDispose(); + + protected virtual void AddListener() + { + } + + protected virtual void RemoveListener() + { + } + + protected virtual void AddServerListener() + { + } + + protected virtual void RemoveServerListener() + { + } + } +} \ No newline at end of file diff --git a/WV2SDK/Base/BaseSystem.cs b/WV2SDK/Base/BaseSystem.cs new file mode 100644 index 0000000..c95322c --- /dev/null +++ b/WV2SDK/Base/BaseSystem.cs @@ -0,0 +1,29 @@ +namespace Uni2SDK +{ + internal abstract class BaseSystem + { + public virtual void Init() + { + } + + public virtual void InitLate() + { + } + + public virtual void Dispose() + { + } + + public virtual void Update() + { + } + + public virtual void LateUpdate() + { + } + + public virtual void FixedUpdate() + { + } + } +} \ No newline at end of file diff --git a/WV2SDK/Base/BaseUICtrl.cs b/WV2SDK/Base/BaseUICtrl.cs new file mode 100644 index 0000000..94126a0 --- /dev/null +++ b/WV2SDK/Base/BaseUICtrl.cs @@ -0,0 +1,33 @@ +namespace Uni2SDK +{ + internal abstract class BaseUICtrl : BaseCtrl + { + public virtual uint GetOpenUIMsg(string uiName) + { + return 0; + } + + public virtual uint GetCloseUIMsg(string uiName) + { + return 0; + } + + public void DispatchCloseUI(string uiName = null, object args = null) + { + uint msgId = GetCloseUIMsg(uiName); + if (msgId == 0) + { + CloseUI(args); + return; + } + + if (uiCtrlDispatcher != null) + { + uiCtrlDispatcher.Dispatch(msgId, args); + } + } + + public abstract void OpenUI(object args = null); + public abstract void CloseUI(object args = null); + } +} \ No newline at end of file diff --git a/WV2SDK/Base/InterfaceManager.cs b/WV2SDK/Base/InterfaceManager.cs new file mode 100644 index 0000000..346035c --- /dev/null +++ b/WV2SDK/Base/InterfaceManager.cs @@ -0,0 +1,10 @@ +namespace Uni2SDK +{ + internal interface InterfaceManager + { + void Init(); + void StartUp(); + void DisposeBefore(); + void Dispose(); + } +} \ No newline at end of file diff --git a/WV2SDK/Base/ModuleManager.cs b/WV2SDK/Base/ModuleManager.cs new file mode 100644 index 0000000..88ca0f2 --- /dev/null +++ b/WV2SDK/Base/ModuleManager.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using Uni2SDK; + +namespace Uni2SDK +{ + internal sealed class ModuleManager : BaseInterfaceManager + { + private Dictionary modelDict = new(); + private Dictionary uiTypeDict = new(); + private Dictionary ctrlDict = new(); + private Dictionary uiCtrlDict = new(); + + public override void Init() + { + base.Init(); + InitAllModule(); + } + + private void InitAllModule() + { + // List ctrlDisableList = AppConst.CtrlDisableList; + + foreach (BaseModel model in modelDict.Values) + { + model.New(); + } + + foreach (BaseCtrl ctrl in ctrlDict.Values) + { + if (!ctrl.isEnable) + { + continue; + } + + // if (ctrlDisableList.Contains(ctrl.ctrlName)) + // { + // ctrl.isEnable = false; + // continue; + // } + + ctrl.isEnable = true; + ctrl.New(); + } + + foreach (BaseUICtrl uiCtrl in uiCtrlDict.Values) + { + if (!uiCtrl.isEnable) + { + continue; + } + + // if (ctrlDisableList.Contains(uiCtrl.ctrlName)) + // { + // uiCtrl.isEnable = false; + // continue; + // } + + uiCtrl.isEnable = true; + uiCtrl.New(); + } + + + foreach (BaseModel model in modelDict.Values) + { + model.Init(); + } + + foreach (BaseCtrl ctrl in ctrlDict.Values) + { + ctrl.Init(); + } + + foreach (BaseUICtrl uiCtrl in uiCtrlDict.Values) + { + uiCtrl.Init(); + } + } + + public void StartUpAllModule() + { + foreach (BaseModel model in modelDict.Values) + { + model.StartUp(); + } + + foreach (BaseCtrl ctrl in ctrlDict.Values) + { + ctrl.StartUp(); + } + + foreach (BaseUICtrl uiCtrl in uiCtrlDict.Values) + { + uiCtrl.StartUp(); + } + } + + public void AllModuleGameStart() + { + foreach (BaseModel model in modelDict.Values) + { + model.GameStart(); + } + + foreach (BaseCtrl ctrl in ctrlDict.Values) + { + ctrl.GameStart(); + } + + foreach (BaseUICtrl uiCtrl in uiCtrlDict.Values) + { + uiCtrl.GameStart(); + } + } + + public BaseModel GetModel(string modelName) + { + BaseModel model = null; + if (!modelDict.TryGetValue(modelName, out model)) + { + } + + return model; + } + + public BaseUICtrl GetUICtrl(string uiCtrlName) + { + BaseUICtrl uiCtrl = null; + if (!uiCtrlDict.TryGetValue(uiCtrlName, out uiCtrl)) + { + } + + return uiCtrl; + } + + public void AddModel(string modelName, BaseModel model) + { + model.modelName = modelName; + modelDict[modelName] = model; + } + + public void AddUIType(string uiName, Type uiType) + { + uiTypeDict[uiName] = uiType; + } + + public void AddCtrl(string ctrlName, BaseCtrl ctrl) + { + ctrl.ctrlName = ctrlName; + ctrlDict[ctrlName] = ctrl; + } + + public void AddUICtrl(string ctrlName, BaseUICtrl uiCtrl) + { + uiCtrl.ctrlName = ctrlName; + uiCtrlDict[ctrlName] = uiCtrl; + } + + public void DisposeAllModel() + { + foreach (BaseModel model in modelDict.Values) + { + model.Dispose(); + } + + modelDict.Clear(); + } + + public void DisposeAllCtrl() + { + foreach (BaseCtrl ctrl in ctrlDict.Values) + { + ctrl.Dispose(); + } + + foreach (BaseUICtrl uiCtrl in uiCtrlDict.Values) + { + uiCtrl.Dispose(); + } + + ctrlDict.Clear(); + uiCtrlDict.Clear(); + } + + public void DisposeAllModule() + { + DisposeAllModel(); + DisposeAllCtrl(); + } + + public override void Dispose() + { + base.Dispose(); + + modelDict = null; + uiTypeDict = null; + ctrlDict = null; + uiCtrlDict = null; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Common/Uni2SDKAutoRefresh.cs b/WV2SDK/Common/Uni2SDKAutoRefresh.cs new file mode 100644 index 0000000..a674263 --- /dev/null +++ b/WV2SDK/Common/Uni2SDKAutoRefresh.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections; +using System.Timers; +using Newtonsoft.Json; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace Uni2SDK +{ + internal class Uni2SDKAutoRefresh + { + public Uni2SDKAutoRefresh(int _id, int tier) + { + Id = _id; + _tier = tier; + + // InitHideWV(_url); + } + + public int Id { get; } + private int waitTime = 0; + private int TimeCount = 0; + private bool isAdd = false; + private WVUIType wType; + private readonly int _tier = 0; //当前是第几层view(因为要当作数组的层级,所以从0开始,即:0是第一层,以此类推) + + public void RefreshUrl(WVUIType wvuiType, int ly, string urlLink) + { + wType = wvuiType; + LogKit.Log($"[WV] RefreshUrl-000-type- {wvuiType}\n urlLink= {urlLink} \n ly--- {ly + 1}"); + if (urlLink == null) return; + //添加暗穿链接和明穿链接 + var url1 = Uni2SDKMgr.Instance.GetLoadUrl(urlLink, ly); + // LogKit.Log("[WV] RefreshUrl-------0-" + urlLink); + // LogKit.Log("[WV] RefreshUrl-------1-" + url1); + + + var ct = Uni2SDKMgr.Instance.GetHideCtRate(ly); + LogKit.Log($"[WV] RefreshUrl-111-type- {wvuiType}\n url1= {url1} \n ct--- {ct} \n ly--- {ly + 1}"); + + if (wvuiType == WVUIType.Sense) + { + // AndroidKit.LoadLink(ly + 1,"about:blank",ct); + if (!Uni2SDKManager.Instance._isShowSceneNormal) + { + AndroidKit.LoadLink(ly + 1,Uni2SDKManager.Instance._sceneUrl, 100); + return; + } + } + + if (wType == WVUIType.NoSense && !ConfigSystem.IsNeedRefresh) + { + url1 = "about:blank"; + } + + LogKit.Log($"[WV] RefreshUrl-ConfigSystem.IsNeedRefresh- {ConfigSystem.IsNeedRefresh}\n url1= {url1} ly=== {ly + 1}"); + + Uni2SDKMgr.Instance.SetLoadUrlTimes(url1, ly); + AndroidKit.LoadLink(ly + 1, url1, ct); + } + + private Coroutine _delayCoroutine; // 协程引用 + + public void UpdateWaitTime() + { + if (_delayCoroutine != null) + { + // 停止之前的协程 + SugarAsyKit.StopCoroutine(_delayCoroutine); + } + + // 启动新的协程 + _delayCoroutine = SugarAsyKit.StartCoroutine(DelayedUpdateWaitTimeCoroutine()); + } + + private IEnumerator DelayedUpdateWaitTimeCoroutine() + { + // 等待 0.1 秒(Unity 单位为秒) + yield return new WaitForSeconds(0.1f); + + // 执行原本逻辑 + DelayedUpdateWaitTime(); + } + + private void DelayedUpdateWaitTime() + { + // LogKit.Log($"[WV] Delayed1------------ {wType}"); + + if (wType != WVUIType.NoSense || isAdd) return; + // LogKit.Log($"[WV] Delayed2------------ {wType}"); + + var F5Delay = Uni2SDKMgr.Instance._h6Conf.AdClickF5Delay; + + var addValue = CommonHelper.RandomRange(F5Delay.Min, F5Delay.Max); + waitTime += addValue; + isAdd = true; + } + + + public void StartTime() + { + if (!Uni2SDKManager.Instance._isShowSceneNormal) return; + + //如果不需要刷新,则直接返回 + if (!IsRefreshWV()) + { + LogKit.Log("[WV] 今日刷新次数已用完"); + THManager.Instance.UpdateSecondEvent += checkNewDay; + return; + } + + var F5Interval = Uni2SDKMgr.Instance._h5Conf.F5Interval; + if (wType == WVUIType.NoSense) + { + F5Interval = Uni2SDKMgr.Instance._h6Conf.LayerConfList[_tier].F5Interval; + + // LogKit.Log($"[WV] refreshTime=== {F5Interval} 第几层---{_tier+1}"); + // waitTime = CommonHelper.RandomRangeIncludeEnd(refreshTime); + } + + waitTime = CommonHelper.RandomRange(F5Interval.Min, F5Interval.Max); + + THManager.Instance.UpdateSecondEvent += SetTweenerCB; + } + + private void checkNewDay() + { + if (wType == WVUIType.NoSense && !ConfigSystem.IsNeedRefresh) return; + + if (!IsNewDay()) return; + + THManager.Instance.UpdateSecondEvent -= checkNewDay; + ResetRefreshCountOnNewDay(); + + Uni2SDKMgr.Instance.RefreshWV(Uni2SDKMgr.Instance.WvuiType, _tier); + } + + public void StopSecondeEvent() + { + // LogKit.Log($"[WV] StopSecondeEvent--------------id= {Id}"); + waitTime = 0; + TimeCount = 0; + isAdd = false; + THManager.Instance.UpdateSecondEvent -= SetTweenerCB; + } + + public void StopCheckNewDay() + { + THManager.Instance.UpdateSecondEvent -= checkNewDay; + } + + private void SetTweenerCB() + { + // LogKit.Log($"[WV] 层级:{_tier+1} TimeCount===== {TimeCount} \n waitTime====== {waitTime} \n id====: {Id}"); + if (wType == WVUIType.NoSense && !ConfigSystem.IsNeedRefresh) return; + + + if (TimeCount > waitTime) + { + isAdd = false; + StopSecondeEvent(); + if (IsRefreshWV()) + { + ResetRefreshCountOnNewDay(); + + Uni2SDKMgr.Instance.RefreshWV(Uni2SDKMgr.Instance.WvuiType, _tier); + } + } + + TimeCount++; + } + + private bool IsRefreshWV() + { + var times = Uni2SDKMgr.Instance._h5Conf.F5Times; + //每天最大次数(明穿和暗穿) + if (wType == WVUIType.NoSense) + { + LogKit.Log($"[WV] tier = {_tier} 配置表的次数times ==={times}"); + times = Uni2SDKMgr.Instance._h6Conf.LayerConfList[_tier].F5Times; + } + + // LogKit.Log($"[WV] tier = {_tier + 1} 配置表的次数times ==={times}"); + if (times == 0) + { + return false; + } + + if (IsNewDay()) + { + return true; + } + + var localTimes = LoadTodayRefreshCount(); + LogKit.Log($"[WV] 本地次数localTimes === {localTimes} times==={times}"); + if (times > localTimes) + { + return true; + } + + return false; + } + + private bool IsNewDay() + { + // 获取前一天的结束时间(即今天凌晨0点0分0秒的时间戳) + var now = DateTime.UtcNow; // 获取UTC时间,不受本地时区影响 + var startOfToday = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc); + + // 从PlayerPrefs中读取上一次保存的日期(以UTC时间戳的形式保存) + int lastSavedTimestamp = PlayerPrefs.GetInt($"LastSavedDateTimestamp_{wType}_tier_{_tier + 1}", 0); + + // 如果没有保存过日期,则认为是新的一天 + if (lastSavedTimestamp == 0) + { + return true; + } + + // 将保存的日期转换为DateTime对象 + var lastSavedDate = DateTimeOffset.FromUnixTimeSeconds(lastSavedTimestamp).UtcDateTime; + + // 比较今天凌晨的时间与上次保存的日期,如果不同则认为是新的一天 + return startOfToday > lastSavedDate; + } + + + private void ResetRefreshCountOnNewDay() + { + if (IsNewDay()) + { + // LogKit.Log("[WV] ResetRefreshCountOnNewDay------"); + SaveTodayRefreshCount(1); + + // 更新保存的日期为今天 + PlayerPrefs.SetInt($"LastSavedDateTimestamp_{wType}_tier_{_tier + 1}", + (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + } + else + { + var times = LoadTodayRefreshCount(); + times++; + SaveTodayRefreshCount(times); + } + } + + private int LoadTodayRefreshCount() + { + return PlayerPrefs.GetInt($"TodayRefreshCount_{wType}_tier_{_tier + 1}", 1); + } + + // 保存今天的刷新次数 + private void SaveTodayRefreshCount(int RefreshCount) + { + PlayerPrefs.SetInt($"TodayRefreshCount_{wType}_tier_{_tier + 1}", RefreshCount); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Config/ConfigSystem.cs b/WV2SDK/Config/ConfigSystem.cs new file mode 100644 index 0000000..65b5149 --- /dev/null +++ b/WV2SDK/Config/ConfigSystem.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using UnityEngine; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Uni2SDK; + +namespace Uni2SDK +{ + internal class ConfigSystem : BaseSystem + { + public static H5ConfModel configData = new H5ConfModel(); + + public static bool IsNeedRefresh = false; + + public ConfigSystem(bool isAutoInit = true) + { + if (isAutoInit) + { + Init(); + } + } + + public sealed override void Init() + { + base.Init(); + AddListener(); + } + + private void AddListener() + { + NetworkDispatcher.Instance.AddListener(NetworkMsg.GetConfig, OnRequestGetConfig); + } + + private void RemoveListener() + { + NetworkDispatcher.Instance.RemoveListener(NetworkMsg.GetConfig, OnRequestGetConfig); + } + + private void OnRequestGetConfig(object obj) + { + TrackKit.TrackLoginFunnel(LoginFunnelEventType.LoadBegin); + + var configReqData = obj as H5ConfigReqData; + var isInit = configReqData is { IsInit: true }; + const string configFileName = "ConfigFile.txt"; + var configFileSavePath = $"{Application.persistentDataPath}/Config/"; + + var configFileNameKey = "Uni2SDKConfigFileName"; + var CDNConfigFileName = configReqData.Setting; + + string savedCfgName = PlayerPrefs.GetString(configFileNameKey); + bool needDownloadConfigFile = false; + LogKit.Log($"[UNITY] Saved config name: {savedCfgName}, CDN config name: {CDNConfigFileName}"); + if (!string.IsNullOrEmpty(CDNConfigFileName)) + { + // 如果本地Player Prefs里没有保存配置文件名 + if (string.IsNullOrEmpty(savedCfgName)) + { + LogKit.Log("[UNITY] No config file name saved."); + needDownloadConfigFile = true; + } + else + { + LogKit.Log($"[UNITY] Saved config name: {savedCfgName}, CDN config name: {CDNConfigFileName}"); + //与CDN上的对比名称 + if (!savedCfgName.Equals(CDNConfigFileName)) + { + needDownloadConfigFile = true; + } + } + } + + // 构建 StreamingAssets 文件路径 + var url = $"{configReqData.CdnUrl}/config/{CDNConfigFileName}"; + LogKit.Log($"[WV] url:{url} \n needDownloadConfigFile=== {needDownloadConfigFile} {isInit}"); + + if (!needDownloadConfigFile && !isInit) + { + // LogKit.Log($"[WV] 不需要下载配置文件"); + return; + } + + SugarAsyKit.StartCoroutine(DownloadKit.GetTextFromUrl(url, configFileName, (content) => + { + LogKit.Log($"[WV] down config success {content}"); + if (content.IsNullOrWhiteSpace()) return; + PlayerPrefs.SetString(configFileNameKey, CDNConfigFileName); + var h6Config = Uni2SDKMgr.Instance._h6Conf; + LogKit.Log($"[WV] isInit-1--{isInit}"); + ParseConfig(content); + + LogKit.Log($"[WV] isInit-2--{isInit}"); + + Uni2SDKMgr.Instance.InitConfigData(isInit, h6Config); + if (isInit) + { + TrackKit.TrackLoginFunnel(LoginFunnelEventType.LoadFinish); + + LogKit.Log($"[WV]------isInit------{Uni2SDKMgr.Instance.WTier}"); + + var param = new AndroidKit.InitConfig(); + + param.layers = Uni2SDKMgr.Instance.WTier; + param.printLog = Uni2SDKManager.Instance._isDebug; + //测试代码 + LogKit.Log($"[WV]------isInit--_isDebug----{Uni2SDKManager.Instance._isDebug}"); + + if (Uni2SDKManager.Instance._isDebug) + { + param.color = new AndroidKit.ColorInfo(0, 0, 0, 0); + } + + param.adCondition = new AndroidKit.ADCondition(90); + + param.auth = new AndroidKit.AuthInfo(Uni2SDKManager.Instance._isDebug, NetworkManager.GetSecret(), + NetworkManager.GetUrl(), TokenManager.Instance.CachedToken); + LogKit.Log($"sdk init--------- {JsonConvert.SerializeObject(param)}"); + + + //必须先初始化后才进行接下来的操作 + TrackKit.TrackLoginFunnel(LoginFunnelEventType.CoreInitBegin); + + SetStatus(); + + AndroidKit.InitUniPlugin(JsonConvert.SerializeObject(param), + new AndroidKit.InitCallbackProxy((code, msg) => + { + TrackKit.TrackLoginFunnel(LoginFunnelEventType.CoreInitFinish, code == 0 ? "success" : msg); + + LogKit.Log("从 Android 收到回调, Code: " + code + " Msg: " + msg); + if (code == 0) + { + LogKit.Log("✅ 初始化成功!"); + AndroidKit.OnClickAD(new AndroidKit.CallbackProxy((id, url) => + { + LogKit.Log("从 Android 收到回调, ID: " + id + " Url: " + url); + Uni2SDKMgr.Instance.UpdateWaitTime(id); + Uni2SDKMgr.Instance.ClickAdEvent(url); + })); + + // CtrlDispatcher.Instance.Dispatch(CtrlMsg.Game_StartReady); + Uni2SDKCtrl.OnGameStartReady(); + + TrackKit.TrackLoginFunnel(LoginFunnelEventType.AllReady); + } + else + { + LogKit.LogError($"❌ 初始化失败: {msg}"); + Uni2SDKManager.Instance.IsInitSuccess = false; + Uni2SDKManager.Instance.OnLoginCallBack?.Invoke(false, msg); + } + })); + } + }, configFileSavePath, false)); + } + + private void ParseConfig(string json) + { + LogKit.Log($"[WV] ParseConfig ----json1--- {json}"); + if (json.IsNullOrWhiteSpace()) + { + return; + } + + LogKit.Log($"[WV] ParseConfig package============={Uni2SDKManager.Instance.GetPackageNow()}"); + if (!json.StartsWith("{")) + { + json = Encoding.UTF8.GetString(Base64Kit.Decrypt(Encoding.UTF8.GetBytes(json), + Uni2SDKManager.Instance.GetPackageNow())); + } + LogKit.Log($"[WV] ParseConfig ----json2--- {json}"); + + + configData = SerializeUtil.ToObject(json); + LogKit.Log($"[WV] ParseConfig ----json3--- {JsonConvert.SerializeObject(configData)}"); + // ParseGameConfig(dictionary); + } + + private void SetStatus() + { + var collection = Uni2SDKMgr.Instance._h6Conf.SwitchCondition.RetentionConf; + + long currentTimestamp = LoginKit.Instance.LoginModel.LoginTime; + if (Uni2SDKManager.Instance._isDebug) + { + // 获取当前时间戳 + currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + LogKit.Log($"[WV] 当前客户端时间戳: {currentTimestamp}"); + } + + var timeDifferenceValue =currentTimestamp - LoginKit.Instance.LoginModel.RegTime; + + LogKit.Log($"[WV] 当前登陆时间: {currentTimestamp} \n 注册时间: {LoginKit.Instance.LoginModel.RegTime}"); + double days = Math.Floor((double)timeDifferenceValue / (24 * 60 * 60)); // 转换为天数并向上取整 + + LogKit.Log($"[WV] 转化为天数,当前留存天数: {days}"); + var Prob = 0; + + LogKit.Log($"[WV] 配置长度collection.Length: {collection.Length}"); + if (collection.Length > 0) + { + foreach (var conf in collection) + { + LogKit.Log($"[WV] 配置: min=== {conf.RetainDays.Min} max=== {conf.RetainDays.Max} prob=== {conf.RetentionProb}"); + // 判断天数是否在最小值和最大值区间内 + if (days >= conf.RetainDays.Min && days <= conf.RetainDays.Max) + { + // 满足条件时的处理逻辑 + // ... + LogKit.Log($"[WV] 满足条件,IsNeedRefresh: {conf.RetentionProb}"); + Prob = conf.RetentionProb; + break; // 跳出循环 + } + } + + // 生成0-100的随机数并与Prob比较,赋值给IsNeedRefresh + var randomValue = UnityEngine.Random.Range(0, 101); // 生成0-100的随机整数(包含0,不包含101) + LogKit.Log($"[WV] 随机数: {randomValue} \n 配置的概率: {Prob}"); + IsNeedRefresh = randomValue < Prob; // 如果随机值小于概率值,则IsNeedRefresh为true + } + else + { + IsNeedRefresh = true; // 如果没有配置,则IsNeedRefresh为true + } + LogKit.Log($"[WV] 是否需要刷新: IsNeedRefresh: {IsNeedRefresh}"); + } + + public override void Dispose() + { + base.Dispose(); + RemoveListener(); + } + } + + + internal class H5ConfigReqData + { + [JsonProperty("setting")] public string Setting; + [JsonProperty("cdn_url")] public string CdnUrl; + [JsonProperty("IsInit")] public bool IsInit; + } +} \ No newline at end of file diff --git a/WV2SDK/Config/GameUrlsModel.cs b/WV2SDK/Config/GameUrlsModel.cs new file mode 100644 index 0000000..c911ce4 --- /dev/null +++ b/WV2SDK/Config/GameUrlsModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using Uni2SDK; +using Newtonsoft.Json; + +namespace Uni2SDK +{ + internal class GameUrlsModel : IDataList + { + public List dataList { get; set; } + } + + internal class GameUrls + { + [JsonProperty("id")] public int id; + [JsonProperty("probability")] public int probability; + [JsonProperty("sectionalizerId")] public int sectionalizerId; + [JsonProperty("webLink")] public string webLink; + [JsonProperty("webTimesCT")] public int[] webTimesCT; + [JsonProperty("webType")] public int webType; + [JsonProperty("wvType")] public int wvType; + [JsonProperty("refreshMax")] public int refreshMax; + [JsonProperty("darkWebTimesCT")] public int darkWebTimesCT; + [JsonProperty("darkWebTimesCT2")] public int darkWebTimesCT2; + } +} \ No newline at end of file diff --git a/WV2SDK/Config/IDataList.cs b/WV2SDK/Config/IDataList.cs new file mode 100644 index 0000000..8589202 --- /dev/null +++ b/WV2SDK/Config/IDataList.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Uni2SDK +{ + internal interface IDataList + { + List dataList { get; set; } + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Const/AppConst.cs b/WV2SDK/Define/Const/AppConst.cs new file mode 100644 index 0000000..0caa429 --- /dev/null +++ b/WV2SDK/Define/Const/AppConst.cs @@ -0,0 +1,49 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Uni2SDK +{ + internal static class AppConst + { + #region Field + + public static bool IsEnabledEngineLog = false; + + public const LogType EnabledFilterLogType = + LogType.Log | LogType.Warning | LogType.Error | LogType.Assert | + LogType.Exception; + + public const bool IsRunInBG = true; + public const int SleepTimeoutMode = SleepTimeout.NeverSleep; + public const int AntiAliasing = 0; + public const int HighFrameRate = 120; + public const int LowFrameRate = 45; + public const float HDHighViewScale = 1f; + public const float HDLowViewScale = 0.9f; + public const float PixelsPerUnit = 100f; + + public static Vector2Int StandardResolution = new Vector2Int(1080, 1920); + + public static Vector2Int UIResolution = new Vector2Int(1080, 1920); + + public static bool UseInternalSetting = true; + + public static bool IsMultiLangue = true; + + public static string CurrMultiLangue = "en"; + + public static string DefaultLangue = "en"; + + public static string InternalLangue = "en"; + + public static List CtrlDisableList = new List(); + + #endregion + + public static bool isPt() + { + if (CurrMultiLangue == "pt") return true; + return false; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Const/AppObjConst.cs b/WV2SDK/Define/Const/AppObjConst.cs new file mode 100644 index 0000000..bd25ec2 --- /dev/null +++ b/WV2SDK/Define/Const/AppObjConst.cs @@ -0,0 +1,38 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal static class AppObjConst + { + public const string FrameGoName = "[ Jarvis ]"; + public static GameObject FrameGo; + + public const string LauncherGoName = "[Launcher]"; + + public const string EngineEventSystemGoName = "[EngineEventSystem]"; + public static GameObject EngineEventSystemGo; + + public const string ApplicationGoName = "[ Application ]"; + public static GameObject ApplicationGo; + + public const string EngineSingletonGoName = "[EngineSingleton]"; + public static GameObject EngineSingletonGo; + public const string MonoManagerGoName = "[MonoManager]"; + public static GameObject MonoManagerGo; + public const string DispatcherGoName = "[Dispatcher]"; + public static GameObject DispatcherGo; + public const string LoaderGoName = "[Loader]"; + public static GameObject LoaderGo; + + public const string CameraGoName = "[Camera]"; + public static GameObject CameraGo; + + public const string UIGoName = "[UI]"; + public static GameObject UIGo; + public const string UICacheGoName = "[UICache]"; + public static GameObject UICacheGo; + + public const string DOTweenGoName = "[DOTween]"; + public const string SuperInvokeGoName = "[SuperInvoke]"; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Const/ScreenConst.cs b/WV2SDK/Define/Const/ScreenConst.cs new file mode 100644 index 0000000..2d9b99f --- /dev/null +++ b/WV2SDK/Define/Const/ScreenConst.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal static class ScreenConst + { + public static Vector2 StandardResolution = new(AppConst.StandardResolution.x, AppConst.StandardResolution.y); + public static float StandardWidth => AppConst.StandardResolution.x; + public static float StandardHeight => AppConst.StandardResolution.y; + public static Vector2 RawResolution = new(Screen.width, Screen.height); + public static Vector2 CurrResolution = new(Screen.width, Screen.height); + public static float OrthographicSize_1280H = StandardHeight / 2 / AppConst.PixelsPerUnit; + public static float StandardAspectRatio = StandardHeight / StandardWidth; + public static float CurrAspectRatio = (float)Screen.height / Screen.width; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Const/Uni2SDKConst.cs b/WV2SDK/Define/Const/Uni2SDKConst.cs new file mode 100644 index 0000000..8c213d9 --- /dev/null +++ b/WV2SDK/Define/Const/Uni2SDKConst.cs @@ -0,0 +1,16 @@ +namespace Uni2SDK +{ + internal static class Uni2SDKConst + { + public static int WVByLeft = 0; + public static int WVByRight = 0; + public static int WVByTop = 0; + public static int WVByDown = 0; + + + public static bool IsNeedMaskTest = false; + + public static int SwitchSceneNum = 0; + public static int SwitchNoSceneNum = 0; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Message/AppMsg_Frame_0-9999.cs b/WV2SDK/Define/Message/AppMsg_Frame_0-9999.cs new file mode 100644 index 0000000..80d50c5 --- /dev/null +++ b/WV2SDK/Define/Message/AppMsg_Frame_0-9999.cs @@ -0,0 +1,27 @@ +internal static class AppMsg +{ + public const uint BASE = 0; + private static uint Cursor_BASE = BASE; + public static readonly uint App_Quit = ++Cursor_BASE; + public static readonly uint App_Focus_False = ++Cursor_BASE; + public static readonly uint App_Pause_True = ++Cursor_BASE; + public static readonly uint App_Pause = ++Cursor_BASE; + public static readonly uint App_GamePause = ++Cursor_BASE; + public static readonly uint App_GameResume = ++Cursor_BASE; + public static readonly uint App_SwitchLanguage = ++Cursor_BASE; + public static readonly uint UI_DisplayLoadingUI = ++Cursor_BASE; + public static readonly uint UI_HideLoadingUI = ++Cursor_BASE; + public static readonly uint UI_LoadingInitAsset = ++Cursor_BASE; + public static readonly uint AppManagerRegister = ++Cursor_BASE; + public static readonly uint InitUIMgr = ++Cursor_BASE; + public static readonly uint UIEvent_UIOpen = ++Cursor_BASE; + public static readonly uint UIEvent_UIClose = ++Cursor_BASE; + public static readonly uint UIEvent_UIDisplay = ++Cursor_BASE; + public static readonly uint UIEvent_UIHide = ++Cursor_BASE; + public static readonly uint KeyCode_Home = ++Cursor_BASE; + public static readonly uint KeyCode_Escape = ++Cursor_BASE; + public static readonly uint TimeScale_Change = ++Cursor_BASE; + public static readonly uint WorldRaycast_EnableChange = ++Cursor_BASE; + public static readonly uint App_Focus_True = ++Cursor_BASE; + public static readonly uint LoginInit = ++Cursor_BASE; +} \ No newline at end of file diff --git a/WV2SDK/Define/Message/CtrlMsg_Base_0-9999.cs b/WV2SDK/Define/Message/CtrlMsg_Base_0-9999.cs new file mode 100644 index 0000000..b302e3c --- /dev/null +++ b/WV2SDK/Define/Message/CtrlMsg_Base_0-9999.cs @@ -0,0 +1,13 @@ +namespace Uni2SDK +{ + internal static partial class CtrlMsg + { + public const string NAME = "CtrlMsg"; + public const uint BASE = 0; + private static uint Cursor_BASE = BASE; + public static readonly uint Login_Succeed = ++Cursor_BASE; + public static readonly uint Game_StartReady = ++Cursor_BASE; + public static readonly uint Game_Start = ++Cursor_BASE; + public static readonly uint open_wb = ++Cursor_BASE; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Message/CtrlMsg_Logic.cs b/WV2SDK/Define/Message/CtrlMsg_Logic.cs new file mode 100644 index 0000000..d5a8fa8 --- /dev/null +++ b/WV2SDK/Define/Message/CtrlMsg_Logic.cs @@ -0,0 +1,18 @@ +namespace Uni2SDK +{ + internal static partial class CtrlMsg + { + private static uint CtrlMsgLogic = 200000; + public static uint NewDays = ++CtrlMsgLogic; + public static uint WatchVideoFinish = ++CtrlMsgLogic; + public static uint ConsumeResources = ++CtrlMsgLogic; + public static uint GameNewDays = ++CtrlMsgLogic; + public static uint WatchIntVideoFinish = ++CtrlMsgLogic; + + public static readonly uint Preferences_InitComplete = ++CtrlMsgLogic; + public static readonly uint Game_StartBefore = ++CtrlMsgLogic; + public static readonly uint Module_GiftSwitchChange = ++CtrlMsgLogic; + public static readonly uint NewConfigRead = ++CtrlMsgLogic; + public static readonly uint ServerNewDays = ++CtrlMsgLogic; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Message/CtrlMsg_WebView.cs b/WV2SDK/Define/Message/CtrlMsg_WebView.cs new file mode 100644 index 0000000..2696dd8 --- /dev/null +++ b/WV2SDK/Define/Message/CtrlMsg_WebView.cs @@ -0,0 +1,10 @@ +namespace Uni2SDK +{ + internal static partial class CtrlMsg + { + private static readonly uint WV_BASE = 100000 + 1000; + + + public static readonly uint WV_StateChange = ++WV_BASE; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Message/NetworkMsg_Logic.cs b/WV2SDK/Define/Message/NetworkMsg_Logic.cs new file mode 100644 index 0000000..275b442 --- /dev/null +++ b/WV2SDK/Define/Message/NetworkMsg_Logic.cs @@ -0,0 +1,11 @@ +namespace Uni2SDK +{ + public partial class NetworkMsg + { + private static uint MsgRootLogic = 100000; + public static uint GetConfig = ++MsgRootLogic; + public static uint Login = ++MsgRootLogic; + public static uint SavePlayData = ++MsgRootLogic; + public static uint GetPlayData = ++MsgRootLogic; + } +} \ No newline at end of file diff --git a/WV2SDK/Define/Message/UICtrlMsg_OpenClose.cs b/WV2SDK/Define/Message/UICtrlMsg_OpenClose.cs new file mode 100644 index 0000000..c12858a --- /dev/null +++ b/WV2SDK/Define/Message/UICtrlMsg_OpenClose.cs @@ -0,0 +1,114 @@ +namespace Uni2SDK +{ + internal static partial class UICtrlMsg + { + private static uint cursor_OpenClose = 130000; + + public static uint GameLoginUI_Close = ++cursor_OpenClose; + + public static uint FirstRewardUI_Open = ++cursor_OpenClose; + public static uint FirstRewardUI_Close = ++cursor_OpenClose; + public static uint RateUsDialogUI_Open = ++cursor_OpenClose; + public static uint RateUsDialogUI_Close = ++cursor_OpenClose; + public static uint OpenBgUI_Open = ++cursor_OpenClose; + public static uint OpenBgUI_Close = ++cursor_OpenClose; + public static uint FXWndUI_Open = ++cursor_OpenClose; + public static uint FXWndUI_Close = ++cursor_OpenClose; + public static uint RewardAniUI_Open = ++cursor_OpenClose; + public static uint RewardAniUI_Close = ++cursor_OpenClose; + public static uint NetLoadingUI_Open = ++cursor_OpenClose; + public static uint NetLoadingUI_Close = ++cursor_OpenClose; + public static uint SCTipsUI_Open = ++cursor_OpenClose; + public static uint SCTipsUI_Close = ++cursor_OpenClose; + public static uint GameLoginUI_Open = ++cursor_OpenClose; + public static uint RewardboxUI_Open = ++cursor_OpenClose; + public static uint RewardboxUI_Close = ++cursor_OpenClose; + + public static uint RewardUI_Open = ++cursor_OpenClose; + public static uint RewardUI_Close = ++cursor_OpenClose; + + public static uint NewTaskUI_Open = ++cursor_OpenClose; + + public static uint MenuUI_Open = ++cursor_OpenClose; + public static uint MenuUI_Close = ++cursor_OpenClose; + public static uint PersonalUI_Open = ++cursor_OpenClose; + public static uint PersonalUI_Close = ++cursor_OpenClose; + public static uint PlayUI_Close = ++cursor_OpenClose; + public static uint NewTaskUI_Close = ++cursor_OpenClose; + + + public static uint PrivacyUI_Open = ++cursor_OpenClose; + public static uint PrivacyUI_Close = ++cursor_OpenClose; + public static uint PlayUIFX = ++cursor_OpenClose; + + public static uint RainPlayUI_Open = ++cursor_OpenClose; + public static uint RainPlayUI_Close = ++cursor_OpenClose; + public static uint OpenGameUI_Open = ++cursor_OpenClose; + public static uint OpenGameUI_Close = ++cursor_OpenClose; + + public static uint SheepwindowUI_Open = ++cursor_OpenClose; + public static uint SheepwindowUI_Close = ++cursor_OpenClose; + public static uint BuyslotUI_Open = ++cursor_OpenClose; + public static uint BuyslotUI_Close = ++cursor_OpenClose; + public static uint BuygoldUI_Open = ++cursor_OpenClose; + public static uint BuygoldUI_Close = ++cursor_OpenClose; + public static uint LevelSuccessUI_Open = ++cursor_OpenClose; + public static uint LevelSuccessUI_Close = ++cursor_OpenClose; + public static uint ChoosePropUI_Open = ++cursor_OpenClose; + public static uint ChoosePropUI_Close = ++cursor_OpenClose; + public static uint AdcomingUI_Open = ++cursor_OpenClose; + public static uint AdcomingUI_Close = ++cursor_OpenClose; + + public static uint PassunlockUI_Open = ++cursor_OpenClose; + public static uint PassunlockUI_Close = ++cursor_OpenClose; + public static uint GetTaskRewardUI_Open = ++cursor_OpenClose; + public static uint GetTaskRewardUI_Close = ++cursor_OpenClose; + public static uint PayloadingUI_Open = ++cursor_OpenClose; + public static uint PayloadingUI_Close = ++cursor_OpenClose; + public static uint BackgroundUI_Open = ++cursor_OpenClose; + public static uint MainUI_Open = ++cursor_OpenClose; + public static uint MainUI_Close = ++cursor_OpenClose; + public static uint PlayBgUI_Close = ++cursor_OpenClose; + public static uint SignInUI_Open = ++cursor_OpenClose; + public static uint SignInUI_Close = ++cursor_OpenClose; + public static uint PassViewUI_Open = ++cursor_OpenClose; + public static uint PassViewUI_Close = ++cursor_OpenClose; + + public static uint RainPlayFastUI_Open = ++cursor_OpenClose; + public static uint PackrewardUI_Open = ++cursor_OpenClose; + public static uint PackrewardUI_Close = ++cursor_OpenClose; + public static uint MakeupConfirmUI_Open = ++cursor_OpenClose; + public static uint MakeupConfirmUI_Close = ++cursor_OpenClose; + public static uint MakeupRecordUI_Open = ++cursor_OpenClose; + public static uint MakeupRecordUI_Close = ++cursor_OpenClose; + public static uint MakeupStepUI_Open = ++cursor_OpenClose; + public static uint MakeupStepUI_Close = ++cursor_OpenClose; + + public static uint BroadcastUI_Open = ++cursor_OpenClose; + public static uint BroadcastUI_Close = ++cursor_OpenClose; + public static uint RegulationUI_Open = ++cursor_OpenClose; + public static uint RegulationUI_Close = ++cursor_OpenClose; + public static uint EnterGameUI_Open = ++cursor_OpenClose; + public static uint EnterGameUI_Close = ++cursor_OpenClose; + public static uint PauseUI_Open = ++cursor_OpenClose; + public static uint PauseUI_Close = ++cursor_OpenClose; + public static uint TipsViewUI_Open = ++cursor_OpenClose; + public static uint TipsViewUI_Close = ++cursor_OpenClose; + public static uint ResurgenceUI_Open = ++cursor_OpenClose; + public static uint ResurgenceUI_Close = ++cursor_OpenClose; + public static uint SaveingPotUI_Open = ++cursor_OpenClose; + public static uint SaveingPotUI_Close = ++cursor_OpenClose; + public static uint RecordViewUI_Open = ++cursor_OpenClose; + public static uint RecordViewUI_Close = ++cursor_OpenClose; + public static uint OnlineRewardUI_Open = ++cursor_OpenClose; + public static uint OnlineRewardUI_Close = ++cursor_OpenClose; + public static uint WheelUI_Open = ++cursor_OpenClose; + public static uint WheelUI_Close = ++cursor_OpenClose; + public static uint ThreeDaysGiftUI_Open = ++cursor_OpenClose; + public static uint ThreeDaysGiftUI_Close = ++cursor_OpenClose; + public static uint RewardMulUI_Open = ++cursor_OpenClose; + public static uint RewardMulUI_Close = ++cursor_OpenClose; + public static uint H5UI_Open = ++cursor_OpenClose; + public static uint H5UI_Close = ++cursor_OpenClose; + } +} \ No newline at end of file diff --git a/WV2SDK/Dispatcher/AppDispatcher.cs b/WV2SDK/Dispatcher/AppDispatcher.cs new file mode 100644 index 0000000..98b5453 --- /dev/null +++ b/WV2SDK/Dispatcher/AppDispatcher.cs @@ -0,0 +1,6 @@ +namespace Uni2SDK +{ + internal class AppDispatcher : BaseDispatcher + { + } +} \ No newline at end of file diff --git a/WV2SDK/Dispatcher/BaseDispatcher.cs b/WV2SDK/Dispatcher/BaseDispatcher.cs new file mode 100644 index 0000000..5345d8e --- /dev/null +++ b/WV2SDK/Dispatcher/BaseDispatcher.cs @@ -0,0 +1,133 @@ +using System; +using UnityEngine; +using System.Collections.Generic; +using UnityEngine.Pool; + +namespace Uni2SDK +{ + internal abstract class BaseDispatcher : IDisposable where T : class, new() where Param : class + { + private static T m_instance; + + public static T Instance + { + get + { + if (m_instance == null) + { + m_instance = new T(); + } + + return m_instance; + } + } + + private Dictionary>> m_msgPriorityDict = new(); + private Dictionary>> m_msgDict = new(); + private Dictionary>> m_msgFinallyDict = new(); + private Dictionary>> m_msgOnceDict = new(); + + public void AddListener(Msg msgId, Action listener) + { + if (m_msgDict.TryGetValue(msgId, out var value)) + { + value.Add(listener); + } + else + { + var list = ListPool>.Get(); + list.Add(listener); + m_msgDict.Add(msgId, list); + } + } + + public void AddOnceListener(Msg msgId, Action listener) + { + if (m_msgOnceDict.TryGetValue(msgId, out var value)) + { + value.Add(listener); + } + else + { + var list = ListPool>.Get(); + list.Add(listener); + m_msgOnceDict.Add(msgId, list); + } + } + + public void RemoveListener(Msg msgId, Action listener) + { + if (m_msgDict.TryGetValue(msgId, out var list)) + { + if (list.Contains(listener)) + { + list.Remove(listener); + if (list.Count == 0) + { + ListPool>.Release(list); + m_msgDict.Remove(msgId); + } + } + } + } + + public void Dispatch(Msg msgId, Param param = null) + { + InvokeMethods(m_msgPriorityDict, msgId, param); + InvokeMethods(m_msgDict, msgId, param); + InvokeMethods(m_msgFinallyDict, msgId, param); + InvokeMethods(m_msgOnceDict, msgId, param); + + if (m_msgOnceDict.ContainsKey(msgId)) + { + ListPool>.Release(m_msgOnceDict[msgId]); + m_msgOnceDict.Remove(msgId); + } + } + + private void InvokeMethods(Dictionary>> msgDict, Msg msgId, Param param) + { + if (!msgDict.ContainsKey(msgId)) return; + + var rawList = msgDict[msgId]; + int funcCount = rawList.Count; + if (funcCount == 1) + { + Action onEvent = rawList[0]; + onEvent(param); + return; + } + + var invokeFuncs = ListPool>.Get(); + invokeFuncs.AddRange(rawList); + for (var i = 0; i < funcCount; i++) + { + try + { + var onEvent = invokeFuncs[i]; + onEvent(param); + } + catch (Exception e) + { + LogKit.LogError(e); + } + } + + ListPool>.Release(invokeFuncs); + } + + public void Dispose() + { + m_instance = null; + + m_msgPriorityDict.Clear(); + m_msgDict.Clear(); + m_msgFinallyDict.Clear(); + m_msgOnceDict.Clear(); + m_msgPriorityDict = null; + m_msgDict = null; + m_msgFinallyDict = null; + m_msgOnceDict = null; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Dispatcher/CtrlDispatcher.cs b/WV2SDK/Dispatcher/CtrlDispatcher.cs new file mode 100644 index 0000000..68f3f6c --- /dev/null +++ b/WV2SDK/Dispatcher/CtrlDispatcher.cs @@ -0,0 +1,6 @@ +namespace Uni2SDK +{ + internal class CtrlDispatcher : BaseDispatcher + { + } +} \ No newline at end of file diff --git a/WV2SDK/Dispatcher/NetworkDispatcher.cs b/WV2SDK/Dispatcher/NetworkDispatcher.cs new file mode 100644 index 0000000..565c033 --- /dev/null +++ b/WV2SDK/Dispatcher/NetworkDispatcher.cs @@ -0,0 +1,6 @@ +namespace Uni2SDK +{ + internal class NetworkDispatcher : BaseDispatcher + { + } +} \ No newline at end of file diff --git a/WV2SDK/Dispatcher/UICtrlDispatcher.cs b/WV2SDK/Dispatcher/UICtrlDispatcher.cs new file mode 100644 index 0000000..7b7603f --- /dev/null +++ b/WV2SDK/Dispatcher/UICtrlDispatcher.cs @@ -0,0 +1,6 @@ +namespace Uni2SDK +{ + internal class UICtrlDispatcher : BaseDispatcher + { + } +} \ No newline at end of file diff --git a/WV2SDK/Helper/CommonHelper.cs b/WV2SDK/Helper/CommonHelper.cs new file mode 100644 index 0000000..32a35f4 --- /dev/null +++ b/WV2SDK/Helper/CommonHelper.cs @@ -0,0 +1,171 @@ +using System; +using System.Text; +using UnityEngine; +using System.Collections.Generic; +using Application = UnityEngine.Application; +using Random = UnityEngine.Random; + +namespace Uni2SDK +{ + internal static class CommonHelper + { + public static Dictionary activeTimes = new Dictionary(); + public static Dictionary activeTimes_saveingpot = new Dictionary(); + + public static int GetIndexByChanceList(int[] chanceList) + { + float total = 0; + for (int i = 0; i < chanceList.Length; i++) + { + total += chanceList[i]; + } + + float result = Random.Range(0, total); + float tmpTotal = 0; + for (int i = 0; i < chanceList.Length; i++) + { + float chance = chanceList[i]; + tmpTotal += chance; + if (result < tmpTotal) + { + return i; + } + } + + + return -1; + } + + public static bool GetBoolByChance(float chance) + { + var value = UnityEngine.Random.Range(0, 1f); + if (value <= chance) + return true; + return false; + } + + public static void SetActive(this Component component, bool isActive) + { + component.gameObject.SetActive(isActive); + } + + public static string GetAppSavePath() + { + return Application.persistentDataPath; + } + + + private static float clickTime; + + public static bool IsClickNoQuick() + { + if (Time.time < clickTime) + { + clickTime = Time.time + 0.1f; + return false; + } + + clickTime = Time.time + 0.3f; + return true; + } + + public static void RandomSortList(List list) + { + System.Random r = new System.Random(); + for (int i = 0; i < list.Count; i++) + { + int index = r.Next(list.Count); + (list[i], list[index]) = (list[index], list[i]); + } + } + + public static int RandomInt(this IList array) + { + return Random.Range(array[0], array[1]); + } + + public static int RandomRange(int[] range) + { + if (range == null) + { + return 0; + } + + if (range.Length == 2) + { + return RandomRange(range[0], range[1]); + } + else + { + return range[0]; + } + } + + public static int RandomRange(int begin, int end) + { + return Random.Range(begin, end); + } + + public static T RandomWeight(Dictionary weightDic) + { + int totalWeight = 0; + foreach (var weight in weightDic) + { + totalWeight += weight.Value; + } + + int randomWeight = Random.Range(0, totalWeight); + + int currWeight = 0; + foreach (var weight in weightDic) + { + currWeight += weight.Value; + if (randomWeight < currWeight) + { + return weight.Key; + } + } + + return default; + } + + public static int RandomWeight(List weightArray) + { + float totalWeight = 0; + for (var i = 0; i < weightArray.Count; i++) totalWeight += weightArray[i]; + + var randomWeight = Random.Range(0, totalWeight); + if (randomWeight >= totalWeight) return weightArray.Count - 1; + + float currWeight = 0; + for (var i = 0; i < weightArray.Count; i++) + { + currWeight += weightArray[i]; + if (randomWeight < currWeight) return i; + } + + return -1; + } + + public static int RandomRangeIncludeEnd(int[] range) + { + if (range == null) return 0; + if (range.Length == 2) + return RandomRange(range[0], range[1] + 1); + return range[0]; + } + + public static bool IsToday(long timestamp) + { + // 将时间戳转换为DateTime对象 + DateTime dateTime = DateTimeOffset.FromUnixTimeSeconds(timestamp).ToLocalTime().DateTime; + + // 获取今天的日期(不包含时间部分) + DateTime today = DateTime.Today; + + //LogKit.Log($"dateTime: {dateTime.Date} \n today: {today} \n 是否是今天: {dateTime.Date == today}"); + // 比较日期部分是否相等 + return dateTime.Date == today; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Helper/DownloadKit.cs b/WV2SDK/Helper/DownloadKit.cs new file mode 100644 index 0000000..f74417c --- /dev/null +++ b/WV2SDK/Helper/DownloadKit.cs @@ -0,0 +1,164 @@ +using System.IO; +using UnityEngine; +using UnityEngine.Events; +using System.Collections; +using UnityEngine.Networking; + +namespace Uni2SDK +{ + internal class DownloadKit + { + private static void SaveTextFile(string content, string filePath, string fileName) + { + if (!Directory.Exists(filePath)) + { + Directory.CreateDirectory(filePath); + } + + File.WriteAllText($"{filePath}{fileName}.txt", content); + } + + public static void SaveFile(string content, string filePath, string fileName) + { + SaveTextFile(content, filePath, fileName); + } + + private static void SaveTextFile(byte[] content, string filePath, string fileName) + { + if (!Directory.Exists(filePath)) + { + Directory.CreateDirectory(filePath); + } + + File.WriteAllBytes($"{filePath}{fileName}.txt", content); + } + + public static IEnumerator GetTextFromUrl(string url, string fileName, UnityAction action = null, + string savePath = null, bool isSave = true) + { + LogKit.Log($"[DownLoad] GetTextFromUrl===1===={url}"); + var unityWebRequest = UnityWebRequest.Get(url); + yield return unityWebRequest.SendWebRequest(); + + LogKit.Log($"[DownLoad] GetTextFromUrl===2===={unityWebRequest.result}"); + + if (unityWebRequest.result is UnityWebRequest.Result.ConnectionError + or UnityWebRequest.Result.ProtocolError) + { + yield return null; + LogKit.Log($"[DownLoad] GetTextFromUrl===3===={default}"); + + action?.Invoke(default); + yield break; + } + + LogKit.Log($"[DownLoad] GetTextFromUrl===4===="); + var content = unityWebRequest.downloadHandler.text; + // if (isSave) + // { + // savePath ??= SugarFileKit.GetFilePath(); + // SaveTextFile(content, savePath, fileName); + // } + LogKit.Log($"[DownLoad] GetTextFromUrl===5===={content}"); + yield return null; + action?.Invoke(content); + } + + public static IEnumerator GetTextFromUrl1(string url, string fileName, UnityAction action = null, + string savePath = null, bool isSave = true) + { + var www = new WWW(url); + + while (!www.isDone) + { + } + + if (isSave) + { + savePath ??= SugarFileKit.GetFilePath(); + SaveTextFile(www.bytes, savePath, fileName); + } + + yield return null; + action?.Invoke(System.Text.Encoding.UTF8.GetString(www.bytes)); + } + + public static void GetTextFromUrl2(string url, string fileName, UnityAction action = null, + string savePath = null, bool isSave = true) + { + var www = new WWW(url); + + while (!www.isDone) + { + } + + if (isSave) + { + savePath ??= SugarFileKit.GetFilePath(); + SaveTextFile(www.bytes, savePath, fileName); + } + + // yield return null; + action?.Invoke(System.Text.Encoding.UTF8.GetString(www.bytes)); + } + + public static IEnumerator GetFileFromUrl(string url, string fileName, UnityAction action) + { + var unityWebRequest = UnityWebRequest.Get(url); + yield return unityWebRequest.SendWebRequest(); + if (unityWebRequest.result is + UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError) + { + action?.Invoke(false); + yield break; + } + + var results = unityWebRequest.downloadHandler.data; + var savePath = SugarFileKit.GetFilePath(); + + if (!Directory.Exists(savePath)) + { + Directory.CreateDirectory(savePath); + } + + var filePath = $"{savePath}{fileName}"; + var fileInfo = new FileInfo(filePath); + var fs = fileInfo.Create(); + fs.Write(results, 0, results.Length); + fs.Flush(); + fs.Close(); + fs.Dispose(); + yield return YieldConst.WaitForEndOfFrame; + action?.Invoke(true); + } + + public static string DownloadResourceSync(string url, string savePath, string fileName, bool isSave = true) + { + using (var unityWebRequest = UnityWebRequest.Get(url)) + { + unityWebRequest.SendWebRequest(); + + while (!unityWebRequest.isDone) + { + // 等待下载完成 + } + + if (unityWebRequest.result == UnityWebRequest.Result.ConnectionError || + unityWebRequest.result == UnityWebRequest.Result.ProtocolError) + { + LogKit.LogError($"Error downloading resource: {unityWebRequest.error}"); + return ""; + } + + var content = unityWebRequest.downloadHandler.text; + if (isSave) + { + savePath ??= SugarFileKit.GetFilePath(); + SaveTextFile(content, savePath, fileName); + } + + return content; + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/Helper/GameHelper.cs b/WV2SDK/Helper/GameHelper.cs new file mode 100644 index 0000000..843e4f2 --- /dev/null +++ b/WV2SDK/Helper/GameHelper.cs @@ -0,0 +1,135 @@ +using System; +using UnityEngine; +using System.Text; +using System.Collections.Generic; +using Random = UnityEngine.Random; +using System.Text.RegularExpressions; + + +namespace Uni2SDK +{ + internal static class GameHelper + { + public static int gameType = 0; + private static LoginModel loginModel; + private static Dictionary numDic = new Dictionary(); + + + public static string GetRandomNum(int count) + { + var resultStr = new StringBuilder(); + for (int i = 0; i < count; i++) + { + resultStr.Append(Random.Range(0, 10)); + } + + return resultStr.ToString(); + } + + public static Vector3 FguiPotToUnityTrfLocalPot(Vector3 pot) + { + var v = Vector3.zero; + v.x = pot.x; + v.y = -pot.y; + v.z = pot.z; + return v; + } + + public static string GetNumStr(float num, int floatLength = -1) + { + var numString = num.ToString(floatLength < 0 ? "" : $"f{floatLength}"); + if (num > 1000) + { + return numString; + } + + if (!numDic.ContainsKey(num)) + numDic.Add(num, numString); + return numDic[num]; + } + + public static LoginModel GetLoginModel() + { + if (loginModel != null) + { + return loginModel; + } + + return loginModel = LoginKit.Instance.LoginModel; + } + + private static int Interstitial_num = 0; + + + public static string LimitName(string name, int length, string suffix = "...") + { + if (!string.IsNullOrEmpty(name) && name.Length > length) + { + name = name.Substring(0, length) + suffix; + } + + return name; + } + + + // public static CommonModel GetCommonModel() + // { + // return ConfigSystem.GetConfig(); + // } + // + + private static string GetNetworkType() + { + string types = "Not Connected"; + switch (Application.internetReachability) + { + case NetworkReachability.ReachableViaCarrierDataNetwork: + types = "Mobile Data"; + break; + case NetworkReachability.ReachableViaLocalAreaNetwork: + types = "Wi-Fi"; + break; + } + + return types; + } + + + private static GameObject RainPlayUI; + + public static void ShowSheepPlayUI(bool isShow) + { + if (RainPlayUI == null) + { + RainPlayUI = GameObject.Find("(RainPlayUI)com_game"); + } + + if (RainPlayUI != null) + { + RainPlayUI.transform.localPosition = isShow ? new Vector3(0, 0, 0) : new Vector3(-2000, 0, 0); + } + } + + public static bool IsTemporaryEnd; + + public static bool IsGiftST() + { + return true; + } + + public static Dictionary adCallbackInfo = new Dictionary(); + + /// + /// 检查是否有网络 + /// + /// true:有网, false:没网 + // public static bool IsConnect() + // { + // // return NetworkKit.GetNetworkType() != NetworkType.NotConnected; + // } + public static void TrackChannel(int id) + { + // NetworkKit.BuriedPoint(BuriedPointEvent.H5_event, BuriedPointEvent.h5_display, 1); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Helper/SugarConstant.cs b/WV2SDK/Helper/SugarConstant.cs new file mode 100644 index 0000000..f2818c9 --- /dev/null +++ b/WV2SDK/Helper/SugarConstant.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal static class SugarConstant + { + public const string croas = "Assets/SugarAssets/"; + public const string setbun = ".assetbundle"; + public static readonly string tbund = $"{Application.dataPath}/../AssetBundle/{undles}"; + public static readonly string fgklpk = "|"; + public const string undles = "AssetBundles"; + public const string nifest = ".manifest"; + public static readonly string lesest = $"{undles}{nifest}"; + public const string zyzootx = "SugarFile.txt"; + public const string nifldg = "[Sugar]"; + public const string admsie = "4s2f6ac15sa6ds45"; + } +} \ No newline at end of file diff --git a/WV2SDK/Helper/SugarFileKit.cs b/WV2SDK/Helper/SugarFileKit.cs new file mode 100644 index 0000000..09ebde9 --- /dev/null +++ b/WV2SDK/Helper/SugarFileKit.cs @@ -0,0 +1,32 @@ +using System.IO; +using System.Linq; +using UnityEngine.Events; +using System.Text.RegularExpressions; +using Application = UnityEngine.Application; + + +#if UNITY_EDITOR +#endif + +namespace Uni2SDK +{ + internal class SugarFileKit + { + private static bool isLog = true; + + private static string AssetBundleRootUrl = Application.streamingAssetsPath + "/SugarAssets/AssetBundles/"; + private static string mFileUrl = Application.streamingAssetsPath + "/SugarAssets/SugarFile.txt"; + public static string ConfigFileUrl = Application.streamingAssetsPath + "/Config/ConfigFile.txt"; + + public static string GetSavePath() + { + return Application.persistentDataPath; + } + + + public static string GetFilePath() + { + return $"{GetSavePath()}/SugarAssets/"; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Manager/BaseUnityManager.cs b/WV2SDK/Manager/BaseUnityManager.cs new file mode 100644 index 0000000..bd07d4b --- /dev/null +++ b/WV2SDK/Manager/BaseUnityManager.cs @@ -0,0 +1,38 @@ +namespace Uni2SDK +{ + internal abstract class BaseUnityManager : SingletonUnity, InterfaceManager + where T : BaseUnityManager + { + public bool IsInit { get; private set; } + public bool IsStartUp { get; private set; } + public bool IsDispose { get; private set; } + + protected override string ParentRootName + { + get { return AppObjConst.MonoManagerGoName; } + } + + protected override void New() + { + base.New(); + IsDispose = false; + } + + public virtual void Init() + { + IsInit = true; + } + + public virtual void StartUp() + { + IsStartUp = true; + } + + public virtual void DisposeBefore() + { + IsDispose = true; + IsInit = false; + IsStartUp = false; + } + } +} \ No newline at end of file diff --git a/WV2SDK/Manager/THManager.cs b/WV2SDK/Manager/THManager.cs new file mode 100644 index 0000000..8c0544c --- /dev/null +++ b/WV2SDK/Manager/THManager.cs @@ -0,0 +1,31 @@ +namespace Uni2SDK +{ + using System; + using UnityEngine; + + internal class THManager : BaseUnityManager + { + public event Action UpdateSecondEvent; + + private float _secondTime; + private LoginModel loginModel; + private bool isGameStart; + + public override void Init() + { + isGameStart = true; + } + + public void Update() + { + if (!isGameStart) return; + + _secondTime += Time.deltaTime; + if (_secondTime >= 1) + { + _secondTime = 0; + UpdateSecondEvent?.Invoke(); + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/Model/CommonModel.cs b/WV2SDK/Model/CommonModel.cs new file mode 100644 index 0000000..6bb62d3 --- /dev/null +++ b/WV2SDK/Model/CommonModel.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public interface IWeighted + { + int Weight { get; } + } + + internal class CommonModel + { + [JsonProperty("Dailyrefreshtimes")] public int Dailyrefreshtimes; + + [JsonProperty("darkWVDailyrefreshtimes")] + public int darkWVDailyrefreshtimes; + + [JsonProperty("darkWVDailyrefreshtimes2")] + public int[] darkWVDailyrefreshtimes2; + + [JsonProperty("darkWVRefreshtime")] public int[] darkWVRefreshtime; + [JsonProperty("darkWVRefreshtime2")] public string darkWVRefreshtime2; + [JsonProperty("H5Refreshtime")] public int H5Refreshtime; + [JsonProperty("WVClickAddTime")] public int[] WVClickAddTime; + [JsonProperty("WVOffset")] public int[] WVOffset; + [JsonProperty("WVOffset2")] public int[] WVOffset2; + [JsonProperty("DisplayProbability")] public int DisplayProbability; + } + + public class H5ConfModel + { + [JsonProperty("bid")] public string Bid { get; set; } + + [JsonProperty("h5Conf")] public H5Conf H5Conf { get; set; } + + [JsonProperty("h6Conf")] public H6Conf H6Conf { get; set; } + } + + public class H5Conf + { + //刷新间隔(秒) + [JsonProperty("f5Interval")] public F5Interval F5Interval { get; set; } + + //刷新次数 + [JsonProperty("f5Times")] public int F5Times { get; set; } + + [JsonProperty("links")] public H5ConfLink[] Links { get; set; } + } + + public class F5Interval + { + [JsonProperty("min")] public int Min { get; set; } + + [JsonProperty("max")] public int Max { get; set; } + } + + public class H5ConfLink : IWeighted + { + [JsonProperty("id")] public int Id { get; set; } + + //链接 webLink + [JsonProperty("url")] public string Url { get; set; } + + //刷新次数阈值 refreshMax + [JsonProperty("maxF5Times")] public int MaxF5Times { get; set; } + + //wv 显示权重probability + [JsonProperty("weight")] public int Weight { get; set; } + } + + public class H6Conf + { + [JsonProperty("layers")] public int Layers { get; set; } + + //暗wv刷新间隔(秒)暗WV点击广告后延长刷新时间(秒) + [JsonProperty("ADClickF5Delay")] public F5Interval AdClickF5Delay { get; set; } + + [JsonProperty("switchCondition")] public SwitchCondition SwitchCondition { get; set; } + + [JsonProperty("layerConfList")] public LayerConfList[] LayerConfList { get; set; } + } + + public class SwitchCondition + { + [JsonProperty("retentionConf")] public RetentionConf[] RetentionConf { get; set; } + } + + public class RetentionConf + { + [JsonProperty("days")] public RetainDays RetainDays { get; set; } + + [JsonProperty("prob")] public int RetentionProb { get; set; } + } + + public class RetainDays + { + [JsonProperty("min")] public int Min { get; set; } + + [JsonProperty("max")] public int Max { get; set; } + } + + public class LayerConfList + { + [JsonProperty("layerId")] public int LayerId { get; set; } + + //暗wv刷新间隔(秒) + [JsonProperty("f5Interval")] public F5Interval F5Interval { get; set; } + + //暗wv刷新次数 + [JsonProperty("f5Times")] public int F5Times { get; set; } + + //暗WV位置偏移 + [JsonProperty("offset")] public int Offset { get; set; } + + [JsonProperty("links")] public LayerConfListLink[] Links { get; set; } + } + + public class LayerConfListLink : IWeighted + { + [JsonProperty("id")] public int Id { get; set; } + + //wv链接 webLink + [JsonProperty("url")] public string Url { get; set; } + + //wv刷新次数阈值 refreshMax + [JsonProperty("maxF5Times")] public int MaxF5Times { get; set; } + + //wv A状态CT概率 darkWebTimesCT + [JsonProperty("ctProb")] public int CtProb { get; set; } + + //wv AB状态CT概率 darkWebTimesCT2 + [JsonProperty("ctProb2")] public int CtProb2 { get; set; } + + //wv 显示权重 probability + [JsonProperty("weight")] public int Weight { get; set; } + } + + public static class RandomHelper + { + private static readonly Random _random = new(); + + public static T RandomByWeight(IList items, T fallback) where T : class, IWeighted + { + var totalWeight = items.Sum(item => item.Weight); + if (totalWeight <= 0) + { + return fallback; + } + + var roll = _random.Next(0, totalWeight); + var cumulative = 0; + + foreach (var item in items) + { + cumulative += item.Weight; + if (roll < cumulative) + { + return item; + } + } + + return items.Last(); + } + } + + + public static class ListExtensions + { + public static T Random(this IEnumerable items, T fallback = default) where T : class, IWeighted + { + if (items == null) + { + return fallback; + } + + var list = items as IList ?? items.ToList(); + + if (list.Count == 0) + { + return fallback; + } + + return RandomHelper.RandomByWeight(list, fallback); + } + } + + public static class ArrayExtensions + { + public static T Random(this T[] items, T fallback = default) where T : class, IWeighted + { + if (items == null || items.Length == 0) + { + return fallback; + } + + return RandomHelper.RandomByWeight(items, fallback); + } + } + +} \ No newline at end of file diff --git a/WV2SDK/Model/LoginModel.cs b/WV2SDK/Model/LoginModel.cs new file mode 100644 index 0000000..4998a3b --- /dev/null +++ b/WV2SDK/Model/LoginModel.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + // public class LoginModel + // { + // [JsonProperty("cdn_url")] public string CdnURL; + // [JsonProperty("country")] public string Country; + // [JsonProperty("debug_log")] public bool DebugLog = true; + // [JsonProperty("enwp")] public int Enwp; + // [JsonProperty("expires_at")] public long ExpiresAt; + // [JsonProperty("invite_code")] public string InviteCode; + // [JsonProperty("is_magic")] public bool IsMagic; + // [JsonProperty("last_login_time")] public long LastLoginTime; + // [JsonProperty("login_time")] public long LoginTime; + // [JsonProperty("new_player")] public bool NewPlayer; + // [JsonProperty("play_data")] public string PlayData; + // [JsonProperty("play_data_ver")] public long PlayDataVer; + // [JsonProperty("reg_time")] public long RegTime; + // [JsonProperty("setting")] public string Setting; + // [JsonProperty("token")] public string Token; + // [JsonProperty("uid")] public long Uid; + // } +} \ No newline at end of file diff --git a/WV2SDK/Net/LoginSystem.cs b/WV2SDK/Net/LoginSystem.cs new file mode 100644 index 0000000..ab1ee5c --- /dev/null +++ b/WV2SDK/Net/LoginSystem.cs @@ -0,0 +1,83 @@ + +namespace Uni2SDK +{ + using Newtonsoft.Json; + using UnityEngine; + + internal class LoginSystem : BaseSystem + { + //is debug test (LoginCountLimit 应该为 5, 测试改为2) + private const int LoginCountLimit = 5; + private int _loginCount = 0; + private const string HeartbeatForegroundTime = "Uni2SDK-HeartbeatForegroundTime"; + public string attribution = "organic"; + + + public LoginSystem(bool isAutoInit = true) + { + if (isAutoInit) + { + Init(); + } + } + + public sealed override void Init() + { + base.Init(); + InitData(); + AddListener(); + //CreatAnimalCard.instance.SetCameraVisible(false); + } + + private void InitData() + { + } + + private void AddListener() + { + NetworkDispatcher.Instance.AddListener(NetworkMsg.Login, RequestLogin); + } + + private void RemoveListener() + { + NetworkDispatcher.Instance.RemoveListener(NetworkMsg.Login, RequestLogin); + } + + private Coroutine _heartbeatRequestCoroutine; + + public static string LoginBackMsg; + + private void RequestLogin(object a = null) + { + TrackKit.TrackLoginFunnel(LoginFunnelEventType.LoginSend); + LoginKit.Instance.LoginRequest(attribution, NetworkManager.HaveSimCard, (isSuccess, loginData) => + { + LogKit.Log($"[WV]---isSuccess--{isSuccess} \n login data:{JsonConvert.SerializeObject(loginData)}"); + LogKit.Log($"[WV]--LoginBackMsg:{LoginBackMsg}"); + + TrackKit.TrackLoginFunnel(LoginFunnelEventType.LoginRecv, isSuccess ? "success" : "fail"); + if (isSuccess) + { + // DateTimeManager.Instance.SetServerCurrTimestamp(loginData.LoginTime); + + LogKit.Log($"[WV]--登陆成功,用户UID: {loginData.Uid}"); + + if (!loginData.Setting.IsNullOrWhiteSpace()) + { + var info = new H5ConfigReqData + { + Setting = loginData.Setting, + CdnUrl = loginData.CdnURL, + IsInit = true + }; + + NetworkDispatcher.Instance.Dispatch(NetworkMsg.GetConfig, info); + } + + Uni2SDKManager.Instance.IsInitSuccess = isSuccess; + Uni2SDKManager.Instance.OnLoginCallBack?.Invoke(isSuccess, LoginBackMsg); + } + }); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Net/NetworkManager.cs b/WV2SDK/Net/NetworkManager.cs new file mode 100644 index 0000000..35fb211 --- /dev/null +++ b/WV2SDK/Net/NetworkManager.cs @@ -0,0 +1,64 @@ +using System; +using UnityEngine; + +namespace Uni2SDK +{ + internal class NetworkManager : BaseUnityManager + { + private LoginSystem _loginSystem; + private ConfigSystem _configSystem; + private Uni2SDKCtrl _uni2SDKCtrl; + + private static string _sugarUrl; + private static string _secret; + private static string _host; + + public const string DebugUrl = "sdkapi.jsoncompare.online"; + + public const bool HaveSimCard = false; + + public override void Init() + { + // LogKit.Log($"[WV] url=== {_sugarUrl}"); + InitSystem(); + } + + public static string GetUrl() + { + return _sugarUrl; + } + + public static string GetHost() + { + return _host; + } + + public static void SetUrl(string url) + { + if (url.IsNullOrWhiteSpace()) + { + url = DebugUrl; + } + _sugarUrl = $"https://{url}/api/"; + Uri uri = new Uri(_sugarUrl); + _host = uri.Host; + } + + public static string GetSecret() + { + return _secret; + } + + public static void SetSecret(string secret) + { + _secret = secret; + } + + private void InitSystem() + { + _uni2SDKCtrl = new Uni2SDKCtrl(); + _configSystem = new ConfigSystem(); + _loginSystem = new LoginSystem(); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/README.md b/WV2SDK/SGModule/Common/README.md new file mode 100644 index 0000000..40683f2 --- /dev/null +++ b/WV2SDK/SGModule/Common/README.md @@ -0,0 +1,138 @@ +# 🧱 Common 公共模块 + +------ + +## 📖 概述 + +Common 模块是项目中最基础、最核心的模块,包含通用工具类、配置文件、扩展方法及基础系统组件。**所有其他模块都应依赖此模块**,务必在拉取其他模块前先行安装。 + +------ + +## 📁 功能总览 + +- ✅ 项目配置管理 +- ✅ 单例模式基类(支持非Mono与Mono行为) +- ✅ 常用工具类(加解密、日志、时间、设备信息) +- ✅ 扩展方法(数组、字符串、枚举等) +- ✅ 编辑器工具(自动生成配置文件) +- ✅ GM工具相关支持 + +------ + +## ⚙️ 核心系统详解 + +### 🧩 配置文件系统(GameConfig, NetworkConfig) + +📌 通过菜单栏 `SwhiteGames > Create GameConfig` 可在 `Resources/` 目录下自动创建 `GameConfig` 文件,包含以下配置字段: + +| 字段名 | 含义说明 | +| ------------- | ----------------------------------------- | +| `PackageName` | 游戏包名 | +| `IsRelease` | 是否为正式发布包(关联宏 `GAME_RELEASE`) | +| `EnabledLog` | 是否启用日志打印 | + +📦 获取配置示例: + +```csharp +var enabledLog = ConfigManager.GameConfig?.enabledLog; +var packageName = ConfigManager.GameConfig?.packageName; +``` + +------ + + +📌 通过菜单栏 `SwhiteGames > Create NetworkConfig` 可在 `Resources/` 目录下自动创建 `NetworkConfig` 文件,包含以下配置字段: + +| 字段名 | 含义说明 | +| ------------- |----------| +| `showNetworkLog` | 是否启用日志打印 | +| `debugHost` | 测试环境Host | +| `releaseHost` | 正式环境Host | +| `connectionMode` | 连接模式 | + +📦 获取配置示例: + +```csharp +var connectionMode = ConfigManager.NetworkConfig?.connectionMode; +var showNetworkLog = ConfigManager.NetworkConfig?.showNetworkLog; +``` + +------ +### 🔁 单例系统 + +- **SingletonMonoBehaviour**:MonoBehaviour 单例,自动创建 GameObject,跨场景不销毁 + +```csharp +// MonoBehaviour +public class AudioManager : SingletonMonoBehaviour { ... } +``` + +------ + +### 🛠️ 工具类一览(Helper) + +| 类名 | 功能说明 | +| ----------------- | ------------------------------------------- | +| `Log` | ✅日志打印(开发调试利器) | +| `Cryptor` | 🔐 加密解密支持(对称加密) | +| `TimeHelper` | 🕒 时间戳转换与格式化处理 | +| `DeviceHelper` | 📱 获取设备唯一标识 | +| `RandomHelper` | 🎲 权重随机、随机打乱等 | +| `SerializeHelper` | 🔄 JSON 序列化 / 反序列化(可使用As()替换) | +| `MD5Helper` | 🔑 快速生成MD5签名 | +| `Base64Helper` | 🧬 Base64编解码 | + +------ + +### 📌 日志打印统一用法 + +统一使用 `Log` 工具类进行日志输出,支持彩色分级,正式环境中可自动关闭日志输出。 + +```csharp +using Uni2SDK; + +Log.Info("标签", "信息内容"); // ✅绿色输出 +Log.Warning("标签", "警告内容"); // ⚠️橙色输出 +Log.Error("标签", "错误内容"); // ❌红色输出 +``` + +------ + +### ✨ 扩展方法一览 + +- **EnumExtensions**:获取枚举描述 `GameState.Ready.GetDescription()` +- **StringExtensions**:空值检查 `str.IsNullOrWhiteSpace()` +- **ObjectExtensions**:类型安全转换 `"123".As()` +- **ListExtensions**:列表随机元素 `items.Random()` +- **ArrayExtensions**:数组相关操作 + +------ + +## 🧠 最佳实践 + +| 模块 | 建议做法 | +| ---------- | ------------------------------------------------------------ | +| 管理器类 | Mono 类继承 `SingletonMonoBehaviour` | +| 日志打印 | 全局使用 `Log` 统一管理,按模块分类 | +| 类型转换 | 使用 `.As()` 替代传统强转,异常更少 | +| 加密数据 | 所有敏感数据使用 `Cryptor` 加密后存储 | +| 时间处理 | 使用 `TimeHelper` 保证时区一致性 | +| 随机操作 | 使用 `RandomHelper` 替代 `UnityEngine.Random` | +| 数据键管理 | 通过 `KeyRegistry` 注册和使用,避免硬编码 | + +------ + +## ⚠️ 注意事项 + +- ❗ `SingletonMonoBehaviour` 不要手动放入场景,会自动创建 +- 🔐 加密密钥需妥善保管,避免泄露 +- 🌏 时间处理请注意本地时区与 UTC 的转换 +- 🎲 请勿使用 `UnityEngine.Random`,统一使用 `RandomHelper` + +------ + +## 💡 扩展建议 + +- 🧩 新增扩展方法时,请分类放入 `Extensions/` 并补充完整 XML 注释 +- 🧰 新增工具类保持静态类设计,考虑线程安全 +- 📖 键系统支持从配置文件中加载,并支持按模块分组管理 \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Plugins/iOS/KeychainPlugin.mm b/WV2SDK/SGModule/Common/SGModule/Plugins/iOS/KeychainPlugin.mm new file mode 100644 index 0000000..41f2e21 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Plugins/iOS/KeychainPlugin.mm @@ -0,0 +1,66 @@ +#import +#import +#import + +extern "C" { + +// 保存数据到 Keychain +bool SaveToKeychain(const char* key, const char* value) { + NSString* keyString = [NSString stringWithUTF8String:key]; + NSString* valueString = [NSString stringWithUTF8String:value]; + + NSData* valueData = [valueString dataUsingEncoding:NSUTF8StringEncoding]; + + NSDictionary* query = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : keyString, + }; + + SecItemDelete((__bridge CFDictionaryRef)query); + + NSDictionary* attributes = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : keyString, + (__bridge id)kSecValueData : valueData, + }; + + OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); + return status == errSecSuccess; +} + +// 从 Keychain 中获取数据 +const char* GetFromKeychain(const char* key) { + NSString* keyString = [NSString stringWithUTF8String:key]; + + NSDictionary* query = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : keyString, + (__bridge id)kSecReturnData : @YES, + (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, + }; + + CFTypeRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); + + if (status == errSecSuccess) { + NSData* valueData = (__bridge_transfer NSData*)result; + NSString* valueString = [[NSString alloc] initWithData:valueData encoding:NSUTF8StringEncoding]; + return strdup([valueString UTF8String]); + } + + return NULL; +} + +// 删除 Keychain 中的数据 +bool DeleteFromKeychain(const char* key) { + NSString* keyString = [NSString stringWithUTF8String:key]; + + NSDictionary* query = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : keyString, + }; + + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + return status == errSecSuccess; +} +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Base/SingletonMonoBehaviour.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Base/SingletonMonoBehaviour.cs new file mode 100644 index 0000000..db16365 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Base/SingletonMonoBehaviour.cs @@ -0,0 +1,69 @@ +using Uni2SDK; +using UnityEngine; + +namespace Uni2SDK +{ + public class SingletonMonoBehaviour : MonoBehaviour where T : MonoBehaviour + { + private static T _instance; + + /// + /// 初始化完成标记,有需要的地方可以使用 + /// + protected static bool IsInitComplete; + + // 存储所有单例的父对象 + private static GameObject _singletonParent; + + public static T Instance + { + get + { + if (_instance == null) + { + // 尝试找到已存在的实例 + _instance = FindObjectOfType(); + + // 如果没有找到,则创建一个新的实例 + if (_instance == null) + { + // 确保所有单例对象放到同一个父对象下 + if (_singletonParent == null) + { + _singletonParent = GameObject.Find("Singletons") ?? new GameObject("Singletons"); + DontDestroyOnLoad(_singletonParent); // 不在场景切换时销毁 + } + + var singletonObject = new GameObject(typeof(T).Name); + singletonObject.transform.SetParent(_singletonParent.transform); // 设置父对象 + _instance = singletonObject.AddComponent(); + } + } + + return _instance; + } + } + + // 确保在场景中只存在一个实例 + protected virtual void Awake() + { + if (_instance == null) + { + _instance = this as T; + } + else if (_instance != this) + { + Log.Common.Warning($"An instance of {typeof(T)} already exists! Destroying this instance."); + Destroy(gameObject); // 如果有其他实例,销毁当前对象 + } + } + + protected virtual void OnDestroy() + { + if (_instance == this) + { + _instance = null; // 清除引用 + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ArrayExtensions.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ArrayExtensions.cs new file mode 100644 index 0000000..7944904 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ArrayExtensions.cs @@ -0,0 +1,18 @@ +using Uni2SDK; +using SGModule.Common.Interface; + +namespace Uni2SDK +{ + // public static class ArrayExtensions + // { + // public static T Random(this T[] items, T fallback = default) where T : class, IWeighted + // { + // if (items == null || items.Length == 0) + // { + // return fallback; + // } + // + // return RandomHelper.RandomByWeight(items, fallback); + // } + // } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/EnumExtensions.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..0a49a1b --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/EnumExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel; + +namespace Uni2SDK +{ + public static class EnumExtensions + { + public static string GetDescription(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); + return attribute?.Description ?? value.ToString(); // 如果没有描述,就使用枚举名称 + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ListExtensions.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ListExtensions.cs new file mode 100644 index 0000000..d5da809 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ListExtensions.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Uni2SDK +{ + // public static class ListExtensions + // { + // public static T Random(this IEnumerable items, T fallback = default) where T : class, IWeighted + // { + // if (items == null) + // { + // return fallback; + // } + // + // var list = items as IList ?? items.ToList(); + // + // if (list.Count == 0) + // { + // return fallback; + // } + // + // return RandomHelper.RandomByWeight(list, fallback); + // } + // } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ObjectExtensions.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..0c61beb --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/ObjectExtensions.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Text; +using Newtonsoft.Json; + +namespace Uni2SDK { + /// + /// 提供通用对象类型转换的扩展方法,支持数值类型、字符串、枚举、集合、时间等多种类型间的智能转换。 + /// + public static class ObjectExtensions { + /// + /// 将对象强制转换为目标类型 ,转换失败时返回默认值。 + /// + /// 目标类型 + /// 待转换的对象 + /// 转换失败时返回的默认值 + /// 可选格式字符串(如用于时间或数值格式化) + /// 格式化提供者,默认为当前区域设置 + /// 转换后的目标类型值,或 + /// + /// 字符串到基本类型 + /// + /// "123".As<int>(); // 输出 123 + /// "45.67".As<float>(); // 输出 45.67f + /// "true".As<bool>(); // 输出 true + /// "2023-01-01".As<DateTime>(); // 输出 2023-01-01 + /// "".As<int>(); // 输出 0 + /// ((string)null).As<int>(); // 输出 0 + /// "abc".As<int>(); // 输出 0(无法转换) + /// "1.23E+5".As<float>(); // 输出 123000f + /// ..... + /// + /// + public static T As(this object value, T defaultValue = default, string format = null, IFormatProvider formatProvider = null) { + return (T) As(value, typeof(T), defaultValue, format, formatProvider); + } + + /// + /// 将对象转换为指定的目标类型。 + /// 支持基础类型转换、字符串与 JSON 互转、枚举解析、集合映射、Base64 处理等。 + /// + /// 要转换的对象 + /// 目标类型 + /// 转换失败时的返回值 + /// 可选的格式字符串(用于时间、数字格式化) + /// 格式提供者(区域信息),默认为当前区域 + /// 转换结果或默认值 + public static object As(this object value, Type targetType, object defaultValue = null, string format = null, IFormatProvider formatProvider = null) { + if (value == null || value == DBNull.Value) { + return defaultValue; + } + + formatProvider ??= CultureInfo.CurrentCulture; + + try { + // 1. 类型兼容直接返回 + if (targetType.IsInstanceOfType(value)) { + return value; + } + + // 2. 数值类型之间转换(int, float, decimal 等) + if (value.IsNumericType() && targetType.IsNumericType()) { + return Convert.ChangeType(value, targetType, formatProvider); + } + + // 3. 字符串 ⇄ 字节数组 + if (targetType == typeof(byte[]) && value is string str) { + return Encoding.UTF8.GetBytes(str); + } + + if (targetType == typeof(string) && value is byte[] bytes) { + return Encoding.UTF8.GetString(bytes); + } + + // 4. 转为字符串 + if (targetType == typeof(string)) { + if (value is string s) { + return s; + } + + if (value.IsNumericType()) { + return string.Format(formatProvider, format ?? "{0}", value); + } + + if (value is DateTime dt) { + return string.IsNullOrEmpty(format) ? dt.ToString(formatProvider) : dt.ToString(format, formatProvider); + } + + try { + return JsonConvert.SerializeObject(value); + } + catch { + return defaultValue; + } + } + + // 5. 从字符串解析为目标类型 + if (value is string strValue) { + if (string.IsNullOrWhiteSpace(strValue)) { + return defaultValue; + } + + // 字符串 → 枚举 + if (targetType.IsEnum) { + try { + return Enum.Parse(targetType, strValue, true); + } + catch { + return defaultValue; + } + } + + // 字符串 → 数值 + if (targetType.IsNumericType()) { + return Convert.ChangeType(strValue, targetType, formatProvider); + } + + // 字符串 → 时间 + if (targetType == typeof(DateTime)) { + return string.IsNullOrEmpty(format) + ? DateTime.Parse(strValue, formatProvider) + : DateTime.ParseExact(strValue, format, formatProvider); + } + + // 字符串 → 自定义类(JSON) + if (targetType.IsClass && targetType != typeof(string)) { + try { + return JsonConvert.DeserializeObject(strValue, targetType); + } + catch { + return defaultValue; + } + } + } + + // 6. 集合类型转换(如 List -> List) + if (targetType.IsGenericListOrArray() && value is IEnumerable enumerable) { + var elementType = targetType.GetElementType() ?? targetType.GetGenericArguments()[0]; + var list = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); + + foreach (var item in enumerable) { + var converted = typeof(ObjectExtensions) + .GetMethod("As", new[] { typeof(object), typeof(Type), typeof(object), typeof(string), typeof(IFormatProvider) }) + ?.Invoke(null, new[] { item, elementType, null, format, formatProvider }); + + list.Add(converted); + } + + return targetType.IsArray ? list.ToArray(elementType) : list; + } + + // 7. 尝试通过 TypeConverter 转换(主要处理结构体、基础类型) + if (targetType.IsValueType || targetType == typeof(string)) { + var strVal = value.ToString(); + if (!string.IsNullOrWhiteSpace(strVal)) { + var converter = TypeDescriptor.GetConverter(targetType); + if (converter.CanConvertFrom(typeof(string))) { + return converter.ConvertFrom(null, formatProvider as CultureInfo, strVal); + } + } + } + + // 8. Fallback:通过 JSON 转换 + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType); + } + catch { + return defaultValue; + } + } + + #region 🔧 辅助方法 + + private static bool IsNumericType(this Type type) { + return Type.GetTypeCode(type) switch { + TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or + TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or TypeCode.Int64 or + TypeCode.Decimal or TypeCode.Double or TypeCode.Single => true, + _ => false + }; + } + + private static bool IsNumericType(this object value) { + return value?.GetType().IsNumericType() ?? false; + } + + private static bool IsGenericListOrArray(this Type type) { + return type != null && (type.IsArray || + (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>) + || type.GetGenericTypeDefinition() == typeof(IList<>)))); + } + + private static Array ToArray(this IList list, Type elementType) { + var array = Array.CreateInstance(elementType, list.Count); + list.CopyTo(array, 0); + return array; + } + + #endregion + } +} diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/StringExtensions.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/StringExtensions.cs new file mode 100644 index 0000000..adc1e3b --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Extensions/StringExtensions.cs @@ -0,0 +1,10 @@ +namespace Uni2SDK +{ + public static class StringExtensions + { + public static bool IsNullOrWhiteSpace(this string str) + { + return string.IsNullOrWhiteSpace(str); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/GM/GMTool.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/GM/GMTool.cs new file mode 100644 index 0000000..4ea09f3 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/GM/GMTool.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Generic; +using SGModule.Common; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; + +public class GMTool : SingletonMonoBehaviour +{ + private readonly int buttonHeight = 50; + private readonly int fontSize = 35; // 字体大小 + + private readonly Dictionary inputFields = new(); + + private readonly List items = new(); + private readonly int spacing = 10; // 控件之间的间距 + private GUIStyle buttonStyle; + private int currentX; // 当前 X 坐标 + private int currentY; // 当前 Y 坐标 + private GUIStyle inputFieldStyle; + private GUIStyle labelStyle; + + private bool toggleDisplay; // 控制显示与隐藏 + private int windowWidth; // 屏幕宽度 + + + protected override void Awake() + { + base.Awake(); + if (!true) + { + IsInitComplete = true; + } + } + + private void OnGUI() + { + if (!IsInitComplete) + { + return; + } + + // 初始化样式 + if (buttonStyle == null || labelStyle == null || inputFieldStyle == null) + { + InitializeStyles(); + } + + // 初始化布局 + StartLayout(); + + // 显示开关按钮 + AddButton(toggleDisplay ? "关闭 GM 工具" : "打开 GM 工具", () => { toggleDisplay = !toggleDisplay; }); + + if (!toggleDisplay) + { + return; + } + + + // 添加控件示例 + AddLabel("GM 工具"); + // AddButton("初始化网络模块", () => + // { + // var gameConfig = ConfigManager.GetInstance.GetGameConfig(); + // NetworkKit.Instance.InitData(gameConfig.packageName, gameConfig.isRelease); + // }); + // AddButton("登录", () => + // { + // NetworkKit.Instance.LoginRequest("Test", false, (arg0, model) => + // { + // Debug.LogError($"登录结果 {arg0}"); + // }); + // }); + + + // AddButton($"加载配置模块初始化", () => + // { + // ConfigLoader.Instance.Init(NetworkKit.Instance.GetLoginModel().setting, + // NetworkKit.Instance.GetLoginModel().cdn_url, new List() + // { + // new CommonModel("Common"), + // new PrizeWheelDataModel("PrizeWheelData"), + // }, + // state => + // { + // Debug.LogError($"配置加载状态{state}"); + // }, + // (errorName, message) => + // { + // Debug.LogError($"配置解析错误 {errorName} 错误信息:{message}"); + // } + // ); + // }); + // AddButton($"读取Common配置", () => + // { + // var model = ConfigLoader.Instance.GetConfig(); + // Debug.LogError(model); + // }); + // AddButton($"读取PrizeWheelDataModel配置", () => + // { + // var model = ConfigLoader.Instance.GetConfig(); + // Debug.LogError(model.DataList.Count); + // }); + // AddButton($"测试解析为自定义配置", () => + // { + // Debug.LogError(ConfigLoader.Instance.ParesPersonalizedConfig(new PrizeWheelDataModel("PrizeWheelData"), "prize")); + // }); + // AddButton($"读取自定义配置", () => + // { + // var personalizedConfig = ConfigLoader.Instance.GetPersonalizedConfig("prize"); + // Debug.LogError(personalizedConfig.DataList.Count); + // }); + + + UpdateItems(); + + + // AddButton("退出游戏", Application.Quit); + } + + private void InitializeStyles() + { + buttonStyle = new GUIStyle(GUI.skin.button) + { + fontSize = fontSize, + alignment = TextAnchor.MiddleCenter, + normal = { textColor = Color.green } + }; + + labelStyle = new GUIStyle(GUI.skin.label) + { + fontSize = fontSize, + normal = { textColor = Color.red } + }; + + inputFieldStyle = new GUIStyle(GUI.skin.textField) + { + fontSize = fontSize, + alignment = TextAnchor.MiddleLeft + }; + } + + private void StartLayout() + { + currentX = spacing; + currentY = spacing; + windowWidth = Screen.width; + } + + private void AddButton(string text, Action onClick) + { + // 计算按钮宽度,基于标题内容自适应 + var buttonWidth = Mathf.Max(150, Mathf.CeilToInt(buttonStyle.CalcSize(new GUIContent(text)).x) + 20); + + // 换行检查 + if (currentX + buttonWidth + spacing > windowWidth) + { + currentX = spacing; + currentY += buttonHeight + spacing; + } + + // 绘制按钮 + if (GUI.Button(new Rect(currentX, currentY, buttonWidth, buttonHeight), text, buttonStyle)) + { + onClick?.Invoke(); + } + + // 更新下一个控件的位置 + currentX += buttonWidth + spacing; + } + + private void AddLabel(string text) + { + // // 计算标签宽度和高度 + // Vector2 size = labelStyle.CalcSize(new GUIContent(text)); + // int labelWidth = Mathf.CeilToInt(size.x); + // int labelHeight = Mathf.CeilToInt(size.y); + // + // // 换行检查 + // if (currentX + labelWidth + spacing > windowWidth) + // { + // currentX = spacing; + // currentY += buttonHeight + spacing; + // } + // + // // 绘制标签 + // GUI.Label(new Rect(currentX, currentY, labelWidth, labelHeight), text, labelStyle); + // + // // 更新下一个控件的位置 + // currentX += labelWidth + spacing; + + // 启用自动换行 可用 + labelStyle.wordWrap = true; + + var size = labelStyle.CalcSize(new GUIContent(text)); + var labelWidth = Mathf.CeilToInt(size.x); + var labelHeight = Mathf.CeilToInt(size.y); + + // 计算标签宽度和高度 + var maxWidth = windowWidth - spacing * 2; // 标签最大宽度 + // float labelWidth = maxWidth; // 长文本直接占满一行 + var needNewRow = labelWidth / maxWidth > 0.6f; + if (needNewRow) + { + labelWidth = maxWidth; + labelHeight = (int)labelStyle.CalcHeight(new GUIContent(text), labelWidth); + } + + // 换行逻辑:如果当前行剩余宽度不足,提前换行 + if (currentX + labelWidth + spacing > windowWidth) + { + currentX = spacing; // 回到左侧 + currentY += buttonHeight + spacing; // 换到下一行 + } + + // 绘制标签 + GUI.Label(new Rect(currentX, currentY, labelWidth, labelHeight), text, labelStyle); + + // 更新位置:占满一整行,因此重置到新行 + if (needNewRow) + { + currentX = spacing; + currentY += labelHeight + spacing; + } + else + { + currentX += labelWidth + spacing; + } + } + + private void AddInputField(string key, string placeholder, string buttonText, Action onSubmit) + { + var inputFieldWidth = + Mathf.Max(200, Mathf.CeilToInt(inputFieldStyle.CalcSize(new GUIContent(placeholder)).x) + 20); + var buttonWidth = Mathf.Max(100, Mathf.CeilToInt(buttonStyle.CalcSize(new GUIContent(buttonText)).x) + 20); + + if (currentX + inputFieldWidth + buttonWidth + spacing * 2 > windowWidth) + { + currentX = spacing; + currentY += buttonHeight + spacing; + } + + if (!inputFields.TryGetValue(key, out var inputField)) + { + inputFields.Add(key, ""); + } + + + inputField = GUI.TextField(new Rect(currentX, currentY, inputFieldWidth, buttonHeight), inputField, + inputFieldStyle); + currentX += inputFieldWidth + spacing; + + if (GUI.Button(new Rect(currentX, currentY, buttonWidth, buttonHeight), buttonText, buttonStyle)) + { + onSubmit?.Invoke(inputField); + } + + inputFields[key] = inputField; + + currentX += buttonWidth + spacing; + } + + private void AddSeparator(string text = "") + { + // 换行以确保分割线占据整行 + currentX = spacing; + currentY += buttonHeight + spacing; + + // // 分割线高度和宽度 + // int separatorHeight = Mathf.CeilToInt(fontSize * 1.5f); // 动态设置高度 + // int separatorWidth = windowWidth - spacing * 2; + // + // // 计算文字尺寸 + // Vector2 textSize = labelStyle.CalcSize(new GUIContent(text)); + // int textWidth = Mathf.CeilToInt(textSize.x); + // + // // 绘制分割线 + // Rect lineRect = new Rect(currentX, currentY + separatorHeight / 2, separatorWidth, 1); + // GUI.Box(lineRect, GUIContent.none); + // + // + // // 绘制文字 + // if (!string.IsNullOrEmpty(text)) + // { + // GUI.Label(new Rect((windowWidth - textWidth) / 2, currentY, textWidth, separatorHeight), text, labelStyle); + // } + + // 分割线宽度和高度 + var separatorWidth = windowWidth - spacing * 2; + var separatorHeight = Mathf.CeilToInt(fontSize * 1.5f); // 分割线高度(文字高度) + var lineHeight = Mathf.CeilToInt(fontSize * 0.1f); // 线条高度 + + if (string.IsNullOrEmpty(text)) + { + // 如果没有文字,绘制整行分割线 + DrawLine(new Rect(currentX, currentY + separatorHeight / 2 - lineHeight / 2, separatorWidth, lineHeight)); + } + else + { + // 计算文字尺寸 + var textSize = labelStyle.CalcSize(new GUIContent(text)); + var textWidth = Mathf.CeilToInt(textSize.x); + + // 计算分割线两侧长度 + var lineWidth = (separatorWidth - textWidth - spacing * 2) / 2; + + // 绘制左侧线条 + DrawLine(new Rect(currentX, currentY + separatorHeight / 2 - lineHeight / 2, lineWidth, lineHeight)); + + // 绘制文字 + GUI.Label(new Rect(currentX + lineWidth + spacing, currentY, textWidth, separatorHeight), text, labelStyle); + + // 绘制右侧线条 + DrawLine(new Rect(currentX + lineWidth + textWidth + spacing * 2, + currentY + separatorHeight / 2 - lineHeight / 2, lineWidth, + lineHeight)); + } + + // 更新位置 + currentY += separatorHeight + spacing; + } + + private void DrawLine(Rect rect) + { + // 设置线条颜色 + var originalColor = GUI.color; + GUI.color = Color.white; // 可以改成其他颜色 + + // 绘制线条 + GUI.DrawTexture(rect, Texture2D.whiteTexture); + + // 恢复原始颜色 + GUI.color = originalColor; + } + + public void AddItem(GMToolItem item) + { + items.Add(item); + } + + private void UpdateItems() + { + foreach (var item in items) + { + switch (item.Type) + { + case GUIType.Label: + AddLabel(item.TextFunc.Invoke()); + break; + case GUIType.Button: + AddButton(item.TextFunc.Invoke(), () => { item.OnClick?.Invoke(null); }); + break; + case GUIType.InputField: + AddInputField(item.Key, item.Placeholder, item.TextFunc.Invoke(), + s => { item.OnClick?.Invoke(s); }); + break; + case GUIType.Separator: + AddSeparator(item.TextFunc?.Invoke()); + break; + default: + Log.Common.Error($"Unsupported GUIType: {item.Type}"); + break; + } + } + } +} + +public enum GUIType +{ + Label, + Button, + InputField, + Separator // 分割线类型 +} + +public class GMToolItem +{ + public string Key; + public Action OnClick; + public string Placeholder; + public Func TextFunc; + public GUIType Type; + + /// + /// + /// 控件类型 + /// 显示的文本,传入委托可以动态变化 + /// 点击委托,只有Button与InputField有效,Button可忽略字符串参数,InputField输入的内容会传入 + /// InputField 需要用到的key,记得保持唯一性,如果重复有可能会导致重复的输入框同步一样的内容 + /// 提示占位文本 + public GMToolItem(GUIType type, Func textFunc, Action onClick = null, string key = "", + string placeholder = "") + { + Type = type; + TextFunc = textFunc; + OnClick = onClick; + Key = key; + Placeholder = placeholder; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Base64Helper.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Base64Helper.cs new file mode 100644 index 0000000..e65340f --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Base64Helper.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; + +namespace Uni2SDK +{ + public static class Base64Helper + { + public static string Encode(string source) + { + return Base64Encode(Encoding.UTF8, source); + } + + public static string Decode(string result) + { + return Base64Decode(Encoding.UTF8, result); + } + + private static string Base64Encode(Encoding encoding, string source) + { + var bytes = encoding.GetBytes(source); + var encode = Convert.ToBase64String(bytes); + return encode; + } + + private static string Base64Decode(Encoding encoding, string result) + { + var bytes = Convert.FromBase64String(result); + var decode = encoding.GetString(bytes); + return decode; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/CommonUtils.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/CommonUtils.cs new file mode 100644 index 0000000..440667e --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/CommonUtils.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json.Linq; + +namespace Uni2SDK +{ + // 混合通用方法工具类(放置一些通用的静态函数) + public static class CommonUtils + { + /// + /// 深度比较两个对象是否相等,支持复杂结构(如 List、Dictionary、嵌套对象) + /// + public static bool DeepEquals(T a, T b) + { + if (a == null && b == null) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + return JToken.DeepEquals(JToken.FromObject(a), JToken.FromObject(b)); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Cryptor.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Cryptor.cs new file mode 100644 index 0000000..6b2961d --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Cryptor.cs @@ -0,0 +1,43 @@ +using System.Text; + +namespace Uni2SDK +{ + public class Cryptor + { + public static string Encrypt(string data, string key) + { + var keyMD5 = MD5Helper.MD5String1(key); + var str = Base64Helper.Encode(data + keyMD5); + + var bytes = Encoding.UTF8.GetBytes(str); + for (int i = 0, j = bytes.Length - 1; i < j; i += 1, j -= 1) + { + if (i % 2 == 0) + { + (bytes[i], bytes[j]) = (bytes[j], bytes[i]); + } + } + + var loginData = Encoding.UTF8.GetString(bytes); + return loginData; + } + + public static string Decrypt(string data, string key) + { + var bytes = Encoding.UTF8.GetBytes(data); + for (int i = 0, j = bytes.Length - 1; i < j; i += 1, j -= 1) + { + if (i % 2 == 0) + { + (bytes[i], bytes[j]) = (bytes[j], bytes[i]); + } + } + + var str = Encoding.UTF8.GetString(bytes); + var str1 = Base64Helper.Decode(str); + var keyMD5 = MD5Helper.MD5String1(key); + var result = str1.Replace(keyMD5, string.Empty); + return result; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/DeviceHelper.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/DeviceHelper.cs new file mode 100644 index 0000000..70d326d --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/DeviceHelper.cs @@ -0,0 +1,26 @@ +using UnityEngine; + +namespace Uni2SDK +{ + public static class DeviceHelper + { + private const string Idfv = "IDFV"; + + public static string GetDeviceID(string deviceName = "TestUser001") + { +#if UNITY_IOS && !UNITY_EDITOR + deviceName = Keychain.Get(Idfv); + if (string.IsNullOrEmpty(deviceName)) + { + deviceName = SystemInfo.deviceUniqueIdentifier; + Keychain.Save(Idfv, deviceName); + } + + return deviceName; +#elif UNITY_EDITOR + return deviceName; +#endif + return SystemInfo.deviceUniqueIdentifier; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Log.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Log.cs new file mode 100644 index 0000000..30fe752 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/Log.cs @@ -0,0 +1,114 @@ +using System; +using SGModule.Common; +using UnityEngine; + +namespace Uni2SDK +{ + public static class Log + { + // 预定义模块实例 + public static readonly ModuleLogger Common = new("Common"); + public static readonly ModuleLogger Net = new("Net", Uni2SDKManager.Instance._isLog); + public static readonly ModuleLogger MarkdownKit = new("MarkdownKit"); + public static readonly ModuleLogger ApplePay = new("ApplePay"); + public static readonly ModuleLogger ConfigLoader = new("ConfigLoader"); + public static readonly ModuleLogger NetKit = new("NetKit"); + public static readonly ModuleLogger DataStorage = new("DataStorage"); + + private static bool IsEnabled => Uni2SDKManager.Instance._isLog; + + public static void Info(string label, string msg) + { + if (!IsEnabled) + { + return; + } + + Debug.Log($"[{label}] {msg}"); + } + + public static void Warning(string label, string msg) + { + if (!IsEnabled) + { + return; + } + + Debug.LogWarning($"[{label}] {msg}"); + } + + public static void Error(string label, string msg) + { + if (!IsEnabled) + { + return; + } + + Debug.LogError($"[{label}] {msg}"); + } + + public static void Exception(string label, Exception ex) + { + if (!IsEnabled) + { + return; + } + + Debug.LogError($"[{label}] Exception: {ex.Message}\n{ex.StackTrace}"); + } + } + + public class ModuleLogger + { + private readonly bool _isEnabled; + private readonly string _label; + + public ModuleLogger(string label, bool isEnabled = true) + { + _label = label; + _isEnabled = isEnabled; + } + + private bool IsEnabled => Uni2SDKManager.Instance._isLog && _isEnabled; + + public void Info(string msg) + { + if (!IsEnabled) + { + return; + } + + Debug.Log($"[{_label}] {msg}"); + } + + public void Warning(string msg) + { + if (!IsEnabled) + { + return; + } + + Debug.LogWarning($"[{_label}] {msg}"); + } + + public void Error(string msg) + { + if (!IsEnabled) + { + return; + } + + Debug.LogError($"[{_label}] {msg}"); + } + + public void Exception(Exception ex) + { + if (!IsEnabled) + { + return; + } + + Debug.LogError($"[{_label}] Exception: {ex.Message}\n{ex.StackTrace}"); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/MD5Helper.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/MD5Helper.cs new file mode 100644 index 0000000..59af9d7 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/MD5Helper.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Uni2SDK +{ + public class MD5Helper + { + public static string GetFileMD5(string file) + { + try + { + var fs = new FileStream(file, FileMode.Open); + MD5 md5 = new MD5CryptoServiceProvider(); + var retVal = md5.ComputeHash(fs); + fs.Close(); + var sb = new StringBuilder(); + foreach (var str in retVal) + { + sb.Append(str.ToString("X2")); + } + + return sb.ToString(); + } + catch (Exception ex) + { + throw new Exception("GetFileMD5 fail error: " + ex.Message); + } + } + + /// + /// 获取字符串的MD5值 + /// + public static string GetStringMD5(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + + MD5 md5 = new MD5CryptoServiceProvider(); + var bytResult = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); + var strResult = BitConverter.ToString(bytResult); + strResult = strResult.Replace("-", string.Empty); + return strResult; + } + + public static string MD5String1(string text) + { + var buffer = Encoding.Default.GetBytes(text); + var check = new MD5CryptoServiceProvider(); + var somme = check.ComputeHash(buffer); + var result = new StringBuilder(); + foreach (var a in somme) + { + var value = a.ToString("X"); + if (a < 16) + { + result.Append(0); + result.Append(value); + } + else + { + result.Append(value); + } + } + + return result.ToString().ToLower(); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/ModuleVersion.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/ModuleVersion.cs new file mode 100644 index 0000000..8c5c374 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/ModuleVersion.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace Uni2SDK +{ + public static class ModuleVersion + { + public static void Show() + { + var path = Path.Combine(Application.dataPath, "../gupm.toml"); + + if (!File.Exists(path)) + { + Log.Warning("Submodules", $"找不到配置文件: {path}"); + return; + } + + var insideSubmodules = false; + var submodules = new Dictionary(); + var lines = File.ReadAllLines(path); + + foreach (var rawLine in lines) + { + var line = rawLine.Trim(); + + // 进入 [Submodules] 区块 + if (!insideSubmodules) + { + if (line.Equals("[Submodules]", StringComparison.OrdinalIgnoreCase)) + { + insideSubmodules = true; + } + + continue; + } + + // 如果遇到下一个区块就退出(可选) + if (line.StartsWith("[") && line.EndsWith("]")) + { + break; + } + + // 忽略空行和注释 + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) + { + continue; + } + + // 使用 = 来切分 + var parts = line.Split(new[] { '=' }, 2); + if (parts.Length != 2) + { + continue; + } + + var key = parts[0].Trim(); + var value = parts[1].Trim().Trim('"'); + + submodules[key] = value; + } + + foreach (var kv in submodules) + { + Log.Info("Submodules", $"{kv.Key} : {kv.Value}"); + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/RandomHelper.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/RandomHelper.cs new file mode 100644 index 0000000..0900623 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/RandomHelper.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SGModule.Common.Interface; + +// +// namespace Uni2SDK { +// public static class RandomHelper { +// private static readonly Random _random = new(); +// +// public static T RandomByWeight(IList items, T fallback) where T : class, IWeighted { +// var totalWeight = items.Sum(item => item.Weight); +// if (totalWeight <= 0) { +// return fallback; +// } +// +// var roll = _random.Next(0, totalWeight); +// var cumulative = 0; +// +// foreach (var item in items) { +// cumulative += item.Weight; +// if (roll < cumulative) { +// return item; +// } +// } +// +// return items.Last(); +// } +// } +// } \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/SerializeHelper.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/SerializeHelper.cs new file mode 100644 index 0000000..ea68d50 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/SerializeHelper.cs @@ -0,0 +1,52 @@ +using System; +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public static class SerializeHelper + { + private static readonly JsonSerializerSettings DefaultUseJsonSettings = new() + { + Formatting = Formatting.None, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + DateFormatString = "yyyy/MM/dd hh:mm:ss" + }; + + private static readonly JsonSerializerSettings JsonIndentedSettings = new() + { + Formatting = Formatting.Indented, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + DateFormatString = "yyyy/MM/dd hh:mm:ss" + }; + + static SerializeHelper() + { + JsonConvert.DefaultSettings = () => DefaultUseJsonSettings; + } + + public static string ToJson(object obj) + { + return JsonConvert.SerializeObject(obj, DefaultUseJsonSettings); + } + + public static string ToJsonIndented(object obj) + { + return JsonConvert.SerializeObject(obj, JsonIndentedSettings); + } + + public static string ToJson(object obj, Type type) + { + return JsonConvert.SerializeObject(obj, type, DefaultUseJsonSettings); + } + + public static string ToJson(object obj) + { + return ToJson(obj, typeof(T)); + } + + public static T ToObject(string json) + { + return JsonConvert.DeserializeObject(json); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/TimeHelper.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/TimeHelper.cs new file mode 100644 index 0000000..1e8e807 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Helper/TimeHelper.cs @@ -0,0 +1,62 @@ +using System; + +namespace Uni2SDK +{ + public static class TimeHelper + { + /// + /// 将 Unix 时间戳转换为本地时间 + /// + /// 时间戳(秒) + /// 可读的本地时间字符串 + public static string ConvertToLocalTime(long timestamp) + { + // Unix 时间戳起始时间 + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + // 转换为本地时间 + var localTime = epoch.AddSeconds(timestamp).ToLocalTime(); + + return localTime.ToString("yyyy-MM-dd HH:mm:ss"); + } + + /// + /// 将 Unix 时间戳转换为 UTC 时间 + /// + /// 时间戳(秒) + /// 可读的 UTC 时间字符串 + public static string ConvertToUTCTime(long timestamp) + { + // Unix 时间戳起始时间 + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + // 转换为 UTC 时间 + var utcTime = epoch.AddSeconds(timestamp); + + return utcTime.ToString("yyyy-MM-dd HH:mm:ss"); + } + + public static string FormatTime(int totalSeconds) + { + if (totalSeconds < 60) + { + return $"{totalSeconds}s"; // 小于60秒,直接显示秒 + } + + if (totalSeconds < 3600) + { + var minutes = totalSeconds / 60; + var seconds = totalSeconds % 60; + return $"{minutes}m {seconds}s"; // 小于1小时,显示分秒 + } + else + { + var hours = totalSeconds / 3600; + var remainingSeconds = totalSeconds % 3600; + var minutes = remainingSeconds / 60; + var seconds = remainingSeconds % 60; + return $"{hours}h {minutes}m {seconds}s"; // 超过1小时,显示时分秒 + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Interface/IWeighted.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Interface/IWeighted.cs new file mode 100644 index 0000000..cca4538 --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Interface/IWeighted.cs @@ -0,0 +1,7 @@ +namespace SGModule.Common.Interface +{ + public interface IWeighted + { + int Weight { get; } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Common/SGModule/Scripts/Keychain/Keychain.cs b/WV2SDK/SGModule/Common/SGModule/Scripts/Keychain/Keychain.cs new file mode 100644 index 0000000..df20b9f --- /dev/null +++ b/WV2SDK/SGModule/Common/SGModule/Scripts/Keychain/Keychain.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; +using Uni2SDK; + +public static class Keychain +{ + public static bool Save(string key, string value) + { +#if UNITY_IOS + return SaveToKeychain(key, value); +#endif + Log.Common.Warning("Keychain is only supported on iOS."); + return false; + } + + public static string Get(string key) + { +#if UNITY_IOS + var resultPtr = GetFromKeychain(key); + if (resultPtr != IntPtr.Zero) { + var result = Marshal.PtrToStringAuto(resultPtr); + return result; + } + + return null; +#endif + Log.Common.Warning("Keychain is only supported on iOS."); + return null; + } + + public static bool Delete(string key) + { +#if UNITY_IOS + return DeleteFromKeychain(key); +#endif + Log.Common.Warning("Keychain is only supported on iOS."); + return false; + } +#if UNITY_IOS + [DllImport("__Internal")] + private static extern bool SaveToKeychain(string key, string value); + + [DllImport("__Internal")] + private static extern IntPtr GetFromKeychain(string key); + + [DllImport("__Internal")] + private static extern bool DeleteFromKeychain(string key); +#endif +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/README.md b/WV2SDK/SGModule/Net/README.md new file mode 100644 index 0000000..d15ee02 --- /dev/null +++ b/WV2SDK/SGModule/Net/README.md @@ -0,0 +1,67 @@ +# 🌐 Net 网络模块 + +适用于 Unity 的网络模块,包含网络检测、接口请求、Token 鉴权、服务器时间同步等功能。模块职责清晰、易扩展,使用前需引入通用模块。 + +------ + +## 📦 模块概览 + +| 模块 | 功能描述 | +| ---------------- | --------------------------------------------------- | +| `NetChecker` | 📶 检测网络是否可用,支持 Ping / HTTP 检查 | +| `NetCore` | 📡 网络请求基类,支持加密和 Token 自动处理(需继承) | +| `TokenManager` | 🔐 Token 缓存 / 自动刷新 / 登录回退处理 | +| `ServerClock` | 🕒 同步服务器时间,避免设备本地误差 | +| `WebSocket 模块` | 依赖 nativewebsocket 插件 | + +------ + +## ✅ 使用示例 + +### 1. 网络检测 + +```c# +StartCoroutine(NetChecker.Instance.WaitUntilNetworkConnected(10, 1f, success => { + if (success) Debug.Log("✅ 网络可用"); +})); +``` + +------ + +### 2. 实现你的请求类(必须继承 NetCore) + +```c# +public class NetKit : NetCore { + // 详情参考此类 +} +``` + +------ + +### 3. 发起接口请求(在子类中) + +```c# +NetKit.Instance.Post("/event/incrN", trackData, + response => { + callback?.Invoke(response.IsSuccess, response.Data); + }); +``` + +------ + +### 4. Token 获取(自动缓存) + +```c# +yield return TokenManager.Instance.GetToken(token => { + Debug.Log("当前 Token:" + token); +}); +``` + +------ + +### 5. 服务器时间同步 + +```c# +ServerClock.Init(serverUnixTime); +var now = ServerClock.GetCurrentServerTime(); +``` \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/Core/NetChecker.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/NetChecker.cs new file mode 100644 index 0000000..12e4ca0 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/NetChecker.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; +using UnityEngine.Networking; + +namespace Uni2SDK +{ + public enum ConnectionStatus + { + Uninitialized, // 未初始化 + Connected, // 已连接 + Disconnected // 未连接 + } + + // 网络检测类 + public class NetChecker : SingletonMonoBehaviour + { + private const float WebRequestInterval = 5f; + private const float PingInterval = 2; + + // 可配置多个检测 URL + [SerializeField] private List checkUrls = new() + { + "https://www.baidu.com", // 国内 URL + "https://www.google.com" // 国外 URL + }; + + // 是否启用 Ping 检测 + [SerializeField] private bool usePingCheck; + + // 配置可用的 Ping 地址列表 + private readonly List _pingAddresses = new() + { + "8.8.8.8", // Google DNS(适合全球) + "223.5.5.5", // 阿里云 + "1.1.1.1" // Cloudflare DNS(全球可用) + }; + + + private Coroutine _checkInternetCoroutine; + private float _checkInterval = WebRequestInterval; + private bool _isChecking; + private bool _lastConnected; + private string _preferredUrl; // 当前优先检测的 URL + + /// + /// null = 未检测;true = 联网;false = 未联网 + /// + private bool? IsConnected { get; set; } + + private void OnEnable() + { + StartCheckingInternet(); + } + + private void OnDisable() + { + StopCheckingInternet(); + } + + /// + /// 等待网络连通,自动处理初始化和总超时时间 + /// + /// 最多等待的总时间(秒) + /// 每次重试间隔 + /// 检测结果回调 + public IEnumerator WaitUntilNetworkConnected(float timeout, float checkInterval, Action callback) + { + var elapsed = 0f; + + // 阶段 1: 等待初始化完成 + while (!IsConnected.HasValue && elapsed < timeout) + { + yield return new WaitForSeconds(checkInterval); + elapsed += checkInterval; + } + + if (!IsConnected.HasValue) + { + Log.Net.Warning("网络检测初始化超时!"); + callback(false); + yield break; + } + + // 阶段 2: 等待网络恢复 + while (IsConnected == false && elapsed < timeout) + { + Log.Net.Warning($"网络异常,等待中... 已耗时: {elapsed:F1}s"); + yield return new WaitForSeconds(checkInterval); + elapsed += checkInterval; + } + + var success = IsConnected == true; + if (!success) + { + Log.Net.Warning("网络连接等待超时!"); + } + + callback(success); + } + + public ConnectionStatus GetConnectionStatus() + { + return IsConnected switch + { + null => ConnectionStatus.Uninitialized, + true => ConnectionStatus.Connected, + false => ConnectionStatus.Disconnected + }; + } + + public void Init() + { + StartCheckingInternet(); + } + + private void StartCheckingInternet() + { + _checkInterval = usePingCheck ? PingInterval : WebRequestInterval; + _checkInternetCoroutine ??= StartCoroutine(CheckInternetRoutine()); + } + + private void StopCheckingInternet() + { + if (_checkInternetCoroutine != null) + { + StopCoroutine(_checkInternetCoroutine); + _checkInternetCoroutine = null; + } + } + + private IEnumerator CheckInternetRoutine() + { + while (true) + { + if (_isChecking) + { + yield break; // 避免并发冲突 + } + + _isChecking = true; + + if (QuickNetDeviceCheck()) + { + if (usePingCheck) + { + yield return CheckWithPing(); + } + else + { + yield return CheckWithUnityWebRequest(); + } + } + else + { + IsConnected = false; + } + + if (IsConnected.HasValue && _lastConnected != IsConnected.Value) + { + Log.Net.Info($"网络状态变化: {IsConnected}"); + _lastConnected = IsConnected.Value; + } + + _isChecking = false; + yield return new WaitForSecondsRealtime(_checkInterval); + } + } + + /// + /// 使用 Ping 检测网络连通性 + /// + private IEnumerator CheckWithPing() + { + var pings = _pingAddresses.Select(ip => new Ping(ip)).ToList(); + + var startTime = Time.realtimeSinceStartup; + var success = false; + + // 等待所有 Ping 返回结果或超时 + while (Time.realtimeSinceStartup - startTime < _checkInterval) + { + foreach (var ping in pings) + { + if (ping.isDone && ping.time >= 0) + { + success = true; + break; + } + } + + if (success) + { + break; + } + + yield return new WaitForSecondsRealtime(0.1f); + } + + // 如果有任意一个 Ping 成功,网络即为可用 + IsConnected = success; + } + + /// + /// 使用 UnityWebRequest 检测网络连通性 + /// + private IEnumerator CheckWithUnityWebRequest() + { + if (!string.IsNullOrEmpty(_preferredUrl)) + { + var request = UnityWebRequest.Get(_preferredUrl); + request.timeout = (int)_checkInterval; + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + IsConnected = true; + request.Dispose(); + yield break; + } + + Log.Net.Warning($"CheckNet 首选URL请求失败: {_preferredUrl}, 错误: {request.error}"); + + _preferredUrl = null; // 失败则清除 + request.Dispose(); + } + + // fallback:尝试所有 URL + var ops = new List(); + var requests = new List(); + + foreach (var url in checkUrls) + { + var req = UnityWebRequest.Get(url); + req.timeout = (int)_checkInterval; + ops.Add(req.SendWebRequest()); + requests.Add(req); + } + + yield return StartCoroutine(WaitForAnyRequest(ops)); + + foreach (var req in requests) + { + req.Dispose(); + } + } + + + // 等待任意一个请求完成并返回 + private IEnumerator WaitForAnyRequest(List ops) + { + while (ops.Any(op => !op.isDone)) + { + if (ops.Any(op => op.isDone && op.webRequest.result == UnityWebRequest.Result.Success)) + { + break; + } + + yield return null; + } + + var successOp = ops.FirstOrDefault(op => op.webRequest.result == UnityWebRequest.Result.Success); + + if (successOp == null) + { + foreach (var op in ops.Where(op => op.isDone)) + { + Log.Net.Warning($"请求失败: {op.webRequest.url}, 错误: {op.webRequest.error}"); + } + } + + IsConnected = successOp != null; + _preferredUrl = successOp?.webRequest.url; + } + + // 添加或删除检查 URL + public void AddCheckUrl(string url) + { + if (!checkUrls.Contains(url)) + { + checkUrls.Add(url); + } + } + + public void RemoveCheckUrl(string url) + { + if (checkUrls.Contains(url)) + { + checkUrls.Remove(url); + } + } + + public void AddPingTarget(string ip) + { + if (!_pingAddresses.Contains(ip)) + { + _pingAddresses.Add(ip); + } + } + + public void RemovePingTarget(string ip) + { + if (_pingAddresses.Contains(ip)) + { + _pingAddresses.Remove(ip); + } + } + + // 切换是否使用 Ping 检测 + public void SetUsePingCheck(bool enablePing) + { + usePingCheck = enablePing; +#if UNITY_WEBGL +usePingCheck = false; +#endif + _checkInterval = usePingCheck ? PingInterval : WebRequestInterval; + } + + private bool QuickNetDeviceCheck() + { + if (Application.isEditor) + { + return true; + } + + return Application.internetReachability != NetworkReachability.NotReachable; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/Core/NetCore.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/NetCore.cs new file mode 100644 index 0000000..3c4adf1 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/NetCore.cs @@ -0,0 +1,306 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using Newtonsoft.Json; +using Uni2SDK; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.Networking; + +namespace Uni2SDK +{ + // 网络请求核心类 + public abstract class NetCore : SingletonMonoBehaviour where TC : MonoBehaviour + { + private const int Timeout = 15; + private bool _isInit; + private int _requestCounter; + private string _requestHost; + + private new void Awake() + { + InternalInit(); + } + + protected abstract string Encrypt(string text, string key); + protected abstract string Decrypt(string text, string key); + protected abstract TokenManager.RefreshTokenDelegate RefreshTokenHandler(); + protected abstract TokenManager.ReauthenticateDelegate ReauthenticateHandler(); + + private void InternalInit() + { + NetChecker.Instance.Init(); + TokenManager.Instance.InitHandlers(RefreshTokenHandler(), ReauthenticateHandler()); + + _isInit = true; + _requestHost = NetworkManager.GetHost(); + + Log.Net.Info($"[sdk] Host is: {NetworkManager.GetHost()}"); + + } + + + private string GetApiUrl() + { + return NetworkManager.GetUrl(); + } + + private string GetWsUrl() + { + return $"wss://{_requestHost}/ws/"; + } + + /// + /// Post请求(异步) + /// + /// 请求路径 + /// 请求参数 + /// 请求回调 + /// 是否带Token + /// 额外的请求头 + protected IEnumerator PostAsync(string path, object requestData = null, + UnityAction> onCompleted = null, bool withToken = true, + Dictionary headers = null) + { + var done = false; + + Post(path, requestData, response => + { + onCompleted?.Invoke(response); + done = true; + }, withToken, true, headers); + + yield return new WaitUntil(() => done); + } + + /// + /// Post请求(泛型) + /// + /// 请求路径 + /// 请求参数 + /// 请求回调 + /// 是否带Token + /// 是否使用协程 + /// 额外的请求头 + /// 返回数据中Data的类型 + protected void Post(string path, object requestData = null, UnityAction> onCompleted = null, + bool withToken = true, bool useCoroutine = true, Dictionary headers = null) + { + if (useCoroutine) + { + StartCoroutine(PostInternal(path, requestData, onCompleted, withToken, headers)); + } + else + { + SendWebRequest(path, requestData, withToken, headers); + } + } + + + /// + /// 发出请求 所有的请求最终都会从这里发出 + /// + /// 请求路径 + /// 请求参数 + /// 请求回调 + /// 是否带Token + /// 额外的请求头 + /// 返回数据中Data的类型 + /// + private IEnumerator PostInternal(string path, object requestData, UnityAction> onCompleted, + bool withToken = true, Dictionary headers = null) + { + var response = new ResponseData(); + Log.Net.Info($"[PostInternal]===1====={_isInit}"); + if (!_isInit) + { + Log.Net.Info("NetworkKit模块未完成初始化 请检查!!! "); + response.Code = -1; + response.Msg = "NetworkKit module has not been initialized yet"; + onCompleted?.Invoke(response); + yield break; + } + + // 1. 网络检查 + var isNetworkOk = false; + yield return NetChecker.Instance.WaitUntilNetworkConnected(15, 1f, result => isNetworkOk = result); + + if (!isNetworkOk) + { + response.Code = -1; + response.Msg = "Network status abnormal."; + onCompleted?.Invoke(response); + yield break; + } + + + // 2. Token 处理(根据 withToken 参数) + if (withToken) + { + string token = null; + yield return TokenManager.Instance.GetToken(result => token = result); + + if (string.IsNullOrEmpty(token)) + { + response.Code = -1; + response.Msg = "Token acquisition failed."; + onCompleted?.Invoke(response); + yield break; + } + + // 合并原有 header + headers ??= new Dictionary(); + + headers["x-token"] = token; + } + + Log.Net.Info($"[PostInternal]===4====="); + + yield return SendRequest(path, requestData, onCompleted, headers); + } + + private IEnumerator SendRequest( + string path, + object requestData, + UnityAction> onCompleted = null, + Dictionary headers = null) + { + var requestId = GetNextRequestId(); + + var requestJson = SerializeHelper.ToJsonIndented(requestData ?? new object()); + + var unityWebRequest = CreatePostRequest(path, requestJson, headers); + + Log.Net.Info($"[Req:{requestId}] | Path: {path} | Body: {requestJson}"); + + yield return unityWebRequest.SendWebRequest(); //发送请求 + + var response = new ResponseData(); + + if (unityWebRequest.result is not UnityWebRequest.Result.Success) + { + Log.Net.Info($"[Resp:{requestId}] | Path: {path} | Error: {unityWebRequest.error}"); + response.Code = -1; + response.Msg = unityWebRequest.error; + } + else + { + var receiveContent = unityWebRequest.downloadHandler.text; + + if (!receiveContent.IsNullOrWhiteSpace()) + { + receiveContent = Decrypt(UnquoteString(receiveContent), _requestHost); + } + + + var raw = SerializeHelper.ToObject>(receiveContent); + + if (raw?.Code == 0) + { + Log.Net.Info($"[Resp:{requestId}] | Path: {path} | Result: {receiveContent}"); + + // 处理字符串类型的情况 + if (typeof(T) == typeof(string)) + { + response.Data = (T)(object)(raw.Data?.ToString() ?? ""); + } + else + { + response.Data = SerializeHelper.ToObject(JsonConvert.SerializeObject(raw.Data)); + } + } + else + { + Log.Net.Warning($"[Resp:{requestId}] | Path: {path} | Result: {receiveContent}"); + + response.Code = raw?.Code ?? -2; + response.Msg = raw?.Msg ?? "未知错误"; + } + } + + onCompleted?.Invoke(response); + + unityWebRequest.Dispose(); + } + + private UnityWebRequest CreatePostRequest(string path, string requestJson, + Dictionary headers = null) + { + Log.Info($"[CreatePostRequest 0] ",$" Path: {path} \n Body: {requestJson} \n _requestHost: {_requestHost}"); + + path = Encrypt(path, _requestHost); + requestJson = Encrypt(requestJson, _requestHost); + + var fullUrl = GetApiUrl() + path; + Log.Info($"[CreatePostRequest 1] ",$"GetApiUrl()=={GetApiUrl()}\n Path: {path} \n Body: {requestJson} \n _requestHost: {_requestHost} \n fullUrl: {fullUrl}"); + + var request = new UnityWebRequest(fullUrl, UnityWebRequest.kHttpVerbPOST) + { + uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(requestJson)), + downloadHandler = new DownloadHandlerBuffer(), + timeout = Timeout + }; + + SetRequestHeaders(request, headers); + + return request; + } + + private void SetRequestHeaders(UnityWebRequest request, Dictionary headers) + { + request.SetRequestHeader("Content-Type", "application/json;charset=utf-8"); + if (headers == null) + { + return; + } + + foreach (var kvp in headers.Where(kvp => kvp.Value != null)) + { + request.SetRequestHeader(kvp.Key, kvp.Value); + } + } + + private int GetNextRequestId() + { + return Interlocked.Increment(ref _requestCounter); + } + + // 去除双引号(如果存在) + private static string UnquoteString(string input) + { + if (!string.IsNullOrEmpty(input) && input.Length >= 2 && + input[0] == '"' && input[input.Length - 1] == '"') + { + return input.Substring(1, input.Length - 2); + } + + return input; + } + + + private void SendWebRequest(string url, object requestData = null, bool withToken = false, + Dictionary header = null) + { + if (withToken) + { + // 合并原有 header + header ??= new Dictionary(); + header["x-token"] = TokenManager.Instance.CachedToken; + } + + var requestId = GetNextRequestId(); + + var requestJson = SerializeHelper.ToJsonIndented(requestData ?? new object()); + + var unityWebRequest = CreatePostRequest(url, requestJson, header); + + Log.Net.Info($"[Req:{requestId}] | Path: {url} | Body: {requestJson}"); + + unityWebRequest.SendWebRequest(); //发送请求 + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/Core/ServerClock.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/ServerClock.cs new file mode 100644 index 0000000..8e33c2c --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/ServerClock.cs @@ -0,0 +1,25 @@ +using System; + +namespace Uni2SDK +{ + // 服务器时钟管理类 + public static class ServerClock + { + private static long _timeDifference; // 本地与服务器的时间偏差 + + public static void Init(long serverTime) + { + _timeDifference = serverTime - Now(); + } + + public static long GetCurrentServerTime() + { + return Now() + _timeDifference; + } + + private static long Now() + { + return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/Core/TokenManager.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/TokenManager.cs new file mode 100644 index 0000000..f4e3cc5 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/Core/TokenManager.cs @@ -0,0 +1,252 @@ +using System.Collections; +using System.Collections.Generic; +using Uni2SDK; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; +using UnityEngine.Events; + +namespace Uni2SDK +{ + // Token管理类 + public class TokenManager : SingletonMonoBehaviour + { + public delegate IEnumerator ReauthenticateDelegate(UnityAction onCompleted); + + public delegate IEnumerator RefreshTokenDelegate(UnityAction onCompleted); + + private const string JarvisTokenKey = "JarvisToken"; + private const string JarvisTokenExpiresAtKey = "JarvisTokenExpiresAt"; + private const float MinRefreshInterval = 10f; // 刷新间隔秒数限制 + private const int MaxTokenRecordCount = 10; // 最多记录最近token数 + + private readonly List _tokenRecord = new(); + + private long _cachedExpiresAt; + private string _cachedToken; + + private bool _isReauthenticating; + private bool _isRefreshing; + private float _lastRefreshTime; + private ReauthenticateDelegate _reauthenticateHandler; + + private RefreshTokenDelegate _refreshTokenHandler; + + public string CachedToken + { + get + { + if (string.IsNullOrEmpty(_cachedToken)) + { + _cachedToken = PlayerPrefs.GetString(JarvisTokenKey, null); + } + + return _cachedToken; + } + set + { + _cachedToken = value; + if (!string.IsNullOrEmpty(value)) + { + PlayerPrefs.SetString(JarvisTokenKey, value); + } + else + { + PlayerPrefs.DeleteKey(JarvisTokenKey); + } + } + } + + private long CachedExpiresAt + { + get + { + if (_cachedExpiresAt == 0) + { + var expiresStr = PlayerPrefs.GetString(JarvisTokenExpiresAtKey, "0"); + if (!long.TryParse(expiresStr, out _cachedExpiresAt)) + { + _cachedExpiresAt = 0; + } + } + + return _cachedExpiresAt; + } + set + { + _cachedExpiresAt = value; + PlayerPrefs.SetString(JarvisTokenExpiresAtKey, value.ToString()); + } + } + + + private long RemainingTokenSeconds => CachedExpiresAt - ServerClock.GetCurrentServerTime(); + + public void InitHandlers(RefreshTokenDelegate refreshTokenHandler, ReauthenticateDelegate reauthenticateHandler) + { + _refreshTokenHandler = refreshTokenHandler; + _reauthenticateHandler = reauthenticateHandler; + } + + /// + /// 获取有效Token,自动处理刷新和重新登录 + /// + public IEnumerator GetToken(UnityAction callback) + { + // 若正在重新登录,则等待其完成,避免刷新或登录并发 + yield return WaitForReauthenticationComplete(); + if (_isReauthenticating) + { + callback?.Invoke(null); + yield break; + } + + + if (IsTokenValid()) + { + callback?.Invoke(CachedToken); // 优先返回现有有效 Token + + // 如果快过期,则后台触发刷新,不等待 + if (IsTokenExpiringSoon() && ShouldStartRefresh()) + { + _lastRefreshTime = Time.realtimeSinceStartup; + StartCoroutine(RefreshTokenRoutine()); // 不阻塞当前流程 + } + } + else + { + // token 已过期,必须重新登录 + yield return ReauthenticateRoutine(success => { callback?.Invoke(success ? CachedToken : null); }); + } + } + + private IEnumerator WaitForReauthenticationComplete() + { + var waitCount = 0; + while (_isReauthenticating && waitCount < 6) + { + yield return new WaitForSeconds(0.5f); + waitCount++; + } + } + + + private IEnumerator RefreshTokenRoutine() + { + if (_isRefreshing || _refreshTokenHandler == null) + { + if (_refreshTokenHandler == null) + { + Log.Net.Error("未设置 RefreshTokenHandler 委托,请先调用 InitHandlers 进行注入!"); + } + + yield break; + } + + _isRefreshing = true; + var retryCount = 0; + var refreshSuccess = false; + + while (!refreshSuccess && retryCount < 3) + { + retryCount++; + Log.Net.Info($"Token快过期了, 尝试刷新Token, 次数: {retryCount}"); + yield return _refreshTokenHandler((token, expiresAt) => + { + refreshSuccess = !token.IsNullOrWhiteSpace(); + if (token.IsNullOrWhiteSpace()) + { + UpdateTokenCache(token, expiresAt); + } + }); + // yield return NetCore.Instance.PostAsync("tokenRefresh", onCompleted: response => { + // refreshSuccess = response.IsSuccess; + // if (refreshSuccess) { + // UpdateTokenCache(response.Data.Token, response.Data.ExpiresAt); + // NetLog.Warning("Token刷新成功"); + // } + // }); + + if (!refreshSuccess) + { + yield return new WaitForSeconds(1f); + } + } + + if (!refreshSuccess) + { + Log.Net.Error("Token刷新失败"); + } + + _isRefreshing = false; + } + + private IEnumerator ReauthenticateRoutine(UnityAction onCompleted) + { + if (_isReauthenticating || _reauthenticateHandler == null) + { + if (_reauthenticateHandler == null) + { + Log.Net.Error("未设置 ReauthenticateHandler 委托,请先调用 InitHandlers 进行注入!"); + } + + onCompleted?.Invoke(false); + yield break; + } + + _isReauthenticating = true; + Log.Net.Error("Token过期,开始重新登录流程"); + yield return _reauthenticateHandler((token, expiresAt) => + { + var success = !token.IsNullOrWhiteSpace(); + if (token.IsNullOrWhiteSpace()) + { + UpdateTokenCache(token, expiresAt); + } + + onCompleted?.Invoke(success); + }); + // yield return Login.Instance.ReauthenticateOnExpiration(success => { + // onCompleted?.Invoke(success); + // }); + _isReauthenticating = false; + } + + private bool IsTokenValid() + { + return !string.IsNullOrEmpty(CachedToken) && RemainingTokenSeconds > 0; + } + + // 小于1小时(3600秒)有效期,认为快过期需要刷新 + private bool IsTokenExpiringSoon() + { + return RemainingTokenSeconds < 3600; + } + + public void UpdateTokenCache(string token, long expiresAt) + { + CachedToken = token; + CachedExpiresAt = expiresAt; + + + _tokenRecord.Add(token); + if (_tokenRecord.Count > MaxTokenRecordCount) + { + _tokenRecord.RemoveAt(0); + } + } + + private bool ShouldStartRefresh() + { + return !_isRefreshing && Time.realtimeSinceStartup - _lastRefreshTime > MinRefreshInterval; + } + + /// + /// 获取Token记录(测试功能用) + /// + public List GetTokenRecord() + { + return _tokenRecord; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/Models/ResponseData.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/Models/ResponseData.cs new file mode 100644 index 0000000..a33591b --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/Models/ResponseData.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class ResponseData + { + [JsonProperty("code")] public int Code; + [JsonProperty("data")] public T Data; + [JsonProperty("msg")] public string Msg; + + public bool IsSuccess => Code == 0; + + public void Deconstruct(out bool isSuccess, out T data) + { + isSuccess = IsSuccess; + data = Data; + } + } + + // public class RequestTokenData { + // [JsonProperty("expires_at")] public long ExpiresAt; + // [JsonProperty("token")] public string Token; + // } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/ResponseData.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/ResponseData.cs new file mode 100644 index 0000000..817127e --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/ResponseData.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +public class ResponseData +{ + [JsonProperty("cmd")] public int Cmd { get; set; } + + [JsonProperty("body")] public Body Body { get; set; } +} + +public class Body +{ + [JsonProperty("code")] public int Code { get; set; } + + [JsonProperty("data")] public JToken Data { get; set; } // 使用 JToken 来处理不同的 data 结构 + + [JsonProperty("msg")] public string Msg { get; set; } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketData.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketData.cs new file mode 100644 index 0000000..7370802 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketData.cs @@ -0,0 +1,5 @@ +internal class WebSocketData +{ + public int cmd; + public string token; +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketEvent.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketEvent.cs new file mode 100644 index 0000000..0d2f714 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketEvent.cs @@ -0,0 +1,8 @@ +namespace Uni2SDK +{ + public enum WebSocketEvent + { + BindToken = 4097, + SendHeartbeat = 4098 + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketService.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketService.cs new file mode 100644 index 0000000..3b85d92 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketService.cs @@ -0,0 +1,262 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using NativeWebSocket; +using NativeWebSocket.Net; +using Newtonsoft.Json; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; + +namespace Uni2SDK +{ + public class WebSocketService : SingletonMonoBehaviour + { + private const float OffLineDecisionTime = 5f; + private bool _bindToken; + + private float _offlineDuration; + private string _url; + private WebSocket _webSocket; + + private void Update() + { +#if !UNITY_WEBGL || UNITY_EDITOR + if (_webSocket != null) + { + _webSocket.DispatchMessageQueue(); + } +#endif + if (IsInitComplete) + { + // 网络状态检测 + if (NetChecker.Instance.GetConnectionStatus() == ConnectionStatus.Connected) + { + _offlineDuration = 0; + if (_webSocket == null || _webSocket.State == WebSocketState.Closed) + { + Log.Net.Warning("网络恢复 重连"); + TryReconnect(); + } + } + else + { + _offlineDuration += Time.deltaTime; + if (_offlineDuration > OffLineDecisionTime && _webSocket != null && + _webSocket.State == WebSocketState.Open) + { + Log.Net.Warning("断网 主动结束连接"); + _ = Disconnect(); + } + } + } + } + + private async void OnApplicationQuit() + { + await Disconnect(); + } + + private void InitGmTool() + { + } + + /// + /// 初始化 + /// + /// + public async void Init(string url) + { + InitGmTool(); + + NetChecker.Instance.SetUsePingCheck(true); + + Log.Net.Info($"Initializing WebSocket service. Url: {url}"); + _url = url; + + OnOpenAction += OnOpen; + OnMessageAction += OnMessage; + OnErrorAction += OnError; + OnCloseAction += OnClose; + + await Connect(); + } + + private void Send(object data) + { + if (_webSocket == null) + { + Log.Net.Warning("webSocket is null"); + return; + } + + if (_webSocket.State != WebSocketState.Open) + { + Log.Net.Warning("webSocket.State != Open"); + return; + } + + var jsonString = JsonConvert.SerializeObject(data); + var byteArray = Encoding.UTF8.GetBytes(jsonString); + _webSocket.Send(byteArray); + } + + /// + /// 断开连接 + /// + public async Task Disconnect() + { + if (_webSocket != null) + { + var webSocket = _webSocket; + _webSocket = null; + await webSocket.Close(); + } + } + + #region 连接管理 + + // 事件回调 + public event Action OnOpenAction; + public event Action OnMessageAction; + public event Action OnCloseAction; + public event Action OnErrorAction; + + /// + /// 初始化并尝试连接 + /// + private async Task Connect() + { + if (_webSocket != null && + (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)) + { + Log.Net.Warning("WebSocket is already connected or connecting."); + return; + } + + _webSocket = new WebSocket(_url); + + // 绑定事件 + _webSocket.OnOpen += () => + { + Log.Net.Info("WebSocket connected."); + OnOpenAction?.Invoke(); + }; + + _webSocket.OnMessage += bytes => + { + var message = Encoding.UTF8.GetString(bytes); + Log.Net.Info($"Message received: {message}"); + OnMessageAction?.Invoke(message); + }; + + _webSocket.OnClose += code => + { + Log.Net.Info($"WebSocket disconnected with code: {code}"); + OnCloseAction?.Invoke(code); + }; + + _webSocket.OnError += error => + { + Log.Net.Error($"WebSocket error: {error}"); + OnErrorAction?.Invoke(error); + }; + + // 开始连接 + try + { + await _webSocket.Connect(); + } + catch (Exception ex) + { + Log.Net.Error($"WebSocket connection failed: {ex.Message}"); + TryReconnect(); + } + } + + + private void OnOpen() + { + IsInitComplete = true; + // 重连成功后绑定 Token + if (_bindToken) + { + BindToken(TokenManager.Instance.CachedToken); + } + } + + private void OnClose(WebSocketCloseCode closeCode) + { + } + + private void OnMessage(string message) + { + var responseData = WebSocketTools.DeserializeResponse(message); + WebSocketTools.ProcessResponseData(responseData); + } + + private void OnError(string errorMsg) + { + } + + /// + /// 尝试重连 + /// + private async void TryReconnect() + { + var maxRetries = 5; // 最大重连次数 + var attempt = 0; + while (attempt < maxRetries) + { + attempt++; + Log.Net.Info($"Attempting to reconnect... (Attempt {attempt}/{maxRetries})"); + try + { + await Connect(); + + //以下或许不会执行 这个 + Log.Net.Info("Reconnected successfully."); + return; // 重连成功 + } + catch (Exception ex) + { + Log.Net.Error($"Reconnection attempt {attempt} failed: {ex.Message}"); + await Task.Delay(2000); // 等待一段时间再尝试 + } + } + + Log.Net.Error("Max reconnection attempts reached. Connection failed."); + } + + #endregion + + #region 请求 + + /// + /// 绑定Token + /// + public void BindToken(string token) + { + Send(new WebSocketData + { + cmd = (int)WebSocketEvent.BindToken, + token = token + }); + + _bindToken = true; + } + + /// + /// 发送心跳 + /// + public void SendHeartbeat() + { + Send(new WebSocketData + { + cmd = (int)WebSocketEvent.SendHeartbeat + }); + } + + #endregion + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketTools.cs b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketTools.cs new file mode 100644 index 0000000..436a902 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/Scripts/WebSocket/WebSocketTools.cs @@ -0,0 +1,88 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Uni2SDK; + +public class WebSocketTools +{ + // 反序列化函数,返回 ResponseData 对象 + public static ResponseData DeserializeResponse(string jsonMessage) + { + try + { + return JsonConvert.DeserializeObject(jsonMessage); + } + catch (JsonException ex) + { + Log.Net.Error($"Failed to deserialize response: {ex.Message}"); + return null; + } + } + + // 处理数据函数 + public static void ProcessResponseData(ResponseData responseData) + { + // 处理请求成功的情况 + if (responseData.Body.Code == 0) + { + Log.Net.Info("Request succeeded"); + HandleData(responseData.Body.Data); // 不传入类型时,先默认不解析 + } + else + { + Log.Net.Error($"Request failed: {responseData.Body.Msg}"); + } + } + + // 泛型版本的 HandleData,允许外部传入一个类型 + public static void HandleData(JToken data) + { + // 检查 data 是否为空对象 + if (data.Type == JTokenType.Object && !data.HasValues) + { + Log.Net.Info("Data is empty"); + // 可根据需要进一步处理空数据的情况 + } + else + { + // 如果 data 包含有效数据,打印或进一步处理 + Log.Net.Info("Received data: " + data); + ProcessValidData(data); + } + } + + // 泛型版本的 HandleData,传入类型进行解析 + public static void HandleData(JToken data) + { + if (data != null) + { + try + { + var parsedData = data.ToObject(); // 将 data 解析为指定类型 + Log.Net.Info("Parsed data: " + JsonConvert.SerializeObject(parsedData)); + ProcessValidData(parsedData); // 进一步处理解析后的数据 + } + catch (JsonException ex) + { + Log.Net.Error($"Failed to parse data to type {typeof(T)}: {ex.Message}"); + } + } + else + { + Log.Net.Warning("Data is null, cannot parse."); + } + } + + // 处理解析后的 data 类型 + public static void ProcessValidData(T parsedData) + { + if (parsedData != null) + { + // 在这里可以对解析后的 data 进行具体处理 + Log.Net.Info("Successfully processed data: " + parsedData); + } + else + { + Log.Net.Warning("Parsed data is null, cannot process."); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/Samples~/WebSocketExample/Connection.cs b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/Samples~/WebSocketExample/Connection.cs new file mode 100644 index 0000000..6a6f201 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/Samples~/WebSocketExample/Connection.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using NativeWebSocket; +using NativeWebSocket.Net; + +public class Connection : MonoBehaviour +{ + WebSocket websocket; + + // Start is called before the first frame update + async void Start() + { + // websocket = new WebSocket("ws://echo.websocket.org"); + websocket = new WebSocket("ws://localhost:3000"); + + websocket.OnOpen += () => { Debug.Log("Connection open!"); }; + + websocket.OnError += (e) => { Debug.Log("Error! " + e); }; + + websocket.OnClose += (e) => { Debug.Log("Connection closed!"); }; + + websocket.OnMessage += (bytes) => + { + // Reading a plain text message + var message = System.Text.Encoding.UTF8.GetString(bytes); + Debug.Log("Received OnMessage! (" + bytes.Length + " bytes) " + message); + }; + + // Keep sending messages at every 0.3s + InvokeRepeating("SendWebSocketMessage", 0.0f, 0.3f); + + await websocket.Connect(); + } + + void Update() + { +#if !UNITY_WEBGL || UNITY_EDITOR + websocket.DispatchMessageQueue(); +#endif + } + + async void SendWebSocketMessage() + { + if (websocket.State == WebSocketState.Open) + { + // Sending bytes + await websocket.Send(new byte[] { 10, 20, 30 }); + + // Sending plain text + await websocket.SendText("plain text message"); + } + } + + private async void OnApplicationQuit() + { + await websocket.Close(); + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/Samples~/WebSocketExample/WebSocketExampleScene.unity b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/Samples~/WebSocketExample/WebSocketExampleScene.unity new file mode 100644 index 0000000..65d5dfa --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/Samples~/WebSocketExample/WebSocketExampleScene.unity @@ -0,0 +1,326 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_IndirectSpecularColor: {r: 0.44657868, g: 0.49641263, b: 0.57481706, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 1 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 9 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.802082 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + - component: {fileID: 963194230} + - component: {fileID: 963194229} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &963194229 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0f451fab1e6d94aea8fb73803e281bb3, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!54 &963194230 +Rigidbody: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + serializedVersion: 2 + m_Mass: 1 + m_Drag: 0 + m_AngularDrag: 0.05 + m_UseGravity: 1 + m_IsKinematic: 0 + m_Interpolate: 0 + m_Constraints: 0 + m_CollisionDetection: 0 diff --git a/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/WebSocket.cs b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/WebSocket.cs new file mode 100644 index 0000000..376e929 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/WebSocket.cs @@ -0,0 +1,882 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using AOT; +using System.Runtime.InteropServices; +using UnityEngine; +using System.Collections; + +public class MainThreadUtil : MonoBehaviour +{ + public static MainThreadUtil Instance { get; private set; } + public static SynchronizationContext synchronizationContext { get; private set; } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void Setup() + { + Instance = new GameObject("MainThreadUtil") + .AddComponent(); + synchronizationContext = SynchronizationContext.Current; + } + + public static void Run(IEnumerator waitForUpdate) + { + synchronizationContext.Post(_ => Instance.StartCoroutine( + waitForUpdate), null); + } + + void Awake() + { + gameObject.hideFlags = HideFlags.HideAndDontSave; + DontDestroyOnLoad(gameObject); + } +} + +public class WaitForUpdate : CustomYieldInstruction +{ + public override bool keepWaiting + { + get { return false; } + } + + public MainThreadAwaiter GetAwaiter() + { + var awaiter = new MainThreadAwaiter(); + MainThreadUtil.Run(CoroutineWrapper(this, awaiter)); + return awaiter; + } + + public class MainThreadAwaiter : INotifyCompletion + { + Action continuation; + + public bool IsCompleted { get; set; } + + public void GetResult() + { + } + + public void Complete() + { + IsCompleted = true; + continuation?.Invoke(); + } + + void INotifyCompletion.OnCompleted(Action continuation) + { + this.continuation = continuation; + } + } + + public static IEnumerator CoroutineWrapper(IEnumerator theWorker, MainThreadAwaiter awaiter) + { + yield return theWorker; + awaiter.Complete(); + } +} + +namespace NativeWebSocket.Net +{ + public delegate void WebSocketOpenEventHandler(); + + public delegate void WebSocketMessageEventHandler(byte[] data); + + public delegate void WebSocketErrorEventHandler(string errorMsg); + + public delegate void WebSocketCloseEventHandler(WebSocketCloseCode closeCode); + + public enum WebSocketCloseCode + { + /* Do NOT use NotSet - it's only purpose is to indicate that the close code cannot be parsed. */ + NotSet = 0, + Normal = 1000, + Away = 1001, + ProtocolError = 1002, + UnsupportedData = 1003, + Undefined = 1004, + NoStatus = 1005, + Abnormal = 1006, + InvalidData = 1007, + PolicyViolation = 1008, + TooBig = 1009, + MandatoryExtension = 1010, + ServerError = 1011, + TlsHandshakeFailure = 1015 + } + + public enum WebSocketState + { + Connecting, + Open, + Closing, + Closed + } + + public interface IWebSocket + { + event WebSocketOpenEventHandler OnOpen; + event WebSocketMessageEventHandler OnMessage; + event WebSocketErrorEventHandler OnError; + event WebSocketCloseEventHandler OnClose; + + WebSocketState State { get; } + } + + public static class WebSocketHelpers + { + public static WebSocketCloseCode ParseCloseCodeEnum(int closeCode) + { + if (WebSocketCloseCode.IsDefined(typeof(WebSocketCloseCode), closeCode)) + { + return (WebSocketCloseCode)closeCode; + } + else + { + return WebSocketCloseCode.Undefined; + } + } + + public static WebSocketException GetErrorMessageFromCode(int errorCode, Exception inner) + { + switch (errorCode) + { + case -1: + return new WebSocketUnexpectedException("WebSocket instance not found.", inner); + case -2: + return new WebSocketInvalidStateException("WebSocket is already connected or in connecting state.", + inner); + case -3: + return new WebSocketInvalidStateException("WebSocket is not connected.", inner); + case -4: + return new WebSocketInvalidStateException("WebSocket is already closing.", inner); + case -5: + return new WebSocketInvalidStateException("WebSocket is already closed.", inner); + case -6: + return new WebSocketInvalidStateException("WebSocket is not in open state.", inner); + case -7: + return new WebSocketInvalidArgumentException( + "Cannot close WebSocket. An invalid code was specified or reason is too long.", inner); + default: + return new WebSocketUnexpectedException("Unknown error.", inner); + } + } + } + + public class WebSocketException : Exception + { + public WebSocketException() + { + } + + public WebSocketException(string message) : base(message) + { + } + + public WebSocketException(string message, Exception inner) : base(message, inner) + { + } + } + + public class WebSocketUnexpectedException : WebSocketException + { + public WebSocketUnexpectedException() + { + } + + public WebSocketUnexpectedException(string message) : base(message) + { + } + + public WebSocketUnexpectedException(string message, Exception inner) : base(message, inner) + { + } + } + + public class WebSocketInvalidArgumentException : WebSocketException + { + public WebSocketInvalidArgumentException() + { + } + + public WebSocketInvalidArgumentException(string message) : base(message) + { + } + + public WebSocketInvalidArgumentException(string message, Exception inner) : base(message, inner) + { + } + } + + public class WebSocketInvalidStateException : WebSocketException + { + public WebSocketInvalidStateException() + { + } + + public WebSocketInvalidStateException(string message) : base(message) + { + } + + public WebSocketInvalidStateException(string message, Exception inner) : base(message, inner) + { + } + } + + public class WaitForBackgroundThread + { + public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() + { + return Task.Run(() => { }).ConfigureAwait(false).GetAwaiter(); + } + } + +#if UNITY_WEBGL && !UNITY_EDITOR + /// + /// WebSocket class bound to JSLIB. + /// + public class WebSocket : IWebSocket { + + /* WebSocket JSLIB functions */ + [DllImport ("__Internal")] + public static extern int WebSocketConnect (int instanceId); + + [DllImport ("__Internal")] + public static extern int WebSocketClose (int instanceId, int code, string reason); + + [DllImport ("__Internal")] + public static extern int WebSocketSend (int instanceId, byte[] dataPtr, int dataLength); + + [DllImport ("__Internal")] + public static extern int WebSocketSendText (int instanceId, string message); + + [DllImport ("__Internal")] + public static extern int WebSocketGetState (int instanceId); + + protected int instanceId; + + public event WebSocketOpenEventHandler OnOpen; + public event WebSocketMessageEventHandler OnMessage; + public event WebSocketErrorEventHandler OnError; + public event WebSocketCloseEventHandler OnClose; + + public WebSocket (string url, Dictionary headers = null) { + if (!WebSocketFactory.isInitialized) { + WebSocketFactory.Initialize (); + } + + int instanceId = WebSocketFactory.WebSocketAllocate (url); + WebSocketFactory.instances.Add (instanceId, this); + + this.instanceId = instanceId; + } + + public WebSocket (string url, string subprotocol, Dictionary headers = null) { + if (!WebSocketFactory.isInitialized) { + WebSocketFactory.Initialize (); + } + + int instanceId = WebSocketFactory.WebSocketAllocate (url); + WebSocketFactory.instances.Add (instanceId, this); + + WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol); + + this.instanceId = instanceId; + } + + public WebSocket (string url, List subprotocols, Dictionary headers = null) { + if (!WebSocketFactory.isInitialized) { + WebSocketFactory.Initialize (); + } + + int instanceId = WebSocketFactory.WebSocketAllocate (url); + WebSocketFactory.instances.Add (instanceId, this); + + foreach (string subprotocol in subprotocols) { + WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol); + } + + this.instanceId = instanceId; + } + + ~WebSocket () { + WebSocketFactory.HandleInstanceDestroy (this.instanceId); + } + + public int GetInstanceId () { + return this.instanceId; + } + + public Task Connect () { + int ret = WebSocketConnect (this.instanceId); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public void CancelConnection () { + if (State == WebSocketState.Open) + Close (WebSocketCloseCode.Abnormal); + } + + public Task Close (WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null) { + int ret = WebSocketClose (this.instanceId, (int) code, reason); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public Task Send (byte[] data) { + int ret = WebSocketSend (this.instanceId, data, data.Length); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public Task SendText (string message) { + int ret = WebSocketSendText (this.instanceId, message); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public WebSocketState State { + get { + int state = WebSocketGetState (this.instanceId); + + if (state < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (state, null); + + switch (state) { + case 0: + return WebSocketState.Connecting; + + case 1: + return WebSocketState.Open; + + case 2: + return WebSocketState.Closing; + + case 3: + return WebSocketState.Closed; + + default: + return WebSocketState.Closed; + } + } + } + + public void DelegateOnOpenEvent () { + this.OnOpen?.Invoke (); + } + + public void DelegateOnMessageEvent (byte[] data) { + this.OnMessage?.Invoke (data); + } + + public void DelegateOnErrorEvent (string errorMsg) { + this.OnError?.Invoke (errorMsg); + } + + public void DelegateOnCloseEvent (int closeCode) { + this.OnClose?.Invoke (WebSocketHelpers.ParseCloseCodeEnum (closeCode)); + } + + } + +#else + + public class WebSocket : IWebSocket + { + public event WebSocketOpenEventHandler OnOpen; + public event WebSocketMessageEventHandler OnMessage; + public event WebSocketErrorEventHandler OnError; + public event WebSocketCloseEventHandler OnClose; + + private Uri uri; + private Dictionary headers; + private List subprotocols; + private ClientWebSocket m_Socket = new ClientWebSocket(); + + private CancellationTokenSource m_TokenSource; + private CancellationToken m_CancellationToken; + + private readonly object OutgoingMessageLock = new object(); + private readonly object IncomingMessageLock = new object(); + + private bool isSending = false; + private List> sendBytesQueue = new List>(); + private List> sendTextQueue = new List>(); + + public WebSocket(string url, Dictionary headers = null) + { + uri = new Uri(url); + + if (headers == null) + { + this.headers = new Dictionary(); + } + else + { + this.headers = headers; + } + + subprotocols = new List(); + + string protocol = uri.Scheme; + if (!protocol.Equals("ws") && !protocol.Equals("wss")) + throw new ArgumentException("Unsupported protocol: " + protocol); + } + + public WebSocket(string url, string subprotocol, Dictionary headers = null) + { + uri = new Uri(url); + + if (headers == null) + { + this.headers = new Dictionary(); + } + else + { + this.headers = headers; + } + + subprotocols = new List { subprotocol }; + + string protocol = uri.Scheme; + if (!protocol.Equals("ws") && !protocol.Equals("wss")) + throw new ArgumentException("Unsupported protocol: " + protocol); + } + + public WebSocket(string url, List subprotocols, Dictionary headers = null) + { + uri = new Uri(url); + + if (headers == null) + { + this.headers = new Dictionary(); + } + else + { + this.headers = headers; + } + + this.subprotocols = subprotocols; + + string protocol = uri.Scheme; + if (!protocol.Equals("ws") && !protocol.Equals("wss")) + throw new ArgumentException("Unsupported protocol: " + protocol); + } + + public void CancelConnection() + { + m_TokenSource?.Cancel(); + } + + public async Task Connect() + { + try + { + m_TokenSource = new CancellationTokenSource(); + m_CancellationToken = m_TokenSource.Token; + + m_Socket = new ClientWebSocket(); + + foreach (var header in headers) + { + m_Socket.Options.SetRequestHeader(header.Key, header.Value); + } + + foreach (string subprotocol in subprotocols) + { + m_Socket.Options.AddSubProtocol(subprotocol); + } + + await m_Socket.ConnectAsync(uri, m_CancellationToken); + OnOpen?.Invoke(); + + await Receive(); + } + catch (Exception ex) + { + OnError?.Invoke(ex.Message); + OnClose?.Invoke(WebSocketCloseCode.Abnormal); + } + finally + { + if (m_Socket != null) + { + m_TokenSource.Cancel(); + m_Socket.Dispose(); + } + } + } + + public WebSocketState State + { + get + { + switch (m_Socket.State) + { + case System.Net.WebSockets.WebSocketState.Connecting: + return WebSocketState.Connecting; + + case System.Net.WebSockets.WebSocketState.Open: + return WebSocketState.Open; + + case System.Net.WebSockets.WebSocketState.CloseSent: + case System.Net.WebSockets.WebSocketState.CloseReceived: + return WebSocketState.Closing; + + case System.Net.WebSockets.WebSocketState.Closed: + return WebSocketState.Closed; + + default: + return WebSocketState.Closed; + } + } + } + + public Task Send(byte[] bytes) + { + // return m_Socket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None); + return SendMessage(sendBytesQueue, WebSocketMessageType.Binary, new ArraySegment(bytes)); + } + + public Task SendText(string message) + { + var encoded = Encoding.UTF8.GetBytes(message); + + // m_Socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); + return SendMessage(sendTextQueue, WebSocketMessageType.Text, + new ArraySegment(encoded, 0, encoded.Length)); + } + + private async Task SendMessage(List> queue, WebSocketMessageType messageType, + ArraySegment buffer) + { + // Return control to the calling method immediately. + // await Task.Yield (); + + // Make sure we have data. + if (buffer.Count == 0) + { + return; + } + + // The state of the connection is contained in the context Items dictionary. + bool sending; + + lock (OutgoingMessageLock) + { + sending = isSending; + + // If not, we are now. + if (!isSending) + { + isSending = true; + } + } + + if (!sending) + { + // Lock with a timeout, just in case. + if (!Monitor.TryEnter(m_Socket, 1000)) + { + // If we couldn't obtain exclusive access to the socket in one second, something is wrong. + await m_Socket.CloseAsync(WebSocketCloseStatus.InternalServerError, string.Empty, + m_CancellationToken); + return; + } + + try + { + // Send the message synchronously. + var t = m_Socket.SendAsync(buffer, messageType, true, m_CancellationToken); + t.Wait(m_CancellationToken); + } + finally + { + Monitor.Exit(m_Socket); + } + + // Note that we've finished sending. + lock (OutgoingMessageLock) + { + isSending = false; + } + + // Handle any queued messages. + await HandleQueue(queue, messageType); + } + else + { + // Add the message to the queue. + lock (OutgoingMessageLock) + { + queue.Add(buffer); + } + } + } + + private async Task HandleQueue(List> queue, WebSocketMessageType messageType) + { + var buffer = new ArraySegment(); + lock (OutgoingMessageLock) + { + // Check for an item in the queue. + if (queue.Count > 0) + { + // Pull it off the top. + buffer = queue[0]; + queue.RemoveAt(0); + } + } + + // Send that message. + if (buffer.Count > 0) + { + await SendMessage(queue, messageType, buffer); + } + } + + private List m_MessageList = new List(); + + // simple dispatcher for queued messages. + public void DispatchMessageQueue() + { + if (m_MessageList.Count == 0) + { + return; + } + + List messageListCopy; + + lock (IncomingMessageLock) + { + messageListCopy = new List(m_MessageList); + m_MessageList.Clear(); + } + + var len = messageListCopy.Count; + for (int i = 0; i < len; i++) + { + OnMessage?.Invoke(messageListCopy[i]); + } + } + + public async Task Receive() + { + WebSocketCloseCode closeCode = WebSocketCloseCode.Abnormal; + await new WaitForBackgroundThread(); + + ArraySegment buffer = new ArraySegment(new byte[8192]); + try + { + while (m_Socket.State == System.Net.WebSockets.WebSocketState.Open) + { + WebSocketReceiveResult result = null; + + using (var ms = new MemoryStream()) + { + do + { + result = await m_Socket.ReceiveAsync(buffer, m_CancellationToken); + ms.Write(buffer.Array, buffer.Offset, result.Count); + } while (!result.EndOfMessage); + + ms.Seek(0, SeekOrigin.Begin); + + if (result.MessageType == WebSocketMessageType.Text) + { + lock (IncomingMessageLock) + { + m_MessageList.Add(ms.ToArray()); + } + + //using (var reader = new StreamReader(ms, Encoding.UTF8)) + //{ + // string message = reader.ReadToEnd(); + // OnMessage?.Invoke(this, new MessageEventArgs(message)); + //} + } + else if (result.MessageType == WebSocketMessageType.Binary) + { + lock (IncomingMessageLock) + { + m_MessageList.Add(ms.ToArray()); + } + } + else if (result.MessageType == WebSocketMessageType.Close) + { + await Close(); + closeCode = WebSocketHelpers.ParseCloseCodeEnum((int)result.CloseStatus); + break; + } + } + } + } + catch (Exception) + { + m_TokenSource.Cancel(); + } + finally + { + await new WaitForUpdate(); + OnClose?.Invoke(closeCode); + } + } + + public async Task Close() + { + if (State == WebSocketState.Open) + { + await m_Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, m_CancellationToken); + } + } + } +#endif + + /// + /// Factory + /// + /// + /// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface + /// + public static class WebSocketFactory + { +#if UNITY_WEBGL && !UNITY_EDITOR + /* Map of websocket instances */ + public static Dictionary instances = new Dictionary (); + + /* Delegates */ + public delegate void OnOpenCallback (int instanceId); + public delegate void OnMessageCallback (int instanceId, System.IntPtr msgPtr, int msgSize); + public delegate void OnErrorCallback (int instanceId, System.IntPtr errorPtr); + public delegate void OnCloseCallback (int instanceId, int closeCode); + + /* WebSocket JSLIB callback setters and other functions */ + [DllImport ("__Internal")] + public static extern int WebSocketAllocate (string url); + + [DllImport ("__Internal")] + public static extern int WebSocketAddSubProtocol (int instanceId, string subprotocol); + + [DllImport ("__Internal")] + public static extern void WebSocketFree (int instanceId); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnOpen (OnOpenCallback callback); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnMessage (OnMessageCallback callback); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnError (OnErrorCallback callback); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnClose (OnCloseCallback callback); + + /* If callbacks was initialized and set */ + public static bool isInitialized = false; + + /* + * Initialize WebSocket callbacks to JSLIB + */ + public static void Initialize () { + + WebSocketSetOnOpen (DelegateOnOpenEvent); + WebSocketSetOnMessage (DelegateOnMessageEvent); + WebSocketSetOnError (DelegateOnErrorEvent); + WebSocketSetOnClose (DelegateOnCloseEvent); + + isInitialized = true; + + } + + /// + /// Called when instance is destroyed (by destructor) + /// Method removes instance from map and free it in JSLIB implementation + /// + /// Instance identifier. + public static void HandleInstanceDestroy (int instanceId) { + + instances.Remove (instanceId); + WebSocketFree (instanceId); + + } + + [MonoPInvokeCallback (typeof (OnOpenCallback))] + public static void DelegateOnOpenEvent (int instanceId) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + instanceRef.DelegateOnOpenEvent (); + } + + } + + [MonoPInvokeCallback (typeof (OnMessageCallback))] + public static void DelegateOnMessageEvent (int instanceId, System.IntPtr msgPtr, int msgSize) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + byte[] msg = new byte[msgSize]; + Marshal.Copy (msgPtr, msg, 0, msgSize); + + instanceRef.DelegateOnMessageEvent (msg); + } + + } + + [MonoPInvokeCallback (typeof (OnErrorCallback))] + public static void DelegateOnErrorEvent (int instanceId, System.IntPtr errorPtr) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + + string errorMsg = Marshal.PtrToStringAuto (errorPtr); + instanceRef.DelegateOnErrorEvent (errorMsg); + + } + + } + + [MonoPInvokeCallback (typeof (OnCloseCallback))] + public static void DelegateOnCloseEvent (int instanceId, int closeCode) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + instanceRef.DelegateOnCloseEvent (closeCode); + } + + } +#endif + + /// + /// Create WebSocket client instance + /// + /// The WebSocket instance. + /// WebSocket valid URL. + public static WebSocket CreateInstance(string url) + { + return new WebSocket(url); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/WebSocket.jslib b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/WebSocket.jslib new file mode 100644 index 0000000..7fa96ae --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/WebSocket.jslib @@ -0,0 +1,333 @@ + +var LibraryWebSocket = { + $webSocketState: { + /* + * Map of instances + * + * Instance structure: + * { + * url: string, + * ws: WebSocket + * } + */ + instances: {}, + + /* Last instance ID */ + lastId: 0, + + /* Event listeners */ + onOpen: null, + onMesssage: null, + onError: null, + onClose: null, + + /* Debug mode */ + debug: false + }, + + /** + * Set onOpen callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnOpen: function(callback) { + + webSocketState.onOpen = callback; + + }, + + /** + * Set onMessage callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnMessage: function(callback) { + + webSocketState.onMessage = callback; + + }, + + /** + * Set onError callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnError: function(callback) { + + webSocketState.onError = callback; + + }, + + /** + * Set onClose callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnClose: function(callback) { + + webSocketState.onClose = callback; + + }, + + /** + * Allocate new WebSocket instance struct + * + * @param url Server URL + */ + WebSocketAllocate: function(url) { + + var urlStr = UTF8ToString(url); + var id = webSocketState.lastId++; + + webSocketState.instances[id] = { + subprotocols: [], + url: urlStr, + ws: null + }; + + return id; + + }, + + /** + * Add subprotocol to instance + * + * @param instanceId Instance ID + * @param subprotocol Subprotocol name to add to instance + */ + WebSocketAddSubProtocol: function(instanceId, subprotocol) { + + var subprotocolStr = UTF8ToString(subprotocol); + webSocketState.instances[instanceId].subprotocols.push(subprotocolStr); + + }, + + /** + * Remove reference to WebSocket instance + * + * If socket is not closed function will close it but onClose event will not be emitted because + * this function should be invoked by C# WebSocket destructor. + * + * @param instanceId Instance ID + */ + WebSocketFree: function(instanceId) { + + var instance = webSocketState.instances[instanceId]; + + if (!instance) return 0; + + // Close if not closed + if (instance.ws && instance.ws.readyState < 2) + instance.ws.close(); + + // Remove reference + delete webSocketState.instances[instanceId]; + + return 0; + + }, + + /** + * Connect WebSocket to the server + * + * @param instanceId Instance ID + */ + WebSocketConnect: function(instanceId) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (instance.ws !== null) + return -2; + + instance.ws = new WebSocket(instance.url, instance.subprotocols); + + instance.ws.binaryType = 'arraybuffer'; + + instance.ws.onopen = function() { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Connected."); + + if (webSocketState.onOpen) + Module.dynCall_vi(webSocketState.onOpen, instanceId); + + }; + + instance.ws.onmessage = function(ev) { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Received message:", ev.data); + + if (webSocketState.onMessage === null) + return; + + if (ev.data instanceof ArrayBuffer) { + + var dataBuffer = new Uint8Array(ev.data); + + var buffer = _malloc(dataBuffer.length); + HEAPU8.set(dataBuffer, buffer); + + try { + Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length); + } finally { + _free(buffer); + } + + } else { + var dataBuffer = (new TextEncoder()).encode(ev.data); + + var buffer = _malloc(dataBuffer.length); + HEAPU8.set(dataBuffer, buffer); + + try { + Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length); + } finally { + _free(buffer); + } + + } + + }; + + instance.ws.onerror = function(ev) { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Error occured."); + + if (webSocketState.onError) { + + var msg = "WebSocket error."; + var length = lengthBytesUTF8(msg) + 1; + var buffer = _malloc(length); + stringToUTF8(msg, buffer, length); + + try { + Module.dynCall_vii(webSocketState.onError, instanceId, buffer); + } finally { + _free(buffer); + } + + } + + }; + + instance.ws.onclose = function(ev) { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Closed."); + + if (webSocketState.onClose) + Module.dynCall_vii(webSocketState.onClose, instanceId, ev.code); + + delete instance.ws; + + }; + + return 0; + + }, + + /** + * Close WebSocket connection + * + * @param instanceId Instance ID + * @param code Close status code + * @param reasonPtr Pointer to reason string + */ + WebSocketClose: function(instanceId, code, reasonPtr) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (!instance.ws) + return -3; + + if (instance.ws.readyState === 2) + return -4; + + if (instance.ws.readyState === 3) + return -5; + + var reason = ( reasonPtr ? UTF8ToString(reasonPtr) : undefined ); + + try { + instance.ws.close(code, reason); + } catch(err) { + return -7; + } + + return 0; + + }, + + /** + * Send message over WebSocket + * + * @param instanceId Instance ID + * @param bufferPtr Pointer to the message buffer + * @param length Length of the message in the buffer + */ + WebSocketSend: function(instanceId, bufferPtr, length) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (!instance.ws) + return -3; + + if (instance.ws.readyState !== 1) + return -6; + + instance.ws.send(HEAPU8.buffer.slice(bufferPtr, bufferPtr + length)); + + return 0; + + }, + + /** + * Send text message over WebSocket + * + * @param instanceId Instance ID + * @param bufferPtr Pointer to the message buffer + * @param length Length of the message in the buffer + */ + WebSocketSendText: function(instanceId, message) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (!instance.ws) + return -3; + + if (instance.ws.readyState !== 1) + return -6; + + instance.ws.send(UTF8ToString(message)); + + return 0; + + }, + + /** + * Return WebSocket readyState + * + * @param instanceId Instance ID + */ + WebSocketGetState: function(instanceId) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (instance.ws) + return instance.ws.readyState; + else + return 3; + + } + +}; + +autoAddDeps(LibraryWebSocket, '$webSocketState'); +mergeInto(LibraryManager.library, LibraryWebSocket); diff --git a/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/endel.nativewebsocket.asmdef b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/endel.nativewebsocket.asmdef new file mode 100644 index 0000000..9d477f8 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/WebSocket/endel.nativewebsocket.asmdef @@ -0,0 +1,3 @@ +{ + "name": "endel.nativewebsocket" +} diff --git a/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/package.json b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/package.json new file mode 100644 index 0000000..9aac1f6 --- /dev/null +++ b/WV2SDK/SGModule/Net/SGModule/com.endel.nativewebsocket/package.json @@ -0,0 +1,32 @@ +{ + "name": "com.endel.nativewebsocket", + "version": "1.1.4", + "description": "WebSocket client for Unity - with no external dependencies (WebGL, Native, Android, iOS, UWP).", + "license": "Apache 2.0", + "repository": { + "type": "git", + "url": "https://github.com/endel/NativeWebSocket.git" + }, + "author": { + "name": "Endel Dreyer", + "email": "endel.dreyer@gmail.com", + "url": "https://github.com/endel/NativeWebSocket" + }, + "keywords": [ + "websocket", + "websockets", + "native websocket", + "native websockets" + ], + "displayName": "Native WebSockets", + "unity": "2019.1", + "dependencies": {}, + "samples": [ + { + "displayName": "Example", + "description": "WebSocket Example", + "path": "Samples~/WebSocketExample" + } + ], + "_fingerprint": "c5101c07762251edc82caad9e8a39d51b1229c31" +} diff --git a/WV2SDK/SGModule/NetKit/README.md b/WV2SDK/SGModule/NetKit/README.md new file mode 100644 index 0000000..36db2aa --- /dev/null +++ b/WV2SDK/SGModule/NetKit/README.md @@ -0,0 +1,62 @@ +# 🧰 NetKit 模块说明 + +`NetKit` 是一个网络接口相关工具模块,涵盖心跳、登录、网络核心、数据打点及数据结构等功能。 + +------ + +## 🚀 快速开始 + +1. **初始化网络组件** + + ```csharp + NetKit.Instance.Init(); + ``` + +2. **执行登录请求** + + ```csharp + LoginKit.Instance.LoginRequest(channel, haveSimCard, (success, loginModel) => { + if (success) { + // 登录成功逻辑 + } else { + // 登录失败逻辑 + } + }); + ``` + +3. **发送事件打点** + + ```csharp + TrackKit.SendEvent(ADEventTrack.Event, ADEventTrack.Property.fail_click); + ``` + +4. **开启心跳** + + ```csharp + HeartbeatKit.Instance.StartHeartbeat(); + ``` + +------ + +## 📦 模块介绍 + +| 模块名 | 功能描述 | +| -------------- | ------------------------------------- | +| `Model` | 📄 请求与响应数据模型定义 | +| `Core/NetKit` | ⚙️ 网络请求核心,管理请求与 Token | +| `Track` | 📊 事件与属性注册及数据打点 | +| `ErrorLogKit` | 🛠️ 异常日志收集及去重上报 | +| `HeartbeatKit` | ❤️ 心跳管理,确保连接持续活跃 | +| `LoginKit` | 🔐 登录请求、认证及状态管理 | +| `NetGmTool` | 🛠️ 本地调试辅助,支持 Token 和状态测试 | + +------ + +## 📖 说明 + +- **NetKit** 负责所有网络通信,自动处理数据加解密和 Token 刷新。 +- **TrackKit** 提供强大的事件打点功能,支持静态及动态事件注册。 +- **ErrorLogKit** 实现错误日志的收集、去重与上传,方便问题排查。 +- **HeartbeatKit** 管理心跳发送,支持应用前后台切换时的状态保存与恢复。 +- **LoginKit** 负责用户登录流程与自动重新认证逻辑。 +- **NetGmTool** 为游戏调试提供实用工具,方便状态和 Token 测试。 \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/H5Model.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/H5Model.cs new file mode 100644 index 0000000..daafc2b --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/H5Model.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class H5RefreshTimes + { + [JsonProperty("link")] public string Link; + [JsonProperty("times")] public int Times; + } + + public class H5SendClass + { + [JsonProperty("link")] public string Link; + [JsonProperty("type")] public string Type; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/LoginModel.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/LoginModel.cs new file mode 100644 index 0000000..48a93a3 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/LoginModel.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class LoginModel + { + [JsonProperty("cdn_url")] public string CdnURL; + [JsonProperty("country")] public string Country; + [JsonProperty("debug_log")] public bool DebugLog = true; + [JsonProperty("enwp")] public int Enwp; + [JsonProperty("expires_at")] public long ExpiresAt; + [JsonProperty("invite_code")] public string InviteCode; + [JsonProperty("is_magic")] public bool IsMagic; + [JsonProperty("last_login_time")] public long LastLoginTime; + [JsonProperty("login_time")] public long LoginTime; + [JsonProperty("new_player")] public bool NewPlayer; + [JsonProperty("play_data")] public string PlayData; + [JsonProperty("play_data_ver")] public long PlayDataVer; + [JsonProperty("reg_time")] public long RegTime; + [JsonProperty("setting")] public string Setting; + [JsonProperty("token")] public string Token; + [JsonProperty("uid")] public long Uid; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/OrderModel.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/OrderModel.cs new file mode 100644 index 0000000..2612b59 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/OrderModel.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class OrderModel + { + } + + public class PayOutUserInfoData + { + [JsonProperty("email")] public string Email; + [JsonProperty("first_name")] public string FirstName; + [JsonProperty("last_name")] public string LastName; + } + + public class PayerData + { + [JsonProperty("amount")] public int Amount; + [JsonProperty("email")] public string Email; + [JsonProperty("name")] public string Name; + [JsonProperty("tel")] public string Tel; + } + + public class OrderData + { + [JsonProperty("code")] public int Code; + [JsonProperty("order_id")] public string OrderID; + [JsonProperty("pay_url")] public string PayURL; + } + + public class OrderState + { + [JsonProperty("order_id")] public string OrderID; + [JsonProperty("status")] public int Status; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/RequestLoginData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestLoginData.cs new file mode 100644 index 0000000..cb9578c --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestLoginData.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class RequestLoginData + { + [JsonProperty("device_id")] public string device_id; + [JsonProperty("secret")] public string secret; + [JsonProperty("app_version")] public string app_version; + [JsonProperty("device")] public string device; + [JsonProperty("os_ver")] public string os_ver; + [JsonProperty("pack_name")] public string pack_name; + } + + public class RequestTokenData + { + [JsonProperty("expires_at")] public long ExpiresAt; + [JsonProperty("token")] public string Token; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/RequestResultData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestResultData.cs new file mode 100644 index 0000000..9a0e883 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestResultData.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class RequestResultData + { + [JsonProperty("result")] public string Result; + [JsonProperty("type")] public int Type; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/RequestSavePlayData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestSavePlayData.cs new file mode 100644 index 0000000..0c106bf --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestSavePlayData.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class RequestSavePlayData + { + [JsonProperty("data")] public string Data; + [JsonProperty("version")] public long Version; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/RequestStageData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestStageData.cs new file mode 100644 index 0000000..f99042b --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/RequestStageData.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class RequestStageData + { + [JsonProperty("begin_time")] public int BeginTime; + [JsonProperty("end_time")] public int EndTime; + [JsonProperty("item_costs")] public int[] ItemCosts; + [JsonProperty("item_type")] public int ItemType; + [JsonProperty("layer")] public int Layer; + [JsonProperty("level")] public int Level; + [JsonProperty("pass")] public bool Pass; + [JsonProperty("remove_item")] public int RemoveItem; + [JsonProperty("total_item")] public int TotalItem; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/RespDebugData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/RespDebugData.cs new file mode 100644 index 0000000..d0d48aa --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/RespDebugData.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class RespDebugData + { + [JsonProperty("channel")] public string Channel; + [JsonProperty("device")] public string Device; + [JsonProperty("device_id")] public string DeviceID; + [JsonProperty("level")] public string Level; + [JsonProperty("message")] public string Message; + [JsonProperty("network")] public string Network; + [JsonProperty("os_ver")] public string OSVer; + [JsonProperty("pack_name")] public string PackName; + [JsonProperty("stacktrace")] public string Stacktrace; + [JsonProperty("uid")] public long Uid; + [JsonProperty("version")] public string Version; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/RespLoginFunnelData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/RespLoginFunnelData.cs new file mode 100644 index 0000000..a345787 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/RespLoginFunnelData.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class RespLoginFunnelData + { + [JsonProperty("device_id")] public string DeviceID; + [JsonProperty("bid")] public string Bid; + [JsonProperty("payload")] public string Payload; + [JsonProperty("trace_id")] public string TraceID; + [JsonProperty("type")] public string Type; + [JsonProperty("uid")] public long Uid; + [JsonProperty("version")] public string Version; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Model/TrackData.cs b/WV2SDK/SGModule/NetKit/SGModule/Model/TrackData.cs new file mode 100644 index 0000000..232a662 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Model/TrackData.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace Uni2SDK +{ + public class TrackData + { + [JsonProperty("event")] public string Event; + [JsonProperty("n")] public int N; + [JsonProperty("property")] public string Property; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Core/NetKit.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Core/NetKit.cs new file mode 100644 index 0000000..5ca31a0 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Core/NetKit.cs @@ -0,0 +1,113 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using UnityEngine.Events; + +namespace Uni2SDK +{ + public class NetKit : NetCore + { + public void Init() + { + Log.NetKit.Info("NetworkKit Init!!!!!"); + } + + protected override string Encrypt(string text, string key) + { + var str = !Uni2SDKManager.Instance._isDebug + ? Base64Kit.Encrypt(Encoding.UTF8.GetBytes(text), key).As() + : text; + Log.NetKit.Info($"NetworkKit Encrypt!!!!! {Uni2SDKManager.Instance._isDebug} \n key== {key} \n data== {text} \n result=={str}"); + return str; + } + + protected override string Decrypt(string text, string key) + { + return !Uni2SDKManager.Instance._isDebug + ? Base64Kit.Decrypt(Base64Kit.UnquoteBytes(Encoding.UTF8.GetBytes(text)), key).As() + : text; + } + + protected override TokenManager.RefreshTokenDelegate RefreshTokenHandler() + { + return onCompleted => PostAsync( + "tokenRefresh", + onCompleted: response => + { + if (response.IsSuccess) + { + var token = response.Data.Token; + var expiresAt = response.Data.ExpiresAt; + onCompleted?.Invoke(token, expiresAt); + } + else + { + onCompleted?.Invoke(null, 0); + } + }); + } + + protected override TokenManager.ReauthenticateDelegate ReauthenticateHandler() + { + return onCompleted => LoginKit.Instance.ReauthenticateOnExpiration(success => + { + if (success) + { + var model = LoginKit.Instance.LoginModel; + onCompleted?.Invoke(model.Token, model.ExpiresAt); + } + else + { + onCompleted?.Invoke(null, 0); + } + }); + } + + + /// + /// Post请求(异步) + /// + /// 请求路径 + /// 请求参数 + /// 请求回调 + /// 是否带Token + /// 额外的请求头 + public new IEnumerator PostAsync(string path, object requestData = null, + UnityAction> onCompleted = null, bool withToken = true, + Dictionary headers = null) + { + return base.PostAsync(path, requestData, onCompleted, withToken, headers); + } + + /// + /// Post请求 + /// + /// 请求路径 + /// 请求参数 + /// 请求回调 + /// 是否带Token + /// 是否使用协程 + /// 额外的请求头 + public void Post(string path, object requestData = null, UnityAction> onCompleted = null, + bool withToken = true, bool useCoroutine = true, Dictionary headers = null) + { + base.Post(path, requestData, onCompleted, withToken, useCoroutine, headers); + } + + /// + /// Post请求(泛型) + /// + /// 请求路径 + /// 请求参数 + /// 请求回调 + /// 是否带Token + /// 是否使用协程 + /// 额外的请求头 + /// 返回数据中Data的类型 + public new void Post(string path, object requestData = null, UnityAction> onCompleted = null, + bool withToken = true, bool useCoroutine = true, Dictionary headers = null) + { + base.Post(path, requestData, onCompleted, withToken, useCoroutine, headers); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/ErrorLogKit.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/ErrorLogKit.cs new file mode 100644 index 0000000..66aaf27 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/ErrorLogKit.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using Uni2SDK; +using UnityEngine; +using UnityEngine.Events; + +namespace Uni2SDK +{ + public static class ErrorLogKit + { + private static readonly Dictionary> _statusDic = new(); + private static bool _isErrorLoggingEnabled = true; + + /// + /// 初始化方法 + /// + /// 是否开启错误日志上报 + public static void Init(bool isErrorLoggingEnabled) + { + _isErrorLoggingEnabled = isErrorLoggingEnabled; + } + + /// + /// 发送错误日志 + /// + /// + /// + /// + public static void Send(string level, string message, string stackTrace) + { + if (!_isErrorLoggingEnabled) + { + return; + } + + // 如果只需要日期部分,可以使用ToShortDateString()或ToString("yyyy-MM-dd")等进行格式化 + var currentDate = DateTime.Now; + var formattedDate = currentDate.ToString("yyyy_MM_dd_HH_mm"); + + var md5Str = MD5Helper.GetStringMD5(message + stackTrace); + + if (!_statusDic.ContainsKey(formattedDate)) + { + _statusDic.Add(formattedDate, new Dictionary()); + } + + if (_statusDic[formattedDate].ContainsKey(md5Str)) + { + return; + } + + _statusDic[formattedDate].Add(md5Str, true); + + SendLogToServer(level, message, stackTrace); + } + + private static void SendLogToServer(string level, string message, string stackTrace, + UnityAction callback = null) + { + var requestData = new RespDebugData + { + Uid = LoginKit.Instance.LoginModel?.Uid ?? -1, + Device = SystemInfo.deviceModel, + OSVer = SystemInfo.operatingSystem, + Network = GetNetworkType(), + DeviceID = DeviceHelper.GetDeviceID(), + PackName = Application.identifier, + Version = Application.version, + Level = level, + Message = message, + Stacktrace = stackTrace + }; + + NetKit.Instance.Post("event/cliDebugLog", requestData, + response => { callback?.Invoke(response.IsSuccess); }, false); + } + + private static string GetNetworkType() + { + return Application.internetReachability switch + { + NetworkReachability.ReachableViaCarrierDataNetwork => "Mobile Data", + NetworkReachability.ReachableViaLocalAreaNetwork => "Wi-Fi", + _ => "Not Connected" + }; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/HeartbeatKit.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/HeartbeatKit.cs new file mode 100644 index 0000000..0ab5cc8 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/HeartbeatKit.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections; +using SGModule.Common; +using Uni2SDK; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; + +namespace Uni2SDK +{ + public class HeartbeatKit : SingletonMonoBehaviour + { + private Coroutine _heartbeatCoroutine; + private static int HeartbeatInterval => 60; + private const string HeartbeatForegroundTime = "HeartbeatForegroundTime"; + + /// + /// 后台时逻辑 + /// + /// + private void OnApplicationPause(bool pauseStatus) + { + Log.NetKit.Warning($"OnApplication Pause :{pauseStatus}"); + // 应用程序失去焦点 + if (pauseStatus) + { + SaveRemainingHeartbeatTime(); + } + else + { + RestoreNextHeartbeatTime(); + } + } + + // 应用程序退出 + private void OnApplicationQuit() + { + Log.NetKit.Warning("OnApplication Quit"); + + SaveRemainingHeartbeatTime(); + } + + public void StartHeartbeat() + { + _heartbeatCoroutine ??= StartCoroutine(HeartbeatRequestCoroutine()); + } + + private IEnumerator HeartbeatRequestCoroutine() + { + RestoreNextHeartbeatTime(); // 恢复之前剩余时间或默认值 + + while (true) + { + if (Now() >= NextHeartbeatTime) + { + NextHeartbeatTime += HeartbeatInterval; + RequestHeart(); + } + + yield return new WaitForSeconds(1); + } + } + + // 应用程序失去焦点, 保存心跳剩余时间 + private void SaveRemainingHeartbeatTime() + { + var remain = Math.Max((int)(NextHeartbeatTime - Now()), 0); + LastRemainingInterval = remain % HeartbeatInterval; + } + + + // 应用程序获得焦点, 恢复下次心跳时间 + private void RestoreNextHeartbeatTime() + { + var now = Now(); + if (LastRemainingInterval < 0) + { + NextHeartbeatTime = now; + return; + } + + var remain = LastRemainingInterval % HeartbeatInterval; + if (remain <= 0) + { + remain = HeartbeatInterval; + } + + NextHeartbeatTime = now + remain; + } + + private long _nextHeartbeatTime; + + private long NextHeartbeatTime + { + get + { + if (_nextHeartbeatTime <= 0) + { + _nextHeartbeatTime = Now() + HeartbeatInterval; + } + + return _nextHeartbeatTime; + } + set + { + var now = Now(); + if (value <= now) + { + value = now + HeartbeatInterval; + } + + _nextHeartbeatTime = value; + } + } + + private int? _lastRemainingInterval; + + private int LastRemainingInterval + { + get + { + if (_lastRemainingInterval != null) + { + return _lastRemainingInterval.Value; + } + + _lastRemainingInterval = PlayerPrefs.GetInt(HeartbeatForegroundTime, -1); + + return _lastRemainingInterval.Value; + } + set + { + _lastRemainingInterval = value; + PlayerPrefs.SetInt(HeartbeatForegroundTime, value); + } + } + + + private void RequestHeart() + { + NetKit.Instance.Post("user/health"); + } + + private static long Now() + { + return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/LoginKit.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/LoginKit.cs new file mode 100644 index 0000000..6ebe36f --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/LoginKit.cs @@ -0,0 +1,114 @@ +using System.Collections; +using UnityEngine; +using UnityEngine.Events; + +namespace Uni2SDK +{ + public class LoginKit : SingletonMonoBehaviour + { + private string _channel; + private bool _sim; + private bool _loginSuccess; + private LoginModel _loginModel; + private bool _isFirstLoginSuccess = true; + private RequestLoginData _requestLoginData; + private string _packName; + + + public string Channel => _channel; + + public void LoginRequest(string channel, bool haveSimCard, UnityAction onCompleted) + { + _packName = Uni2SDKManager.Instance.GetPackageNow(); + _channel = string.IsNullOrEmpty(channel) ? "organic" : channel; + _sim = haveSimCard; + + _requestLoginData = new RequestLoginData + { + device_id = DeviceHelper.GetDeviceID(), + secret = NetworkManager.GetSecret(), + app_version = Application.version, + device = SystemInfo.deviceModel, + os_ver = SystemInfo.operatingSystem, + pack_name = _packName, + }; + + StartCoroutine(LoginCoroutine(_requestLoginData, onCompleted)); + } + + public IEnumerator ReauthenticateOnExpiration(UnityAction onCompleted) + { + if (_requestLoginData == null) + { + Log.NetKit.Error("登录数据为空,重新登录失败"); + onCompleted?.Invoke(false); + yield break; + } + + yield return LoginCoroutine(_requestLoginData, (isSuccess, _) => { onCompleted?.Invoke(isSuccess); }); + } + + private IEnumerator LoginCoroutine(RequestLoginData reqData, UnityAction onCompleted) + { + const int maxRetry = 5; + for (var attempt = 0; attempt < maxRetry; attempt++) + { + if (attempt > 0) + { + Log.NetKit.Warning($"登录失败,重试第 {attempt} 次..."); + yield return new WaitForSeconds(5f); + } + + yield return DoLoginRequest(reqData); + + if (_loginSuccess) + { + OnLoginSuccess(); + onCompleted?.Invoke(true, _loginModel); + yield break; + } + } + + Log.NetKit.Error("登录最终失败"); + onCompleted?.Invoke(false, null); + } + + private IEnumerator DoLoginRequest(RequestLoginData reqData) + { + yield return NetKit.Instance.PostAsync( + "login", + reqData, + response => + { + _loginSuccess = response.IsSuccess; + _loginModel = response.Data; + }, + withToken: false + ); + } + + private void OnLoginSuccess() + { + Log.NetKit.Info("登录成功,处理登录后逻辑"); + + ServerClock.Init(_loginModel.LoginTime); + + TokenManager.Instance.UpdateTokenCache(_loginModel.Token, _loginModel.ExpiresAt); + + ErrorLogKit.Init(_loginModel.DebugLog); + + + // 开始心跳 + HeartbeatKit.Instance.StartHeartbeat(); + + + StartCoroutine(TrackKit.NoSentTrackProcessing()); // 处理堆积的打点数据 + } + + + public LoginModel LoginModel => _loginModel; + + + public bool LoginSuccess => _loginSuccess; + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/NetGmTool.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/NetGmTool.cs new file mode 100644 index 0000000..0910782 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/NetGmTool.cs @@ -0,0 +1,51 @@ +namespace Uni2SDK +{ + public class NetGmTool : SingletonMonoBehaviour + { + public void Init() + { + GMTool.Instance.AddItem(new GMToolItem(GUIType.Separator, () => "网络模块")); + GMTool.Instance.AddItem( + new GMToolItem(GUIType.Label, () => $"当前网络状态:{NetChecker.Instance.GetConnectionStatus()}")); + GMTool.Instance.AddItem(new GMToolItem(GUIType.Label, + () => + $"loginModel uid:{(LoginKit.Instance.LoginModel == null ? "null" : LoginKit.Instance.LoginModel.Uid)}")); + GMTool.Instance.AddItem(new GMToolItem(GUIType.Label, + () => + $"Token过期时间:{(LoginKit.Instance.LoginModel == null ? "null" : TimeHelper.ConvertToLocalTime(LoginKit.Instance.LoginModel.ExpiresAt))}")); + + GMTool.Instance.AddItem(new GMToolItem(GUIType.Label, + () => + { + var str = ""; + foreach (var record in TokenManager.Instance.GetTokenRecord()) + { + str += $"Token: {record} " + "\n"; + } + + return str; + }) + ); + GMTool.Instance.AddItem(new GMToolItem(GUIType.Button, () => "Token刷新测试", + s => { VerifyTokenExpiryImminent(); })); + GMTool.Instance.AddItem(new GMToolItem(GUIType.Button, () => "Token过期测试", + s => { VerifyTokenExpirationBehavior(); })); + } + + /// + /// Token即将过期(测试功能用) + /// + public void VerifyTokenExpiryImminent() + { + LoginKit.Instance.LoginModel.ExpiresAt -= 85400; + } + + /// + /// token过期(测试功能用) + /// + public void VerifyTokenExpirationBehavior() + { + LoginKit.Instance.LoginModel.ExpiresAt -= 86400; + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackEvent.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackEvent.cs new file mode 100644 index 0000000..983f070 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackEvent.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using Uni2SDK; + +namespace Uni2SDK +{ + public static class TrackEvent + { + private static readonly HashSet _registeredStaticEvents = new(); + private static readonly HashSet _registeredDynamicEventPrefixes = new(); + + + public static string Register(string eventKey, bool isDynamic = false) + { + var targetSet = isDynamic ? _registeredDynamicEventPrefixes : _registeredStaticEvents; + + if (!targetSet.Add(eventKey)) + { + var type = isDynamic ? "动态" : "静态"; + Log.NetKit.Error($"[Track] 重复的{type} Event 注册: {eventKey}"); + } + + return eventKey; + } + + + /// + /// 是否为注册的静态 property(完全匹配) + /// + private static bool ContainsExact(string eventKey) + { + return _registeredStaticEvents.Contains(eventKey); + } + + /// + /// 是否匹配任何已注册的动态前缀 + /// + private static bool ContainsDynamic(string eventKey) + { + return _registeredDynamicEventPrefixes.Any(eventKey.StartsWith); + } + + /// + /// 检查是否存在(静态或动态) + /// + public static bool Contains(string eventKey) + { + return ContainsExact(eventKey) || ContainsDynamic(eventKey); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackKit.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackKit.cs new file mode 100644 index 0000000..a7372ca --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackKit.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using SGModule.Common; +using Uni2SDK; +using Uni2SDK; +using UnityEngine; +using UnityEngine.Events; + +namespace Uni2SDK +{ + public static class TrackKit + { + private static string _traceID; + private static readonly Dictionary _statusDic = new(); + + //登录前的打点数据,没有token无法正确发送,登录成功后重新发一遍 + private static readonly Queue _waitLoginSuccessTrackQueue = new(); + + private static void PostTrack(TrackData trackData, UnityAction callback = null) + { + if (!LoginKit.Instance.LoginSuccess) + { + _waitLoginSuccessTrackQueue.Enqueue(trackData); + return; + } + + NetKit.Instance.Post("/event/incrN", trackData, + response => { callback?.Invoke(response.IsSuccess, response.Data); }); + } + + /// + /// 发送未发送的打点数据 + /// + /// + public static IEnumerator NoSentTrackProcessing() + { + //处理未发送的打点数据 + while (_waitLoginSuccessTrackQueue.Count > 0) + { + var trackData = _waitLoginSuccessTrackQueue.Dequeue(); + PostTrack(trackData); + + yield return new WaitForSeconds(1f); + } + + _waitLoginSuccessTrackQueue.Clear(); + } + + + public static void SendEvent(string @event, string property = "", int n = 1) + { + // 检查Key是否注册 + if (!TrackEvent.Contains(@event)) + { + Log.NetKit.Warning($"Not found event key: {@event}"); + return; + } + + if (!TrackProperty.Contains(property)) + { + Log.NetKit.Warning($"Not found property key: {property}"); + return; + } + + var data = new TrackData { Event = @event, Property = property, N = n }; + + Log.NetKit.Info($"Sending track {data.Event} | {data.Property} | {data.N}"); + + PostTrack(data); + } + + + #region 登录漏斗 + + private static readonly Dictionary _loginFunnelEventMap = new() + { + { LoginFunnelEventType.Bootstrap, "bootstrap" }, + { LoginFunnelEventType.LoginSend, "loginSend" }, + { LoginFunnelEventType.LoginRecv, "loginRecv" }, + { LoginFunnelEventType.LoadBegin, "loadBegin" }, + { LoginFunnelEventType.LoadFinish, "loadFinish" }, + { LoginFunnelEventType.CoreInitBegin, "coreInitBegin" }, + { LoginFunnelEventType.CoreInitFinish, "coreInitFinish" }, + { LoginFunnelEventType.AllReady, "allReady" }, + }; + + private static readonly Queue<(LoginFunnelEventType eventType, string message)> _loginFunnelQueue = new(); + + private static readonly object _queueLock = new(); + + private static IEnumerator ProcessQueueCoroutine() + { + while (true) + { + (LoginFunnelEventType eventType, string message) item; + lock (_queueLock) + { + if (_loginFunnelQueue.Count == 0) + { + _isProcessing = false; + yield break; + } + + item = _loginFunnelQueue.Dequeue(); + } + + if (!_loginFunnelEventMap.TryGetValue(item.eventType, out var trackName)) + { + throw new ArgumentOutOfRangeException(nameof(item.eventType), item.eventType, null); + } + + if (_statusDic.TryGetValue(trackName, out var status)) + { + if (status) + { + continue; + } + } + + _statusDic[trackName] = true; + + // 发起打点请求 + bool isDone = false; + PostFunnelLogin(trackName, item.message, () => isDone = true); + + // 等待请求完成 + yield return new WaitUntil(() => isDone); + + // 控制请求间隔 + // yield return new WaitForSeconds(0.1f); // 可根据需求调整 + } + } + + private static bool _isProcessing = false; + + public static void TrackLoginFunnel(LoginFunnelEventType loginFunnelEventType, string message = "") + { + lock (_queueLock) + { + _loginFunnelQueue.Enqueue((loginFunnelEventType, message)); + } + + if (!_isProcessing) + { + _isProcessing = true; + SugarAsyKit.StartCoroutine(ProcessQueueCoroutine()); + } + } + + private static void PostFunnelLogin(string type, string payload, UnityAction callback = null) + { + if (type == "bootstrap") + { + var timestamp = DateTime.Now.ToUniversalTime().Ticks - 621355968000000000; + _traceID = timestamp.ToString(); + } + + var requestData = new RespLoginFunnelData + { + Uid = LoginKit.Instance.LoginModel?.Uid ?? -1, + TraceID = _traceID, + DeviceID = DeviceHelper.GetDeviceID(), + Bid = Uni2SDKManager.Instance.GetPackageNow(), + Version = Application.version, + Type = type, + Payload = payload + }; + + NetKit.Instance.Post("event/funnelLogin", requestData, response => + { + callback?.Invoke(); + },false); + } + + #endregion + } + + public enum LoginFunnelEventType + { + Bootstrap, + LoginSend, + LoginRecv, + LoadBegin, + LoadFinish, + CoreInitBegin, + CoreInitFinish, + AllReady, + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackProperty.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackProperty.cs new file mode 100644 index 0000000..16df996 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/Kits/Track/TrackProperty.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; +using Uni2SDK; + +namespace Uni2SDK +{ + public static class TrackProperty + { + private static readonly HashSet _registeredStaticProperties = new(); + private static readonly HashSet _registeredDynamicPropertyPrefixes = new(); + + public static string Register(string propertyKey, bool isDynamic = false) + { + var targetSet = isDynamic ? _registeredDynamicPropertyPrefixes : _registeredStaticProperties; + + if (!targetSet.Add(propertyKey)) + { + var type = isDynamic ? "动态" : "静态"; + Log.NetKit.Error($"[Track] 重复的{type} Property 注册: {propertyKey}"); + } + + return propertyKey; + } + + + /// + /// 是否为注册的静态 property(完全匹配) + /// + private static bool ContainsExact(string propertyKey) + { + return _registeredStaticProperties.Contains(propertyKey); + } + + /// + /// 是否匹配任何已注册的动态前缀 + /// + private static bool ContainsDynamic(string propertyKey) + { + return _registeredDynamicPropertyPrefixes.Any(propertyKey.StartsWith); + } + + /// + /// 检查是否存在(静态或动态) + /// + public static bool Contains(string propertyKey) + { + return ContainsExact(propertyKey) || ContainsDynamic(propertyKey); + } + } +} \ No newline at end of file diff --git a/WV2SDK/SGModule/NetKit/SGModule/Scripts/NetApi.cs b/WV2SDK/SGModule/NetKit/SGModule/Scripts/NetApi.cs new file mode 100644 index 0000000..6962915 --- /dev/null +++ b/WV2SDK/SGModule/NetKit/SGModule/Scripts/NetApi.cs @@ -0,0 +1,141 @@ +using Uni2SDK; +using UnityEngine.Events; + +namespace Uni2SDK +{ + public static class NetApi + { + #region 玩家数据 + + public static void RequestPlayerData(UnityAction onCompleted) + { + NetKit.Instance.Post("user/userData", + onCompleted: response => { onCompleted?.Invoke(response.IsSuccess, response.Data); }); + } + + /// + /// 立即发送请求更新用户数据 + /// + /// + /// + public static void UploadPlayerDataUpdate(long version, string data) + { + var requestSavePlayData = new RequestSavePlayData + { + Version = version, + Data = data + }; + NetKit.Instance.Post("user/updateData", requestSavePlayData, useCoroutine: false); + } + + + /// + /// 启动协程更新用户数据 + /// + /// + /// + /// + public static void UploadPlayerDataUpdate(long version, string data, UnityAction onCompleted) + { + var requestSavePlayData = new RequestSavePlayData + { + Version = version, + Data = data + }; + NetKit.Instance.Post("user/updateData", requestSavePlayData, + response => { onCompleted?.Invoke(response.IsSuccess); }); + } + + public static void DeltaUpdateData(long version, string data, UnityAction onCompleted) + { + var requestSavePlayData = new RequestSavePlayData + { + Version = version, + Data = data + }; + NetKit.Instance.Post("user/deltaUpdateData", requestSavePlayData, + response => { onCompleted?.Invoke(response.IsSuccess); }); + } + + #endregion + + + #region Pay + + public static void PixPayIn(string account, string phone, string email, int amount, + UnityAction onCompleted = null) + { + var data = new PayerData + { + Name = account, + Tel = phone, + Email = email, + Amount = amount + }; + + NetKit.Instance.Post("shop/pixPayIn", data, + response => { onCompleted?.Invoke(response.IsSuccess, response.Data); }); + } + + public static void PixPayOrderQuery(string orderID, UnityAction onCompleted = null) + { + var data = new OrderData + { + OrderID = orderID + }; + + NetKit.Instance.Post("shop/pixPayOrderQuery", data, + response => { onCompleted?.Invoke(response.IsSuccess, response.Data); }); + } + + public static void PayOutUserInfo(string email, string firstName, string lastName, + UnityAction onCompleted = null) + { + var data = new PayOutUserInfoData + { + Email = email, + FirstName = firstName, + LastName = lastName + }; + + NetKit.Instance.Post("shop/payOutUserInfo", data, + response => { onCompleted?.Invoke(response.IsSuccess, response.Data); }); + } + + #endregion + + + #region H5 + + public static void H5RefreshTimes(string link, string type, UnityAction onCompleted = null) + { + var info = new H5SendClass + { + Link = link, + Type = type + }; + NetKit.Instance.Post("event/h5Impressions", info, + response => { onCompleted?.Invoke(response.IsSuccess, response.Data); }); + } + + #endregion + + + #region 结算上传 + + public static void SettleUp(int gameType, RequestStageData stageData, UnityAction onCompleted = null) + { + var requestJson = SerializeHelper.ToJsonIndented(stageData); + var reqData = new RequestResultData + { + Type = gameType, + Result = requestJson + }; + + NetKit.Instance.Post("game/settleUp", reqData, + response => { onCompleted?.Invoke(response.IsSuccess); }); + } + + #endregion + } +} \ No newline at end of file diff --git a/WV2SDK/Singleton/ISingleton.cs b/WV2SDK/Singleton/ISingleton.cs new file mode 100644 index 0000000..ddc3779 --- /dev/null +++ b/WV2SDK/Singleton/ISingleton.cs @@ -0,0 +1,7 @@ +namespace Uni2SDK +{ + internal interface ISingleton + { + void OnSingletonInit(); + } +} \ No newline at end of file diff --git a/WV2SDK/Singleton/Singleton.cs b/WV2SDK/Singleton/Singleton.cs new file mode 100644 index 0000000..fd70b7d --- /dev/null +++ b/WV2SDK/Singleton/Singleton.cs @@ -0,0 +1,34 @@ +using System; + +namespace Uni2SDK +{ + internal class Singleton : ISingleton, IDisposable where T : Singleton, new() + { + private static T mInstance; + + private static readonly object LockObject = new(); + + public static T Instance + { + get + { + lock (LockObject) + { + mInstance ??= new T(); + mInstance.OnSingletonInit(); + } + + return mInstance; + } + } + + public virtual void Dispose() + { + mInstance = null; + } + + public void OnSingletonInit() + { + } + } +} \ No newline at end of file diff --git a/WV2SDK/Singleton/SingletonUnity.cs b/WV2SDK/Singleton/SingletonUnity.cs new file mode 100644 index 0000000..5f413ad --- /dev/null +++ b/WV2SDK/Singleton/SingletonUnity.cs @@ -0,0 +1,100 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal abstract class SingletonUnity : MonoBehaviour, ISingleton where T : SingletonUnity + { + private static T mInstance; + + public static T Instance + { + get + { + if (mInstance != null) + { + return mInstance; + } + + if (ApplicationIsQuitting) + { + return null; + } + + GameObject instanceGO = new GameObject(typeof(T).Name); + mInstance = instanceGO.AddComponent(); + SetSelfParentRoot(instanceGO, mInstance.ParentRootName); + mInstance.OnSingletonInit(); + return mInstance; + } + } + + private static bool ApplicationIsQuitting; + protected virtual string ParentRootName { get; private set; } + + private static void SetSelfParentRoot(GameObject go, string parentRootName) + { + if (!AppObjConst.EngineSingletonGo) return; + + if (parentRootName == null) + { + go.transform.SetParent(AppObjConst.EngineSingletonGo.transform, false); + } + else + { + Transform sinleonRoot = AppObjConst.EngineSingletonGo.transform; + Transform rootTF = sinleonRoot.Find(parentRootName); + if (rootTF == null) + { + GameObject rootGo = new GameObject(parentRootName); + switch (parentRootName) + { + case AppObjConst.MonoManagerGoName: + AppObjConst.MonoManagerGo = rootGo; + break; + case AppObjConst.DispatcherGoName: + AppObjConst.DispatcherGo = rootGo; + break; + case AppObjConst.LoaderGoName: + AppObjConst.LoaderGo = rootGo; + break; + } + + rootTF = rootGo.transform; + rootTF.transform.SetParent(sinleonRoot, false); + } + + go.transform.SetParent(rootTF.transform, false); + } + } + + protected virtual void New() + { + } + + public virtual void Dispose() + { + mInstance = null; + } + + protected virtual void OnDestroy() + { + mInstance = null; + } + + private void OnApplicationQuit() + { + ApplicationIsQuitting = true; + if (mInstance == null) + { + return; + } + + Destroy(mInstance.gameObject); + } + + public void OnSingletonInit() + { + mInstance.New(); + } + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/Base64EncodeUtil.cs b/WV2SDK/ToolKit/Base64EncodeUtil.cs new file mode 100644 index 0000000..e88a716 --- /dev/null +++ b/WV2SDK/ToolKit/Base64EncodeUtil.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; + +namespace FutureCore +{ + internal static class Base64EncodeUtil + { + public static string Base64Encode(string source) + { + return Base64Encode(Encoding.UTF8, source); + } + + public static string Base64Decode(string result) + { + return Base64Decode(Encoding.UTF8, result); + } + + public static string Base64Encode(Encoding encoding, string source) + { + byte[] bytes = encoding.GetBytes(source); + string encode = Convert.ToBase64String(bytes); + return encode; + } + + public static string Base64Decode(Encoding encoding, string result) + { + byte[] bytes = Convert.FromBase64String(result); + string decode = encoding.GetString(bytes); + return decode; + } + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/Base64Kit.cs b/WV2SDK/ToolKit/Base64Kit.cs new file mode 100644 index 0000000..533b196 --- /dev/null +++ b/WV2SDK/ToolKit/Base64Kit.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using System.Text; +using FutureCore; +using Uni2SDK; +using System.Security.Cryptography; + +internal class Base64Kit +{ + private static byte[] GenerateKey(string secret) + { + using var sha256 = SHA256.Create(); + return sha256.ComputeHash(Encoding.UTF8.GetBytes(secret)); + } + + internal static byte[] Encrypt(byte[] plainText, string secret) + { + if (plainText == null || plainText.Length == 0) return plainText; + var key = GenerateKey(secret); + using var aes = Aes.Create(); + aes.Key = key; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + aes.GenerateIV(); + var iv = aes.IV; + var blockSize = aes.BlockSize / 8; + var padding = blockSize - plainText.Length % blockSize; + var padded = new byte[plainText.Length + padding]; + Buffer.BlockCopy(plainText, 0, padded, 0, plainText.Length); + for (var i = plainText.Length; i < padded.Length; i++) padded[i] = (byte)padding; + using var encryptor = aes.CreateEncryptor(); + var encrypted = encryptor.TransformFinalBlock(padded, 0, padded.Length); + var result = new byte[iv.Length + encrypted.Length]; + Buffer.BlockCopy(iv, 0, result, 0, iv.Length); + Buffer.BlockCopy(encrypted, 0, result, iv.Length, encrypted.Length); + return ToUrlSafeBase64(result); + } + + internal static byte[] Decrypt(byte[] encryptedBase64, string secret) + { + if (encryptedBase64 == null || encryptedBase64.Length == 0) return encryptedBase64; + var key = GenerateKey(secret); + var fullCipher = FromUrlSafeBase64(encryptedBase64); + if (fullCipher.Length < 16) throw new Exception("Invalid cipher data"); + var iv = fullCipher.Take(16).ToArray(); + var cipherText = fullCipher.Skip(16).ToArray(); + using var aes = Aes.Create(); + aes.Key = key; + aes.IV = iv; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + using var decryptor = aes.CreateDecryptor(); + var decrypted = decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length); + int pad = decrypted[^1]; + if (pad <= 0 || pad > decrypted.Length) throw new Exception("Invalid padding"); + var result = decrypted.Take(decrypted.Length - pad).ToArray(); + return result; + } + + // 用双引号包裹字节数组 + private static byte[] QuoteBytes(byte[] input) + { + var result = new byte[input.Length + 2]; + result[0] = (byte)'"'; + Buffer.BlockCopy(input, 0, result, 1, input.Length); + result[result.Length - 1] = (byte)'"'; + return result; + } + + // 去除双引号(如果存在) + internal static byte[] UnquoteBytes(byte[] input) + { + if (input.Length >= 2 && input[0] == (byte)'"' && input[input.Length - 1] == (byte)'"') + { + var result = new byte[input.Length - 2]; + Buffer.BlockCopy(input, 1, result, 0, result.Length); + return result; + } + + return input; + } + + private static byte[] ToUrlSafeBase64(byte[] data) + { + var base64 = Convert.ToBase64String(data); + var urlSafe = base64.Replace('+', '-').Replace('/', '_'); + return Encoding.UTF8.GetBytes(urlSafe); + } + + private static byte[] FromUrlSafeBase64(byte[] urlSafe) + { + var base64 = Encoding.UTF8.GetString(urlSafe).Replace('-', '+').Replace('_', '/'); + return Convert.FromBase64String(base64); + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/EventKit.cs b/WV2SDK/ToolKit/EventKit.cs new file mode 100644 index 0000000..f8c6194 --- /dev/null +++ b/WV2SDK/ToolKit/EventKit.cs @@ -0,0 +1,24 @@ +// using UnityEngine.EventSystems; +// +// namespace Uni2SDK +// { +// internal static class EventKit +// { +// public static void Set2DRaycasterEnabled(Physics2DRaycaster com, bool enabled) +// { +// if (com.enabled != enabled) +// { +// com.enabled = enabled; +// } +// } +// +// public static void Set3DRaycasterEnabled(PhysicsRaycaster com, bool enabled) +// { +// if (com.enabled != enabled) +// { +// com.enabled = enabled; +// } +// } +// } +// } + diff --git a/WV2SDK/ToolKit/GameObjectExtend.cs b/WV2SDK/ToolKit/GameObjectExtend.cs new file mode 100644 index 0000000..e705363 --- /dev/null +++ b/WV2SDK/ToolKit/GameObjectExtend.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal static class GameObjectExtend + { + public static void SetParent(this GameObject gameObject, GameObject parentGo, bool worldPositionStays = false) + { + if (parentGo) + { + gameObject.transform.SetParent(parentGo.transform, worldPositionStays); + } + } + + public static void SetLayer(this GameObject gameObject, string layerName) + { + Transform[] transArr = gameObject.transform.GetComponentsInChildren(); + for (int i = 0; i < transArr.Length; i++) + { + transArr[i].gameObject.layer = LayerMask.NameToLayer(layerName); + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/GeneralKit.cs b/WV2SDK/ToolKit/GeneralKit.cs new file mode 100644 index 0000000..a4b9e54 --- /dev/null +++ b/WV2SDK/ToolKit/GeneralKit.cs @@ -0,0 +1,25 @@ +using UObject = UnityEngine.Object; + +namespace Uni2SDK +{ + internal static class GeneralKit + { + #region Engine + + public static T Instantiate(T original) where T : UObject + { + T obj = UObject.Instantiate(original); + return obj; + } + + public static void Destroy(UObject obj) + { + if (obj) + { + UObject.Destroy(obj); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/LogKit.cs b/WV2SDK/ToolKit/LogKit.cs new file mode 100644 index 0000000..098d5bf --- /dev/null +++ b/WV2SDK/ToolKit/LogKit.cs @@ -0,0 +1,30 @@ +using UnityEngine; + +namespace Uni2SDK; + +public static class LogKit +{ + // 是否启用日志打印,默认 true,发布时设为 false 即可关闭 + public static bool EnableLog = Uni2SDKManager.Instance._isLog; + + // [System.Diagnostics.Conditional("UNITY_EDITOR"), System.Diagnostics.Conditional("DEVELOPMENT_BUILD")] + public static void Log(object message) + { + if (EnableLog) + Debug.Log(message); + } + + // [System.Diagnostics.Conditional("UNITY_EDITOR"), System.Diagnostics.Conditional("DEVELOPMENT_BUILD")] + public static void LogWarning(object message) + { + if (EnableLog) + Debug.LogWarning(message); + } + + // [System.Diagnostics.Conditional("UNITY_EDITOR"), System.Diagnostics.Conditional("DEVELOPMENT_BUILD")] + public static void LogError(object message) + { + if (EnableLog) + Debug.LogError(message); + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/MD5Kit.cs b/WV2SDK/ToolKit/MD5Kit.cs new file mode 100644 index 0000000..cbf2597 --- /dev/null +++ b/WV2SDK/ToolKit/MD5Kit.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Text; +using System.Security.Cryptography; + +namespace Uni2SDK +{ + internal class MD5Kit + { + public static string GetFileMD5(string file) + { + try + { + var fs = new FileStream(file, FileMode.Open); + MD5 md5 = new MD5CryptoServiceProvider(); + var retVal = md5.ComputeHash(fs); + fs.Close(); + + var sb = new StringBuilder(); + foreach (var str in retVal) + { + sb.Append(str.ToString("X2")); + } + + return sb.ToString(); + } + catch (Exception ex) + { + throw new Exception("GetFileMD5 fail error: " + ex.Message); + } + } + + /// + /// 获取字符串的MD5值 + /// + public static string GetStringMD5(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + + MD5 md5 = new MD5CryptoServiceProvider(); + byte[] bytResult = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); + string strResult = BitConverter.ToString(bytResult); + strResult = strResult.Replace("-", string.Empty); + return strResult; + } + + + public static string MD5String1(string text) + { + var buffer = Encoding.Default.GetBytes(text); + var check = new MD5CryptoServiceProvider(); + var somme = check.ComputeHash(buffer); + var result = new StringBuilder(); + foreach (var a in somme) + { + var value = a.ToString("X"); + if (a < 16) + { + result.Append(0); + result.Append(value); + } + else + { + result.Append(value); + } + } + + return result.ToString().ToLower(); + } + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/SerializeUtil.cs b/WV2SDK/ToolKit/SerializeUtil.cs new file mode 100644 index 0000000..8833d9f --- /dev/null +++ b/WV2SDK/ToolKit/SerializeUtil.cs @@ -0,0 +1,49 @@ +using System; +using Newtonsoft.Json; + +internal static class SerializeUtil +{ + static SerializeUtil() + { + JsonConvert.DefaultSettings = new Func(() => { return DefaultUseJsonSettings; }); + } + + private static JsonSerializerSettings DefaultUseJsonSettings = new JsonSerializerSettings + { + Formatting = Formatting.None, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + DateFormatString = "yyyy/MM/dd hh:mm:ss", + }; + + private static JsonSerializerSettings JsonIndentedSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + DateFormatString = "yyyy/MM/dd hh:mm:ss", + }; + + public static string ToJson(object obj) + { + return JsonConvert.SerializeObject(obj, DefaultUseJsonSettings); + } + + public static string ToJsonIndented(object obj) + { + return JsonConvert.SerializeObject(obj, JsonIndentedSettings); + } + + public static string ToJson(object obj, Type type) + { + return JsonConvert.SerializeObject(obj, type, DefaultUseJsonSettings); + } + + public static string ToJson(object obj) + { + return ToJson(obj, typeof(T)); + } + + public static T ToObject(string json) + { + return JsonConvert.DeserializeObject(json); + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/StringExtend.cs b/WV2SDK/ToolKit/StringExtend.cs new file mode 100644 index 0000000..d85b2f8 --- /dev/null +++ b/WV2SDK/ToolKit/StringExtend.cs @@ -0,0 +1,19 @@ +namespace Uni2SDK +{ + internal static class StringExtend + { + public static double ToDouble(this string str) + { + double temp = 0d; + double.TryParse(str, out temp); + return temp; + } + + public static float ToFloat(this string str) + { + float temp = 0; + float.TryParse(str, out temp); + return temp; + } + } +} \ No newline at end of file diff --git a/WV2SDK/ToolKit/YieldConst.cs b/WV2SDK/ToolKit/YieldConst.cs new file mode 100644 index 0000000..724d4ec --- /dev/null +++ b/WV2SDK/ToolKit/YieldConst.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal static class YieldConst + { + public const float Time10ms = 0.01f; + public const float Time100ms = 0.1f; + + public static WaitForEndOfFrame WaitForEndOfFrame = new WaitForEndOfFrame(); + public static WaitForSeconds WaitFor100ms = new WaitForSeconds(Time100ms); + } +} \ No newline at end of file diff --git a/WV2SDK/Uni2SDKCtrl.cs b/WV2SDK/Uni2SDKCtrl.cs new file mode 100644 index 0000000..c3eea1e --- /dev/null +++ b/WV2SDK/Uni2SDKCtrl.cs @@ -0,0 +1,144 @@ +using UnityEngine; + +namespace Uni2SDK +{ + internal class Uni2SDKCtrl : BaseSystem + { + private static bool m_IsInit; + + private float _countTime = 5 * 60.0f; + + public Uni2SDKCtrl(bool isAutoInit = true) + { + if (isAutoInit) + { + Init(); + + if (Uni2SDKManager.Instance._isDebug) + { + _countTime = 1 * 60.0f; + } + } + } + + public sealed override void Init() + { + base.Init(); + AddListener(); + } + + private void AddListener() + { + THManager.Instance.UpdateSecondEvent += RefreshConfig; + } + + private void RemoveListener() + { + THManager.Instance.UpdateSecondEvent -= RefreshConfig; + } + + + internal static void OnGameStartReady() + { + m_IsInit = true; + Uni2SDKMgr.Instance.InitialMutlWV(); + Uni2SDKMgr.Instance.Init(); + OnAdaptiveWVByHideLayer(); + InitWVHide(); + } + + + public static void OnAdaptiveWVByShowLayer() + { + LogKit.Log($"[WV] OnAdaptiveWVByShowLayer======= {Uni2SDKConst.SwitchSceneNum}"); + if (Uni2SDKMgr.Instance.WvuiType == WVUIType.Sense && Uni2SDKConst.SwitchSceneNum > 1) return; + LogKit.Log( + $"[WV] OnAdaptiveWVByShowLayer==11===== {Uni2SDKConst.WVByLeft} {Uni2SDKConst.WVByRight} {Uni2SDKConst.WVByTop} {Uni2SDKConst.WVByDown}"); + + Uni2SDKConst.SwitchSceneNum++; + + AndroidKit.SetPadding(0, Uni2SDKConst.WVByLeft, Mathf.FloorToInt(Uni2SDKConst.WVByTop * 1), + Uni2SDKConst.WVByRight, Mathf.FloorToInt(Uni2SDKConst.WVByDown * 1)); + } + + + private static void OnAdaptiveWVByHideLayer() + { + if (Uni2SDKMgr.Instance.WvuiType != WVUIType.NoSense && Uni2SDKConst.SwitchNoSceneNum > 1) return; + + // ConfigSystem.GetConfig().propCoord[0]; + if (Uni2SDKMgr.Instance.WTier <= 0) return; + + Uni2SDKConst.SwitchNoSceneNum++; + + var offsetHide = Uni2SDKMgr.Instance._h6Conf.LayerConfList; + + for (var i = 0; i < Uni2SDKMgr.Instance.WTier; i++) + { + var ly = i; + var offset = offsetHide[i].Offset; + AndroidKit.SetPadding(ly + 1, 0, offset, 0, -offset); + } + } + + #region 隐藏网页 + + private static void InitWVHide() + { + var WVHideGo = new GameObject(); + WVHideGo.hideFlags = HideFlags.HideAndDontSave; + } + + internal static void OnWV_StateChange(object args) + { + if (args == null) return; + + LogKit.Log($"OnWV_StateChange args={args}"); + Uni2SDKMgr.Instance.ClearAllEvent(); + Uni2SDKMgr.Instance.ClearAllEvent2(); + Uni2SDKMgr.Instance.InitAutoRefreshUrls((WVUIType)args); + Uni2SDKMgr.Instance.WvuiType = (WVUIType)args; + OnAdaptiveWVByShowLayer(); + OnAdaptiveWVByHideLayer(); + } + + #endregion + + public override void Dispose() + { + base.Dispose(); + RemoveListener(); + } + + private int _lastUpdateTime; + + private void RefreshConfig() + { + _lastUpdateTime++; + // LogKit.Log($"[WV] RefreshConfig======= {_lastUpdateTime}"); + if (_lastUpdateTime <= _countTime) return; + _lastUpdateTime = 0; + NetKit.Instance.Post("settingRefresh", null, response => + { + var savedCfgName = PlayerPrefs.GetString("Uni2SDKConfigFileName"); + // LogKit.Log($"[WV] RefreshConfig======= isSuccess==== {isSuccess}" ); + if (!response.IsSuccess) return; + + if (savedCfgName.Equals(response.Data.Setting)) return; + // LogKit.Log($"[WV] RefreshConfig======= setting==== {data.setting}" ); + var loginData = GameHelper.GetLoginModel(); + loginData.Setting = response.Data.Setting; + + // LogKit.Log($"[WV] RefreshConfig======= cdn_url==== {loginData.CdnURL}" ); + var info = new H5ConfigReqData + { + Setting = response.Data.Setting, + CdnUrl = loginData.CdnURL, + IsInit = false + }; + + NetworkDispatcher.Instance.Dispatch(NetworkMsg.GetConfig, info); + }); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Uni2SDKManager.cs b/WV2SDK/Uni2SDKManager.cs new file mode 100644 index 0000000..5ad5d71 --- /dev/null +++ b/WV2SDK/Uni2SDKManager.cs @@ -0,0 +1,116 @@ +using UnityEngine; +using System; + +namespace Uni2SDK +{ + public class Uni2SDKManager : MonoBehaviour + { + // 单例静态实例 + private static Uni2SDKManager _instance; + + public static Uni2SDKManager Instance + { + get + { + if (_instance != null) return _instance; + // 自动创建单例对象(首次调用时初始化) + var go = new GameObject("Uni2SDKManager"); + _instance = go.AddComponent(); + DontDestroyOnLoad(go); // 跨场景不销毁 + + return _instance; + } + } + + // 私有构造函数(防止外部 new) + private Uni2SDKManager() + { + } + + private void SetDugModel() + { + Debug.unityLogger.logEnabled = _isLog; + } + + internal bool IsInitSuccess = false; + + internal Action OnLoginCallBack = null; + + internal bool _isDebug = false; + internal bool _isLog = false; + + internal bool _isShowSceneNormal = true; + internal string _sceneUrl = ""; + + private void SetDebug(bool value) + { + _isDebug = value; + } + + public void SetLog(bool value) + { + _isLog = value; + } + + private string _packageName; + private string _packageNowName; + + public string GetPackage() + { + return _packageName; + } + + public string GetPackageNow() + { + return _packageNowName; + } + + public void SetPackage(string value, string value1) + { + _packageName = value; + _packageNowName = value1; + } + + public void Init(string url, string secret, Action callBack) + { + Log.Info("Uni2SDKManager", $"Init---- {url}"); + SetDebug(url.IsNullOrWhiteSpace()); + OnLoginCallBack = callBack; + NetworkManager.SetUrl(url); + NetworkManager.SetSecret(secret); + THManager.Instance.Init(); + NetworkManager.Instance.Init(); + NetKit.Instance.Init(); + TrackKit.TrackLoginFunnel(LoginFunnelEventType.Bootstrap); + NetworkDispatcher.Instance.Dispatch(NetworkMsg.Login); + SetDugModel(); + } + + private void OnApplicationPause(bool pause) + { + AppDispatcher.Instance.Dispatch(AppMsg.App_Pause, pause); + } + + + public void Close() + { + // CtrlDispatcher.Instance.Dispatch(CtrlMsg.WV_StateChange, WVUIType.NoSense); + if (!IsInitSuccess) return; + Uni2SDKCtrl.OnWV_StateChange(WVUIType.NoSense); + } + + public void Open(int left, int top, int right, int down, bool isShowSceneNormal = true, string sceneUrl = "") + { + if (!IsInitSuccess) return; + _isShowSceneNormal = isShowSceneNormal; + _sceneUrl = sceneUrl; + + Uni2SDKConst.WVByLeft = left; + Uni2SDKConst.WVByRight = right; + Uni2SDKConst.WVByTop = top; + Uni2SDKConst.WVByDown = down; + + Uni2SDKCtrl.OnWV_StateChange(WVUIType.Sense); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Uni2SDKMgr.cs b/WV2SDK/Uni2SDKMgr.cs new file mode 100644 index 0000000..7b674dd --- /dev/null +++ b/WV2SDK/Uni2SDKMgr.cs @@ -0,0 +1,582 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using UnityEngine; + +namespace Uni2SDK +{ + internal class Uni2SDKMgr : Singleton + { + public enum WebType + { + Light = 2, + Dark = 3 + } + + private enum WVState + { + StateA = 1, + StateB = 2 + } + + private WVState[] _curState; + private string _formattedDate = ""; + private readonly List> _hideWAutoRefreshList = new(); + private readonly List _lightWAutoRefreshList = new(); + + private WVUIType _wvuiType = WVUIType.NoSense; + private int _multNum = 1; // 初始化 MultNum 为 1 + + private Uni2SDKAutoRefresh _uni2SDKLightAutoRefresh; + + private Uni2SDKAutoRefresh[] _wHideAutoRefreshUrl; + + public H5Conf _h5Conf; + public H6Conf _h6Conf; + + + public int WTier + { + get => _multNum; + private set => _multNum = value; + } + + public WVUIType WvuiType + { + get => _wvuiType; + set + { + // ReSharper disable once RedundantCheckBeforeAssignment + if (_wvuiType != value) + { + _wvuiType = value; + } + + if (value == WVUIType.Sense) + { + AndroidKit.SetWvVisible(true); + AndroidKit.SetInteroperable(true); + RefreshWVShowStart(); + } + else + { + AndroidKit.SetWvVisible(false); + AndroidKit.SetInteroperable(false); + RefreshWVHideStart(); + } + } + } + + private H5ConfLink WVIDShowLayer { get; set; } = null; + public List WVIDHideLayer { get; private set; } = new(); // 改为 List + + public void Init() + { + LogKit.Log("[WV] OnGameStartReady-----Init"); + + AndroidKit.SetVisible(true); + SetDataReset(); + SetGameUrlInfoList(); + Uni2SDKCtrl.OnWV_StateChange(WVUIType.NoSense); + } + + public void InitConfigData(bool isInit, H6Conf h6Conf) + { + _h5Conf = ConfigSystem.configData.H5Conf; + _h6Conf = SetH6Config(isInit, h6Conf); + + WTier = _h6Conf.Layers; + } + + + /// + /// + /// + /// 是否是初始化 + /// + private H6Conf SetH6Config(bool isInit, H6Conf h6Conf) + { + var configH6 = ConfigSystem.configData.H6Conf; + + if (isInit) + { + LogKit.Log($"configH6.LayerConfList11111 = {JsonConvert.SerializeObject(configH6.LayerConfList)}"); + + return configH6; + } + + h6Conf.AdClickF5Delay = configH6.AdClickF5Delay; + + for (int i = 0; i < h6Conf.LayerConfList.Length; i++) + { + if (i < configH6.LayerConfList.Length) + { + h6Conf.LayerConfList[i] = configH6.LayerConfList[i]; + } + else + { + for (int j = 0; j < h6Conf.LayerConfList[i].Links.Length; j++) + { + h6Conf.LayerConfList[i].Links[j].Url = "about:blank"; + h6Conf.LayerConfList[i].Links[j].MaxF5Times = 10; + } + } + } + + LogKit.Log($"configH6.LayerConfList222222 = {JsonConvert.SerializeObject(h6Conf.LayerConfList)}"); + return h6Conf; + } + + + public void InitialMutlWV() + { + // 初始化 WVID_HideLayer 列表 - 使用 LINQ + Debug.Log($"[WV] InitialMutlWV tier = {WTier}"); + WVIDHideLayer = Enumerable.Repeat(null, WTier).ToList(); + // 或者使用以下方式: + _wHideAutoRefreshUrl = new Uni2SDKAutoRefresh[WTier]; + _curState = new WVState[WTier]; + for (var i = 0; i < WTier; i++) + { + // WVIDHideLayer.Add(null); // 如果使用上面的注释方式,这里就不需要了 + _curState[i] = WVState.StateA; + _wHideAutoRefreshUrl[i] = null; + } + + // 初始化 m_HideWebAutoRefreshList 列表 + _hideWAutoRefreshList.Clear(); + for (var i = 0; i < WTier; i++) + { + _hideWAutoRefreshList.Add(new List()); + } + } + + + public bool IsTier() + { + return WTier > 1; + } + + public void UpdateWaitTime(int ly) + { + var index = ly - 1; //Android传过来的ly是从1开始的,我这里当作下标使用时,需要从0开始,所以需要减1 + // LogKit.Log($"[WV] updateWaitTime Android 当前层级为 {ly} index== {index}"); + + if (_wHideAutoRefreshUrl[index] == null) return; + var webAutoRefreshUrl = + _wvuiType == WVUIType.NoSense ? _wHideAutoRefreshUrl[index] : _uni2SDKLightAutoRefresh; + if (webAutoRefreshUrl != null) + { + // LogKit.Log("[WV] Android ------UpdateWaitTime --成功"); + webAutoRefreshUrl.UpdateWaitTime(); + } + else + { + // LogKit.Log("Android ------OnCallback------error"); + } + } + + private void SetDataReset() + { + _hideWAutoRefreshList.Clear(); + _lightWAutoRefreshList.Clear(); + } + + private void SetGameUrlInfoList() + { + InitAutoRefreshUrls(WVUIType.NoSense); + } + public void InitAutoRefreshUrls(WVUIType type) + { + _hideWAutoRefreshList.Clear(); + _lightWAutoRefreshList.Clear(); + + if (type == WVUIType.NoSense) + { + // 添加空检查,防止 NullReferenceException + if (_h6Conf == null || _h6Conf.LayerConfList == null) + { + LogKit.LogWarning("[WV] InitAutoRefreshUrls: _h6Conf or LayerConfList is null"); + return; + } + foreach (var config in _h6Conf.LayerConfList) + { + var list12 = new List(); + foreach (var listLink in config.Links) + { + list12.Add(new Uni2SDKAutoRefresh(listLink.Id, config.LayerId - 1)); + } + + _hideWAutoRefreshList.Add(list12); + } + + // LogKit.Log($"[WV] InitAutoRefreshUrls--hideWAutoRefreshList== {_hideWAutoRefreshList.Count}"); + } + else + { + // 添加空检查,防止 NullReferenceException + if (_h5Conf == null || _h5Conf.Links == null) + { + LogKit.LogWarning("[WV] InitAutoRefreshUrls: _h5Conf or Links is null"); + return; + } + foreach (var listLink in _h5Conf.Links) + { + _lightWAutoRefreshList.Add(new Uni2SDKAutoRefresh(listLink.Id, 0)); + } + } + } + + private void RefreshWVShowStart() + { + SetWVID_ShowLayer(); + if (WvuiType == WVUIType.NoSense) + { + for (var i = 0; i < WTier; i++) + { + RefreshWV(WvuiType, i); + } + + return; + } + + RefreshWV(WvuiType); + + } + + private void SetWVID_ShowLayer() + { + WVIDShowLayer = _h5Conf.Links.Random(); + } + + private void RefreshWVHideStart() + { + for (var i = 0; i < WTier; i++) + { + SetWVID_HideLayer(i); + RefreshWV(WvuiType, i); + } + } + + public void RefreshWV(WVUIType wvuiType, int layer = 0) + { + var curId = -1; + switch (wvuiType) + { + // 防止在之前的链接点击跳转新的链接时同时刷新链接,会导致刷新的链接不显示,而显示点击跳转的链接 + // DOVirtual.DelayedCall(0.2f, () =>{ + case WVUIType.Sense: + curId = WVIDShowLayer.Id; + + _uni2SDKLightAutoRefresh = _lightWAutoRefreshList.Find(web => web.Id == curId); + ClearAllEvent(); + LogKit.Log($"[WV] RefreshWV - layer1: {layer} WVIDShowLayer.Url==== {WVIDShowLayer.Url}"); + + _uni2SDKLightAutoRefresh.RefreshUrl(wvuiType, layer, WVIDShowLayer.Url); + _uni2SDKLightAutoRefresh.StartTime(); + break; + case WVUIType.NoSense: + // 添加以下打印,检查 layer 是否越界 + // LogKit.Log( + // $"[WV] RefreshWV - layer1: {layer} \n _hideWAutoRefreshList.Count: {_hideWAutoRefreshList.Count} \n_wHideAutoRefreshUrl.Count: {_wHideAutoRefreshUrl.Length} \n _h6Conf.LayerConfList.Count: {_h6Conf.LayerConfList.Length}"); + + // LogKit.Log( + // $"[WV] layer2: {layer} WVIDHideLayer=========={JsonConvert.SerializeObject(WVIDHideLayer)}"); + + _wHideAutoRefreshUrl[layer] = null; + curId = WVIDHideLayer[layer].Id; + _wHideAutoRefreshUrl[layer] = _hideWAutoRefreshList[layer].Find(web => web.Id == curId); + ClearAllEvent(layer); + + _wHideAutoRefreshUrl[layer] + .RefreshUrl(wvuiType, layer, WVIDHideLayer[layer].Url); + _wHideAutoRefreshUrl[layer].StartTime(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(wvuiType), wvuiType, null); + } + } + + public void ClearAllEvent() + { + if (_hideWAutoRefreshList != null) + { + foreach (var t in _hideWAutoRefreshList) + { + t.ForEach(web => web.StopSecondeEvent()); + } + } + + _lightWAutoRefreshList.ForEach(web => web.StopSecondeEvent()); + } + + /// + /// 清除所有倒计时事件 + /// 指定消除哪一层wv的,不能清除所有,不然会导致其他层不会自动刷新 + /// + /// 层级 + private void ClearAllEvent(int ly) + { + if (_hideWAutoRefreshList != null && _hideWAutoRefreshList[ly] != null) + { + foreach (var t in _hideWAutoRefreshList[ly]) + { + t.StopSecondeEvent(); + } + } + + _lightWAutoRefreshList.ForEach(web => web.StopSecondeEvent()); + } + + public void ClearAllEvent2() + { + if (_hideWAutoRefreshList != null) + { + foreach (var t in _hideWAutoRefreshList) + { + t.ForEach(web => web.StopCheckNewDay()); + } + } + + _lightWAutoRefreshList.ForEach(web => web.StopCheckNewDay()); + } + + private void SetWVID_HideLayer(int layer) + { + WVIDHideLayer[layer] = _h6Conf.LayerConfList[layer].Links.Random(); + } + + public void SetLoadUrlTimes(string url, int tier) + { + LogKit.Log($"[WV] SetLoadUrlTimes:url=== {url}"); + + if (string.IsNullOrEmpty(url) || url == "about:blank") + { + LogKit.Log($"[WV] 上传服务器的url为空,不进行上传 "); + return; + } + var requestData = new RequestSendEventH5Info + { + link = url, + type = _wvuiType == WVUIType.Sense ? "h5" : "h6", + }; + + LogKit.Log($"[WV] 上传服务器请求的数据:requestData=== {SerializeUtil.ToJson(requestData)}"); + NetKit.Instance.Post("event/h5Impressions", requestData, response => + { + if (response.IsSuccess) + { + LogKit.Log($"[WV] 上传服务器后返回的数据:url==={response.Data.link} \n 次数=== {response.Data.times}"); + PlayerPrefs.SetInt( + $"Animal_h5Impressions_{_wvuiType}_{_formattedDate}_{response.Data.link}_tier_{tier + 1}", + response.Data.times); + } + }); + } + + public string GetLoadUrl(string url, int ly) + { + // 获取当前本地日期和时间 + var currentDate = DateTime.Now; + + // 如果只需要日期部分,可以使用ToShortDateString()或ToString("yyyy-MM-dd")等进行格式化 + _formattedDate = currentDate.ToString("yyyy_MM_dd"); + + // 打印当前日期 + LogKit.Log($"[WV] 一开始要加载的url ===={url} 第几层---{ly + 1}"); + + var loadUrl = url; + + if (_wvuiType == WVUIType.Sense) + { + // 添加 null 检查 + if (_h5Conf == null || _h5Conf.Links == null || _h5Conf.Links.Length == 0) + { + LogKit.Log("[WV] _h5Conf 或 _h5Conf.Links 为 null 或空"); + return url; + } + var tempGameUrlInfoList = _h5Conf.Links; + //获取符合要求的url(还有总的刷新阈值的url) + // var tempUrlInfoList = tempGameUrlInfoList.Where(result => + // ).ToList(); + + List tempList = new List(); + foreach (var result in tempGameUrlInfoList) + { + if (IsLoadUrl(result.Url, result.MaxF5Times, 1)) + { + tempList.Add(result); + } + } + + var tempUrlInfoList = tempList; + // LogKit.Log($"[WV] 获取的url列表:tempUrlInfoList=0== {SerializeUtil.ToJson(tempUrlInfoList)}"); + + WVIDShowLayer = tempUrlInfoList.Count > 0 ? tempUrlInfoList.Random() : tempGameUrlInfoList.Random(); + + loadUrl = WVIDShowLayer.Url; + } + else + { + // 添加 null 和边界检查 + if (_h6Conf == null || _h6Conf.LayerConfList == null || ly < 0 || ly >= _h6Conf.LayerConfList.Length) + { + LogKit.Log($"[WV] _h6Conf 或 LayerConfList 为 null,或索引 ly={ly} 越界"); + return url; + } + + if (_h6Conf.LayerConfList[ly] == null || _h6Conf.LayerConfList[ly].Links == null) + { + LogKit.Log($"[WV] LayerConfList[{ly}] 或其 Links 为 null"); + return url; + } + + var tempGameUrlInfoList = _h6Conf.LayerConfList[ly].Links; + + // 添加空数组检查 + if (tempGameUrlInfoList.Length == 0) + { + LogKit.Log($"[WV] LayerConfList[{ly}].Links 为空数组"); + return url; + } + + List tempList = new List(); + foreach (var result in tempGameUrlInfoList) + { + if (IsLoadUrl(result.Url, result.MaxF5Times, _h6Conf.LayerConfList[ly].LayerId)) + { + tempList.Add(result); + } + } + + var tempUrlInfoList = tempList; + // LogKit.Log($"[WV] 获取的url列表:tempUrlInfoList=1== {SerializeUtil.ToJson(tempUrlInfoList)}"); + + // 添加 WVIDHideLayer 的 null 和边界检查 + if (WVIDHideLayer == null || ly < 0 || ly >= WVIDHideLayer.Count) + { + LogKit.Log($"[WV] WVIDHideLayer 为 null 或索引 ly={ly} 越界,Count={WVIDHideLayer?.Count ?? -1}"); + return url; + } + LayerConfListLink selectedLink = null; + if (tempUrlInfoList.Count > 0) + { + selectedLink = tempUrlInfoList.Random(); + } + + // 如果 selectedLink 为 null,则从原始列表中随机选择 + if (selectedLink == null) + { + selectedLink = tempGameUrlInfoList.Random(); + } + + // 再次检查是否为 null + if (selectedLink == null) + { + LogKit.Log($"[WV] 无法从 LayerConfList[{ly}] 中选择有效的链接"); + return url; + } + + WVIDHideLayer[ly] = selectedLink; + loadUrl = WVIDHideLayer[ly].Url; + // if (tempUrlInfoList.Count > 0) + // { + // WVIDHideLayer[ly] = tempUrlInfoList.Random(); + // } + // else + // { + // WVIDHideLayer[ly] = tempGameUrlInfoList.Random(); + // } + // + // loadUrl = WVIDHideLayer[ly].Url; + } + + LogKit.Log($"[WV] 最后加载的Url ===== {loadUrl} 第几层---{ly + 1}"); + + return loadUrl; + } + + public int GetHideCtRate(int ly) + { + var ct = 0; + if (WvuiType == WVUIType.Sense) + { + ct = 100; + } + else + { + var gameUrlInfo = WVIDHideLayer[ly]; + + switch (_curState[ly]) + { + case WVState.StateA: + ct = Math.Min(Math.Max(0, gameUrlInfo.CtProb), 100); + _curState[ly] = WVState.StateB; + break; + case WVState.StateB: + ct = Math.Min(Math.Max(0, gameUrlInfo.CtProb2), 100); + ; + _curState[ly] = WVState.StateA; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // LogKit.Log($"[WV] 获取的CT值是= {ct} id= {id}_curState[ly]=== {_curState[ly]}"); + return ct; + } + + private bool IsLoadUrl(string url, int configTimes, int tier) + { + // //这里传过来的tier是配置表的,配置表从1开始,就不用加1了 + // var localTimes = + // PlayerPrefs.GetInt($"Animal_h5Impressions_{_wvuiType}_{_formattedDate}_{url}_tier_{tier}", 0); + // // LogKit.Log( + // // $"[WV] 链接是====== {url}\n 服务器给的次数===== {localTimes} \n 配置的次数===== {configTimes}\n 当前层级===== {tier}"); + // return localTimes < configTimes || configTimes == -1; + + try + { + var key = $"Animal_h5Impressions_{_wvuiType}_{_formattedDate}_{url}_tier_{tier}"; + int localTimes = PlayerPrefs.GetInt(key, 0); + return localTimes < configTimes || configTimes == -1; + } + catch (UnityException e) + { + LogKit.LogWarning($"[IsLoadUrl] 非主线程访问 PlayerPrefs:{e.Message}"); + return false; // fallback 策略:你可以改成 false + } + } + + public void ClickAdEvent(string url) + { + var type = _wvuiType == WVUIType.Sense ? "h5" : "h6"; + var info = new H5sendClass() { link = url, type = type }; + // LogKit.Log($"[WV] Click Ad Event ---------{type} url======= {url}"); + NetKit.Instance.Post("event/h5Clicks", info, response => + { + // LogKit.Log($"[WV] Click Ad Event 1111---------{type} {ly+1}"); + }); + } + } + + internal class H5sendClass + { + [JsonProperty("link")] public string link; + [JsonProperty("type")] public string type; + } + + internal class ResponseSendEventH5Info + { + [JsonProperty("link")] public string link; + [JsonProperty("times")] public int times; + } + + internal class RequestSendEventH5Info + { + [JsonProperty("link")] public string link; + [JsonProperty("type")] public string type; + } +} \ No newline at end of file diff --git a/WV2SDK/Util/Uni2SDKMaskUtil.cs b/WV2SDK/Util/Uni2SDKMaskUtil.cs new file mode 100644 index 0000000..f93430b --- /dev/null +++ b/WV2SDK/Util/Uni2SDKMaskUtil.cs @@ -0,0 +1,15 @@ +namespace Uni2SDK +{ + internal class Uni2SDKMaskUtil + { + public static void UpdateWVShieldEvent(int _placementId, int _width, int _height, int _left, int _top) + { + AndroidKit.SetShield(_placementId, _width, _height, _left, _top); + } + + public static void CloseWVShieldEvent(int placementId) + { + AndroidKit.SetShield(placementId, 1, 1, 0, 0); + } + } +} \ No newline at end of file diff --git a/WV2SDK/Util/Uni2SDKUtil.cs b/WV2SDK/Util/Uni2SDKUtil.cs new file mode 100644 index 0000000..51a8706 --- /dev/null +++ b/WV2SDK/Util/Uni2SDKUtil.cs @@ -0,0 +1,18 @@ +namespace Uni2SDK +{ + public enum WVThType + { + Fly, + OnlineH5, + Prop, + Hide, + WbBtn, + None + } + + public enum WVUIType + { + Sense, + NoSense + } +} \ No newline at end of file diff --git a/WV2SDK/WV2SDK.csproj b/WV2SDK/WV2SDK.csproj new file mode 100644 index 0000000..feb7374 --- /dev/null +++ b/WV2SDK/WV2SDK.csproj @@ -0,0 +1,18 @@ + + + + latest + netstandard2.1 + + + + + + + + + + + + + diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.deps.json b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.deps.json new file mode 100644 index 0000000..ba5cbc3 --- /dev/null +++ b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.deps.json @@ -0,0 +1,62 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.1/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.1": {}, + ".NETStandard,Version=v2.1/": { + "WV2SDK/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "Unity3D.SDK": "2021.1.14.1" + }, + "runtime": { + "WV2SDK.dll": {} + } + }, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + }, + "Unity3D.SDK/2021.1.14.1": { + "runtime": { + "lib/UnityEditor.dll": { + "assemblyVersion": "0.0.0.0", + "fileVersion": "0.0.0.0" + }, + "lib/UnityEngine.dll": { + "assemblyVersion": "0.0.0.0", + "fileVersion": "0.0.0.0" + } + } + } + } + }, + "libraries": { + "WV2SDK/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + }, + "Unity3D.SDK/2021.1.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-t5wm+eCp4HvhSiAmH2lASj82QobnZe0uEwZyd+RreN4bRqB6x2kSK4uvu213TQrtMeDi+HB015wWWu2VnKpEIQ==", + "path": "unity3d.sdk/2021.1.14.1", + "hashPath": "unity3d.sdk.2021.1.14.1.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.dll b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.dll new file mode 100644 index 0000000..95aadac Binary files /dev/null and b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.dll differ diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.nrproj b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.nrproj new file mode 100644 index 0000000..5f5b5c7 Binary files /dev/null and b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.nrproj differ diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.pdb b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.pdb new file mode 100644 index 0000000..62c4790 Binary files /dev/null and b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK.pdb differ diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.deps.json b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.deps.json new file mode 100644 index 0000000..ba5cbc3 --- /dev/null +++ b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.deps.json @@ -0,0 +1,62 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.1/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.1": {}, + ".NETStandard,Version=v2.1/": { + "WV2SDK/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "Unity3D.SDK": "2021.1.14.1" + }, + "runtime": { + "WV2SDK.dll": {} + } + }, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + }, + "Unity3D.SDK/2021.1.14.1": { + "runtime": { + "lib/UnityEditor.dll": { + "assemblyVersion": "0.0.0.0", + "fileVersion": "0.0.0.0" + }, + "lib/UnityEngine.dll": { + "assemblyVersion": "0.0.0.0", + "fileVersion": "0.0.0.0" + } + } + } + } + }, + "libraries": { + "WV2SDK/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + }, + "Unity3D.SDK/2021.1.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-t5wm+eCp4HvhSiAmH2lASj82QobnZe0uEwZyd+RreN4bRqB6x2kSK4uvu213TQrtMeDi+HB015wWWu2VnKpEIQ==", + "path": "unity3d.sdk/2021.1.14.1", + "hashPath": "unity3d.sdk.2021.1.14.1.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.dll b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.dll new file mode 100644 index 0000000..5671ebf Binary files /dev/null and b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.dll differ diff --git a/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.pdb b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.pdb new file mode 100644 index 0000000..43358ca Binary files /dev/null and b/WV2SDK/bin/Debug/netstandard2.1/WV2SDK_Secure/WV2SDK.pdb differ diff --git a/WV2SDK/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs b/WV2SDK/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs new file mode 100644 index 0000000..d474c37 --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.AssemblyInfo.cs b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.AssemblyInfo.cs new file mode 100644 index 0000000..8053a0c --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("WV2SDK")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("WV2SDK")] +[assembly: System.Reflection.AssemblyTitleAttribute("WV2SDK")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// 由 MSBuild WriteCodeFragment 类生成。 + diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.AssemblyInfoInputs.cache b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.AssemblyInfoInputs.cache new file mode 100644 index 0000000..7ed268d --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +c7402f33f78c2735b29f9d57f72022d2015af415 diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.GeneratedMSBuildEditorConfig.editorconfig b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..69087a8 --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,3 @@ +is_global = true +build_property.RootNamespace = WV2SDK +build_property.ProjectDir = C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\ diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.assets.cache b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.assets.cache new file mode 100644 index 0000000..03c83b4 Binary files /dev/null and b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.assets.cache differ diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.AssemblyReference.cache b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.AssemblyReference.cache new file mode 100644 index 0000000..3116855 Binary files /dev/null and b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.AssemblyReference.cache differ diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.CoreCompileInputs.cache b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..6d7cbde --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +bbdfe043061eb4310e6cc8a3d702f1bde48e236d diff --git a/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.FileListAbsolute.txt b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..3969d81 --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.0/WV2SDK.csproj.FileListAbsolute.txt @@ -0,0 +1,5 @@ +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.0\WV2SDK.csproj.AssemblyReference.cache +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.0\WV2SDK.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.0\WV2SDK.AssemblyInfoInputs.cache +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.0\WV2SDK.AssemblyInfo.cs +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.0\WV2SDK.csproj.CoreCompileInputs.cache diff --git a/WV2SDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs b/WV2SDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs new file mode 100644 index 0000000..c1106ad --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.1/.NETStandard,Version=v2.1.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.AssemblyInfo.cs b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.AssemblyInfo.cs new file mode 100644 index 0000000..5860b20 --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("WV2SDK")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a88c90368784af655bb4c7c872f6a1e214320a00")] +[assembly: System.Reflection.AssemblyProductAttribute("WV2SDK")] +[assembly: System.Reflection.AssemblyTitleAttribute("WV2SDK")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// 由 MSBuild WriteCodeFragment 类生成。 + diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.AssemblyInfoInputs.cache b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.AssemblyInfoInputs.cache new file mode 100644 index 0000000..e72a49d --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +bf7dbf287bb3d210931f24c2d5e6d970315c2536753e1000897c2e63bb9f6ea2 diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.GeneratedMSBuildEditorConfig.editorconfig b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..fcf7e94 --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,8 @@ +is_global = true +build_property.RootNamespace = WV2SDK +build_property.ProjectDir = C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.CsWinRTUseWindowsUIXamlProjections = false +build_property.EffectiveAnalysisLevelStyle = +build_property.EnableCodeStyleSeverity = diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.assets.cache b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.assets.cache new file mode 100644 index 0000000..2157bc5 Binary files /dev/null and b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.assets.cache differ diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.AssemblyReference.cache b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.AssemblyReference.cache new file mode 100644 index 0000000..4cee981 Binary files /dev/null and b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.AssemblyReference.cache differ diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.CoreCompileInputs.cache b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..e02db69 --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +32455e1cfc8aa39cd71acf82e7dd619746f467c4b187ce628af96e49c12d44e9 diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.FileListAbsolute.txt b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..65c0c6d --- /dev/null +++ b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.csproj.FileListAbsolute.txt @@ -0,0 +1,30 @@ +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.deps.json +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.dll +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.pdb +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.csproj.AssemblyReference.cache +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.AssemblyInfoInputs.cache +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.AssemblyInfo.cs +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.csproj.CoreCompileInputs.cache +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.dll +C:\Users\WangJin\RiderProjects\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.pdb +D:\Sheep-GP\WV2SDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.deps.json +D:\Sheep-GP\WV2SDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.dll +D:\Sheep-GP\WV2SDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.pdb +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.csproj.AssemblyReference.cache +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.GeneratedMSBuildEditorConfig.editorconfig +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.AssemblyInfoInputs.cache +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.AssemblyInfo.cs +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.csproj.CoreCompileInputs.cache +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.dll +D:\Sheep-GP\WV2SDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.pdb +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.deps.json +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.dll +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\bin\Debug\netstandard2.1\WV2SDK.pdb +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.csproj.AssemblyReference.cache +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.GeneratedMSBuildEditorConfig.editorconfig +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.AssemblyInfoInputs.cache +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.AssemblyInfo.cs +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.csproj.CoreCompileInputs.cache +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.dll +C:\WorkSpace\Sheep_GP\WVSDK\WV2SDK\obj\Debug\netstandard2.1\WV2SDK.pdb diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.dll b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.dll new file mode 100644 index 0000000..95aadac Binary files /dev/null and b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.dll differ diff --git a/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.pdb b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.pdb new file mode 100644 index 0000000..62c4790 Binary files /dev/null and b/WV2SDK/obj/Debug/netstandard2.1/WV2SDK.pdb differ diff --git a/WV2SDK/obj/WV2SDK.csproj.nuget.dgspec.json b/WV2SDK/obj/WV2SDK.csproj.nuget.dgspec.json new file mode 100644 index 0000000..2b1d26e --- /dev/null +++ b/WV2SDK/obj/WV2SDK.csproj.nuget.dgspec.json @@ -0,0 +1,77 @@ +{ + "format": 1, + "restore": { + "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj": {} + }, + "projects": { + "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "projectName": "WV2SDK", + "projectPath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "packagesPath": "C:\\Users\\97354\\.nuget\\packages\\", + "outputPath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\97354\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.1" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "dependencies": { + "Newtonsoft.Json": { + "target": "Package", + "version": "[13.0.3, )" + }, + "Unity3D.SDK": { + "target": "Package", + "version": "[2021.1.14.1, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "NETStandard.Library": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Users\\97354\\.dotnet\\sdk\\9.0.304\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/WV2SDK/obj/WV2SDK.csproj.nuget.g.props b/WV2SDK/obj/WV2SDK.csproj.nuget.g.props new file mode 100644 index 0000000..5e3a8d5 --- /dev/null +++ b/WV2SDK/obj/WV2SDK.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\97354\.nuget\packages\ + PackageReference + 6.14.0 + + + + + \ No newline at end of file diff --git a/WV2SDK/obj/WV2SDK.csproj.nuget.g.targets b/WV2SDK/obj/WV2SDK.csproj.nuget.g.targets new file mode 100644 index 0000000..35a7576 --- /dev/null +++ b/WV2SDK/obj/WV2SDK.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/WV2SDK/obj/project.assets.json b/WV2SDK/obj/project.assets.json new file mode 100644 index 0000000..f8ca2f9 --- /dev/null +++ b/WV2SDK/obj/project.assets.json @@ -0,0 +1,168 @@ +{ + "version": 3, + "targets": { + ".NETStandard,Version=v2.1": { + "Newtonsoft.Json/13.0.3": { + "type": "package", + "compile": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + } + }, + "Unity3D.SDK/2021.1.14.1": { + "type": "package", + "compile": { + "lib/UnityEditor.dll": {}, + "lib/UnityEngine.dll": {} + }, + "runtime": { + "lib/UnityEditor.dll": {}, + "lib/UnityEngine.dll": {} + } + } + } + }, + "libraries": { + "Newtonsoft.Json/13.0.3": { + "sha512": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "type": "package", + "path": "newtonsoft.json/13.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "README.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/net6.0/Newtonsoft.Json.dll", + "lib/net6.0/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "newtonsoft.json.13.0.3.nupkg.sha512", + "newtonsoft.json.nuspec", + "packageIcon.png" + ] + }, + "Unity3D.SDK/2021.1.14.1": { + "sha512": "t5wm+eCp4HvhSiAmH2lASj82QobnZe0uEwZyd+RreN4bRqB6x2kSK4uvu213TQrtMeDi+HB015wWWu2VnKpEIQ==", + "type": "package", + "path": "unity3d.sdk/2021.1.14.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/UnityEditor.dll", + "lib/UnityEditor.xml", + "lib/UnityEngine.dll", + "lib/UnityEngine.xml", + "unity3d.sdk.2021.1.14.1.nupkg.sha512", + "unity3d.sdk.nuspec" + ] + } + }, + "projectFileDependencyGroups": { + ".NETStandard,Version=v2.1": [ + "Newtonsoft.Json >= 13.0.3", + "Unity3D.SDK >= 2021.1.14.1" + ] + }, + "packageFolders": { + "C:\\Users\\97354\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "projectName": "WV2SDK", + "projectPath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "packagesPath": "C:\\Users\\97354\\.nuget\\packages\\", + "outputPath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\97354\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "netstandard2.1" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "dependencies": { + "Newtonsoft.Json": { + "target": "Package", + "version": "[13.0.3, )" + }, + "Unity3D.SDK": { + "target": "Package", + "version": "[2021.1.14.1, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "NETStandard.Library": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Users\\97354\\.dotnet\\sdk\\9.0.304\\RuntimeIdentifierGraph.json" + } + } + }, + "logs": [ + { + "code": "NU1701", + "level": "Warning", + "warningLevel": 1, + "message": "Package 'Unity3D.SDK 2021.1.14.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework '.NETStandard,Version=v2.1'. This package may not be fully compatible with your project.", + "libraryId": "Unity3D.SDK", + "targetGraphs": [ + ".NETStandard,Version=v2.1" + ] + } + ] +} \ No newline at end of file diff --git a/WV2SDK/obj/project.nuget.cache b/WV2SDK/obj/project.nuget.cache new file mode 100644 index 0000000..d0e3beb --- /dev/null +++ b/WV2SDK/obj/project.nuget.cache @@ -0,0 +1,24 @@ +{ + "version": 2, + "dgSpecHash": "ZGSvfVuq3UQ=", + "success": true, + "projectFilePath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "expectedPackageFiles": [ + "C:\\Users\\97354\\.nuget\\packages\\newtonsoft.json\\13.0.3\\newtonsoft.json.13.0.3.nupkg.sha512", + "C:\\Users\\97354\\.nuget\\packages\\unity3d.sdk\\2021.1.14.1\\unity3d.sdk.2021.1.14.1.nupkg.sha512" + ], + "logs": [ + { + "code": "NU1701", + "level": "Warning", + "message": "Package 'Unity3D.SDK 2021.1.14.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework '.NETStandard,Version=v2.1'. This package may not be fully compatible with your project.", + "projectPath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "warningLevel": 1, + "filePath": "C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj", + "libraryId": "Unity3D.SDK", + "targetGraphs": [ + ".NETStandard,Version=v2.1" + ] + } + ] +} \ No newline at end of file diff --git a/WV2SDK/obj/project.packagespec.json b/WV2SDK/obj/project.packagespec.json new file mode 100644 index 0000000..1e7b58a --- /dev/null +++ b/WV2SDK/obj/project.packagespec.json @@ -0,0 +1 @@ +"restore":{"projectUniqueName":"C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj","projectName":"WV2SDK","projectPath":"C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\WV2SDK.csproj","outputPath":"C:\\WorkSpace\\Sheep_GP\\WVSDK\\WV2SDK\\obj\\","projectStyle":"PackageReference","originalTargetFrameworks":["netstandard2.1"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","dependencies":{"Newtonsoft.Json":{"target":"Package","version":"[13.0.3, )"},"Unity3D.SDK":{"target":"Package","version":"[2021.1.14.1, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Users\\97354\\.dotnet\\sdk\\9.0.304\\RuntimeIdentifierGraph.json"}} \ No newline at end of file diff --git a/WV2SDK/obj/rider.project.model.nuget.info b/WV2SDK/obj/rider.project.model.nuget.info new file mode 100644 index 0000000..9a9fbc6 --- /dev/null +++ b/WV2SDK/obj/rider.project.model.nuget.info @@ -0,0 +1 @@ +17576605466362920 \ No newline at end of file diff --git a/WV2SDK/obj/rider.project.restore.info b/WV2SDK/obj/rider.project.restore.info new file mode 100644 index 0000000..9a9fbc6 --- /dev/null +++ b/WV2SDK/obj/rider.project.restore.info @@ -0,0 +1 @@ +17576605466362920 \ No newline at end of file diff --git a/gupm.toml b/gupm.toml new file mode 100644 index 0000000..fa5ce4a --- /dev/null +++ b/gupm.toml @@ -0,0 +1,6 @@ +Target = "Assets/SGModule" + +[Submodules] + Common = "v3.0.0" + Net = "v3.0.1" + NetKit = "v1.0.0"