fix:1、h5 sdk创建项目
This commit is contained in:
Generated
+13
@@ -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
|
||||||
Generated
+1
@@ -0,0 +1 @@
|
|||||||
|
Uni2SDK
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
|
||||||
|
<option name="chatAppRouterInfo" value="chat-session" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/WV2SDK/SGModule/Common" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/WV2SDK/SGModule/Net" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/WV2SDK/SGModule/NetKit" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+91
@@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="f5aba6ca-b379-4e04-b676-c9f3054f5cfc" name="更改" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="HighlightingSettingsPerFile">
|
||||||
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/2dec11d5819b49f8a1f65bb52ca0c6594b8800/5e/c912a4e9/SystemInfo.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 5
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2wlL91IO8A6fKN8ujd10moYxx3C" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RunManager" selected="附加到 Unity 编辑器.附加到 Unity 编辑器">
|
||||||
|
<configuration name="启动 Unity" type="RunUnityExe" factoryName="Unity Executable">
|
||||||
|
<option name="EXE_PATH" value="D:\Software\2022.3.51f1\Editor\Unity.exe" />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="-projectPath C:\Users\WangJin\RiderProjects\WV2SDK -debugCodeOptimization" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="C:\Users\WangJin\RiderProjects\WV2SDK" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="单元测试(批处理模式)" type="RunUnityExe" factoryName="Unity Executable">
|
||||||
|
<option name="EXE_PATH" value="D:\Software\2022.3.51f1\Editor\Unity.exe" />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="-runTests -batchmode -projectPath C:\Users\WangJin\RiderProjects\WV2SDK -testResults Logs/results.xml -logFile Logs/Editor.log -testPlatform EditMode -debugCodeOptimization" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="C:\Users\WangJin\RiderProjects\WV2SDK" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="附加到 Unity 编辑器" type="UNITY_DEBUG_RUN_CONFIGURATION" factoryName="Unity Debug" show_console_on_std_err="false" show_console_on_std_out="false" port="50000" address="localhost">
|
||||||
|
<option name="allowRunningInParallel" value="false" />
|
||||||
|
<option name="listenPortForConnections" value="false" />
|
||||||
|
<option name="pid" />
|
||||||
|
<option name="projectPathOnTarget" />
|
||||||
|
<option name="runtimes">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="selectedOptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="默认任务">
|
||||||
|
<changelist id="f5aba6ca-b379-4e04-b676-c9f3054f5cfc" name="更改" comment="" />
|
||||||
|
<created>1746611058126</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1746611058126</updated>
|
||||||
|
<workItem from="1746611059193" duration="3219000" />
|
||||||
|
<workItem from="1746617701623" duration="865000" />
|
||||||
|
<workItem from="1746669462531" duration="17886000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
|
||||||
|
<component name="UnityProjectDiscoverer">
|
||||||
|
<option name="hasUnityReference" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+16
@@ -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
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=D_003A_005CSoftware_005C2022_002E3_002E51f1_005CEditor_005CData_005CManaged_005CUnityEditor_002Edll/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=D_003A_005CSoftware_005C2022_002E3_002E51f1_005CEditor_005CData_005CManaged_005CUnityEngine_002Edll/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAndroidJavaClass_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F7e_003F8c2a15c3_003FAndroidJavaClass_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAndroidJavaObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F9c_003Fcd751205_003FAndroidJavaObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAndroidJavaProxy_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2dec11d5819b49f8a1f65bb52ca0c6594b8800_003F2b_003Fa2c063d2_003FAndroidJavaProxy_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApplication_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F82_003F3f439b88_003FApplication_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApplication_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2dec11d5819b49f8a1f65bb52ca0c6594b8800_003F4c_003Fc3b69730_003FApplication_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArray_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F21e30202a5e64048906aa5882fda6e57c2ca0_003F3a_003F2dff1d11_003FArray_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACipherMode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb3813fd701e44754ba7f3643ce0a932a22d10_003F4c_003F62102cfc_003FCipherMode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADateTime_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5ee4e2db8cb642d8bc4a6567a75d8e9acef20_003Fc8_003F7587bb5a_003FDateTime_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADebug_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003Ffd_003Fb4f96c02_003FDebug_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADrawCameraMode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9affee9a82544ff3a82f7a79a1c8a4b59eee00_003Fce_003Fc65142fa_003FDrawCameraMode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEncoding_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F21e30202a5e64048906aa5882fda6e57c2ca0_003F14_003F0bfa3173_003FEncoding_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEncoding_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F21e30202a5e64048906aa5882fda6e57c2ca0_003F2b_003F1acb994f_003FEncoding_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbc5819c5fa23406c8eeb3856f1a6db7d8110_003F6d_003F9a469a18_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F64e462c7b1c14015a92a1d88e719f16f8138_003F5c_003F68732f8d_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIList_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F21e30202a5e64048906aa5882fda6e57c2ca0_003Fb9_003Fe96698c9_003FIList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AILogger_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003Fc3_003F96375ef9_003FILogger_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonConvert_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff3981d658666ec9cc1e6958fe22f8614bfe02ccb6625b056c3ece3729c9262_003FJsonConvert_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonoBehaviour_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F74_003F09019129_003FMonoBehaviour_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AObject_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F21e30202a5e64048906aa5882fda6e57c2ca0_003F8b_003F2e19f053_003FObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APlayerPrefs_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F04_003F14d54b3d_003FPlayerPrefs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARuntimeInitializeLoadType_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003Fb0_003Fefc73fe9_003FRuntimeInitializeLoadType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASHA256_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb3813fd701e44754ba7f3643ce0a932a22d10_003F36_003F17bc275e_003FSHA256_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5ee4e2db8cb642d8bc4a6567a75d8e9acef20_003Fbd_003F468c66b3_003FString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystemInfo_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F24_003Ff855021a_003FSystemInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystemInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2dec11d5819b49f8a1f65bb52ca0c6594b8800_003F5e_003Fc912a4e9_003FSystemInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystemInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003F97354_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F24_003Ff855021a_003FSystemInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATask_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F21e30202a5e64048906aa5882fda6e57c2ca0_003F50_003F8e9aa6ea_003FTask_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUnityWebRequest_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003F0d_003F4c7574d7_003FUnityWebRequest_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWaitUntil_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffc2b95e932684823bd041114d53488f63a8800_003Fde_003F8763d224_003FWaitUntil_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebSocket_002Ecs_002Fl_003AC_0021_003FUsers_003FWangJin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3332df50fbce457aaa2c1e90dc8553da5b10_003F0d_003F848e09af_003FWebSocket_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=D_003A_005CSoftware_005C2022_002E3_002E51f1_005CEditor_005CData_005CManaged_005CUnityEngine_002Edll/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Android交互工具(v3.0)
|
||||||
|
/// 核心优化:统一调用模板 + 智能参数处理
|
||||||
|
/// </summary>
|
||||||
|
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<AndroidJavaObject>("currentActivity");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 MyActivity 的单例对象
|
||||||
|
using (AndroidJavaClass myActivityClass = new AndroidJavaClass(UniPluginPack + ".UniPluginClass"))
|
||||||
|
{
|
||||||
|
Bridge = myActivityClass.CallStatic<AndroidJavaObject>("getInstance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogKit.LogError($"Android初始化失败: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 通用调用模板
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一调用方法(无返回值)
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一调用方法(带返回值)
|
||||||
|
/// </summary>
|
||||||
|
private static T Call<T>(string method, params object[] args)
|
||||||
|
{
|
||||||
|
if (Bridge == null) return default;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Bridge.Call<T>(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<int, string> _onCallback;
|
||||||
|
|
||||||
|
public CallbackProxy(Action<int, string> 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<int, string> _onCallback;
|
||||||
|
|
||||||
|
public InitCallbackProxy(Action<int, string> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Uni2SDK;
|
||||||
|
|
||||||
|
public class MainThreadDispatcher : MonoBehaviour
|
||||||
|
{
|
||||||
|
private static readonly Queue<Action> _actions = new Queue<Action>();
|
||||||
|
private static MainThreadDispatcher _instance;
|
||||||
|
|
||||||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||||
|
static void Init()
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
var obj = new GameObject("[MainThreadDispatcher]");
|
||||||
|
_instance = obj.AddComponent<MainThreadDispatcher>();
|
||||||
|
DontDestroyOnLoad(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunOnMainThread(Action action)
|
||||||
|
{
|
||||||
|
lock (_actions)
|
||||||
|
{
|
||||||
|
_actions.Enqueue(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
lock (_actions)
|
||||||
|
{
|
||||||
|
while (_actions.Count > 0)
|
||||||
|
{
|
||||||
|
_actions.Dequeue()?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
internal class AsyncKitUnityBehaviour : SingletonUnity<AsyncKitUnityBehaviour>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, AsyncDealData> _asyncDataDict = new Dictionary<string, AsyncDealData>();
|
||||||
|
|
||||||
|
|
||||||
|
private readonly Dictionary<string, AsyncDealData> _updateActionDict = new Dictionary<string, AsyncDealData>();
|
||||||
|
|
||||||
|
private readonly List<string> _keyClean = new List<string>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal abstract class BaseInterfaceManager<T> : IDisposable, InterfaceManager
|
||||||
|
where T : BaseInterfaceManager<T>, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal interface InterfaceManager
|
||||||
|
{
|
||||||
|
void Init();
|
||||||
|
void StartUp();
|
||||||
|
void DisposeBefore();
|
||||||
|
void Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Uni2SDK;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal sealed class ModuleManager : BaseInterfaceManager<ModuleManager>
|
||||||
|
{
|
||||||
|
private Dictionary<string, BaseModel> modelDict = new();
|
||||||
|
private Dictionary<string, Type> uiTypeDict = new();
|
||||||
|
private Dictionary<string, BaseCtrl> ctrlDict = new();
|
||||||
|
private Dictionary<string, BaseUICtrl> uiCtrlDict = new();
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
base.Init();
|
||||||
|
InitAllModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitAllModule()
|
||||||
|
{
|
||||||
|
// List<string> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<H5ConfModel>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Uni2SDK;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal class GameUrlsModel : IDataList<GameUrls>
|
||||||
|
{
|
||||||
|
public List<GameUrls> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal interface IDataList<T>
|
||||||
|
{
|
||||||
|
List<T> dataList { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string> CtrlDisableList = new List<string>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static bool isPt()
|
||||||
|
{
|
||||||
|
if (CurrMultiLangue == "pt") return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal class AppDispatcher : BaseDispatcher<AppDispatcher, uint, object>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine.Pool;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal abstract class BaseDispatcher<T, Msg, Param> : 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<Msg, List<Action<Param>>> m_msgPriorityDict = new();
|
||||||
|
private Dictionary<Msg, List<Action<Param>>> m_msgDict = new();
|
||||||
|
private Dictionary<Msg, List<Action<Param>>> m_msgFinallyDict = new();
|
||||||
|
private Dictionary<Msg, List<Action<Param>>> m_msgOnceDict = new();
|
||||||
|
|
||||||
|
public void AddListener(Msg msgId, Action<Param> listener)
|
||||||
|
{
|
||||||
|
if (m_msgDict.TryGetValue(msgId, out var value))
|
||||||
|
{
|
||||||
|
value.Add(listener);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var list = ListPool<Action<Param>>.Get();
|
||||||
|
list.Add(listener);
|
||||||
|
m_msgDict.Add(msgId, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOnceListener(Msg msgId, Action<Param> listener)
|
||||||
|
{
|
||||||
|
if (m_msgOnceDict.TryGetValue(msgId, out var value))
|
||||||
|
{
|
||||||
|
value.Add(listener);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var list = ListPool<Action<Param>>.Get();
|
||||||
|
list.Add(listener);
|
||||||
|
m_msgOnceDict.Add(msgId, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveListener(Msg msgId, Action<Param> listener)
|
||||||
|
{
|
||||||
|
if (m_msgDict.TryGetValue(msgId, out var list))
|
||||||
|
{
|
||||||
|
if (list.Contains(listener))
|
||||||
|
{
|
||||||
|
list.Remove(listener);
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
ListPool<Action<Param>>.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<Action<Param>>.Release(m_msgOnceDict[msgId]);
|
||||||
|
m_msgOnceDict.Remove(msgId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeMethods(Dictionary<Msg, List<Action<Param>>> msgDict, Msg msgId, Param param)
|
||||||
|
{
|
||||||
|
if (!msgDict.ContainsKey(msgId)) return;
|
||||||
|
|
||||||
|
var rawList = msgDict[msgId];
|
||||||
|
int funcCount = rawList.Count;
|
||||||
|
if (funcCount == 1)
|
||||||
|
{
|
||||||
|
Action<Param> onEvent = rawList[0];
|
||||||
|
onEvent(param);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var invokeFuncs = ListPool<Action<Param>>.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<Action<Param>>.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal class CtrlDispatcher : BaseDispatcher<CtrlDispatcher, uint, object>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal class NetworkDispatcher : BaseDispatcher<NetworkDispatcher, uint, object>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal class UICtrlDispatcher : BaseDispatcher<UICtrlDispatcher, uint, object>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int, int> activeTimes = new Dictionary<int, int>();
|
||||||
|
public static Dictionary<int, int> activeTimes_saveingpot = new Dictionary<int, int>();
|
||||||
|
|
||||||
|
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<T>(List<T> 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<int> 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<T>(Dictionary<T, int> 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<int> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string> 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<string> 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<string> 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<bool> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<float, string> numDic = new Dictionary<float, string>();
|
||||||
|
|
||||||
|
|
||||||
|
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<CommonModel>();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
|
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<string, string> adCallbackInfo = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查是否有网络
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true:有网, false:没网 </returns>
|
||||||
|
// public static bool IsConnect()
|
||||||
|
// {
|
||||||
|
// // return NetworkKit.GetNetworkType() != NetworkType.NotConnected;
|
||||||
|
// }
|
||||||
|
public static void TrackChannel(int id)
|
||||||
|
{
|
||||||
|
// NetworkKit.BuriedPoint(BuriedPointEvent.H5_event, BuriedPointEvent.h5_display, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal abstract class BaseUnityManager<T> : SingletonUnity<T>, InterfaceManager
|
||||||
|
where T : BaseUnityManager<T>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
internal class THManager : BaseUnityManager<THManager>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<T>(IList<T> 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<T>(this IEnumerable<T> items, T fallback = default) where T : class, IWeighted
|
||||||
|
{
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = items as IList<T> ?? items.ToList();
|
||||||
|
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RandomHelper.RandomByWeight(list, fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ArrayExtensions
|
||||||
|
{
|
||||||
|
public static T Random<T>(this T[] items, T fallback = default) where T : class, IWeighted
|
||||||
|
{
|
||||||
|
if (items == null || items.Length == 0)
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RandomHelper.RandomByWeight(items, fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
internal class NetworkManager : BaseUnityManager<NetworkManager>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<AudioManager> { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
### 🛠️ 工具类一览(Helper)
|
||||||
|
|
||||||
|
| 类名 | 功能说明 |
|
||||||
|
| ----------------- | ------------------------------------------- |
|
||||||
|
| `Log` | ✅日志打印(开发调试利器) |
|
||||||
|
| `Cryptor` | 🔐 加密解密支持(对称加密) |
|
||||||
|
| `TimeHelper` | 🕒 时间戳转换与格式化处理 |
|
||||||
|
| `DeviceHelper` | 📱 获取设备唯一标识 |
|
||||||
|
| `RandomHelper` | 🎲 权重随机、随机打乱等 |
|
||||||
|
| `SerializeHelper` | 🔄 JSON 序列化 / 反序列化(可使用As<T>()替换) |
|
||||||
|
| `MD5Helper` | 🔑 快速生成MD5签名 |
|
||||||
|
| `Base64Helper` | 🧬 Base64编解码 |
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
### 📌 日志打印统一用法
|
||||||
|
|
||||||
|
统一使用 `Log` 工具类进行日志输出,支持彩色分级,正式环境中可自动关闭日志输出。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Uni2SDK;
|
||||||
|
|
||||||
|
Log.Info("标签", "信息内容"); // ✅绿色输出
|
||||||
|
Log.Warning("标签", "警告内容"); // ⚠️橙色输出
|
||||||
|
Log.Error("标签", "错误内容"); // ❌红色输出
|
||||||
|
```
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
### ✨ 扩展方法一览
|
||||||
|
|
||||||
|
- **EnumExtensions**:获取枚举描述 `GameState.Ready.GetDescription()`
|
||||||
|
- **StringExtensions**:空值检查 `str.IsNullOrWhiteSpace()`
|
||||||
|
- **ObjectExtensions**:类型安全转换 `"123".As<int>()`
|
||||||
|
- **ListExtensions**:列表随机元素 `items.Random()`
|
||||||
|
- **ArrayExtensions**:数组相关操作
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 🧠 最佳实践
|
||||||
|
|
||||||
|
| 模块 | 建议做法 |
|
||||||
|
| ---------- | ------------------------------------------------------------ |
|
||||||
|
| 管理器类 | Mono 类继承 `SingletonMonoBehaviour` |
|
||||||
|
| 日志打印 | 全局使用 `Log` 统一管理,按模块分类 |
|
||||||
|
| 类型转换 | 使用 `.As<T>()` 替代传统强转,异常更少 |
|
||||||
|
| 加密数据 | 所有敏感数据使用 `Cryptor` 加密后存储 |
|
||||||
|
| 时间处理 | 使用 `TimeHelper` 保证时区一致性 |
|
||||||
|
| 随机操作 | 使用 `RandomHelper` 替代 `UnityEngine.Random` |
|
||||||
|
| 数据键管理 | 通过 `KeyRegistry` 注册和使用,避免硬编码 |
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
- ❗ `SingletonMonoBehaviour` 不要手动放入场景,会自动创建
|
||||||
|
- 🔐 加密密钥需妥善保管,避免泄露
|
||||||
|
- 🌏 时间处理请注意本地时区与 UTC 的转换
|
||||||
|
- 🎲 请勿使用 `UnityEngine.Random`,统一使用 `RandomHelper`
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 💡 扩展建议
|
||||||
|
|
||||||
|
- 🧩 新增扩展方法时,请分类放入 `Extensions/` 并补充完整 XML 注释
|
||||||
|
- 🧰 新增工具类保持静态类设计,考虑线程安全
|
||||||
|
- 📖 键系统支持从配置文件中加载,并支持按模块分组管理
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <Security/Security.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using Uni2SDK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour
|
||||||
|
{
|
||||||
|
private static T _instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化完成标记,有需要的地方可以使用
|
||||||
|
/// </summary>
|
||||||
|
protected static bool IsInitComplete;
|
||||||
|
|
||||||
|
// 存储所有单例的父对象
|
||||||
|
private static GameObject _singletonParent;
|
||||||
|
|
||||||
|
public static T Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
// 尝试找到已存在的实例
|
||||||
|
_instance = FindObjectOfType<T>();
|
||||||
|
|
||||||
|
// 如果没有找到,则创建一个新的实例
|
||||||
|
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<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; // 清除引用
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Uni2SDK;
|
||||||
|
using SGModule.Common.Interface;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
// public static class ArrayExtensions
|
||||||
|
// {
|
||||||
|
// public static T Random<T>(this T[] items, T fallback = default) where T : class, IWeighted
|
||||||
|
// {
|
||||||
|
// if (items == null || items.Length == 0)
|
||||||
|
// {
|
||||||
|
// return fallback;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return RandomHelper.RandomByWeight(items, fallback);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -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(); // 如果没有描述,就使用枚举名称
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
// public static class ListExtensions
|
||||||
|
// {
|
||||||
|
// public static T Random<T>(this IEnumerable<T> items, T fallback = default) where T : class, IWeighted
|
||||||
|
// {
|
||||||
|
// if (items == null)
|
||||||
|
// {
|
||||||
|
// return fallback;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var list = items as IList<T> ?? items.ToList();
|
||||||
|
//
|
||||||
|
// if (list.Count == 0)
|
||||||
|
// {
|
||||||
|
// return fallback;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return RandomHelper.RandomByWeight(list, fallback);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
/// <summary>
|
||||||
|
/// 提供通用对象类型转换的扩展方法,支持数值类型、字符串、枚举、集合、时间等多种类型间的智能转换。
|
||||||
|
/// </summary>
|
||||||
|
public static class ObjectExtensions {
|
||||||
|
/// <summary>
|
||||||
|
/// 将对象强制转换为目标类型 <typeparamref name="T" />,转换失败时返回默认值。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">目标类型</typeparam>
|
||||||
|
/// <param name="value">待转换的对象</param>
|
||||||
|
/// <param name="defaultValue">转换失败时返回的默认值</param>
|
||||||
|
/// <param name="format">可选格式字符串(如用于时间或数值格式化)</param>
|
||||||
|
/// <param name="formatProvider">格式化提供者,默认为当前区域设置</param>
|
||||||
|
/// <returns>转换后的目标类型值,或 <paramref name="defaultValue" />。</returns>
|
||||||
|
/// <example>
|
||||||
|
/// 字符串到基本类型
|
||||||
|
/// <code>
|
||||||
|
/// "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
|
||||||
|
/// .....
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public static T As<T>(this object value, T defaultValue = default, string format = null, IFormatProvider formatProvider = null) {
|
||||||
|
return (T) As(value, typeof(T), defaultValue, format, formatProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将对象转换为指定的目标类型。
|
||||||
|
/// 支持基础类型转换、字符串与 JSON 互转、枚举解析、集合映射、Base64 处理等。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">要转换的对象</param>
|
||||||
|
/// <param name="targetType">目标类型</param>
|
||||||
|
/// <param name="defaultValue">转换失败时的返回值</param>
|
||||||
|
/// <param name="format">可选的格式字符串(用于时间、数字格式化)</param>
|
||||||
|
/// <param name="formatProvider">格式提供者(区域信息),默认为当前区域</param>
|
||||||
|
/// <returns>转换结果或默认值</returns>
|
||||||
|
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<string> -> List<int>)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static bool IsNullOrWhiteSpace(this string str)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using SGModule.Common;
|
||||||
|
using Uni2SDK;
|
||||||
|
using Uni2SDK;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class GMTool : SingletonMonoBehaviour<GMTool>
|
||||||
|
{
|
||||||
|
private readonly int buttonHeight = 50;
|
||||||
|
private readonly int fontSize = 35; // 字体大小
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> inputFields = new();
|
||||||
|
|
||||||
|
private readonly List<GMToolItem> 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<ConfigModel>()
|
||||||
|
// {
|
||||||
|
// new CommonModel("Common"),
|
||||||
|
// new PrizeWheelDataModel("PrizeWheelData"),
|
||||||
|
// },
|
||||||
|
// state =>
|
||||||
|
// {
|
||||||
|
// Debug.LogError($"配置加载状态{state}");
|
||||||
|
// },
|
||||||
|
// (errorName, message) =>
|
||||||
|
// {
|
||||||
|
// Debug.LogError($"配置解析错误 {errorName} 错误信息:{message}");
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// AddButton($"读取Common配置", () =>
|
||||||
|
// {
|
||||||
|
// var model = ConfigLoader.Instance.GetConfig<CommonModel>();
|
||||||
|
// Debug.LogError(model);
|
||||||
|
// });
|
||||||
|
// AddButton($"读取PrizeWheelDataModel配置", () =>
|
||||||
|
// {
|
||||||
|
// var model = ConfigLoader.Instance.GetConfig<PrizeWheelDataModel>();
|
||||||
|
// Debug.LogError(model.DataList.Count);
|
||||||
|
// });
|
||||||
|
// AddButton($"测试解析为自定义配置", () =>
|
||||||
|
// {
|
||||||
|
// Debug.LogError(ConfigLoader.Instance.ParesPersonalizedConfig(new PrizeWheelDataModel("PrizeWheelData"), "prize"));
|
||||||
|
// });
|
||||||
|
// AddButton($"读取自定义配置", () =>
|
||||||
|
// {
|
||||||
|
// var personalizedConfig = ConfigLoader.Instance.GetPersonalizedConfig<PrizeWheelDataModel>("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<string> 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<string> OnClick;
|
||||||
|
public string Placeholder;
|
||||||
|
public Func<string> TextFunc;
|
||||||
|
public GUIType Type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">控件类型</param>
|
||||||
|
/// <param name="textFunc">显示的文本,传入委托可以动态变化</param>
|
||||||
|
/// <param name="onClick">点击委托,只有Button与InputField有效,Button可忽略字符串参数,InputField输入的内容会传入</param>
|
||||||
|
/// <param name="key">InputField 需要用到的key,记得保持唯一性,如果重复有可能会导致重复的输入框同步一样的内容</param>
|
||||||
|
/// <param name="placeholder">提示占位文本</param>
|
||||||
|
public GMToolItem(GUIType type, Func<string> textFunc, Action<string> onClick = null, string key = "",
|
||||||
|
string placeholder = "")
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
TextFunc = textFunc;
|
||||||
|
OnClick = onClick;
|
||||||
|
Key = key;
|
||||||
|
Placeholder = placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
// 混合通用方法工具类(放置一些通用的静态函数)
|
||||||
|
public static class CommonUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 深度比较两个对象是否相等,支持复杂结构(如 List、Dictionary、嵌套对象)
|
||||||
|
/// </summary>
|
||||||
|
public static bool DeepEquals<T>(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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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($"<color=#4CAF50>[{label}]</color> {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warning(string label, string msg)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogWarning($"<color=orange>[{label}]</color> {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(string label, string msg)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogError($"<color=red>[{label}]</color> {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Exception(string label, Exception ex)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogError($"<color=red>[{label}] Exception: {ex.Message}</color>\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($"<color=#4CAF50>[{_label}]</color> {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Warning(string msg)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogWarning($"<color=orange>[{_label}]</color> {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string msg)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogError($"<color=red>[{_label}]</color> {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Exception(Exception ex)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogError($"<color=red>[{_label}] Exception: {ex.Message}</color>\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取字符串的MD5值
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string, string>();
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<T>(IList<T> 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();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -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<T>(object obj)
|
||||||
|
{
|
||||||
|
return ToJson(obj, typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ToObject<T>(string json)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<T>(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public static class TimeHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将 Unix 时间戳转换为本地时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timestamp">时间戳(秒)</param>
|
||||||
|
/// <returns>可读的本地时间字符串</returns>
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 Unix 时间戳转换为 UTC 时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timestamp">时间戳(秒)</param>
|
||||||
|
/// <returns>可读的 UTC 时间字符串</returns>
|
||||||
|
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小时,显示时分秒
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace SGModule.Common.Interface
|
||||||
|
{
|
||||||
|
public interface IWeighted
|
||||||
|
{
|
||||||
|
int Weight { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# 🌐 Net 网络模块
|
||||||
|
|
||||||
|
适用于 Unity 的网络模块,包含网络检测、接口请求、Token 鉴权、服务器时间同步等功能。模块职责清晰、易扩展,使用前需引入通用模块。
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 📦 模块概览
|
||||||
|
|
||||||
|
| 模块 | 功能描述 |
|
||||||
|
| ---------------- | --------------------------------------------------- |
|
||||||
|
| `NetChecker` | 📶 检测网络是否可用,支持 Ping / HTTP 检查 |
|
||||||
|
| `NetCore<T>` | 📡 网络请求基类,支持加密和 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<NetKit> {
|
||||||
|
// 详情参考此类
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
### 3. 发起接口请求(在子类中)
|
||||||
|
|
||||||
|
```c#
|
||||||
|
NetKit.Instance.Post<TrackData>("/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();
|
||||||
|
```
|
||||||
@@ -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<NetChecker>
|
||||||
|
{
|
||||||
|
private const float WebRequestInterval = 5f;
|
||||||
|
private const float PingInterval = 2;
|
||||||
|
|
||||||
|
// 可配置多个检测 URL
|
||||||
|
[SerializeField] private List<string> checkUrls = new()
|
||||||
|
{
|
||||||
|
"https://www.baidu.com", // 国内 URL
|
||||||
|
"https://www.google.com" // 国外 URL
|
||||||
|
};
|
||||||
|
|
||||||
|
// 是否启用 Ping 检测
|
||||||
|
[SerializeField] private bool usePingCheck;
|
||||||
|
|
||||||
|
// 配置可用的 Ping 地址列表
|
||||||
|
private readonly List<string> _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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// null = 未检测;true = 联网;false = 未联网
|
||||||
|
/// </summary>
|
||||||
|
private bool? IsConnected { get; set; }
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
StartCheckingInternet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
StopCheckingInternet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 等待网络连通,自动处理初始化和总超时时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeout">最多等待的总时间(秒)</param>
|
||||||
|
/// <param name="checkInterval">每次重试间隔</param>
|
||||||
|
/// <param name="callback">检测结果回调</param>
|
||||||
|
public IEnumerator WaitUntilNetworkConnected(float timeout, float checkInterval, Action<bool> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 Ping 检测网络连通性
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 UnityWebRequest 检测网络连通性
|
||||||
|
/// </summary>
|
||||||
|
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<UnityWebRequestAsyncOperation>();
|
||||||
|
var requests = new List<UnityWebRequest>();
|
||||||
|
|
||||||
|
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<UnityWebRequestAsyncOperation> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TC> : SingletonMonoBehaviour<TC> 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/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Post请求(异步)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">请求路径</param>
|
||||||
|
/// <param name="requestData">请求参数</param>
|
||||||
|
/// <param name="onCompleted">请求回调</param>
|
||||||
|
/// <param name="withToken">是否带Token</param>
|
||||||
|
/// <param name="headers">额外的请求头</param>
|
||||||
|
protected IEnumerator PostAsync<T>(string path, object requestData = null,
|
||||||
|
UnityAction<ResponseData<T>> onCompleted = null, bool withToken = true,
|
||||||
|
Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
var done = false;
|
||||||
|
|
||||||
|
Post<T>(path, requestData, response =>
|
||||||
|
{
|
||||||
|
onCompleted?.Invoke(response);
|
||||||
|
done = true;
|
||||||
|
}, withToken, true, headers);
|
||||||
|
|
||||||
|
yield return new WaitUntil(() => done);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Post请求(泛型)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">请求路径</param>
|
||||||
|
/// <param name="requestData">请求参数</param>
|
||||||
|
/// <param name="onCompleted">请求回调</param>
|
||||||
|
/// <param name="withToken">是否带Token</param>
|
||||||
|
/// <param name="useCoroutine">是否使用协程</param>
|
||||||
|
/// <param name="headers">额外的请求头</param>
|
||||||
|
/// <typeparam name="T">返回数据中Data的类型</typeparam>
|
||||||
|
protected void Post<T>(string path, object requestData = null, UnityAction<ResponseData<T>> onCompleted = null,
|
||||||
|
bool withToken = true, bool useCoroutine = true, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
if (useCoroutine)
|
||||||
|
{
|
||||||
|
StartCoroutine(PostInternal(path, requestData, onCompleted, withToken, headers));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendWebRequest(path, requestData, withToken, headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发出请求 所有的请求最终都会从这里发出
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">请求路径</param>
|
||||||
|
/// <param name="requestData">请求参数</param>
|
||||||
|
/// <param name="onCompleted">请求回调</param>
|
||||||
|
/// <param name="withToken">是否带Token</param>
|
||||||
|
/// <param name="headers">额外的请求头</param>
|
||||||
|
/// <typeparam name="T">返回数据中Data的类型</typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
private IEnumerator PostInternal<T>(string path, object requestData, UnityAction<ResponseData<T>> onCompleted,
|
||||||
|
bool withToken = true, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
var response = new ResponseData<T>();
|
||||||
|
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<string, string>();
|
||||||
|
|
||||||
|
headers["x-token"] = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Net.Info($"[PostInternal]===4=====");
|
||||||
|
|
||||||
|
yield return SendRequest(path, requestData, onCompleted, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator SendRequest<T>(
|
||||||
|
string path,
|
||||||
|
object requestData,
|
||||||
|
UnityAction<ResponseData<T>> onCompleted = null,
|
||||||
|
Dictionary<string, string> 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<T>();
|
||||||
|
|
||||||
|
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<ResponseData<object>>(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<T>(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<string, string> 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<string, string> 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<string, string> header = null)
|
||||||
|
{
|
||||||
|
if (withToken)
|
||||||
|
{
|
||||||
|
// 合并原有 header
|
||||||
|
header ??= new Dictionary<string, string>();
|
||||||
|
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(); //发送请求
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TokenManager>
|
||||||
|
{
|
||||||
|
public delegate IEnumerator ReauthenticateDelegate(UnityAction<string, long> onCompleted);
|
||||||
|
|
||||||
|
public delegate IEnumerator RefreshTokenDelegate(UnityAction<string, long> 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<string> _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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取有效Token,自动处理刷新和重新登录
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerator GetToken(UnityAction<string> 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<RequestTokenData>("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<bool> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Token记录(测试功能用)
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetTokenRecord()
|
||||||
|
{
|
||||||
|
return _tokenRecord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public class ResponseData<T>
|
||||||
|
{
|
||||||
|
[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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
internal class WebSocketData
|
||||||
|
{
|
||||||
|
public int cmd;
|
||||||
|
public string token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public enum WebSocketEvent
|
||||||
|
{
|
||||||
|
BindToken = 4097,
|
||||||
|
SendHeartbeat = 4098
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WebSocketService>
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断开连接
|
||||||
|
/// </summary>
|
||||||
|
public async Task Disconnect()
|
||||||
|
{
|
||||||
|
if (_webSocket != null)
|
||||||
|
{
|
||||||
|
var webSocket = _webSocket;
|
||||||
|
_webSocket = null;
|
||||||
|
await webSocket.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 连接管理
|
||||||
|
|
||||||
|
// 事件回调
|
||||||
|
public event Action OnOpenAction;
|
||||||
|
public event Action<string> OnMessageAction;
|
||||||
|
public event Action<WebSocketCloseCode> OnCloseAction;
|
||||||
|
public event Action<string> OnErrorAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化并尝试连接
|
||||||
|
/// </summary>
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试重连
|
||||||
|
/// </summary>
|
||||||
|
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 请求
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 绑定Token
|
||||||
|
/// </summary>
|
||||||
|
public void BindToken(string token)
|
||||||
|
{
|
||||||
|
Send(new WebSocketData
|
||||||
|
{
|
||||||
|
cmd = (int)WebSocketEvent.BindToken,
|
||||||
|
token = token
|
||||||
|
});
|
||||||
|
|
||||||
|
_bindToken = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送心跳
|
||||||
|
/// </summary>
|
||||||
|
public void SendHeartbeat()
|
||||||
|
{
|
||||||
|
Send(new WebSocketData
|
||||||
|
{
|
||||||
|
cmd = (int)WebSocketEvent.SendHeartbeat
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ResponseData>(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<T>(JToken data)
|
||||||
|
{
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsedData = data.ToObject<T>(); // 将 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>(T parsedData)
|
||||||
|
{
|
||||||
|
if (parsedData != null)
|
||||||
|
{
|
||||||
|
// 在这里可以对解析后的 data 进行具体处理
|
||||||
|
Log.Net.Info("Successfully processed data: " + parsedData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Net.Warning("Parsed data is null, cannot process.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+326
@@ -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
|
||||||
@@ -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<MainThreadUtil>();
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// WebSocket class bound to JSLIB.
|
||||||
|
/// </summary>
|
||||||
|
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<string, string> 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<string, string> 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<string> subprotocols, Dictionary<string, string> 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<string, string> headers;
|
||||||
|
private List<string> 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<ArraySegment<byte>> sendBytesQueue = new List<ArraySegment<byte>>();
|
||||||
|
private List<ArraySegment<byte>> sendTextQueue = new List<ArraySegment<byte>>();
|
||||||
|
|
||||||
|
public WebSocket(string url, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
uri = new Uri(url);
|
||||||
|
|
||||||
|
if (headers == null)
|
||||||
|
{
|
||||||
|
this.headers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
subprotocols = new List<string>();
|
||||||
|
|
||||||
|
string protocol = uri.Scheme;
|
||||||
|
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||||
|
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocket(string url, string subprotocol, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
uri = new Uri(url);
|
||||||
|
|
||||||
|
if (headers == null)
|
||||||
|
{
|
||||||
|
this.headers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
subprotocols = new List<string> { subprotocol };
|
||||||
|
|
||||||
|
string protocol = uri.Scheme;
|
||||||
|
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||||
|
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocket(string url, List<string> subprotocols, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
uri = new Uri(url);
|
||||||
|
|
||||||
|
if (headers == null)
|
||||||
|
{
|
||||||
|
this.headers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
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<byte>(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<byte>(encoded, 0, encoded.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendMessage(List<ArraySegment<byte>> queue, WebSocketMessageType messageType,
|
||||||
|
ArraySegment<byte> 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<ArraySegment<byte>> queue, WebSocketMessageType messageType)
|
||||||
|
{
|
||||||
|
var buffer = new ArraySegment<byte>();
|
||||||
|
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<byte[]> m_MessageList = new List<byte[]>();
|
||||||
|
|
||||||
|
// simple dispatcher for queued messages.
|
||||||
|
public void DispatchMessageQueue()
|
||||||
|
{
|
||||||
|
if (m_MessageList.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<byte[]> messageListCopy;
|
||||||
|
|
||||||
|
lock (IncomingMessageLock)
|
||||||
|
{
|
||||||
|
messageListCopy = new List<byte[]>(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<byte> buffer = new ArraySegment<byte>(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
|
||||||
|
///
|
||||||
|
/// <summary>
|
||||||
|
/// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface
|
||||||
|
/// </summary>
|
||||||
|
public static class WebSocketFactory
|
||||||
|
{
|
||||||
|
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||||
|
/* Map of websocket instances */
|
||||||
|
public static Dictionary<Int32, WebSocket> instances = new Dictionary<Int32, WebSocket> ();
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when instance is destroyed (by destructor)
|
||||||
|
/// Method removes instance from map and free it in JSLIB implementation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instanceId">Instance identifier.</param>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create WebSocket client instance
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The WebSocket instance.</returns>
|
||||||
|
/// <param name="url">WebSocket valid URL.</param>
|
||||||
|
public static WebSocket CreateInstance(string url)
|
||||||
|
{
|
||||||
|
return new WebSocket(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"name": "endel.nativewebsocket"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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 测试。
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public class RequestResultData
|
||||||
|
{
|
||||||
|
[JsonProperty("result")] public string Result;
|
||||||
|
[JsonProperty("type")] public int Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Uni2SDK
|
||||||
|
{
|
||||||
|
public class RequestSavePlayData
|
||||||
|
{
|
||||||
|
[JsonProperty("data")] public string Data;
|
||||||
|
[JsonProperty("version")] public long Version;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user