fix:1、h5 sdk创建项目

This commit is contained in:
2026-07-06 09:56:40 +08:00
commit b37bfe58c4
162 changed files with 10855 additions and 0 deletions
+13
View File
@@ -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
+1
View File
@@ -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
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+9
View File
@@ -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>
+91
View File
@@ -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">{
&quot;associatedIndex&quot;: 5
}</component>
<component name="ProjectId" id="2wlL91IO8A6fKN8ujd10moYxx3C" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.lookFeel&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</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
View File
@@ -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
+33
View File
@@ -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>
+2
View File
@@ -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>
+229
View File
@@ -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
}
}
+41
View File
@@ -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();
}
}
}
}
+52
View File
@@ -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--;
}
}
}
}
}
+92
View File
@@ -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);
}
}
}
}
+63
View File
@@ -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);
}
}
}
+115
View File
@@ -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()
{
}
}
}
+55
View File
@@ -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;
}
}
}
+108
View File
@@ -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()
{
}
}
}
+29
View File
@@ -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()
{
}
}
}
+33
View File
@@ -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);
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Uni2SDK
{
internal interface InterfaceManager
{
void Init();
void StartUp();
void DisposeBefore();
void Dispose();
}
}
+201
View File
@@ -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;
}
}
}
+257
View File
@@ -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);
}
}
}
+245
View File
@@ -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;
}
}
+26
View File
@@ -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;
}
}
+9
View File
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Uni2SDK
{
internal interface IDataList<T>
{
List<T> dataList { get; set; }
}
}
+49
View File
@@ -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;
}
}
}
+38
View File
@@ -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]";
}
}
+16
View File
@@ -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;
}
}
+16
View File
@@ -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;
}
}
+18
View File
@@ -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;
}
}
+10
View File
@@ -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;
}
}
+11
View File
@@ -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;
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Uni2SDK
{
internal class AppDispatcher : BaseDispatcher<AppDispatcher, uint, object>
{
}
}
+133
View File
@@ -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;
}
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Uni2SDK
{
internal class CtrlDispatcher : BaseDispatcher<CtrlDispatcher, uint, object>
{
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Uni2SDK
{
internal class NetworkDispatcher : BaseDispatcher<NetworkDispatcher, uint, object>
{
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Uni2SDK
{
internal class UICtrlDispatcher : BaseDispatcher<UICtrlDispatcher, uint, object>
{
}
}
+171
View File
@@ -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;
}
}
}
+164
View File
@@ -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;
}
}
}
}
+135
View File
@@ -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);
}
}
}
+18
View File
@@ -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";
}
}
+32
View File
@@ -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/";
}
}
}
+38
View File
@@ -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;
}
}
}
+31
View File
@@ -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();
}
}
}
}
+202
View File
@@ -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);
}
}
}
+24
View File
@@ -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;
// }
}
+83
View File
@@ -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);
}
});
}
}
}
+64
View File
@@ -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();
}
}
}
+138
View File
@@ -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&lt;int&gt;(); // 输出 123
/// "45.67".As&lt;float&gt;(); // 输出 45.67f
/// "true".As&lt;bool&gt;(); // 输出 true
/// "2023-01-01".As&lt;DateTime&gt;(); // 输出 2023-01-01
/// "".As&lt;int&gt;(); // 输出 0
/// ((string)null).As&lt;int&gt;(); // 输出 0
/// "abc".As&lt;int&gt;(); // 输出 0(无法转换)
/// "1.23E+5".As&lt;float&gt;(); // 输出 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
}
+67
View File
@@ -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.");
}
}
}
@@ -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();
}
}
@@ -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);
@@ -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"
}
+62
View File
@@ -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