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