feat: 受击音效更新类别控制

This commit is contained in:
m0_75251201
2025-11-05 21:34:21 +08:00
parent 5d69efbc3f
commit 786025f720
50 changed files with 2078 additions and 501 deletions

View File

@@ -6,7 +6,10 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAimTargetFinder_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003Fa9_003F4ac92f25_003FAimTargetFinder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAudioManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F95_003F5092a809_003FAudioManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABehaviour_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc39a522eee05469b8171a6cfeb646c591543b0_003Fc1_003Faa1a31b2_003FBehaviour_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABlackScreen_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F83_003Fdd84d8e1_003FBlackScreen_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABuffsDisplay_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F72_003F6940c184_003FBuffsDisplay_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABuff_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fc9_003F95f738f9_003FBuff_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AButton_005FLoadMainMenu_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fae_003F1c447fe1_003FButton_005FLoadMainMenu_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACameraArmControl_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003Ff5_003F4622470d_003FCameraArmControl_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACameraArm_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003Fdc_003Fc73e8a9f_003FCameraArm_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACameraArm_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Ff6_003F5678008c_003FCameraArm_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -17,14 +20,23 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACharacterMainControl_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F4c_003Faffc257a_003FCharacterMainControl_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACopyTextButton_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fc4_003F9995fb11_003FCopyTextButton_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACustomFaceInstance_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Ff5_003F7e299618_003FCustomFaceInstance_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADamageInfo_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F0b_003F36368d2b_003FDamageInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADamageTypes_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Faf_003Fe7c272fb_003FDamageTypes_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADuckovScreenCapturer_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003F59_003F92754824_003FDuckovScreenCapturer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFadeElement_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F9b_003Fbe78cf8b_003FFadeElement_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFadeGroup_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F4e_003F69f8fcae_003FFadeGroup_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameCamera_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003Fd2_003F4160411a_003FGameCamera_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F94_003Fdd33b16b_003FGameManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameplayDataSettings_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003Fa4_003Ffea6779d_003FGameplayDataSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameplayDataSettings_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F1f_003F8b814bde_003FGameplayDataSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameplayUIManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fcc_003Fe767eb5a_003FGameplayUIManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameRulesManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fb1_003Ff82c3168_003FGameRulesManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInventoryData_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F71a3d79537274de2b084d0fd207549d617200_003F5e_003F6152b6b7_003FInventoryData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AItemDisplay_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F73_003Fa6db3a6c_003FItemDisplay_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALevelConfig_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003Fcc_003Fcfd90334_003FLevelConfig_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALevelManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fe0_003F6f24caa1_003FLevelManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMainCharacterStatValueDisplay_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F08_003Fd04928fd_003FMainCharacterStatValueDisplay_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMainMenu_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F21_003F0ec534a6_003FMainMenu_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AModBehaviour_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F15eaac0daac842bca117926c0c9be2781a00_003F37_003Fa09d99b2_003FModBehaviour_002Ecs_002Fz_003A4_002D3/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AModBehaviour_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Ff3_003Ff5f2b19a_003FModBehaviour_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AModBehaviour_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Faf3c3aa7ce14487c914dade28d96504e70a00_003F26_003Fe998e749_003FModBehaviour_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -32,10 +44,17 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AModInfo_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F3c_003F9c6fe4cd_003FModInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AModManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fe5_003Ff729e34a_003FModManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonoBehaviour_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc39a522eee05469b8171a6cfeb646c591543b0_003F02_003F65608a5e_003FMonoBehaviour_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMultiInteractionMenu_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003F88_003F0d34c6e2_003FMultiInteractionMenu_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMultiInteraction_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Ff8_003F995da332_003FMultiInteraction_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOcclusionFadeChecker_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003F05_003F7aa60d94_003FOcclusionFadeChecker_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOcclusionFadeChecker_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fa8_003Fd12e1cf7_003FOcclusionFadeChecker_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOcclusionFadeManager_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F492b6cef9eac4cd497c1ffcbfcb9161f197200_003F70_003F6aba3c97_003FOcclusionFadeManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsPanel_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fe1_003Fac8e1feb_003FOptionsPanel_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APrefabPool_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2fb2e03687cc4f6182420d823a41eca07200_003F05_003F8982f5f5_003FPrefabPool_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneLoadingContext_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fbf_003F2b3732af_003FSceneLoadingContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AScriptableObject_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc39a522eee05469b8171a6cfeb646c591543b0_003F4d_003F1a33cb14_003FScriptableObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATitle_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fe8_003F42124cdd_003FTitle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUIPrefabsReference_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae7b55f2e79e4a30b19151f53aa9af29197600_003Fb5_003F49d2159e_003FUIPrefabsReference_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVolumeComponent_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb02e352b027d497b8571da05e536c2f682000_003F36_003F74cee2c4_003FVolumeComponent_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVolume_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb02e352b027d497b8571da05e536c2f682000_003Ffa_003F02e0206c_003FVolume_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003A_005F003CModule_005F003E_002Ecs_002Fl_003AC_0021_003FUsers_003FLenovo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Faf3c3aa7ce14487c914dade28d96504e70a00_003F9c_003Ff3cdecda_003F_005F003CModule_005F003E_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("折纸的小箱子")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.1")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.1+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.1+5d69efbc3f80a5422cef0884e02fb27adf20b467")]
[assembly: System.Reflection.AssemblyProductAttribute("HideCharacter")]
[assembly: System.Reflection.AssemblyTitleAttribute("HideCharacter")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.1")]

View File

@@ -1 +1 @@
0c41c29df034d7303af5a922ba9d31f37af5b0419be39221c78f9e624f5d890a
769c2595fb11f290049896c9b627f3b39daef1dc9f833f4947c915142e78d8d2

View File

@@ -1 +1 @@
17619928534493660
17623343068138064

View File

@@ -0,0 +1,488 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
//替换为你的mod命名空间, 防止多个同名ModConfigAPI冲突
namespace HitFeedback.Api {
/// <summary>
/// ModConfig 安全接口封装类 - 提供不抛异常的静态接口
/// ModConfig Safe API Wrapper Class - Provides non-throwing static interfaces
/// </summary>
public static class ModConfigAPI
{
public static string ModConfigName = "ModConfig";
//Ensure this match the number of ModConfig.ModBehaviour.VERSION
//这里确保版本号与ModConfig.ModBehaviour.VERSION匹配
private const int ModConfigVersion = 1;
private static string TAG = $"ModConfig_v{ModConfigVersion}";
private static Type modBehaviourType;
private static Type optionsManagerType;
public static bool isInitialized = false;
private static bool versionChecked = false;
private static bool isVersionCompatible = false;
/// <summary>
/// 检查版本兼容性
/// Check version compatibility
/// </summary>
private static bool CheckVersionCompatibility()
{
if (versionChecked)
return isVersionCompatible;
try
{
// 尝试获取 ModConfig 的版本号
// Try to get ModConfig version number
var versionField = modBehaviourType.GetField("VERSION", BindingFlags.Public | BindingFlags.Static);
if (versionField != null && versionField.FieldType == typeof(int))
{
var modConfigVersion = (int)versionField.GetValue(null);
isVersionCompatible = (modConfigVersion == ModConfigVersion);
if (!isVersionCompatible)
{
Debug.LogError($"[{TAG}] 版本不匹配API版本: {ModConfigVersion}, ModConfig版本: {modConfigVersion}");
return false;
}
Debug.Log($"[{TAG}] 版本检查通过: {ModConfigVersion}");
versionChecked = true;
return true;
}
else
{
// 如果找不到版本字段,发出警告但继续运行(向后兼容)
// If version field not found, warn but continue (backward compatibility)
Debug.LogWarning($"[{TAG}] 未找到版本信息字段,跳过版本检查");
isVersionCompatible = true;
versionChecked = true;
return true;
}
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 版本检查失败: {ex.Message}");
isVersionCompatible = false;
versionChecked = true;
return false;
}
}
/// <summary>
/// 初始化 ModConfigAPI检查必要的函数是否存在
/// Initialize ModConfigAPI, check if necessary functions exist
/// </summary>
public static bool Initialize()
{
try
{
if (isInitialized)
return true;
// 获取 ModBehaviour 类型
// Get ModBehaviour type
modBehaviourType = FindTypeInAssemblies("ModConfig.ModBehaviour");
if (modBehaviourType == null)
{
Debug.LogWarning($"[{TAG}] ModConfig.ModBehaviour 类型未找到ModConfig 可能未加载");
return false;
}
// 获取 OptionsManager_Mod 类型
// Get OptionsManager_Mod type
optionsManagerType = FindTypeInAssemblies("ModConfig.OptionsManager_Mod");
if (optionsManagerType == null)
{
Debug.LogWarning($"[{TAG}] ModConfig.OptionsManager_Mod 类型未找到");
return false;
}
// 检查版本兼容性
// Check version compatibility
if (!CheckVersionCompatibility())
{
Debug.LogWarning($"[{TAG}] ModConfig version mismatch!!!");
return false;
}
// 检查必要的静态方法是否存在
// Check if necessary static methods exist
string[] requiredMethods = {
"AddDropdownList",
"AddInputWithSlider",
"AddBoolDropdownList",
"AddOnOptionsChangedDelegate",
"RemoveOnOptionsChangedDelegate",
};
foreach (var methodName in requiredMethods)
{
var method = modBehaviourType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
if (method == null)
{
Debug.LogError($"[{TAG}] 必要方法 {methodName} 未找到");
return false;
}
}
isInitialized = true;
Debug.Log($"[{TAG}] ModConfigAPI 初始化成功");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 初始化失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 在所有已加载的程序集中查找类型
/// </summary>
private static Type FindTypeInAssemblies(string typeName)
{
try
{
// 获取当前域中的所有程序集
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
// 检查程序集名称是否包含 ModConfig
if (assembly.FullName.Contains("ModConfig"))
{
Debug.Log($"[{TAG}] 找到 ModConfig 相关程序集: {assembly.FullName}");
}
// 尝试在该程序集中查找类型
var type = assembly.GetType(typeName);
if (type != null)
{
Debug.Log($"[{TAG}] 在程序集 {assembly.FullName} 中找到类型 {typeName}");
return type;
}
}
catch (Exception ex)
{
// 忽略单个程序集的查找错误
continue;
}
}
// 记录所有已加载的程序集用于调试
Debug.LogWarning($"[{TAG}] 在所有程序集中未找到类型 {typeName},已加载程序集数量: {assemblies.Length}");
foreach (var assembly in assemblies.Where(a => a.FullName.Contains("ModConfig")))
{
Debug.Log($"[{TAG}] ModConfig 相关程序集: {assembly.FullName}");
}
return null;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 程序集扫描失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 安全地添加选项变更事件委托
/// Safely add options changed event delegate
/// </summary>
/// <param name="action">事件处理委托,参数为变更的选项键名</param>
/// <returns>是否成功添加</returns>
public static bool SafeAddOnOptionsChangedDelegate(Action<string> action)
{
if (!Initialize())
return false;
if (action == null)
{
Debug.LogWarning($"[{TAG}] 不能添加空的事件委托");
return false;
}
try
{
var method = modBehaviourType.GetMethod("AddOnOptionsChangedDelegate", BindingFlags.Public | BindingFlags.Static);
method.Invoke(null, new object[] { action });
Debug.Log($"[{TAG}] 成功添加选项变更事件委托");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 添加选项变更事件委托失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 安全地移除选项变更事件委托
/// Safely remove options changed event delegate
/// </summary>
/// <param name="action">要移除的事件处理委托</param>
/// <returns>是否成功移除</returns>
public static bool SafeRemoveOnOptionsChangedDelegate(Action<string> action)
{
if (!Initialize())
return false;
if (action == null)
{
Debug.LogWarning($"[{TAG}] 不能移除空的事件委托");
return false;
}
try
{
var method = modBehaviourType.GetMethod("RemoveOnOptionsChangedDelegate", BindingFlags.Public | BindingFlags.Static);
method.Invoke(null, new object[] { action });
Debug.Log($"[{TAG}] 成功移除选项变更事件委托");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 移除选项变更事件委托失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 安全地添加下拉列表配置项
/// Safely add dropdown list configuration item
/// </summary>
public static bool SafeAddDropdownList(string modName, string key, string description, System.Collections.Generic.SortedDictionary<string, object> options, Type valueType, object defaultValue)
{
key = $"{modName}_{key}";
if (!Initialize())
return false;
try
{
var method = modBehaviourType.GetMethod("AddDropdownList", BindingFlags.Public | BindingFlags.Static);
method.Invoke(null, new object[] { modName, key, description, options, valueType, defaultValue });
Debug.Log($"[{TAG}] 成功添加下拉列表: {modName}.{key}");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 添加下拉列表失败 {modName}.{key}: {ex.Message}");
return false;
}
}
/// <summary>
/// 安全地添加带滑条的输入框配置项
/// Safely add input box with slider configuration item
/// </summary>
public static bool SafeAddInputWithSlider(string modName, string key, string description, Type valueType, object defaultValue, UnityEngine.Vector2? sliderRange = null)
{
key = $"{modName}_{key}";
if (!Initialize())
return false;
try
{
var method = modBehaviourType.GetMethod("AddInputWithSlider", BindingFlags.Public | BindingFlags.Static);
// 处理可空参数
// Handle nullable parameters
var parameters = sliderRange.HasValue ?
new object[] { modName, key, description, valueType, defaultValue, sliderRange.Value } :
new object[] { modName, key, description, valueType, defaultValue, null };
method.Invoke(null, parameters);
Debug.Log($"[{TAG}] 成功添加滑条输入框: {modName}.{key}");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 添加滑条输入框失败 {modName}.{key}: {ex.Message}");
return false;
}
}
/// <summary>
/// 安全地添加布尔下拉列表配置项
/// Safely add boolean dropdown list configuration item
/// </summary>
public static bool SafeAddBoolDropdownList(string modName, string key, string description, bool defaultValue)
{
key = $"{modName}_{key}";
if (!Initialize())
return false;
try
{
var method = modBehaviourType.GetMethod("AddBoolDropdownList", BindingFlags.Public | BindingFlags.Static);
method.Invoke(null, new object[] { modName, key, description, defaultValue });
Debug.Log($"[{TAG}] 成功添加布尔下拉列表: {modName}.{key}");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 添加布尔下拉列表失败 {modName}.{key}: {ex.Message}");
return false;
}
}
/// <summary>
/// 安全地加载配置值
/// Safely load configuration value
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
/// <param name="key">配置键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>加载的值或默认值</returns>
public static T SafeLoad<T>(string mod_name, string key, T defaultValue = default(T))
{
key = $"{mod_name}_{key}";
if (!Initialize())
return defaultValue;
if (string.IsNullOrEmpty(key))
{
Debug.LogWarning($"[{TAG}] 配置键不能为空");
return defaultValue;
}
try
{
var loadMethod = optionsManagerType.GetMethod("Load", BindingFlags.Public | BindingFlags.Static);
if (loadMethod == null)
{
Debug.LogError($"[{TAG}] 未找到 OptionsManager_Mod.Load 方法");
return defaultValue;
}
// 获取泛型方法
var genericLoadMethod = loadMethod.MakeGenericMethod(typeof(T));
var result = genericLoadMethod.Invoke(null, new object[] { key, defaultValue });
Debug.Log($"[{TAG}] 成功加载配置: {key} = {result}");
return (T)result;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 加载配置失败 {key}: {ex.Message}");
return defaultValue;
}
}
/// <summary>
/// 安全地保存配置值
/// Safely save configuration value
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
/// <param name="key">配置键</param>
/// <param name="value">要保存的值</param>
/// <returns>是否保存成功</returns>
public static bool SafeSave<T>(string mod_name, string key, T value)
{
key = $"{mod_name}_{key}";
if (!Initialize())
return false;
if (string.IsNullOrEmpty(key))
{
Debug.LogWarning($"[{TAG}] 配置键不能为空");
return false;
}
try
{
var saveMethod = optionsManagerType.GetMethod("Save", BindingFlags.Public | BindingFlags.Static);
if (saveMethod == null)
{
Debug.LogError($"[{TAG}] 未找到 OptionsManager_Mod.Save 方法");
return false;
}
// 获取泛型方法
var genericSaveMethod = saveMethod.MakeGenericMethod(typeof(T));
genericSaveMethod.Invoke(null, new object[] { key, value });
Debug.Log($"[{TAG}] 成功保存配置: {key} = {value}");
return true;
}
catch (Exception ex)
{
Debug.LogError($"[{TAG}] 保存配置失败 {key}: {ex.Message}");
return false;
}
}
/// <summary>
/// 检查 ModConfig 是否可用
/// Check if ModConfig is available
/// </summary>
public static bool IsAvailable()
{
return Initialize();
}
/// <summary>
/// 获取 ModConfig 版本信息(如果存在)
/// Get ModConfig version information (if exists)
/// </summary>
public static string GetVersionInfo()
{
if (!Initialize())
return "ModConfig 未加载 | ModConfig not loaded";
try
{
// 尝试获取版本信息(如果 ModBehaviour 有相关字段或属性)
// Try to get version information (if ModBehaviour has related fields or properties)
var versionField = modBehaviourType.GetField("VERSION", BindingFlags.Public | BindingFlags.Static);
if (versionField != null && versionField.FieldType == typeof(int))
{
var modConfigVersion = (int)versionField.GetValue(null);
var compatibility = (modConfigVersion == ModConfigVersion) ? "兼容" : "不兼容";
return $"ModConfig v{modConfigVersion} (API v{ModConfigVersion}, {compatibility})";
}
var versionProperty = modBehaviourType.GetProperty("VERSION", BindingFlags.Public | BindingFlags.Static);
if (versionProperty != null)
{
var versionValue = versionProperty.GetValue(null);
return versionValue?.ToString() ?? "未知版本 | Unknown version";
}
return "ModConfig 已加载(版本信息不可用) | ModConfig loaded (version info unavailable)";
}
catch
{
return "ModConfig 已加载(版本检查失败) | ModConfig loaded (version check failed)";
}
}
/// <summary>
/// 检查版本兼容性
/// Check version compatibility
/// </summary>
public static bool IsVersionCompatible()
{
if (!Initialize())
return false;
return isVersionCompatible;
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using UnityEngine; // 假设在Unity环境中使用Debug.LogError
@@ -9,78 +11,140 @@ namespace HitFeedback
public KeyCode hotKey = KeyCode.F8;
public Dictionary<string, float> probability = new Dictionary<string, float>();
/// <summary>
/// 当伤害具有这些特性之一时,将播放音频反馈。
/// </summary>
public HashSet<DamageFeature> audioDamageFeatures = new HashSet<DamageFeature>();
public void LoadConfig(string filename)
{
if (!File.Exists(filename))
{
Debug.LogError($"Config file not found: {filename}");
return; // 如果文件不存在,就没必要继续了
return;
}
// 清空旧的概率数据,确保每次加载都是从新开始
probability.Clear();
audioDamageFeatures.Clear(); // 清空旧的音频伤害特性数据
try
{
using (var sr = new StreamReader(filename))
{
string line;
var lineNumber = 0; // 用于错误报告
var lineNumber = 0;
string currentSection = ""; // 用于解析节
while ((line = sr.ReadLine()) != null)
{
lineNumber++;
line = line.Trim(); // 移除行首尾的空白字符
line = line.Trim();
// 忽略空行和注释行
if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#"))
{
continue;
}
// 处理节标题,例如 [General] 或 [AudioFeatures]
if (line.StartsWith("[") && line.EndsWith("]"))
{
currentSection = line.Substring(1, line.Length - 2).Trim();
continue; // 跳过节标题行
}
// 查找等号
var separatorIndex = line.IndexOf('=');
if (separatorIndex == -1)
{
Debug.LogWarning($"Skipping malformed line in config file '{filename}' at line {lineNumber}: No '=' found. Line: '{line}'");
Debug.LogWarning(
$"Skipping malformed line in config file '{filename}' at line {lineNumber}: No '=' found. Line: '{line}'");
continue;
}
var key = line.Substring(0, separatorIndex).Trim();
var valueStr = line.Substring(separatorIndex + 1).Trim();
// 解析 hotKey
if (key.Equals("hotKey", System.StringComparison.OrdinalIgnoreCase))
if (currentSection.Equals("General", StringComparison.OrdinalIgnoreCase))
{
try
// 解析 hotKey
if (key.Equals("hotKey", StringComparison.OrdinalIgnoreCase))
{
hotKey = (KeyCode)System.Enum.Parse(typeof(KeyCode), valueStr, true);
}
catch (System.ArgumentException)
{
Debug.LogError($"Invalid KeyCode '{valueStr}' in config file '{filename}' at line {lineNumber}. Using default F8.");
hotKey = KeyCode.F8; // 设置为默认值或保持不变
try
{
hotKey = (KeyCode)Enum.Parse(typeof(KeyCode), valueStr, true);
}
catch (ArgumentException)
{
Debug.LogError(
$"Invalid KeyCode '{valueStr}' in config file '{filename}' at line {lineNumber}. Using default F8.");
hotKey = KeyCode.F8;
}
}
}
// 解析 probability 字典项
else
else if (currentSection.Equals("Probabilities", StringComparison.OrdinalIgnoreCase))
{
if (float.TryParse(valueStr, out var probValue))
// 解析 probability 字典项
if (float.TryParse(valueStr, NumberStyles.Float, CultureInfo.InvariantCulture,
out var probValue))
{
probability[key] = probValue;
}
else
{
Debug.LogWarning($"Invalid float value '{valueStr}' for key '{key}' in config file '{filename}' at line {lineNumber}. Skipping entry.");
Debug.LogWarning(
$"Invalid float value '{valueStr}' for key '{key}' in config file '{filename}' at line {lineNumber}. Skipping entry.");
}
}
else if (currentSection.Equals("AudioFeatures", StringComparison.OrdinalIgnoreCase))
{
// 解析 audioDamageFeatures 集合项
// 键是 'feature' (或者可以随意定义,只要值是我们关心的)
// 值是 ExtendedDamageFeature 的枚举名称
if (key.Equals("feature", StringComparison.OrdinalIgnoreCase)) // 假设所有特征都用同一个键"feature"
{
foreach (var featureName in valueStr.Split(new char[] { ',', '|' },
StringSplitOptions.RemoveEmptyEntries))
{
try
{
// 确保 ExtendedDamageFeature 是 [Flags] 枚举
var feature = (DamageFeature)Enum.Parse(typeof(DamageFeature),
featureName.Trim(), true);
audioDamageFeatures.Add(feature);
}
catch (ArgumentException)
{
Debug.LogWarning(
$"Invalid ExtendedDamageFeature '{featureName.Trim()}' in config file '{filename}' at line {lineNumber}. Skipping entry.");
}
}
}
else
{
try
{
var feature =
(DamageFeature)Enum.Parse(typeof(DamageFeature), key, true);
if (bool.TryParse(valueStr, out bool includeFeature) && includeFeature)
{
audioDamageFeatures.Add(feature);
}
// 如果是 false则不添加到集合或者可以从集合中移除如果默认是都包含
}
catch (ArgumentException)
{
Debug.LogWarning(
$"Invalid ExtendedDamageFeature key '{key}' in config file '{filename}' at line {lineNumber}. Skipping entry.");
}
}
}
// 如果存在其他节,可以在这里添加 else if 处理
}
}
}
catch (System.Exception ex)
catch (Exception ex)
{
Debug.LogError($"Error reading config file '{filename}': {ex.Message}");
}
}
/// <summary>
/// 将当前配置存储到指定INI文件。
/// </summary>
@@ -89,17 +153,17 @@ namespace HitFeedback
{
try
{
// 确保目录存在
var directory = Path.GetDirectoryName(filename);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (var sw = new StreamWriter(filename))
{
sw.WriteLine("; HitFeedback Configuration File");
sw.WriteLine("; Generated by HitFeedback.Config class");
sw.WriteLine(); // 空行
sw.WriteLine();
sw.WriteLine("[General]");
sw.WriteLine($"hotKey = {hotKey.ToString()}");
sw.WriteLine();
@@ -108,22 +172,113 @@ namespace HitFeedback
sw.WriteLine("[Probabilities]");
foreach (var kvp in probability)
{
// 使用 InvariantCulture 确保浮点数的格式在不同区域设置下都一致
sw.WriteLine($"{kvp.Key} = {kvp.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)}");
sw.WriteLine($"{kvp.Key} = {kvp.Value.ToString(CultureInfo.InvariantCulture)}");
}
}
else
{
sw.WriteLine("; No probabilities currently configured.");
}
sw.WriteLine();
if (audioDamageFeatures.Count > 0)
{
sw.WriteLine("[AudioFeatures]");
// 假设每个特性一行,值为 true
foreach (var feature in audioDamageFeatures)
{
sw.WriteLine($"{feature.ToString()} = True");
}
// 或者如果你想用一个键存储所有特性(用逗号或竖线分隔)
// sw.WriteLine($"features = {string.Join(",", audioDamageFeatures.Select(f => f.ToString()))}");
}
else
{
sw.WriteLine("; No audio damage features currently configured.");
}
sw.WriteLine();
}
Debug.Log($"Config saved to: {filename}");
}
catch (System.Exception ex)
catch (Exception ex)
{
Debug.LogError($"Error saving config file '{filename}': {ex.Message}");
}
}
/// <summary>
/// 根据DamageInfo的特性判断是否应该播放音频反馈。
/// 如果DamageInfo的任何一个特性在audioDamageFeatures集合中则返回true。
/// </summary>
/// <param name="damageInfo">要检查的DamageInfo对象。</param>
/// <returns>如果应该播放音频反馈则为true否则为false。</returns>
public bool ShouldPlayAudioFeedback(DamageInfo damageInfo)
{
if (audioDamageFeatures.Count == 0)
{
return false;
}
// 将DamageInfo的各种布尔属性/条件转换为DamageFeature组合
DamageFeature currentDamageFeatures = DamageFeature.Undefined;
if (damageInfo.damageType == DamageTypes.normal)
{
currentDamageFeatures |= DamageFeature.NormalDamage;
}
else if (damageInfo.damageType == DamageTypes.realDamage)
{
currentDamageFeatures |= DamageFeature.RealDamage;
}
if (damageInfo.isFromBuffOrEffect)
{
currentDamageFeatures |= DamageFeature.BuffOrEffectDamage;
}
if (damageInfo.ignoreArmor)
{
currentDamageFeatures |= DamageFeature.ArmorIgnoringDamage;
}
if (damageInfo.crit > 0) // crit > 0 表示是暴击
{
currentDamageFeatures |= DamageFeature.CriticalDamage;
}
if (damageInfo.armorPiercing > 0)
{
currentDamageFeatures |= DamageFeature.ArmorPiercingDamage;
}
if (damageInfo.isExplosion)
{
currentDamageFeatures |= DamageFeature.ExplosionDamage;
}
if (damageInfo.armorBreak > 0)
{
currentDamageFeatures |= DamageFeature.ArmorBreakingDamage;
}
if (damageInfo.elementFactors != null && damageInfo.elementFactors.Count > 0)
{
currentDamageFeatures |= DamageFeature.ElementalDamage;
}
if (damageInfo.buffChance > 0 || damageInfo.buff != null)
{
currentDamageFeatures |= DamageFeature.OnHitBuffApply;
}
if (damageInfo.bleedChance > 0)
{
currentDamageFeatures |= DamageFeature.OnHitBleed;
}
foreach (var configuredFeature in audioDamageFeatures)
{
if (configuredFeature == DamageFeature.Undefined)
{
continue;
}
if ((currentDamageFeatures & configuredFeature) == configuredFeature)
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
namespace HitFeedback
{
[Flags]
public enum DamageFeature
{
/// <summary>
/// 未指定或未分类的伤害特性。
/// </summary>
Undefined = 0,
/// <summary>
/// 普通物理伤害或其他未特别定义的伤害(对应 DamageTypes.normal
/// </summary>
NormalDamage = 1,
/// <summary>
/// 真实伤害,不受护甲或其他减伤效果影响(对应 DamageTypes.realDamage
/// </summary>
RealDamage = 2,
/// <summary>
/// 伤害来自增益Buff或持续效果。
/// (基于 DamageInfo.isFromBuffOrEffect)
/// </summary>
BuffOrEffectDamage = 4,
/// <summary>
/// 伤害无视目标的护甲。
/// (基于 DamageInfo.ignoreArmor)
/// </summary>
ArmorIgnoringDamage = 8,
/// <summary>
/// 伤害是暴击伤害。
/// (基于 DamageInfo.crit > 0)
/// </summary>
CriticalDamage = 16,
/// <summary>
/// 伤害包含护甲穿透效果。
/// (基于 DamageInfo.armorPiercing > 0)
/// </summary>
ArmorPiercingDamage = 32,
/// <summary>
/// 伤害是爆炸类型。
/// (基于 DamageInfo.isExplosion)
/// </summary>
ExplosionDamage = 64,
/// <summary>
/// 伤害具有护甲破坏效果。
/// (基于 DamageInfo.armorBreak > 0)
/// </summary>
ArmorBreakingDamage = 128,
/// <summary>
/// 伤害可能带有元素效果(如果有 elementFactors 存在且非空)。
/// </summary>
ElementalDamage = 256,
/// <summary>
/// 伤害可能附带Buff效果。
/// (基于 DamageInfo.buffChance > 0 或 buff != null)
/// </summary>
/// <remarks>
/// 注意:这个可以与 BuffOrEffectDamage 区分BuffOrEffectDamage 是伤害本身来自Buff
/// 而นี้是伤害造成时附带施加Buff的效果。
/// </remarks>
OnHitBuffApply = 512,
/// <summary>
/// 伤害可能附带流血效果。
/// (基于 DamageInfo.bleedChance > 0)
/// </summary>
OnHitBleed = 1024,
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using Duckov;
using HitFeedback.Api;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
@@ -11,24 +12,26 @@ using Random = UnityEngine.Random;
namespace HitFeedback
{
public class ModBehaviour:Duckov.Modding.ModBehaviour
public class ModBehaviour : Duckov.Modding.ModBehaviour
{
public const string AudioFolderName = "audio";
public const string ConfigFileName = "config.ini";
public string audioFolderPath;
public string configFilePath;
public Dictionary<string,float> audioFilePath = new Dictionary<string,float>();
public Dictionary<string, float> audioProbability = new Dictionary<string, float>();
public Health health;
public Config config=new Config();
public Config config = new Config();
public float totalWeight;
public const string MOD_SETTING_NAME = "受击反馈";
private void Update()
{
if (Input.GetKeyDown(KeyCode.F8))
if (Input.GetKeyDown(config.hotKey))
{
PlayRandomAudioClip();
}
@@ -37,16 +40,16 @@ namespace HitFeedback
protected override void OnAfterSetup()
{
LevelManager.OnLevelInitialized += OnSceneLoaded;
audioFolderPath=Path.Combine(info.path,AudioFolderName);
configFilePath=Path.Combine(info.path, ConfigFileName);
audioFolderPath = Path.Combine(info.path, AudioFolderName);
configFilePath = Path.Combine(info.path, ConfigFileName);
FindWavFiles();
config.LoadConfig(configFilePath);
ApplyConfig();
foreach (var f in audioFilePath)
{
totalWeight+=f.Value;
}
InitializeSetting();
UpdateTotalWeight();
}
protected override void OnBeforeDeactivate()
{
LevelManager.OnLevelInitialized -= OnSceneLoaded;
@@ -58,13 +61,22 @@ namespace HitFeedback
SaveConfig();
}
private void UpdateTotalWeight()
{
totalWeight = 0;
foreach (var f in audioProbability)
{
totalWeight += f.Value;
}
}
private void ApplyConfig()
{
foreach (var f in config.probability)
{
if (audioFilePath.ContainsKey(f.Key))
if (audioProbability.ContainsKey(f.Key))
{
audioFilePath[f.Key] = f.Value;
audioProbability[f.Key] = f.Value;
}
}
}
@@ -72,28 +84,36 @@ namespace HitFeedback
private void SaveConfig()
{
config.probability.Clear();
foreach (var f in audioFilePath)
foreach (var f in audioProbability)
{
config.probability.Add(f.Key, f.Value);
}
config.SaveConfig(configFilePath);
}
private void FindWavFiles()
{
audioFilePath.Clear();
audioProbability.Clear();
if (!Directory.Exists(audioFolderPath))
{
return;
}
try
{
string[] files = Directory.GetFiles(audioFolderPath, "*.wav", SearchOption.TopDirectoryOnly);
if (files.Length > 0)
var audioFiles = new List<string>();
string[] wavFiles = Directory.GetFiles(audioFolderPath, "*.wav", SearchOption.TopDirectoryOnly);
audioFiles.AddRange(wavFiles);
string[] mp3Files = Directory.GetFiles(audioFolderPath, "*.mp3", SearchOption.TopDirectoryOnly);
audioFiles.AddRange(mp3Files);
string[] oggFiles = Directory.GetFiles(audioFolderPath, "*.ogg", SearchOption.TopDirectoryOnly);
audioFiles.AddRange(oggFiles);
if (audioFiles.Count > 0)
{
foreach (string filePath in files)
foreach (var filePath in audioFiles)
{
audioFilePath.Add(Path.GetFileName(filePath), 1);
audioProbability.Add(Path.GetFileName(filePath), 1);
}
}
}
@@ -120,8 +140,10 @@ namespace HitFeedback
{
if (health)
{
health.OnHurtEvent.RemoveListener(OnHurtEvent);
}
health = CharacterMainControl.Main?.Health;
if (health)
{
@@ -132,15 +154,16 @@ namespace HitFeedback
private void OnHurtEvent(DamageInfo damageInfo)
{
PlayRandomAudioClip();
if (config.ShouldPlayAudioFeedback(damageInfo))
PlayRandomAudioClip();
}
public void PlayRandomAudioClip()
{
if (audioFilePath.Count > 0)
if (audioProbability.Count > 0)
{
var randomIndex = Random.Range(0, totalWeight);
foreach (var f in audioFilePath)
foreach (var f in audioProbability)
{
randomIndex -= f.Value;
if (randomIndex <= 0)
@@ -155,5 +178,182 @@ namespace HitFeedback
Debug.LogWarning("Mod Feedback: No audio clips loaded to play.");
}
}
public void InitializeSetting()
{
if (!Api.ModConfigAPI.Initialize())
{
return;
}
foreach (var audio in audioProbability)
{
ModConfigAPI.SafeAddInputWithSlider(MOD_SETTING_NAME, audio.Key, $"音频\"{audio.Key}\"播放概率",
typeof(float), audio.Value, new Vector2(0, 100));
}
foreach (DamageFeature value in Enum.GetValues(typeof(DamageFeature)))
{
ModConfigAPI.SafeAddBoolDropdownList(MOD_SETTING_NAME, value.ToString(),
$"受到{ToSingleFeatureChineseString(value)}时触发",
config.audioDamageFeatures.Contains(value));
}
var hotkeyOptions = GenerateCommonKeyCodeOptions();
ModConfigAPI.SafeAddDropdownList(
MOD_SETTING_NAME,
"hotKey",
"主动触发的热键",
hotkeyOptions,
typeof(int),
config.hotKey
);
ModConfigAPI.SafeAddOnOptionsChangedDelegate(OnConfigChange);
}
private void OnConfigChange(string key)
{
key = key[(MOD_SETTING_NAME.Length + 1)..];
if (key == "hotKey")
{
config.hotKey = (KeyCode)ModConfigAPI.SafeLoad(MOD_SETTING_NAME, key, (int)(config.hotKey));
return;
}
if (audioProbability.ContainsKey(key))
{
var value = ModConfigAPI.SafeLoad(MOD_SETTING_NAME, key, audioProbability[key]);
audioProbability[key] = value;
}
if (Enum.TryParse(key, out DamageFeature damageInfo))
{
var current=config.audioDamageFeatures.Contains(damageInfo);
if (ModConfigAPI.SafeLoad(MOD_SETTING_NAME, key, current))
{
config.audioDamageFeatures.Add(damageInfo);
}
else if (current)
{
config.audioDamageFeatures.Remove(damageInfo);
}
}
UpdateTotalWeight();
}
/// <summary>
/// 生成包含常用 KeyCode 的 SortedDictionary。
/// </summary>
/// <returns>一个 SortedDictionary键是 KeyCode 的字符串表示,值是 KeyCode 枚举本身。</returns>
private static SortedDictionary<string, object> GenerateCommonKeyCodeOptions()
{
var options = new SortedDictionary<string, object>();
// 字母键
for (var c = 'A'; c <= 'Z'; c++)
{
var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), c.ToString());
options.Add(c.ToString(), keyCode);
}
// 数字键(主键盘)
for (var i = 0; i <= 9; i++)
{
var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), "Alpha" + i.ToString());
options.Add(i.ToString(), keyCode);
}
// 数字键盘
for (var i = 0; i <= 9; i++)
{
var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), "Keypad" + i.ToString());
options.Add($"Num_{i}", keyCode); // 加前缀区分主键盘数字
}
// 功能键
for (var i = 1; i <= 12; i++)
{
var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), "F" + i.ToString());
options.Add($"F{i}", keyCode);
}
// 常用控制键
options.Add("空格", (int)KeyCode.Space);
options.Add("回车", (int)KeyCode.Return);
options.Add("Esc", (int)KeyCode.Escape);
options.Add("Shift (左)", (int)KeyCode.LeftShift);
options.Add("Shift (右)", (int)KeyCode.RightShift);
options.Add("Ctrl (左)", (int)KeyCode.LeftControl);
options.Add("Ctrl (右)", (int)KeyCode.RightControl);
options.Add("Alt (左)", (int)KeyCode.LeftAlt);
options.Add("Alt (右)", (int)KeyCode.RightAlt);
options.Add("Tab", (int)KeyCode.Tab);
options.Add("Backspace", (int)KeyCode.Backspace);
options.Add("Delete", (int)KeyCode.Delete);
options.Add("Home", (int)KeyCode.Home);
options.Add("End", (int)KeyCode.End);
options.Add("PageUp", (int)KeyCode.PageUp);
options.Add("PageDown", (int)KeyCode.PageDown);
options.Add("插入", (int)KeyCode.Insert);
// 方向键
options.Add("向上", (int)KeyCode.UpArrow);
options.Add("向下", (int)KeyCode.DownArrow);
options.Add("向左", (int)KeyCode.LeftArrow);
options.Add("向右", (int)KeyCode.RightArrow);
// 鼠标按键
options.Add("鼠标左键", (int)KeyCode.Mouse0);
options.Add("鼠标右键", (int)KeyCode.Mouse1);
options.Add("鼠标中键", (int)KeyCode.Mouse2);
// 其他一些常用键
options.Add("~", (int)KeyCode.BackQuote);
options.Add("-", (int)KeyCode.Minus);
options.Add("=", (int)KeyCode.Equals);
options.Add("[", (int)KeyCode.LeftBracket);
options.Add("]", (int)KeyCode.RightBracket);
options.Add("\\", (int)KeyCode.Backslash);
options.Add(";", (int)KeyCode.Semicolon);
options.Add("'", (int)KeyCode.Quote);
options.Add(",", (int)KeyCode.Comma);
options.Add(".", (int)KeyCode.Period);
options.Add("/", (int)KeyCode.Slash);
return options;
}
private static string ToSingleFeatureChineseString(DamageFeature feature)
{
switch (feature)
{
case DamageFeature.Undefined:
return "未指定特性";
case DamageFeature.NormalDamage:
return "普通伤害";
case DamageFeature.RealDamage:
return "真实伤害";
case DamageFeature.BuffOrEffectDamage:
return "增益/效果伤害";
case DamageFeature.ArmorIgnoringDamage:
return "无视护甲伤害";
case DamageFeature.CriticalDamage:
return "暴击伤害";
case DamageFeature.ArmorPiercingDamage:
return "护甲穿透伤害";
case DamageFeature.ExplosionDamage:
return "爆炸伤害";
case DamageFeature.ArmorBreakingDamage:
return "护甲破坏伤害";
case DamageFeature.ElementalDamage:
return "元素伤害";
case DamageFeature.OnHitBuffApply:
return "命中附加增益";
case DamageFeature.OnHitBleed:
return "命中附加流血";
default:
return "未知特性"; // 处理未定义或将来添加的特性
}
}
}
}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("HitFeedback")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")]
[assembly: System.Reflection.AssemblyProductAttribute("HitFeedback")]
[assembly: System.Reflection.AssemblyTitleAttribute("HitFeedback")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
044e9b3ac36cbf242c64f55f40a9077d40727cb97e540d335e1e29dbc7dccff1
fa83406df4fd7108a6156ab0271b4cddce6abc73c66cbcefdb2f1419643481ef

View File

@@ -1 +1 @@
17059da2b4ba38151f18bb4edeeed66662c124b29efddf4c48967bc01f547046
86c7d211b447f2631e25f9656fa8ef4b9b63f5a671cedbbe00854ccaccdd00b3

View File

@@ -1 +1 @@
17620100186005303
17623343068138064

View File

@@ -1,490 +1,465 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.EventSystems; // 用于UI射线检测
using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Collections; // 用于 IEnumerable
using System.Collections.Generic; // 用于 HashSet, List, Dictionary
using System.Linq; // 用于 OrderBy
namespace SceneSnapshot
{
internal class PrintTool : MonoBehaviour
{
private const string FOLDER_NAME = "GameObjectSnapshots";
private const string BASE_FOLDER_NAME = "GameObjectSnapshots"; // 主文件夹名称
private const int MAX_REFLECTION_DEPTH = 3; // 最大反射深度,防止循环引用或过深的对象图
private const int MAX_COLLECTION_ELEMENTS_TO_PRINT = 5; // 集合最多打印的元素数量
private void Update()
{
if (Input.GetKeyDown(KeyCode.F2)) CaptureAndPrintSceneInfo();
if (Input.GetKeyDown(KeyCode.F2))
{
CaptureAndPrintSnapshot();
}
}
private void CaptureAndPrintSceneInfo()
private void CaptureAndPrintSnapshot()
{
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var outputFolderPath = Path.Combine(desktopPath, FOLDER_NAME);
// 获取桌面路径
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
// 1. 创建主文件夹 (如果不存在)
string baseOutputPath = Path.Combine(desktopPath, BASE_FOLDER_NAME);
try
{
if (!Directory.Exists(outputFolderPath))
if (!Directory.Exists(baseOutputPath))
{
Directory.CreateDirectory(outputFolderPath);
Debug.Log($"创建输出文件夹: {outputFolderPath}");
Directory.CreateDirectory(baseOutputPath);
Debug.Log($"创建主快照文件夹: {baseOutputPath}");
}
}
catch (Exception ex)
catch (Exception e)
{
Debug.LogError($"创建文件夹失败: {outputFolderPath} - {ex.Message}");
Debug.LogError($"无法创建主快照文件夹 {baseOutputPath}: {e.Message}");
return;
}
var activeSceneName = SceneManager.GetActiveScene().name;
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var fileName = $"{activeSceneName}_FullSnapshot_{timestamp}.txt"; // 修改文件名以示区别
var fullFilePath = Path.Combine(outputFolderPath, fileName);
var sb = new StringBuilder();
sb.AppendLine("=================================================");
sb.AppendLine($"场景信息快照 - 活跃场景: {activeSceneName}");
sb.AppendLine($"生成时间: {DateTime.Now}");
sb.AppendLine("=================================================");
sb.AppendLine();
sb.AppendLine("--- 鼠标位置对象信息 ---");
AppendMouseHoverObjectInfo(sb);
sb.AppendLine();
sb.AppendLine("--- 所有加载场景的活跃游戏对象层次结构及其组件 ---");
// 遍历所有已加载的场景
for (var i = 0; i < SceneManager.sceneCount; i++)
{
var currentScene = SceneManager.GetSceneAt(i);
// 打印场景名称作为分割线
sb.AppendLine($"\n===== 场景: {currentScene.name} ===== " +
(currentScene == SceneManager.GetActiveScene() ? "(活跃场景)" : ""));
GameObject[] rootObjects = currentScene.GetRootGameObjects();
if (rootObjects.Length == 0)
sb.AppendLine(" - 该场景没有根游戏对象。");
else
foreach (var go in rootObjects)
AppendGameObjectInfo(go, 0, sb);
}
sb.AppendLine("=================================================");
// 2. 在主文件夹内创建带时间戳的子文件夹
string timestampFolderName = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string currentSnapshotOutputPath = Path.Combine(baseOutputPath, timestampFolderName);
try
{
File.WriteAllText(fullFilePath, sb.ToString(), Encoding.UTF8);
Debug.Log($"场景信息已成功保存到: {fullFilePath}");
if (!Directory.Exists(currentSnapshotOutputPath))
{
Directory.CreateDirectory(currentSnapshotOutputPath);
}
}
catch (Exception ex)
catch (Exception e)
{
Debug.LogError($"保存文件失败: {fullFilePath} - {ex.Message}");
}
}
/// <summary>
/// 递归地将游戏对象的名称、活跃状态、组件及其子对象的层次结构追加到StringBuilder。
/// **注意:此方法只会处理活跃状态为 activeSelf 的对象。**
/// </summary>
/// <param name="go">要处理的游戏对象。</param>
/// <param name="indentLevel">当前缩进级别。</param>
/// <param name="sb">StringBuilder实例。</param>
private void AppendGameObjectInfo(GameObject go, int indentLevel, StringBuilder sb)
{
// 只有当对象自身是激活状态时才处理和打印
if (!go || !go.activeSelf) return;
var indent = new string(' ', indentLevel * 4); // 每个层级使用4个空格缩进
// 打印游戏对象名称和活跃状态
sb.AppendLine(
$"{indent}[{go.name}] (ActiveSelf: {go.activeSelf}, ActiveInHierarchy: {go.activeInHierarchy})");
// 打印所有组件
var components = go.GetComponents<Component>();
foreach (var comp in components)
if (comp) // 某些组件可能在运行时被销毁
sb.AppendLine($"{indent} - Component: {comp.GetType().Name}");
// 递归处理子对象
foreach (Transform child in go.transform) AppendGameObjectInfo(child.gameObject, indentLevel + 1, sb);
}
/// <summary>
/// 尝试检测鼠标位置下方的UI元素或场景对象并将其路径和组件信息追加到StringBuilder。
/// </summary>
/// <param name="sb">StringBuilder实例。</param>
private void AppendMouseHoverObjectInfo(StringBuilder sb)
{
// 首先尝试Raycast UI元素
var eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
var results = new List<RaycastResult>();
if (EventSystem.current)
{
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
}
if (results.Count > 0)
{
// UI元素优先级更高
var uiObject = results[0].gameObject;
var uiPath = GetGameObjectPath(uiObject);
sb.AppendLine($"鼠标下方UI路径: {uiPath}");
sb.AppendLine($" - 所在场景: {uiObject.scene.name}");
// 添加UI对象组件信息
sb.AppendLine(" - UI对象组件信息:");
AppendGameObjectComponentInfo(sb, uiObject, " "); // 增加缩进
Debug.LogError($"无法创建快照子文件夹 {currentSnapshotOutputPath}: {e.Message}");
return;
}
// 如果没有UI元素尝试Raycast场景对象
if (Camera.main != null)
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
var sceneObjectPath = GetGameObjectPath(hit.collider.gameObject);
sb.AppendLine($"鼠标下方场景对象路径: {sceneObjectPath}");
sb.AppendLine($" - 所在场景: {hit.collider.gameObject.scene.name}");
Debug.Log($"开始生成场景快照到: {currentSnapshotOutputPath}");
// 添加场景对象组件信息
sb.AppendLine(" - 场景对象组件信息:");
AppendGameObjectComponentInfo(sb, hit.collider.gameObject, " "); // 增加缩进
return;
// Part 1: 打印所有对象的对象树及其组件
PrintAllGameObjectsTree(currentSnapshotOutputPath);
// Part 2: 打印鼠标位置对象的组件值
PrintMouseHoveredObjectDetails(currentSnapshotOutputPath);
Debug.Log("场景快照生成完毕!");
}
/// <summary>
/// 打印所有场景对象的对象树包括DontDestroyOnLoad及其组件。
/// </summary>
private void PrintAllGameObjectsTree(string outputPath)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("--- 所有激活场景对象树 ---");
sb.AppendLine("--------------------------\n");
// 用于存储按场景分组的根对象
var sceneRootGameObjects = new Dictionary<Scene, List<GameObject>>();
// 用于存储 DontDestroyOnLoad 对象
var dontDestroyOnLoadRoots = new List<GameObject>();
// 1. 遍历所有加载的场景,获取其根对象
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
sceneRootGameObjects[scene] = new List<GameObject>(scene.GetRootGameObjects());
}
// 2. 查找 DontDestroyOnLoad 对象
// DontDestroyOnLoad 对象不属于任何通过 SceneManager.GetSceneAt 获取的“普通”场景
// 它们通常在特殊的 "DontDestroyOnLoad" 场景中在Unity编辑器中可见但在运行时无法直接通过 SceneManager.GetSceneAt 访问。
// 因此我们遍历所有活跃的GameObject找出那些是根对象但又不属于任何已知场景的。
GameObject[] allActiveGameObjectsInHierarchy = FindObjectsOfType<GameObject>(); // 获取所有活跃的GameObject
foreach (GameObject go in allActiveGameObjectsInHierarchy)
{
if (go.transform.parent == null) // 这是一个根对象
{
bool foundInLoadedScene = false;
foreach (var kvp in sceneRootGameObjects)
{
if (kvp.Value.Contains(go))
{
foundInLoadedScene = true;
break;
}
}
if (!foundInLoadedScene)
{
// 如果它不是任何已加载场景的根对象那么它可能是DontDestroyOnLoad对象
dontDestroyOnLoadRoots.Add(go);
}
}
}
// 3. 打印普通场景的对象树
foreach (var kvp in sceneRootGameObjects)
{
Scene currentScene = kvp.Key;
List<GameObject> roots = kvp.Value;
sb.AppendLine($"=== 场景: {currentScene.name} (路径: {currentScene.path}, 已加载: {currentScene.isLoaded}) ===\n");
// 按名称排序根对象以保持输出一致性
foreach (GameObject root in roots.OrderBy(g => g.name))
{
PrintGameObjectRecursive(root, 0, sb, new HashSet<GameObject>());
}
}
// 4. 打印 DontDestroyOnLoad 对象的对象树
if (dontDestroyOnLoadRoots.Count > 0)
{
// 检查是否已经有一个伪的 "DontDestroyOnLoad" 场景被 Unity 在某些情境下自动添加
// 如果是,为了避免重复,且让输出更清晰,可以先尝试移除这些。
// 但是在 FindObjectsOfType 之后再分组,这种方式更健壮,不用管它是否有“场景”
sb.AppendLine("\n=== DontDestroyOnLoad 对象 ===\n");
foreach (GameObject root in dontDestroyOnLoadRoots.OrderBy(g => g.name))
{
PrintGameObjectRecursive(root, 0, sb, new HashSet<GameObject>());
}
}
string filePath = Path.Combine(outputPath, "SceneObjectTree.txt");
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); // 使用UTF8编码以支持更多字符
Debug.Log($"场景对象树已保存到: {filePath}");
}
/// <summary>
/// 递归打印GameObject及其子级和组件。
/// </summary>
private void PrintGameObjectRecursive(GameObject go, int depth, StringBuilder sb, HashSet<GameObject> visited)
{
// 防止循环引用或重复打印
if (visited.Contains(go))
{
sb.AppendLine($"{GetIndent(depth)}{go.name} (循环引用检测到!)");
return;
}
visited.Add(go);
string indent = GetIndent(depth);
sb.AppendLine($"{indent}GameObject: {go.name} (激活状态: {go.activeSelf}, 标签: {go.tag}, 层: {LayerMask.LayerToName(go.layer)})");
Component[] components = go.GetComponents<Component>();
foreach (Component comp in components)
{
if (comp == null) continue; // 避免NRE尽管不常见
sb.AppendLine($"{indent} 组件: {comp.GetType().Name}");
}
for (int i = 0; i < go.transform.childCount; i++)
{
PrintGameObjectRecursive(go.transform.GetChild(i).gameObject, depth + 1, sb, visited);
}
// 重要在递归完成后通常不需要从visited中移除GameObject
// 因为一个GameObject在对象树中只会以唯一的路径出现一次
// (除非它以某种非常规方式被引用但这不属于标准GameObject层级)。
// 对于值类型或简单引用可以在PrintObjectProperties中在处理完后移除。
// 对于GameObject层级一旦访问完其所有子节点它在该“分支”的任务就完成了。
// 如果不同根节点下可能会有相同的GameObject引用例如通过Inspector引用
// 那visited集合的作用是防止在*当前递归路径*中再次遇到同一个GameObject从而避免死循环。
// 对于整个场景树的打印visited集合可以保持不变因为我们不期望同一个GameObject作为不同根节点的子物体链中的一部分。
// visited.Remove(go); // 对于GameObject树结构这通常是不必要的因为每个GameObject在树中只有一个父级。
}
/// <summary>
/// 打印鼠标位置对象的组件值。
/// </summary>
private void PrintMouseHoveredObjectDetails(string outputPath)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("--- 鼠标悬停对象详细信息 ---");
sb.AppendLine("----------------------------\n");
GameObject hoveredObject = GetHoveredObject();
if (hoveredObject != null)
{
sb.AppendLine($"悬停的GameObject: {hoveredObject.name} (激活状态: {hoveredObject.activeSelf}, 标签: {hoveredObject.tag}, 层: {LayerMask.LayerToName(hoveredObject.layer)})");
sb.AppendLine($"组件及其值:\n");
Component[] components = hoveredObject.GetComponents<Component>();
foreach (Component comp in components)
{
if (comp == null) continue;
sb.AppendLine($" === 组件: {comp.GetType().Name} ===");
// 使用反射打印组件的字段和属性值
PrintObjectProperties(comp, 0, sb, new HashSet<object>(), " "); // 初始缩进4个空格
sb.AppendLine();
}
}
else
{
sb.AppendLine("警告: 场景中没有主摄像机(Camera.main)或未被标记为 'MainCamera'。无法检测鼠标下的场景对象。");
sb.AppendLine("当前鼠标下方没有对象。");
}
sb.AppendLine("鼠标位置处没有检测到UI元素或场景对象。");
string filePath = Path.Combine(outputPath, "MouseHoverObjectDetails.txt");
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); // 使用UTF8编码以支持更多字符
Debug.Log($"鼠标悬停对象详情已保存到: {filePath}");
}
/// <summary>
/// 为指定的GameObject追加其所有组件的信息
/// 获取鼠标下方的GameObject优先UI其次3D场景对象
/// </summary>
/// <param name="sb">StringBuilder实例。</param>
/// <param name="obj">目标GameObject。</param>
/// <param name="indent">当前的缩进字符串。</param>
private void AppendGameObjectComponentInfo(StringBuilder sb, GameObject obj, string indent)
private GameObject GetHoveredObject()
{
Component[] components = obj.GetComponents<Component>();
if (components.Length == 0)
// 优先检测UI对象
if (EventSystem.current != null)
{
sb.AppendLine($"{indent} - 无组件");
PointerEventData eventData = new PointerEventData(EventSystem.current);
eventData.position = Input.mousePosition;
List<RaycastResult> uiRaycastResults = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, uiRaycastResults);
// 过滤掉非 interactable 的UI元素或者不包含 CanvasRenderer 的元素可能更关注可见和可交互的UI
foreach (var result in uiRaycastResults)
{
if (result.gameObject != null && result.gameObject.GetComponent<CanvasRenderer>() != null)
{
return result.gameObject; // 返回第一个有效的UI元素
}
}
}
else
{
Debug.LogWarning("场景中没有EventSystem无法检测UI对象。请确保场景中存在一个EventSystem GameObject。");
}
// 如果没有UI对象则检测3D场景对象
if (Camera.main != null)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 仅检测默认层,或可配置的层
if (Physics.Raycast(ray, out RaycastHit hit))
{
return hit.collider.gameObject;
}
}
else
{
Debug.LogWarning("场景中没有主摄像机(Tagged 'MainCamera')无法进行3D射线检测。请确保主摄像机正确标记。");
}
return null;
}
/// <summary>
/// 使用反射递归打印对象的字段和属性值。
/// </summary>
/// <param name="obj">要打印的对象。</param>
/// <param name="currentDepth">当前反射深度。</param>
/// <param name="sb">StringBuilder用于构建输出。</param>
/// <param name="visitedObjects">用于检测循环引用的已访问对象集合。</param>
/// <param name="indent">当前缩进字符串。</param>
private void PrintObjectProperties(object obj, int currentDepth, StringBuilder sb, HashSet<object> visitedObjects, string indent)
{
if (obj == null)
{
sb.AppendLine("null");
return;
}
foreach (var component in components)
Type type = obj.GetType();
// 1. 处理基本类型、字符串、枚举
// 注意string是引用类型但行为上通常被视为值类型其ToString是其自身
if (type.IsPrimitive || obj is string || type.IsEnum || obj is decimal || obj is DateTime)
{
if (component == null)
{
sb.AppendLine($"{indent} - (空组件)");
continue;
}
// 尝试获取Behaviour的enabled状态
string enabledStatus = (component is Behaviour behaviour) ? behaviour.enabled.ToString() : "N/A";
sb.AppendLine($"{indent} - 组件: {component.GetType().Name} (Enabled: {enabledStatus})");
AppendComponentPropertiesAndFields(sb, component, indent + " "); // 更深一层缩进,显示组件的属性/字段
}
}
/// <summary>
/// 利用反射为指定的组件追加其公共属性和字段的信息。
/// </summary>
/// <param name="sb">StringBuilder实例。</param>
/// <param name="component">目标组件。</param>
/// <param name="indent">当前的缩进字符串。</param>
private void AppendComponentPropertiesAndFields(StringBuilder sb, Component component, string indent)
{
var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
var componentType = component.GetType();
// 收集所有公共实例属性和字段
var members = new List<MemberInfo>();
members.AddRange(componentType.GetProperties(bindingFlags));
members.AddRange(componentType.GetFields(bindingFlags));
bool hasPrintedAnything = false;
foreach (var member in members)
{
// 排除一些常见或特定组件上可能导致冗余或问题的属性/字段
if (IsMemberToExclude(member, component)) continue;
object value = null;
string memberName = member.Name;
try
{
if (member is PropertyInfo pi)
{
if (pi.CanRead) // 确保属性可读
{
value = pi.GetValue(component);
}
else
{
// 忽略不可读的属性
continue;
}
}
else if (member is FieldInfo fi)
{
value = fi.GetValue(component);
}
}
catch (Exception ex)
{
// 捕获获取值时可能发生的异常
sb.AppendLine($"{indent}- {memberName}: <获取失败: {ex.GetType().Name}>");
hasPrintedAnything = true;
continue;
}
// 格式化值进行显示初始深度为0
string formattedValue = FormatValueForDisplay(value, 0);
sb.AppendLine($"{indent}- {memberName}: {formattedValue}");
hasPrintedAnything = true;
sb.AppendLine($"({type.Name}) {obj}"); // 显示类型名,更清晰
return;
}
if (!hasPrintedAnything)
// 2. 处理常见的Unity值类型如Vector3, Quaternion, Color, Rect等
// 这些类型通常有很好的ToString()方法,且不应过度深入反射其内部字段
if (obj is Vector2 || obj is Vector3 || obj is Vector4 || obj is Quaternion ||
obj is Color || obj is Color32 || obj is Rect || obj is Bounds ||
obj is AnimationCurve || obj is LayerMask || obj is Matrix4x4)
{
sb.AppendLine($"{indent}- (无公共属性或字段)");
sb.AppendLine($"({type.Name}) {obj}"); // 显示类型名,更清晰
return;
}
}
/// <summary>
/// 判断一个成员是否应该被排除在打印列表之外。
/// 用于过滤掉冗余或可能导致深度递归的成员。
/// </summary>
/// <param name="member">要检查的MemberInfo。</param>
/// <param name="component">该成员所属的组件实例。</param>
/// <returns>如果应排除则返回true。</returns>
private bool IsMemberToExclude(MemberInfo member, Component component)
{
// 排除从UnityEngine.Object继承的常见属性这些通常是GameObject级别的元数据
// 或者可能导致不必要的递归。
// 特别是gameObject和transform它们的类型就是GameObject和Transform递归它们没有意义
// 且其值就是组件所依附的GameObject和Transform已经通过GetGameObjectPath显示了。
switch (member.Name)
// 3. 处理UnityEngine.Object类型但不是Component或GameObject本身
// 例如 Material, Texture, ScriptableObject等通常只打印其名称或ToString()就足够
if (typeof(UnityEngine.Object).IsAssignableFrom(type) && !(obj is Component) && !(obj is GameObject))
{
case "hideFlags": // Unity内部的标志通常不需要显示
case "name": // GameObject的名称已在路径中显示
case "tag": // GameObject的标签可从GameObject直接获取
case "layer": // GameObject的层可从GameObject直接获取
case "useGUILayout": // Unity内部GUI相关的通常不作为组件值关心
case "runInEditMode": // Unity编辑器模式相关通常不作为组件值关心
// 对于Component基类上的gameObject和transform属性它们直接指向宿主对象和其Transform。
// 打印它们本身就是重复的且可能误导(不是组件内部的独特“值”)。
case "gameObject":
case "transform":
case "isStatic": // GameObject的isStatic状态
return true;
// 对于这些Unity对象ToString()通常会返回对象名和类型,足够了
sb.AppendLine($"({type.Name}) {obj.ToString()}");
return;
}
// 进一步过滤掉一些Unity内部或编辑器相关的属性这些属性通常在运行时不提供有用的组件值信息。
if (member.DeclaringType == typeof(Behaviour) || member.DeclaringType == typeof(MonoBehaviour))
{
switch (member.Name)
{
case "isActiveAndEnabled": // 行为体的激活状态通常与enabled一起考虑
return true;
}
}
return false;
}
/// <summary>
/// 格式化对象值以进行显示特别是针对Unity的常见类型以避免循环打印和提供简洁输出。
/// </summary>
/// <param name="value">要格式化的值。</param>
/// <param name="currentDepth">当前的递归深度。</param>
/// <returns>格式化后的字符串。</returns>
private string FormatValueForDisplay(object value, int currentDepth = 0)
{
if (value == null) return "null";
Type type = value.GetType();
// 深度限制检查:如果超过最大深度,则返回提示信息
// 4. 检查最大反射深度
if (currentDepth >= MAX_REFLECTION_DEPTH)
{
// 对于集合,提供更具体一些的信息
if (value is Array array1) return $"Array (Count: {array1.Length}, Max Depth Reached)";
if (value is IList list1) return $"List (Count: {list1.Count}, Max Depth Reached)";
if (value is IDictionary dictionary) return $"Dictionary (Count: {dictionary.Count}, Max Depth Reached)";
return $"[{type.Name} (Max Depth Reached)]";
sb.AppendLine($"{indent}... (达到最大反射深度)");
return;
}
// 1. 基本类型、字符串、枚举直接ToString()
if (type.IsPrimitive || type == typeof(string) || type.IsEnum)
// 5. 检查循环引用(仅对引用类型有效,且不是字符串那种特殊引用类型)
if (!type.IsValueType && !type.IsPrimitive && !(obj is string))
{
return value.ToString();
if (visitedObjects.Contains(obj))
{
sb.AppendLine($"{indent}... (检测到循环引用: {type.Name})");
return;
}
visitedObjects.Add(obj); // 标记为已访问
}
// 2. 常见的Unity结构体特殊格式化避免深度递归并提供简洁输出
if (value is Vector2 vec2) return $"({vec2.x:F2}, {vec2.y:F2})";
if (value is Vector3 vec3) return $"({vec3.x:F2}, {vec3.y:F2}, {vec3.z:F2})";
if (value is Vector4 vec4) return $"({vec4.x:F2}, {vec4.y:F2}, {vec4.z:F2}, {vec4.w:F2})";
// Quaternion默认ToString()会显示x,y,z,w但有时EulerAngles更直观。
if (value is Quaternion q)
return
$"Q({q.x:F2}, {q.y:F2}, {q.z:F2}, {q.w:F2}) (Euler: {q.eulerAngles.x:F2}, {q.eulerAngles.y:F2}, {q.eulerAngles.z:F2})";
if (value is Color color) return $"RGBA({color.r:F2}, {color.g:F2}, {color.b:F2}, {color.a:F2})";
if (value is Rect rect)
return $"Rect(Pos:({rect.xMin:F2},{rect.yMin:F2}) Size:({rect.width:F2},{rect.height:F2}))";
if (value is Bounds bounds)
return
$"Bounds(Center:({bounds.center.x:F2},{bounds.center.y:F2},{bounds.center.z:F2}) Extents:({bounds.extents.x:F2},{bounds.extents.y:F2},{bounds.extents.z:F2}))";
if (value is LayerMask layerMask)
// 6. 处理集合类型数组、List、Dictionary等但不包括字符串
if (obj is IEnumerable enumerable)
{
// LayerMask的值可能代表多个层或一个单一层。LayerToName只能转换单一层。
// 对于多个层,返回其原始值更有意义。
return $"LayerMask(Value: {layerMask.value})";
// 对于字典直接打印IEnumerable会导致键值对混乱需要特殊处理
if (obj is IDictionary dictionary)
{
sb.AppendLine($"({type.Name}) Count={dictionary.Count} {{");
int count = 0;
foreach (DictionaryEntry entry in dictionary)
{
if (count >= MAX_COLLECTION_ELEMENTS_TO_PRINT)
{
sb.AppendLine($"{indent + " "}...(已截断,显示了{MAX_COLLECTION_ELEMENTS_TO_PRINT}对键值)");
break;
}
sb.Append($"{indent + " "}[Key]: ");
PrintObjectProperties(entry.Key, currentDepth + 1, sb, visitedObjects, indent + " "); // 额外的缩进
sb.Append($"{indent + " "}[Value]: ");
PrintObjectProperties(entry.Value, currentDepth + 1, sb, visitedObjects, indent + " "); // 额外的缩进
count++;
}
sb.AppendLine($"{indent}}}");
}
else // 普通的IEnumerableList, Array等
{
int elementCount = 0;
if (obj is ICollection collection)
elementCount = collection.Count;
else if (obj is Array array)
elementCount = array.Length;
else // 对于无法直接获取Count的IEnumerable需要遍历统计
{
var list = new List<object>();
foreach (var item in enumerable) list.Add(item);
elementCount = list.Count;
enumerable = list; // 重新赋值为可以重复遍历的list
}
sb.AppendLine($"({type.Name}) Count={elementCount} [");
int count = 0;
foreach (var item in enumerable)
{
if (count >= MAX_COLLECTION_ELEMENTS_TO_PRINT)
{
sb.AppendLine($"{indent + " "}...(已截断,显示了{MAX_COLLECTION_ELEMENTS_TO_PRINT}个元素)");
break;
}
sb.Append($"{indent + " "}- ");
PrintObjectProperties(item, currentDepth + 1, sb, visitedObjects, indent + " ");
count++;
}
sb.AppendLine($"{indent}]");
}
// 集合本身通常不直接导致循环引用其自身,其内部元素才可能。
// 故在处理集合后可以从visitedObjects中移除集合对象防止它阻止其他路径对它的访问。
if (!type.IsValueType && !type.IsPrimitive && !(obj is string)) visitedObjects.Remove(obj);
return;
}
// 3. GameObject/Component引用只打印名称或类型避免无限递归
if (value is GameObject go) return $"GameObject:'{go.name}'";
if (value is Component comp)
return $"Component:'{comp.GetType().Name}' on GameObject:'{comp.gameObject.name}'";
// 7. 处理一般对象(类或结构体)的字段和属性
sb.AppendLine($"({type.Name}) {{");
// 4. 集合类型:现在会打印内容,调用专门的辅助方法
if (value is Array array)
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
// 字段
FieldInfo[] fields = type.GetFields(flags);
foreach (FieldInfo field in fields)
{
return FormatCollectionForDisplay(array, currentDepth + 1);
if (field.IsStatic) continue; // 跳过静态字段
if (field.IsDefined(typeof(ObsoleteAttribute), true)) continue; // 跳过过时字段
string propertyIndent = indent + " ";
sb.Append($"{propertyIndent}{field.Name}: ");
try
{
object fieldValue = field.GetValue(obj);
PrintObjectProperties(fieldValue, currentDepth + 1, sb, visitedObjects, propertyIndent);
}
catch (Exception e)
{
sb.AppendLine($"<无法获取值: {e.Message}>");
}
}
if (value is IList list)
// 属性
PropertyInfo[] properties = type.GetProperties(flags);
foreach (PropertyInfo prop in properties)
{
return FormatCollectionForDisplay(list, currentDepth + 1);
// 跳过特殊名称属性如Unity内部的hideFlags、不可读属性、带索引器属性、过时属性
if (prop.IsSpecialName || !prop.CanRead || prop.GetIndexParameters().Length > 0 ||
prop.IsDefined(typeof(ObsoleteAttribute), true)) continue;
string propertyIndent = indent + " ";
sb.Append($"{propertyIndent}{prop.Name}: ");
try
{
object propValue = prop.GetValue(obj);
PrintObjectProperties(propValue, currentDepth + 1, sb, visitedObjects, propertyIndent);
}
catch (Exception e)
{
sb.AppendLine($"<无法获取值: {e.Message}>");
}
}
if (value is IDictionary dict)
{
return FormatCollectionForDisplay(dict, currentDepth + 1);
}
sb.AppendLine($"{indent}}}");
// 对于其他不可转换为Array/IList/IDictionary的IEnumerable但又不是字符串的类型
if (value is IEnumerable enumerable && !(value is string))
{
return FormatCollectionForDisplay(enumerable, currentDepth + 1);
}
// 5. 其他复杂引用类型:只打印其类型名称,默认不深入其内部 (除非深度允许,但在深度限制前这里只会显示类型名)
return type.Name; // 例如: Material, Texture2D等只显示类型名
// 在对象处理完毕后,从已访问集合中移除(如果它是引用类型),
// 这允许在对象图的不同路径中再次遇到它(如果需要),但防止当前路径的循环。
if (!type.IsValueType && !type.IsPrimitive && !(obj is string)) visitedObjects.Remove(obj);
}
/// <summary>
/// 格式化集合对象以进行显示,支持深度限制和元素数量限制
/// 获取指定深度的缩进字符串
/// </summary>
/// <param name="collection">要格式化的集合。</param>
/// <param name="currentDepth">当前的递归深度。</param>
/// <returns>格式化后的集合字符串。</returns>
private string FormatCollectionForDisplay(IEnumerable collection, int currentDepth)
private string GetIndent(int depth)
{
if (collection == null) return "null collection";
Type collectionType = collection.GetType();
StringBuilder sb = new StringBuilder();
// 尝试获取集合的类型名,去除可能的反引号(用于泛型类型)
string collectionTypeName = collectionType.IsGenericType
? collectionType.Name.Substring(0, collectionType.Name.IndexOf('`'))
: collectionType.Name.Replace("[]", "");
sb.Append(collectionTypeName);
sb.Append(" [");
int i = 0;
foreach (var item in collection)
{
if (i >= MAX_COLLECTION_ELEMENTS_TO_PRINT)
{
sb.Append(", ...");
break;
}
if (i > 0) sb.Append(", ");
if (item is DictionaryEntry entry) // 针对非泛型IDictionary
{
sb.Append(
$"{{{FormatValueForDisplay(entry.Key, currentDepth + 1)}: {FormatValueForDisplay(entry.Value, currentDepth + 1)}}}");
}
else
{
Type itemType = item?.GetType();
// 针对泛型IDictionary其元素是KeyValuePair<TKey, TValue>
if (itemType != null && itemType.IsGenericType &&
itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
// 使用反射获取Key和Value属性
PropertyInfo keyProperty = itemType.GetProperty("Key");
PropertyInfo valueProperty = itemType.GetProperty("Value");
if (keyProperty != null && valueProperty != null)
{
object key = keyProperty.GetValue(item);
object value = valueProperty.GetValue(item);
sb.Append(
$"{{{FormatValueForDisplay(key, currentDepth + 1)}: {FormatValueForDisplay(value, currentDepth + 1)}}}");
}
else
{
// Fallback in case Key/Value properties aren't found
sb.Append(FormatValueForDisplay(item, currentDepth + 1));
}
}
else
{
// 格式化普通集合元素
sb.Append(FormatValueForDisplay(item, currentDepth + 1));
}
}
i++;
}
// 尝试获取集合的实际数量
string countInfo = "N/A";
if (collection is ICollection c)
{
countInfo = c.Count.ToString();
}
else if (collection is Array a)
{
countInfo = a.Length.ToString();
}
sb.Append($"] (Count: {countInfo})");
return sb.ToString();
return new string(' ', depth * 2);
}
/// <summary>
/// 获取给定游戏对象的完整层次路径。
/// </summary>
/// <param name="go">要获取路径的游戏对象。</param>
/// <returns>游戏对象的完整路径,例如 "Parent/Child/Object"。</returns>
private string GetGameObjectPath(GameObject go)
{
if (go == null) return "N/A";
var path = go.name;
var currentTransform = go.transform;
while (currentTransform.parent != null)
{
currentTransform = currentTransform.parent;
path = currentTransform.name + "/" + path;
}
return path;
}
}
}
}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("折纸的小箱子")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")]
[assembly: System.Reflection.AssemblyProductAttribute("SceneSnapshot")]
[assembly: System.Reflection.AssemblyTitleAttribute("SceneSnapshot")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0")]

View File

@@ -1 +1 @@
532b9b8318b6010ae03f608127d55a2c59d0b50d9243b633f698b5d460668837
4cb78221af3251625ca99f740da024b47f366123d3acdfd330074e35f359044e

View File

@@ -1 +1 @@
17619862901687226
17623343068138064

View File

@@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
namespace Theme
{
public class ModBehaviour:Duckov.Modding.ModBehaviour
public class ModBehaviour : Duckov.Modding.ModBehaviour
{
private StringBuilder _outputStringBuilder;
protected override void OnAfterSetup()
{
base.OnAfterSetup();
}
protected override void OnBeforeDeactivate()
{
base.OnBeforeDeactivate();
}
}
}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Theme")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")]
[assembly: System.Reflection.AssemblyProductAttribute("Theme")]
[assembly: System.Reflection.AssemblyTitleAttribute("Theme")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
9389535ab653bc716c7ef81ccb8542bee3753b2c1f4be9c952401b4ec0aa9c5d
c1519aee9b121f1df8699539b785671792d60ca0acb5c9d1d51d6039c2fa5de5

View File

@@ -0,0 +1 @@
ff440d05ee1ce379e101695273a889a69b97880c4ff8ae9ffb67b8c73e02feef

View File

@@ -0,0 +1,7 @@
D:\steam\steamapps\common\Escape from Duckov\Duckov_Data\Mods\Theme\Theme.dll
D:\vs_project\DuckovMods\Theme\obj\Release\Theme.csproj.AssemblyReference.cache
D:\vs_project\DuckovMods\Theme\obj\Release\Theme.GeneratedMSBuildEditorConfig.editorconfig
D:\vs_project\DuckovMods\Theme\obj\Release\Theme.AssemblyInfoInputs.cache
D:\vs_project\DuckovMods\Theme\obj\Release\Theme.AssemblyInfo.cs
D:\vs_project\DuckovMods\Theme\obj\Release\Theme.csproj.CoreCompileInputs.cache
D:\vs_project\DuckovMods\Theme\obj\Release\Theme.dll

BIN
Theme/obj/Release/Theme.dll Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
17622424783469959

View File

@@ -1 +1 @@
17622424783469959
17623343068138064

View File

@@ -0,0 +1,73 @@
using Duckov.UI;
using Duckov.Utilities;
using TMPro;
using UIFrame.Utilities;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace UIFrame
{
public class GameOriginMainMenuUI
{
public GameObject mainMenuContainer;
public Image? title;
public TMP_Text[]? allTexts;
public Sprite titleSprite;
public bool linkMainMenu=false;
public void Initialize()
{
SceneLoader.onAfterSceneInitialize += OnAfterSceneInitialize;
}
public void Cleanup()
{
SceneLoader.onAfterSceneInitialize -= OnAfterSceneInitialize;
}
private void OnAfterSceneInitialize(SceneLoadingContext sceneLoadingContext)
{
linkMainMenu = false;
LinkMainMenuObj();
}
public void LinkMainMenuObj()
{
mainMenuContainer = GameObject.Find("MainMenuContainer");
if(!mainMenuContainer)
{
Debug.LogWarning("Could not find Main Menu Container");
return;
}
Debug.Log("Main Menu Container initialized");
allTexts = mainMenuContainer.GetComponentsInChildren<TMP_Text>();
title = GameObjectTool.FindChildByName(mainMenuContainer.transform, "MainTitle")?.GetComponent<Image>();
linkMainMenu = true;
}
public bool SetFont(TMP_FontAsset font)
{
if(allTexts == null || allTexts.Length == 0)
return false;
foreach (var text in allTexts)
{
text.font = font;
}
return true;
}
public bool SetTitle(Sprite texture)
{
titleSprite=texture;
if(title==null)
return false;
title.sprite = texture;
return true;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace UIFrame.Manager
{
public class TextureManager
{
}
}

View File

@@ -1,19 +1,69 @@
using System;
using Duckov.Modding;
using Duckov.Options.UI;
using Duckov.UI;
using Duckov.UI.Animations;
using Duckov.Utilities;
using HarmonyLib;
using UnityEngine;
namespace UIFrame
{
public class ModBehaviour:Duckov.Modding.ModBehaviour
{
private const string MOD_ID="UIFrame";
private GameObject? workerObject;
private Harmony? harmony;
protected override void OnAfterSetup()
{
Debug.Log("OnAfterSetup");
CreateAPIObject();
if (harmony == null)
{
harmony=new HarmonyLib.Harmony(MOD_ID);
}
harmony.PatchAll();
Test();
}
protected override void OnBeforeDeactivate()
{
Debug.Log("OnBeforeDeactivate");
ClearAPIObject();
harmony?.UnpatchAll(MOD_ID);
harmony = null;
}
private void CreateAPIObject()
{
if(workerObject)
return;
workerObject = new GameObject($"{MOD_ID}_APIObject");
workerObject.AddComponent<UIFrameWorker>();
}
private void ClearAPIObject()
{
if(!workerObject)
return;
Destroy(workerObject);
workerObject = null;
}
private void Test()
{
if(!UIFrameAPI.Initialize())
return;
if (UIFrameAPI.SetGameTitle(@"C:\Users\Lenovo\Pictures\异噬.png"))
{
Debug.Log("设置标题完成");
}
else
{
Debug.Log("标题设置失败");
}
}
}
}

View File

@@ -0,0 +1,15 @@
using UnityEngine;
namespace UIFrame.Patch
{
[HarmonyLib.HarmonyPatch(typeof(SceneLoader), "LoadMainMenu")]
public class PatchSceneLoaderLoadMainMenu
{
public static void Postfix()
{
Debug.Log("LoadMainMenu called");
}
}
}

38
UIFrame/Singleton.cs Normal file
View File

@@ -0,0 +1,38 @@
using System;
namespace UIFrame
{
public abstract class Singleton<T> where T : class
{
private static T? _instance;
private static readonly object _lock = new object(); // 用于多线程安全
public static T? Instance
{
get
{
// 在多线程环境下,确保只有一个线程能够创建实例
lock (_lock)
{
if (_instance == null)
{
_instance = Activator.CreateInstance(typeof(T), true) as T;
if (_instance == null)
{
throw new InvalidOperationException($"无法为类型 {typeof(T).Name} 创建单例实例。" +
"请确保它有一个私有的无参构造函数。");
}
}
return _instance;
}
}
}
protected Singleton()
{
if (_instance != null)
{
throw new InvalidOperationException($"试图创建第二个单例实例 {typeof(T).Name}。请通过 Singleton<{typeof(T).Name}>.Instance 访问。");
}
}
}
}

View File

@@ -18,5 +18,8 @@
<Reference Include="$(DuckovPath)\Duckov_Data\Managed\Unity*" Private="False" />
<Reference Include="$(DuckovPath)\Duckov_Data\Managed\FMODUnity.dll" Private="False" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.4.1" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,99 @@
using System.Collections.Generic;
using UIFrameAPI;
using UnityEngine;
//改为自己的命名空间更好,这是一个画布单元,一个命名空间一个画布
namespace UIFrame
{
public class UIFrameAPI
//反射虽然很好用,但我认为用组件传递高效
public static class UIFrameAPI
{
private static UIFrameAPIComponent? _apiComponent;
private static bool createdCanvas = false;
private static readonly string NameSpace = typeof(UIFrameAPI).Namespace ?? "UIFrame";
public static Dictionary<string, Texture2D> textureCache = new Dictionary<string, Texture2D>();
public static Dictionary<string, Sprite> spriteCache = new Dictionary<string, Sprite>();
/// <summary>
/// 初始化API
/// </summary>
/// <returns></returns>
public static bool Initialize()
{
if (_apiComponent!=null)
return true;
_apiComponent = Object.FindObjectOfType<UIFrameAPIComponent>();
return _apiComponent;
}
/// <summary>
/// 设置标题图片(游戏中的标题是图片)
/// </summary>
/// <param name="imageFilePath">图片路径</param>
/// <returns></returns>
public static bool SetGameTitle(string imageFilePath)
{
var texture=LoadSprite(imageFilePath);
if(texture==null)
{
return false;
}
return _apiComponent&&_apiComponent.SetTitleImage(texture);
}
/// <summary>
/// 设置标题图片(游戏中的标题是图片)
/// </summary>
/// <param name="sprite">贴图</param>
/// <returns></returns>
public static bool SetGameTitle(Sprite sprite)
{
return _apiComponent&&_apiComponent.SetTitleImage(sprite);
}
/// <summary>
/// 加载图片文件为Texture2D
/// Texture2D实际存储了图片图片的数据会上传显卡
/// 此函数默认加载后不保留内存备份,即不可读像素
/// 函数会建立文件到图片的索引缓存
/// </summary>
/// <param name="imageFilePath"></param>
/// <returns></returns>
public static Texture2D? LoadImage(string imageFilePath)
{
if (!textureCache.ContainsKey(imageFilePath))
{
var texture=_apiComponent.LoadTexture(imageFilePath);
if (texture != null)
{
textureCache[imageFilePath] = texture;
}
else
{
Debug.LogError($"加载图片:{imageFilePath}失败");
return null;
}
}
return textureCache[imageFilePath];
}
/// <summary>
/// 加载图片为Sprite
/// 直接简单的调用LoadImage再创建一个代表此图片的Sprite
/// Sprite只是表明了图片的处理方式所以为了灵活性建议自己创建
/// 此函数会缓存地址到Sprite的索引
/// </summary>
/// <param name="imageFilePath"></param>
/// <returns></returns>
public static Sprite? LoadSprite(string imageFilePath)
{
var texture=LoadImage(imageFilePath);
if (texture==null)
return null;
if (!spriteCache.ContainsKey(imageFilePath))
{
spriteCache[imageFilePath] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
}
return spriteCache[imageFilePath];
}
}
}

View File

@@ -0,0 +1,21 @@
using TMPro;
using UnityEngine;
namespace UIFrameAPI
{
public abstract class UIFrameAPIComponent:MonoBehaviour
{
public abstract bool CreateCanvas(string name);
//设置游戏主菜单的原版标题
public abstract bool SetTitleImage(Sprite sprite);
//创建一个TMP字体
public abstract TMP_FontAsset CreateFontAsset(string fontFilePath);
//设置游戏字体
public abstract bool SetFont(TMP_FontAsset font);
public abstract Texture2D? LoadTexture(string imageFilePath);
}
}

65
UIFrame/UIFrameWorker.cs Normal file
View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using TMPro;
using UIFrameAPI;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore.LowLevel;
namespace UIFrame
{
public class UIFrameWorker : UIFrameAPIComponent
{
public GameOriginMainMenuUI gameOriginMainMenuUI = new GameOriginMainMenuUI();
public Dictionary<string, Canvas> canvasDic = new Dictionary<string, Canvas>();
private void Awake()
{
gameOriginMainMenuUI.Initialize();
}
private void OnDestroy()
{
gameOriginMainMenuUI.Cleanup();
}
public override bool CreateCanvas(string name)
{
return false;
}
public override bool SetTitleImage(Sprite sprite)
{
return gameOriginMainMenuUI.SetTitle(sprite);
}
public override TMP_FontAsset CreateFontAsset(string fontFilePath)
{
var font = Font.CreateDynamicFontFromOSFont(fontFilePath, 24);
var tmpFont = TMP_FontAsset.CreateFontAsset(
font,
samplingPointSize: 72, // 采样点大小,影响字体质量
atlasPadding: 4, // 图集内字符间距
renderMode: GlyphRenderMode.SDFAA, // 推荐使用 SDF 抗锯齿模式
atlasWidth: 1024, // 图集宽度 (2的幂)
atlasHeight: 1024, // 图集高度 (2的幂)
atlasPopulationMode: AtlasPopulationMode.Dynamic, // 动态填充
enableMultiAtlasSupport: true // 启用多图集支持
);
return tmpFont;
}
public override bool SetFont(TMP_FontAsset font)
{
return gameOriginMainMenuUI.SetFont(font);
}
public override Texture2D? LoadTexture(string imageFilePath)
{
return Utilities.ImageLoader.LoadImageFromFile(imageFilePath);
}
}
}

View File

@@ -1,7 +0,0 @@
namespace UIFrame
{
public static class UIManager
{
}
}

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
using UnityEngine;
namespace UIFrame.Utilities
{
public class GameObjectTool
{
/// <summary>
/// 在指定父对象下查找第一个匹配名称的子GameObject可以是孙子、曾孙等
/// </summary>
/// <param name="parent">要查找的父Transform。</param>
/// <param name="name">要查找的GameObject的名称。</param>
/// <returns>找到的GameObject如果未找到则返回null。</returns>
public static GameObject? FindChildByName(Transform parent, string name)
{
// 查找父对象本身是否就是目标对象
if (parent.name.Equals(name))
{
return parent.gameObject;
}
// 遍历所有直接子对象
foreach (Transform child in parent)
{
// 检查当前子对象是否是目标对象
if (child.name.Equals(name))
{
return child.gameObject;
}
// 递归查找子对象的子对象
var found = FindChildByName(child, name);
if (found)
{
return found;
}
}
return null;
}
/// <summary>
/// 在指定父对象下查找所有匹配名称的子GameObject可以是孙子、曾孙等不区分大小写。
/// </summary>
/// <param name="parent">要查找的父Transform。</param>
/// <param name="name">要查找的GameObject的名称。</param>
/// <returns>所有找到的GameObject列表。</returns>
public static List<GameObject> FindChildrenByName(Transform parent, string name)
{
var foundObjects = new List<GameObject>();
FindChildrenByNameRecursive(parent, name, foundObjects);
return foundObjects;
}
private static void FindChildrenByNameRecursive(Transform currentTransform, string name, List<GameObject> foundObjects)
{
// 检查当前对象是否是目标对象
if (currentTransform.name.Equals(name))
{
foundObjects.Add(currentTransform.gameObject);
}
// 遍历所有子对象并递归查找
foreach (Transform child in currentTransform)
{
FindChildrenByNameRecursive(child, name, foundObjects);
}
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.IO;
using UnityEngine;
namespace UIFrame.Utilities
{
public class ImageLoader
{
/// <summary>
/// 从指定文件路径加载图片并创建 Texture2D。
/// 支持常用的图片格式 (如 .png, .jpg, .jpeg, .bmp, .tga)
/// </summary>
/// <param name="filePath">图片文件的绝对路径。</param>
/// <param name="createNewTexture">如果为true则创建一个新的Texture2D对象并加载图片数据。如果为false它会尝试加载到默认的空白Texture2D对象上但通常建议使用true。</param>
/// <param name="linear">指定纹理是否加载为线性颜色空间 (true) 或 sRGB 颜色空间 (false)。</param>
/// <returns>加载成功的 Texture2D 对象,如果失败则返回 null。</returns>
public static Texture2D? LoadImageFromFile(string filePath, bool createNewTexture = true, bool linear = false)
{
if (string.IsNullOrEmpty(filePath))
{
Debug.LogError("ImageLoader: 图片文件路径为空或无效。");
return null;
}
if (!File.Exists(filePath))
{
Debug.LogError($"ImageLoader: 文件不存在于路径: {filePath}");
return null;
}
Texture2D? texture = null;
try
{
var fileData = File.ReadAllBytes(filePath);
if (createNewTexture)
{
texture = new Texture2D(2, 2, TextureFormat.RGBA32, false, linear);
}
else if (texture == null)
{
Debug.LogError("ImageLoader: 未能提供现有Texture2D用于加载且createNewTexture为false。");
return null;
}
var success = texture.LoadImage(fileData, true);
if (!success)
{
Debug.LogError($"ImageLoader: 无法加载图片数据到Texture2D。请检查文件是否为有效的图片格式或是否损坏: {filePath}");
UnityEngine.Object.Destroy(texture); // 销毁失败的纹理对象
return null;
}
return texture;
}
catch (Exception ex)
{
Debug.LogError($"ImageLoader: 加载图片时发生错误: {filePath} - {ex.Message}");
if (texture != null)
{
UnityEngine.Object.Destroy(texture); // 发生异常时销毁已创建的纹理
}
return null;
}
}
}
}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("UIFrame")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")]
[assembly: System.Reflection.AssemblyProductAttribute("UIFrame")]
[assembly: System.Reflection.AssemblyTitleAttribute("UIFrame")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
eb1c4a5bd10a6004eb2efc2bf4354383e0389ddf5e8c9585686a601d75e8a73a
2fde23c3fee254f6fc8f4689686f8631f1cec9f5b900327d5b657c6830a03267

View File

@@ -1 +1 @@
26cd918e8bf85dee638761fbef4e2117b1734f804498932e0dc10264a1740877
8df4ddcbe4b7016ad51bf9e0b9ea8b606b36f4d0bbae7f1650b893a49036873e

Binary file not shown.

View File

@@ -49,6 +49,12 @@
"frameworks": {
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"dependencies": {
"Lib.Harmony": {
"target": "Package",
"version": "[2.4.1, )"
}
},
"imports": [
"net461",
"net462",

View File

@@ -1,11 +1,165 @@
{
"version": 3,
"targets": {
".NETStandard,Version=v2.1": {}
".NETStandard,Version=v2.1": {
"Lib.Harmony/2.4.1": {
"type": "package",
"dependencies": {
"Lib.Harmony.Ref": "2.4.1"
},
"compile": {
"lib/netstandard2.0/_._": {}
},
"runtime": {
"lib/netstandard2.0/_._": {}
}
},
"Lib.Harmony.Ref/2.4.1": {
"type": "package",
"dependencies": {
"System.Reflection.Emit": "4.7.0"
},
"compile": {
"ref/netstandard2.0/0Harmony.dll": {
"related": ".xml"
}
}
},
"System.Reflection.Emit/4.7.0": {
"type": "package",
"compile": {
"ref/netstandard2.1/_._": {}
},
"runtime": {
"lib/netstandard2.1/_._": {}
}
}
}
},
"libraries": {
"Lib.Harmony/2.4.1": {
"sha512": "iLTZi/kKKB18jYEIwReZSx2xXyVUh4J1swReMgvYBBBn4tzA1Nd0PJlVyntY5BDdSiXSxzmvjc/3OYfFs0YwFg==",
"type": "package",
"path": "lib.harmony/2.4.1",
"files": [
".nupkg.metadata",
".signature.p7s",
"HarmonyLogo.png",
"LICENSE",
"README.md",
"lib.harmony.2.4.1.nupkg.sha512",
"lib.harmony.nuspec",
"lib/net35/0Harmony.dll",
"lib/net35/0Harmony.pdb",
"lib/net35/0Harmony.xml",
"lib/net452/0Harmony.dll",
"lib/net452/0Harmony.pdb",
"lib/net452/0Harmony.xml",
"lib/net472/0Harmony.dll",
"lib/net472/0Harmony.pdb",
"lib/net472/0Harmony.xml",
"lib/net48/0Harmony.dll",
"lib/net48/0Harmony.pdb",
"lib/net48/0Harmony.xml",
"lib/net5.0/0Harmony.dll",
"lib/net5.0/0Harmony.pdb",
"lib/net5.0/0Harmony.xml",
"lib/net6.0/0Harmony.dll",
"lib/net6.0/0Harmony.pdb",
"lib/net6.0/0Harmony.xml",
"lib/net7.0/0Harmony.dll",
"lib/net7.0/0Harmony.pdb",
"lib/net7.0/0Harmony.xml",
"lib/net8.0/0Harmony.dll",
"lib/net8.0/0Harmony.pdb",
"lib/net8.0/0Harmony.xml",
"lib/net9.0/0Harmony.dll",
"lib/net9.0/0Harmony.pdb",
"lib/net9.0/0Harmony.xml",
"lib/netcoreapp3.0/0Harmony.dll",
"lib/netcoreapp3.0/0Harmony.pdb",
"lib/netcoreapp3.0/0Harmony.xml",
"lib/netcoreapp3.1/0Harmony.dll",
"lib/netcoreapp3.1/0Harmony.pdb",
"lib/netcoreapp3.1/0Harmony.xml",
"lib/netstandard2.0/_._"
]
},
"Lib.Harmony.Ref/2.4.1": {
"sha512": "+u1y2Qd6OlSUQ8JtrsrSo3adnAsrXMJ2YPYtbW+FAmdPI5yw34M9VX4bKl8ZwRuUzaGzZIz+oGMbn/yS4fWtZw==",
"type": "package",
"path": "lib.harmony.ref/2.4.1",
"files": [
".nupkg.metadata",
".signature.p7s",
"HarmonyLogo.png",
"LICENSE",
"README.md",
"lib.harmony.ref.2.4.1.nupkg.sha512",
"lib.harmony.ref.nuspec",
"ref/netstandard2.0/0Harmony.dll",
"ref/netstandard2.0/0Harmony.xml"
]
},
"System.Reflection.Emit/4.7.0": {
"sha512": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==",
"type": "package",
"path": "system.reflection.emit/4.7.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"lib/MonoAndroid10/_._",
"lib/MonoTouch10/_._",
"lib/net45/_._",
"lib/netcore50/System.Reflection.Emit.dll",
"lib/netcoreapp2.0/_._",
"lib/netstandard1.1/System.Reflection.Emit.dll",
"lib/netstandard1.1/System.Reflection.Emit.xml",
"lib/netstandard1.3/System.Reflection.Emit.dll",
"lib/netstandard2.0/System.Reflection.Emit.dll",
"lib/netstandard2.0/System.Reflection.Emit.xml",
"lib/netstandard2.1/_._",
"lib/xamarinios10/_._",
"lib/xamarinmac20/_._",
"lib/xamarintvos10/_._",
"lib/xamarinwatchos10/_._",
"ref/MonoAndroid10/_._",
"ref/MonoTouch10/_._",
"ref/net45/_._",
"ref/netcoreapp2.0/_._",
"ref/netstandard1.1/System.Reflection.Emit.dll",
"ref/netstandard1.1/System.Reflection.Emit.xml",
"ref/netstandard1.1/de/System.Reflection.Emit.xml",
"ref/netstandard1.1/es/System.Reflection.Emit.xml",
"ref/netstandard1.1/fr/System.Reflection.Emit.xml",
"ref/netstandard1.1/it/System.Reflection.Emit.xml",
"ref/netstandard1.1/ja/System.Reflection.Emit.xml",
"ref/netstandard1.1/ko/System.Reflection.Emit.xml",
"ref/netstandard1.1/ru/System.Reflection.Emit.xml",
"ref/netstandard1.1/zh-hans/System.Reflection.Emit.xml",
"ref/netstandard1.1/zh-hant/System.Reflection.Emit.xml",
"ref/netstandard2.0/System.Reflection.Emit.dll",
"ref/netstandard2.0/System.Reflection.Emit.xml",
"ref/netstandard2.1/_._",
"ref/xamarinios10/_._",
"ref/xamarinmac20/_._",
"ref/xamarintvos10/_._",
"ref/xamarinwatchos10/_._",
"runtimes/aot/lib/netcore50/System.Reflection.Emit.dll",
"runtimes/aot/lib/netcore50/System.Reflection.Emit.xml",
"system.reflection.emit.4.7.0.nupkg.sha512",
"system.reflection.emit.nuspec",
"useSharedDesignerContext.txt",
"version.txt"
]
}
},
"libraries": {},
"projectFileDependencyGroups": {
".NETStandard,Version=v2.1": []
".NETStandard,Version=v2.1": [
"Lib.Harmony >= 2.4.1"
]
},
"packageFolders": {
"C:\\Users\\Lenovo\\.nuget\\packages\\": {},
@@ -56,6 +210,12 @@
"frameworks": {
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"dependencies": {
"Lib.Harmony": {
"target": "Package",
"version": "[2.4.1, )"
}
},
"imports": [
"net461",
"net462",

View File

@@ -1,8 +1,12 @@
{
"version": 2,
"dgSpecHash": "A4ZumVBkozg=",
"dgSpecHash": "b0JcKXS9rW0=",
"success": true,
"projectFilePath": "D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj",
"expectedPackageFiles": [],
"expectedPackageFiles": [
"C:\\Users\\Lenovo\\.nuget\\packages\\lib.harmony\\2.4.1\\lib.harmony.2.4.1.nupkg.sha512",
"C:\\Users\\Lenovo\\.nuget\\packages\\lib.harmony.ref\\2.4.1\\lib.harmony.ref.2.4.1.nupkg.sha512",
"C:\\Users\\Lenovo\\.nuget\\packages\\system.reflection.emit\\4.7.0\\system.reflection.emit.4.7.0.nupkg.sha512"
],
"logs": []
}

View File

@@ -1 +1 @@
"restore":{"projectUniqueName":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","projectName":"UIFrame","projectPath":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","outputPath":"D:\\vs_project\\DuckovMods\\UIFrame\\obj\\","projectStyle":"PackageReference","fallbackFolders":["D:\\vsShare\\NuGetPackages"],"originalTargetFrameworks":["netstandard2.1"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.306\\RuntimeIdentifierGraph.json"}}
"restore":{"projectUniqueName":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","projectName":"UIFrame","projectPath":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","outputPath":"D:\\vs_project\\DuckovMods\\UIFrame\\obj\\","projectStyle":"PackageReference","fallbackFolders":["D:\\vsShare\\NuGetPackages"],"originalTargetFrameworks":["netstandard2.1"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","dependencies":{"Lib.Harmony":{"target":"Package","version":"[2.4.1, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.306\\RuntimeIdentifierGraph.json"}}

View File

@@ -1 +1 @@
17620796287866771
17623343068138064