This commit is contained in:
2026-10-08 17:04:24 +08:00
parent e60e3bdb99
commit 4a8c66dcaf
24 changed files with 1132 additions and 228 deletions
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: a263e71fce79f2f4cb5b5f1fa6b7291a m_DefaultGroup: a263e71fce79f2f4cb5b5f1fa6b7291a
m_currentHash: m_currentHash:
serializedVersion: 2 serializedVersion: 2
Hash: e88239bd385df7eeb8432db63ac72d7d Hash: 3b2ddf0423273ab9d68b887ff1cc1809
m_OptimizeCatalogSize: 0 m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0 m_BuildRemoteCatalog: 0
m_BundleLocalCatalog: 0 m_BundleLocalCatalog: 0
@@ -51,6 +51,13 @@ MonoBehaviour:
- PTCBZS - PTCBZS
- ZSYX - ZSYX
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4ccd2be5159a8434689185dad378dd01
m_Address: Assets/Art/FengLei/Audio/FLZY_bgm_default.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 55692611ed57c594fa0d7ecba7dcf704 - m_GUID: 55692611ed57c594fa0d7ecba7dcf704
m_Address: Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14901_0_dev.prefab m_Address: Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_14901_0_dev.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -70,6 +77,41 @@ MonoBehaviour:
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: [] m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8e07162e07c25974cae59b29d808aaf2
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_BgFree.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 964dfa5d6b6f9b947983721026622299
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_Shoot.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- PTCBZS
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 98b1baf987b5c8942baa65ac95c04a15
m_Address: Assets/Audios/HHWK/HHWK_ziyouxiBGM.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a5531410c3acf2a45990658c82c7db06
m_Address: Assets/Art/FengLei/Audio/FLZY_bgm_20.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b9f66d4b543101b4a8f6529f7e1c3fc8
m_Address: Assets/Audios/HHWK/HHWK_youxiBGM2.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c0665105ad467074a9d951a074b6c71b - m_GUID: c0665105ad467074a9d951a074b6c71b
m_Address: Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/PW_Enemy_4000.prefab m_Address: Assets/MyGame/aa/Game/XJMH/Boss/XingJiXunHang/PW_Enemy_4000.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -77,6 +119,20 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c37a24b1b9382e04c86ecae674a7e071
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_Bg.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ec58d755545af58489ae35ed82c1e07d
m_Address: Assets/Audios/HHWK/HHWK_youxiBGM.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: fd77a1da22db21441ab0b116128a5e3a - m_GUID: fd77a1da22db21441ab0b116128a5e3a
m_Address: Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_in_14053_0_dev.prefab m_Address: Assets/MyGame/aa/Game/ZhanShen/Game/display_wargod_in_14053_0_dev.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -206,13 +206,6 @@ MonoBehaviour:
- FLZY - FLZY
- FishPrefab - FishPrefab
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4ccd2be5159a8434689185dad378dd01
m_Address: Assets/Art/FengLei/Audio/FLZY_bgm_default.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4f07750ceee651741ba0228ffd45e954 - m_GUID: 4f07750ceee651741ba0228ffd45e954
m_Address: Assets/Art/FengLei/bundles/by_fish_base/bundles/prefab/fishbase@/12.prefab m_Address: Assets/Art/FengLei/bundles/by_fish_base/bundles/prefab/fishbase@/12.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -528,13 +521,6 @@ MonoBehaviour:
- FLZY - FLZY
- FishPrefab - FishPrefab
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a5531410c3acf2a45990658c82c7db06
m_Address: Assets/Art/FengLei/Audio/FLZY_bgm_20.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a718714f431d35247b001c93ad1683b9 - m_GUID: a718714f431d35247b001c93ad1683b9
m_Address: Assets/Art/TuJianFishTexture/1595.png m_Address: Assets/Art/TuJianFishTexture/1595.png
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1200,13 +1200,6 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- HHWK - HHWK
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 98b1baf987b5c8942baa65ac95c04a15
m_Address: Assets/Audios/HHWK/HHWK_ziyouxiBGM.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9a71ec700d97e9442be4e7ca1ec88b88 - m_GUID: 9a71ec700d97e9442be4e7ca1ec88b88
m_Address: Assets/Art/HHWK/TuJianTexture/fish_1022 Variant.png m_Address: Assets/Art/HHWK/TuJianTexture/fish_1022 Variant.png
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1501,13 +1494,6 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- HHWK - HHWK
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b9f66d4b543101b4a8f6529f7e1c3fc8
m_Address: Assets/Audios/HHWK/HHWK_youxiBGM2.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: bbddd2883bdfd1644ba49d4261a73d27 - m_GUID: bbddd2883bdfd1644ba49d4261a73d27
m_Address: Assets/game_download/games/game177/prefab/fish/Game177_yilong_sj_2.prefab m_Address: Assets/game_download/games/game177/prefab/fish/Game177_yilong_sj_2.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1963,13 +1949,6 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- HHWK - HHWK
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ec58d755545af58489ae35ed82c1e07d
m_Address: Assets/Audios/HHWK/HHWK_youxiBGM.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ec8b574c01d5d354db2c62b9737d7c49 - m_GUID: ec8b574c01d5d354db2c62b9737d7c49
m_Address: Assets/Art/HHWK/TuJianTexture/Fish_2009 Variant.png m_Address: Assets/Art/HHWK/TuJianTexture/Fish_2009 Variant.png
m_ReadOnly: 0 m_ReadOnly: 0
@@ -178,6 +178,12 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1abfe2b699a5f764c896951d8bc859ad
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_1.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1c991808cacc37a4884f95a3bd88a29c - m_GUID: 1c991808cacc37a4884f95a3bd88a29c
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_FlyCoin.ogg m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_FlyCoin.ogg
m_ReadOnly: 0 m_ReadOnly: 0
@@ -423,6 +429,12 @@ MonoBehaviour:
- TuJian - TuJian
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3b6ce020d078b524ab9f8ea29d7fe431
m_Address: Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_zd.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3c361fb6838b59a40b183bc1dbf9ae2a - m_GUID: 3c361fb6838b59a40b183bc1dbf9ae2a
m_Address: Assets/Art/XJMH/pw/pw_prefab/game/enemy/XJMHEnemy/PW_Enemy_2.prefab m_Address: Assets/Art/XJMH/pw/pw_prefab/game/enemy/XJMHEnemy/PW_Enemy_2.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -801,6 +813,12 @@ MonoBehaviour:
- Audio - Audio
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 689bc9268484c3c4fab1743b9c99ee9e
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_2.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 68e9db06ce8a52043bce8e20dd84fb7e - m_GUID: 68e9db06ce8a52043bce8e20dd84fb7e
m_Address: Assets/Art/XJMH/pw/pw_prefab/game/enemy/XJMHEnemy/PW_Enemy_9.prefab m_Address: Assets/Art/XJMH/pw/pw_prefab/game/enemy/XJMHEnemy/PW_Enemy_9.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -983,13 +1001,6 @@ MonoBehaviour:
- Audio - Audio
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8e07162e07c25974cae59b29d808aaf2
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_BgFree.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8e9f22d048bac0a41a86b7a8f89efb0f - m_GUID: 8e9f22d048bac0a41a86b7a8f89efb0f
m_Address: Assets/MyGame/aa/Game/XJMH/Boss/XiaoChouPuKe/PW_Enemy_113.prefab m_Address: Assets/MyGame/aa/Game/XJMH/Boss/XiaoChouPuKe/PW_Enemy_113.prefab
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1032,11 +1043,10 @@ MonoBehaviour:
- TuJian - TuJian
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 964dfa5d6b6f9b947983721026622299 - m_GUID: 9bf752fc3d63b374998b79d5b9e169a0
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_Shoot.ogg m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_3.png
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- Audio
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9cd48272e3e176b4b86b3ce703cfa03b - m_GUID: 9cd48272e3e176b4b86b3ce703cfa03b
@@ -1128,6 +1138,12 @@ MonoBehaviour:
- TuJian - TuJian
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: adf779a3413c62744b56021a0ad5c93d
m_Address: Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_ss.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: aea38e560a41ff44ba0a68d3b3194052 - m_GUID: aea38e560a41ff44ba0a68d3b3194052
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_TextDragon.ogg m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_TextDragon.ogg
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1156,6 +1172,12 @@ MonoBehaviour:
- Audio - Audio
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b40ddb35d439d4546ab220424de7e930
m_Address: Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_sd.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b494aa76324a7c84ba8d1b26c9b3d231 - m_GUID: b494aa76324a7c84ba8d1b26c9b3d231
m_Address: Assets/Art/XJMH/pw/pw_sprite/handbook/PW_Enemy_32.png m_Address: Assets/Art/XJMH/pw/pw_sprite/handbook/PW_Enemy_32.png
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1247,13 +1269,6 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c37a24b1b9382e04c86ecae674a7e071
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_Bg.ogg
m_ReadOnly: 0
m_SerializedLabels:
- Audio
- Default
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c37fb4f7c76b1154a9d20ad974a14010 - m_GUID: c37fb4f7c76b1154a9d20ad974a14010
m_Address: Assets/Art/XJMH/pw/pw_sprite/handbook/PW_Enemy_104.png m_Address: Assets/Art/XJMH/pw/pw_sprite/handbook/PW_Enemy_104.png
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1525,6 +1540,12 @@ MonoBehaviour:
- FishPrefab - FishPrefab
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e54c60eb5da5278478389292015f8e8a
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_4.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e590af03990a88a4c842c66df7dde040 - m_GUID: e590af03990a88a4c842c66df7dde040
m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_Prop_GammaStorm_Attack.ogg m_Address: Assets/MyGame/aa/Game/XJMH/Sound/PW_Prop_GammaStorm_Attack.ogg
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1733,48 +1754,6 @@ MonoBehaviour:
- Audio - Audio
- XJMH - XJMH
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b40ddb35d439d4546ab220424de7e930
m_Address: Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_sd.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3b6ce020d078b524ab9f8ea29d7fe431
m_Address: Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_zd.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: adf779a3413c62744b56021a0ad5c93d
m_Address: Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_ss.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1abfe2b699a5f764c896951d8bc859ad
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_1.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 689bc9268484c3c4fab1743b9c99ee9e
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_2.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9bf752fc3d63b374998b79d5b9e169a0
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_3.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e54c60eb5da5278478389292015f8e8a
m_Address: Assets/Art/XJMH/pw/pw_sprite/freemodel/PW_FreeItem_4.png
m_ReadOnly: 0
m_SerializedLabels:
- XJMH
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0 m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 3d7ea06370502a34e82011fabf822b45, type: 2} m_Settings: {fileID: 11400000, guid: 3d7ea06370502a34e82011fabf822b45, type: 2}
m_SchemaSet: m_SchemaSet:
+14 -1
View File
@@ -3855,6 +3855,7 @@ GameObject:
- component: {fileID: 2075409202} - component: {fileID: 2075409202}
- component: {fileID: 2075409203} - component: {fileID: 2075409203}
- component: {fileID: 2075409204} - component: {fileID: 2075409204}
- component: {fileID: 2075409205}
m_Layer: 0 m_Layer: 0
m_Name: Manager m_Name: Manager
m_TagString: Untagged m_TagString: Untagged
@@ -3917,7 +3918,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
cam: {fileID: 1985717029} cam: {fileID: 1985717029}
enableBot: 0 enableBot: 1
botConfig: botConfig:
enabled: 1 enabled: 1
aiState: active aiState: active
@@ -4342,6 +4343,18 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
audioMixer: {fileID: 24100000, guid: ca43f9a7549657a49a2978e97c7a621a, type: 2} audioMixer: {fileID: 24100000, guid: ca43f9a7549657a49a2978e97c7a621a, type: 2}
--- !u!114 &2075409205
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2075409194}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bd70cad3c45605a4f824c9eaf1eb081d, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1001 &3569938244496052059 --- !u!1001 &3569938244496052059
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f650bd50b29116409d46a87a7828a35
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,513 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Profiling;
/// <summary>
/// Addressables / AssetBundle 已加载包实时监控悬浮窗(调试插件)。
///
/// 【用途】
/// 实时查看当前进程里加载了哪些 AA 包(bundle)、数量与内存概况,
/// 用于定位"进房加载资源时内存暴涨 / 被 iOS jetsam 杀掉"这类问题。
/// 注意:本项目所有 Addressables 组都是 LZMA 压缩,包一旦被打开就会整体解压进内存
/// (实测 fish_hhwk 解压 487MB、fish_flzy 436MB、fish_xjmh 219MB),
/// 所以看到哪个大包出现时,概览里的内存数字会立刻抬升,可直接对上。
///
/// 【使用方式】
/// 1. 编辑器 Play / Development Build:自动出现在左上角,无需任何操作;
/// 2. 其他构建(如 Release 真机包):加 AA_MONITOR 宏,或把本组件手动挂到任意常驻物体上。
///
/// 【操作】
/// - 左上角小标签:可拖动;轻点 = 展开 / 收起面板(编辑器下 F9 也可切换)
/// - 面板:上一页 / 下一页翻页;"导出日志"把完整列表 + 内存打入 Player Log(真机看 Xcode / idevice 日志)
/// - 每次有新包加载 / 卸载时自动打日志(logOnChange,默认开)
///
/// 【注意】
/// 项目 Active Input Handling = Input System(New),IMGUI 收不到点击事件,
/// 所以本窗口的交互(拖动 / 轻点 / 翻页)全部用新输入系统自行采样实现,不依赖 GUILayout 按钮;
/// 也因此悬浮层上的点击无法从游戏输入里"吃掉",测试时的小误触属正常现象。
/// </summary>
public class AABundleMonitor : MonoBehaviour
{
#region 配置
/// <summary>新包加载 / 卸载时自动输出日志(真机排查用,建议保持开启)</summary>
public static bool logOnChange = true;
/// <summary>刷新间隔(秒):0.5 秒扫一次已加载包列表,避免每帧开销</summary>
private const float RefreshInterval = 0.5f;
/// <summary>新增包高亮保持时长(秒)</summary>
private const float HighlightDuration = 5f;
#endregion
#region 自动安装
#if UNITY_EDITOR || DEVELOPMENT_BUILD || AA_MONITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void AutoInstall()
{
if (FindFirstObjectByType<AABundleMonitor>() != null) return;
var go = new GameObject("[AABundleMonitor]");
DontDestroyOnLoad(go);
go.AddComponent<AABundleMonitor>();
}
#endif
#endregion
#region 数据
/// <summary>单个已加载包的记录</summary>
private sealed class BundleRecord
{
public string name; // 完整 bundle 名(导出日志用)
public string shortName; // 截断后的显示名
public float firstSeen; // 首次出现时间(realtimeSinceStartup)
public bool streamedScene; // 是否是场景包
public string tag; // 内容标识(扫描包内资源路径得出,如 XJMH/HHWK/FLZY/LOGIN)
public int assetCount; // 包内资源数量(GetAllAssetNames 只读目录,不会加载资源)
public List<string> samplePaths; // 样例资源路径(导出日志用,帮助辨认包内容)
}
private readonly Dictionary<string, BundleRecord> _records = new Dictionary<string, BundleRecord>();
private readonly List<BundleRecord> _sorted = new List<BundleRecord>(); // 按加载时间倒序(最新在前)
private readonly HashSet<string> _loadedNow = new HashSet<string>(); // 本次扫描到的包名
private readonly List<string> _removed = new List<string>(); // 本次扫描发现已卸载的包名
private float _appStartTime;
private float _nextRefreshTime;
// 悬浮标签(可拖动)
private Vector2 _ballPos = new Vector2(16f, 16f);
private Rect _ballRect;
private bool _ballDragging;
private Vector2 _ballDragOffset;
private bool _ballMoved;
// 面板
private bool _panelOpen = true;
private int _page;
private int _rowsPerPage = 12;
// 上一帧 OnGUI 画出的按钮区域(供自采样点击命中,IMGUI 事件在本项目不可用)
private Rect _prevPageRect, _nextPageRect, _exportRect;
// 样式缓存
private GUIStyle _styleTitle, _styleLine, _styleOld, _styleNew, _styleButton, _styleWrap;
// 编辑器 PlayMode Script 名称(仅编辑器下有效,反射读取,失败为空)
private string _editorPlayModeName;
#endregion
#region 生命周期
private void Awake()
{
_appStartTime = Time.realtimeSinceStartup;
if (Application.isEditor) _editorPlayModeName = ReadEditorPlayModeName();
Refresh();
}
private void Update()
{
PollPointer();
if (Time.unscaledTime >= _nextRefreshTime)
Refresh();
}
#endregion
#region 扫描已加载包
private void Refresh()
{
_nextRefreshTime = Time.unscaledTime + RefreshInterval;
int addedCount = 0;
_loadedNow.Clear();
var bundles = AssetBundle.GetAllLoadedAssetBundles();
if (bundles != null)
{
foreach (var b in bundles)
{
if (b == null) continue;
string n = b.name;
_loadedNow.Add(n);
if (!_records.ContainsKey(n))
{
string baseName = n.EndsWith(".bundle") ? n.Substring(0, n.Length - 7) : n;
var rec = new BundleRecord
{
name = n,
shortName = baseName.Length > 16 ? baseName.Substring(0, 16) + "…" : baseName,
firstSeen = Time.realtimeSinceStartup,
streamedScene = b.isStreamedSceneAssetBundle
};
// ⭐ 扫描包内资源清单辨认内容(GetAllAssetNames 只读包目录、不会把资源加载进内存)
try
{
var assets = b.GetAllAssetNames();
if (assets != null && assets.Length > 0)
{
rec.assetCount = assets.Length;
rec.tag = GuessTag(assets);
rec.samplePaths = new List<string>();
for (int i = 0; i < assets.Length && rec.samplePaths.Count < 4; i++)
rec.samplePaths.Add(assets[i]);
}
}
catch { }
_records[n] = rec;
addedCount++;
if (logOnChange)
Debug.Log(string.IsNullOrEmpty(rec.tag)
? $"[AA包监控] + {n}({rec.assetCount} 项)"
: $"[AA包监控] + {n}【{rec.tag}】({rec.assetCount} 项)");
}
}
}
// 已卸载的包:从记录中移除
_removed.Clear();
foreach (var kv in _records)
if (!_loadedNow.Contains(kv.Key)) _removed.Add(kv.Key);
foreach (var n in _removed)
{
_records.Remove(n);
if (logOnChange)
Debug.Log($"[AA包监控] - {n}");
}
// 排序:最新加载的排最前(进房时新增的包会直接顶到列表上方)
_sorted.Clear();
_sorted.AddRange(_records.Values);
_sorted.Sort((a, b) => b.firstSeen.CompareTo(a.firstSeen));
if (logOnChange && (addedCount > 0 || _removed.Count > 0))
Debug.Log($"[AA包监控] 变动:+{addedCount} / -{_removed.Count},当前共 {_records.Count} 个包 | {MemSummary()}");
}
private static string MemSummary()
{
long allocated = Profiler.GetTotalAllocatedMemoryLong();
long reserved = Profiler.GetTotalReservedMemoryLong();
long mono = Profiler.GetMonoUsedSizeLong();
return $"内存: 已分配 {allocated / 1048576f:F1}MB / 预留 {reserved / 1048576f:F1}MB / Mono {mono / 1048576f:F1}MB";
}
/// <summary>
/// 根据包内资源路径猜内容标识:优先识别玩法关键词(XJMH/HHWK/FLZY),
/// 否则取出现最多的路径段(如 login/home/zhanshen 等),识别不出返回空。
/// </summary>
private static string GuessTag(string[] assets)
{
int sample = Mathf.Min(assets.Length, 400);
int xjmh = 0, hhwk = 0, flzy = 0;
var freq = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < sample; i++)
{
string a = assets[i].ToLowerInvariant();
if (a.Contains("xjmh")) xjmh++;
else if (a.Contains("hhwk")) hhwk++;
else if (a.Contains("flzy") || a.Contains("fenglei")) flzy++;
var parts = a.Split('/');
for (int k = 1; k < parts.Length && k <= 3; k++)
{
string seg = parts[k];
if (seg.Length == 0 || seg.Length > 12) continue;
if (seg == "assets" || seg == "mygame" || seg == "art" || seg == "game" || seg == "aa" || seg == "ui") continue;
freq.TryGetValue(seg, out int c);
freq[seg] = c + 1;
}
}
int minHits = Mathf.Max(3, sample / 5);
if (xjmh >= minHits && xjmh >= hhwk && xjmh >= flzy) return "XJMH";
if (hhwk >= minHits && hhwk >= xjmh && hhwk >= flzy) return "HHWK";
if (flzy >= minHits && flzy >= xjmh && flzy >= hhwk) return "FLZY";
string best = null;
int bestCount = 0;
foreach (var kv in freq)
if (kv.Value > bestCount) { bestCount = kv.Value; best = kv.Key; }
return bestCount >= minHits ? best.ToUpperInvariant() : null;
}
/// <summary>
/// 编辑器里显示当前 Addressables Play Mode Script 状态:
/// Use Asset Database / Simulate Groups 模式下资源直接走工程读取、不产生 bundle,
/// 本窗口看不到包属正常;0 个包时附带"去哪看真实包"的操作指引。
/// </summary>
private string EditorStatusLine()
{
if (!Application.isEditor) return null;
string mode = string.IsNullOrEmpty(_editorPlayModeName) ? "未知" : _editorPlayModeName;
if (_records.Count == 0 && mode != "Use Existing Build")
return $"编辑器 PlayMode:{mode} —— 此模式资源不走 bundle,0 个包属正常。" +
"要看真实包:构建 Windows 内容后切 Use Existing Build,或用真机 Dev Build";
return $"编辑器 PlayMode:{mode}";
}
/// <summary>
/// 反射读取 Addressables 编辑器设置里的 PlayMode Script 索引
/// (0=Use Asset Database 1=Simulate Groups 2=Use Existing Build 3=Default Build Script)。
/// 用反射是为了不依赖 Unity.Addressables.Editor 程序集,运行时/真机编译不受影响。
/// </summary>
private static string ReadEditorPlayModeName()
{
try
{
var t = Type.GetType("UnityEditor.AddressableAssets.Settings.ProjectConfigData, Unity.Addressables.Editor");
var prop = t?.GetProperty("ActivePlayModeIndex", BindingFlags.Public | BindingFlags.Static);
if (prop != null)
{
int idx = (int)prop.GetValue(null);
switch (idx)
{
case 0: return "Use Asset Database";
case 1: return "Simulate Groups";
case 2: return "Use Existing Build";
case 3: return "Default Build Script";
}
return $"index={idx}";
}
}
catch { }
return "未知";
}
#endregion
#region 输入(新输入系统自采样:拖动标签 / 轻点切换 / 翻页 / 导出)
private void PollPointer()
{
// 键盘便捷开关(编辑器 / 带键盘设备;真机为 null 自动跳过)
var kb = Keyboard.current;
if (kb != null && kb.f9Key.wasPressedThisFrame)
_panelOpen = !_panelOpen;
Vector2 screenPos;
bool isPressed, wasPressed, wasReleased;
var ts = Touchscreen.current;
var m = Mouse.current;
bool touchActive = ts != null &&
(ts.primaryTouch.press.isPressed || ts.primaryTouch.press.wasPressedThisFrame || ts.primaryTouch.press.wasReleasedThisFrame);
if (touchActive)
{
screenPos = ts.primaryTouch.position.ReadValue();
isPressed = ts.primaryTouch.press.isPressed;
wasPressed = ts.primaryTouch.press.wasPressedThisFrame;
wasReleased = ts.primaryTouch.press.wasReleasedThisFrame;
}
else if (m != null)
{
screenPos = m.position.ReadValue();
isPressed = m.leftButton.isPressed;
wasPressed = m.leftButton.wasPressedThisFrame;
wasReleased = m.leftButton.wasReleasedThisFrame;
}
else
{
return;
}
// 输入系统的坐标原点在左下,OnGUI 在左上
var guiPos = new Vector2(screenPos.x, Screen.height - screenPos.y);
if (wasPressed)
OnPointerPressed(guiPos);
if (_ballDragging)
{
if (isPressed)
{
Vector2 target = guiPos - _ballDragOffset;
if (Vector2.Distance(target, _ballPos) > 6f) _ballMoved = true;
_ballPos = ClampToScreen(target);
}
else if (wasReleased)
{
_ballDragging = false;
if (!_ballMoved) _panelOpen = !_panelOpen; // 轻点 = 切换面板
}
}
}
private void OnPointerPressed(Vector2 guiPos)
{
_ballDragging = false;
_ballMoved = false;
if (_ballRect.Contains(guiPos))
{
_ballDragging = true;
_ballDragOffset = guiPos - _ballPos;
return;
}
if (!_panelOpen) return;
if (_prevPageRect.Contains(guiPos)) { _page--; return; }
if (_nextPageRect.Contains(guiPos)) { _page++; return; }
if (_exportRect.Contains(guiPos)) { ExportLog(); return; }
}
private static Vector2 ClampToScreen(Vector2 pos)
{
return new Vector2(
Mathf.Clamp(pos.x, 0f, Mathf.Max(0f, Screen.width - 160f)),
Mathf.Clamp(pos.y, 0f, Mathf.Max(0f, Screen.height - 64f)));
}
#endregion
#region 绘制
private void OnGUI()
{
EnsureStyles();
DrawBall();
if (_panelOpen)
DrawPanel();
}
private void DrawBall()
{
_ballRect = new Rect(_ballPos.x, _ballPos.y, 152f, 56f);
GUI.color = new Color(0f, 0f, 0f, 0.72f);
GUI.DrawTexture(_ballRect, Texture2D.whiteTexture);
GUI.color = Color.white;
GUI.Label(new Rect(_ballRect.x + 12f, _ballRect.y + 3f, 140f, 26f), $"AA包 {_records.Count}", _styleTitle);
GUI.Label(new Rect(_ballRect.x + 12f, _ballRect.y + 28f, 140f, 24f), _panelOpen ? "点我收起" : "点我展开", _styleLine);
}
private void DrawPanel()
{
float w = Mathf.Min(Screen.width - 32f, 980f);
float h = Mathf.Min(Screen.height - 140f, 1500f);
float x = Mathf.Clamp(_ballPos.x, 8f, Mathf.Max(8f, Screen.width - w - 8f));
float y = Mathf.Clamp(_ballPos.y + 64f, 8f, Mathf.Max(8f, Screen.height - h - 8f));
var panel = new Rect(x, y, w, h);
GUI.color = new Color(0f, 0f, 0f, 0.84f);
GUI.DrawTexture(panel, Texture2D.whiteTexture);
GUI.color = Color.white;
string status = EditorStatusLine();
float statusH = status == null ? 0f : 58f;
float headerH = 108f + statusH;
const float footerH = 64f;
const float rowH = 30f;
// ---- 概要 ----
GUI.Label(new Rect(panel.x + 16f, panel.y + 8f, w - 32f, 30f), $"AA 已加载包监控 共 {_records.Count} 个", _styleTitle);
GUI.Label(new Rect(panel.x + 16f, panel.y + 42f, w - 32f, 26f), MemSummary(), _styleLine);
GUI.Label(new Rect(panel.x + 16f, panel.y + 70f, w - 32f, 26f),
$"运行 {(Time.realtimeSinceStartup - _appStartTime):F0}s 黄色 = 近 {HighlightDuration:F0} 秒新增(新加载的包在列表最上方)", _styleLine);
if (status != null)
GUI.Label(new Rect(panel.x + 16f, panel.y + 96f, w - 32f, statusH), status, _styleWrap);
// ---- 列表分页 ----
_rowsPerPage = Mathf.Max(4, (int)((panel.height - headerH - footerH - 12f) / rowH));
int totalPages = Mathf.Max(1, Mathf.CeilToInt(_sorted.Count / (float)_rowsPerPage));
_page = Mathf.Clamp(_page, 0, totalPages - 1);
int start = _page * _rowsPerPage;
float rowY = panel.y + headerH;
for (int i = 0; i < _rowsPerPage; i++)
{
int idx = start + i;
if (idx >= _sorted.Count) break;
var r = _sorted[idx];
bool isNew = Time.realtimeSinceStartup - r.firstSeen < HighlightDuration;
var style = isNew ? _styleNew : _styleOld;
string sceneTag = r.streamedScene ? "[场景] " : "";
string info = string.IsNullOrEmpty(r.tag)
? (r.assetCount > 0 ? $"{r.assetCount}项" : "")
: $"【{r.tag}】{r.assetCount}项";
string text = $"{sceneTag}{r.shortName} {info} (+{r.firstSeen - _appStartTime:F0}s)";
GUI.Label(new Rect(panel.x + 18f, rowY, w - 36f, rowH), text, style);
rowY += rowH;
}
// ---- 底部按钮(自采样点击,IMGUI 按钮不可用)----
float btnY = panel.yMax - 52f;
float btnW = 150f;
_prevPageRect = new Rect(panel.x + 16f, btnY, btnW, 44f);
_nextPageRect = new Rect(panel.x + 16f + btnW + 12f, btnY, btnW, 44f);
_exportRect = new Rect(panel.xMax - btnW - 16f, btnY, btnW, 44f);
DrawFakeButton(_prevPageRect, "上一页");
DrawFakeButton(_nextPageRect, "下一页");
DrawFakeButton(_exportRect, "导出日志");
GUI.Label(new Rect(panel.x + 16f + (btnW + 12f) * 2f, btnY + 10f, 160f, 26f), $"{_page + 1} / {totalPages}", _styleLine);
}
private void DrawFakeButton(Rect r, string text)
{
GUI.color = new Color(1f, 1f, 1f, 0.22f);
GUI.DrawTexture(r, Texture2D.whiteTexture);
GUI.color = Color.white;
GUI.Label(new Rect(r.x, r.y + 8f, r.width, 28f), text, _styleButton);
}
private void EnsureStyles()
{
if (_styleTitle != null) return;
_styleTitle = new GUIStyle(GUI.skin.label) { fontSize = 26, fontStyle = FontStyle.Bold };
_styleTitle.normal.textColor = Color.white;
_styleLine = new GUIStyle(GUI.skin.label) { fontSize = 22 };
_styleLine.normal.textColor = new Color(0.82f, 0.9f, 1f);
_styleOld = new GUIStyle(GUI.skin.label) { fontSize = 23 };
_styleOld.normal.textColor = Color.white;
_styleNew = new GUIStyle(GUI.skin.label) { fontSize = 23, fontStyle = FontStyle.Bold };
_styleNew.normal.textColor = new Color(1f, 0.82f, 0.2f);
_styleButton = new GUIStyle(GUI.skin.label) { fontSize = 24, alignment = TextAnchor.UpperCenter };
_styleButton.normal.textColor = Color.white;
_styleWrap = new GUIStyle(GUI.skin.label) { fontSize = 20, wordWrap = true };
_styleWrap.normal.textColor = new Color(0.95f, 0.9f, 0.62f);
}
#endregion
#region 导出
private void ExportLog()
{
var sb = new StringBuilder();
sb.AppendLine($"[AA包监控] 导出时间 {DateTime.Now:yyyy-MM-dd HH:mm:ss},共 {_records.Count} 个包 | {MemSummary()}");
foreach (var r in _sorted)
sb.AppendLine($" {(r.streamedScene ? "[场景] " : "")}{r.name} (+{r.firstSeen - _appStartTime:F1}s)");
string text = sb.ToString();
Debug.Log(text);
try { GUIUtility.systemCopyBuffer = text; } catch { }
}
#endregion
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd70cad3c45605a4f824c9eaf1eb081d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+16 -4
View File
@@ -261,12 +261,15 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
if (btn_TurretMinus != null) if (btn_TurretMinus != null)
btn_TurretMinus.onClick.AddListener(() => ChangeTurretMinusConsumeNum()); btn_TurretMinus.onClick.AddListener(() => ChangeTurretMinusConsumeNum());
// ⭐ 自动攻击开关:先检测自身 GameObject 激活状态,没激活就激活(预制体/外部可能把它藏了, // ⭐ 自动攻击开关:默认隐藏,等玩家入座时由 ShowPlayer 按"是否本地座位"决定显隐。
// 隐藏状态下点不到,自动攻击无法开关);再挂监听:关闭时本地座位炮台切回 Idle(原 GameMainPanel 的 toggle_SkillAuto 逻辑) // ⚠️ 以前这里是"没激活就无条件激活"(为修预制体把按钮藏了、点不到的问题),
// 但第二个人加入时他的座位才第一次激活、Awake 此刻才执行 → 会把他的"自动"按钮也打开,
// 屏幕上出现两个"自动"按钮。改为默认隐藏 + 入座时按本地/他座显隐(本地按钮不会再被藏)。
// 监听照旧:关闭时本地座位炮台切回 Idle(原 GameMainPanel 的 toggle_SkillAuto 逻辑)
if (toggle_AutoAttack != null) if (toggle_AutoAttack != null)
{ {
if (!toggle_AutoAttack.gameObject.activeSelf) if (toggle_AutoAttack.gameObject.activeSelf)
toggle_AutoAttack.gameObject.SetActive(true); toggle_AutoAttack.gameObject.SetActive(false);
toggle_AutoAttack.onValueChanged.AddListener(OnAutoAttackToggleChanged); toggle_AutoAttack.onValueChanged.AddListener(OnAutoAttackToggleChanged);
} }
@@ -1101,6 +1104,11 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
transform.GetChild(1).gameObject.SetActive(false); transform.GetChild(1).gameObject.SetActive(false);
} }
// ⭐ "自动"按钮只属于本地玩家:本地座位显示、其他座位隐藏
// (第二个人加入时他的座位才第一次激活,Awake 已改为默认隐藏,这里按座位归属再定一次)
if (toggle_AutoAttack != null)
toggle_AutoAttack.gameObject.SetActive(user == GameManager.Instance?.user?.id);
// 如果是本地玩家的座位,自动注册并加载装备 // 如果是本地玩家的座位,自动注册并加载装备
if (user == GameManager.Instance?.user?.id) if (user == GameManager.Instance?.user?.id)
{ {
@@ -1134,6 +1142,10 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
/// </summary> /// </summary>
public void ShowWaiting() public void ShowWaiting()
{ {
// ⭐ 离座:自动按钮一并隐藏(XJMH 也要处理——空座位不该留"自动"按钮)
if (toggle_AutoAttack != null)
toggle_AutoAttack.gameObject.SetActive(false);
if (turretControlMode == TurretControlMode.XJMH) return; if (turretControlMode == TurretControlMode.XJMH) return;
if (transform.childCount > 0) if (transform.childCount > 0)
@@ -1,4 +1,4 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Spine.Unity; using Spine.Unity;
using System; using System;
using System.Collections; using System.Collections;
@@ -20,10 +20,20 @@ public class AudioResourcesData
static Dictionary<int, AudioClip> audioClipDefaultDic=new Dictionary<int, AudioClip>(); static Dictionary<int, AudioClip> audioClipDefaultDic=new Dictionary<int, AudioClip>();
static Dictionary<int, AudioClip> audioClipGameDic=new Dictionary<int, AudioClip>(); static Dictionary<int, AudioClip> audioClipGameDic=new Dictionary<int, AudioClip>();
const string Audio_AALabel="Audio"; const string Audio_AALabel="Audio";
// ⭐ 句柄显式管理(修复"包只增不减"的根源):
// 之前 Load 的句柄直接丢弃(泄漏,包永不卸载),UnLoad 再重新 Load 一遍拿句柄释放
// (净引用数 0,等于什么都没卸载)。现在:加载时保存句柄,卸载时真正 Release。
// 注意:游戏音频物理上打在鱼包里(Audio+玩法 交集 = 鱼包),释放本句柄是鱼包能卸载的前提之一。
/// <summary>默认音频句柄(Audio+Default,进程常驻,不卸载)</summary>
static AsyncOperationHandle<IList<AudioClip>> _defaultAudioHandle;
/// <summary>当前玩法游戏音频句柄(Audio+玩法,进房加载/离房释放)</summary>
static AsyncOperationHandle<IList<AudioClip>> _gameAudioHandle;
static AudioResourcesData() static AudioResourcesData()
{ {
//这里要添加默认的音效 背景 按钮点击 //这里要添加默认的音效 背景 按钮点击
LoadDefaultAudioGame(); LoadDefaultAudioGame();
} }
@@ -39,7 +49,7 @@ public class AudioResourcesData
name_IDDic[item.audioResourcesPath]=item.audioID; name_IDDic[item.audioResourcesPath]=item.audioID;
} }
var keys = new List<string> { Audio_AALabel, "Default" }; var keys = new List<string> { Audio_AALabel, "Default" };
await Addressables.LoadAssetsAsync<AudioClip>(keys, audio => _defaultAudioHandle = Addressables.LoadAssetsAsync<AudioClip>(keys, audio =>
{ {
Debug.Log("加载音频: " + audio.name); Debug.Log("加载音频: " + audio.name);
if (name_IDDic.ContainsKey(audio.name)) if (name_IDDic.ContainsKey(audio.name))
@@ -47,7 +57,8 @@ public class AudioResourcesData
Debug.Log("加载音频ID: " + name_IDDic[audio.name]); Debug.Log("加载音频ID: " + name_IDDic[audio.name]);
audioClipDefaultDic.Add(name_IDDic[audio.name],audio); audioClipDefaultDic.Add(name_IDDic[audio.name],audio);
} }
}, MergeMode.Intersection).Task; }, MergeMode.Intersection);
await _defaultAudioHandle.Task;
foreach (var item in audioClipDefaultDic) foreach (var item in audioClipDefaultDic)
{ {
audioClipDic.Add(item.Key, item.Value); audioClipDic.Add(item.Key, item.Value);
@@ -82,13 +93,22 @@ public class AudioResourcesData
try try
{ {
await Addressables.LoadAssetsAsync<AudioClip>(keys, audio => // ⭐ 防御:上一轮句柄未释放(如离房被跳过)时先释放,避免引用计数累加
if (_gameAudioHandle.IsValid())
Addressables.Release(_gameAudioHandle);
// ⭐ 双标签交集(Audio + 当前玩法):只加载当前玩法标记的音频。
// Union 会按 "Audio" 标签把其它玩法的音频包一并打开,
// 而这些音频物理上和鱼的 Prefab 打在同一个 LZMA 包里 → 整包解压进内存导致 iOS 被杀
// 句柄保存到 _gameAudioHandle,离房时 UnLoadAudioGame 真正释放 → 鱼包才能卸载
_gameAudioHandle = Addressables.LoadAssetsAsync<AudioClip>(keys, audio =>
{ {
if (name_IDDic != null && name_IDDic.ContainsKey(audio.name)) if (name_IDDic != null && name_IDDic.ContainsKey(audio.name))
{ {
audioClipGameDic[name_IDDic[audio.name]] = audio; audioClipGameDic[name_IDDic[audio.name]] = audio;
} }
}, MergeMode.Union).Task; }, MergeMode.Intersection);
await _gameAudioHandle.Task;
audioClipDic = new Dictionary<int, AudioClip>(); audioClipDic = new Dictionary<int, AudioClip>();
if (audioClipDefaultDic != null) if (audioClipDefaultDic != null)
@@ -107,40 +127,21 @@ public class AudioResourcesData
Debug.LogError($"加载游戏音频失败: {e.Message}"); Debug.LogError($"加载游戏音频失败: {e.Message}");
} }
} }
public static async Task UnLoadAudioGame() public static Task UnLoadAudioGame()
{ {
GameType gameType = GameManager.Instance.gameData.currGameType; // ⭐ 真正的卸载:释放加载时保存的句柄(引用归零后 Audio+玩法 命中的 bundle 才会真正卸载)。
var keys = new List<string> { Audio_AALabel, gameType.ToString() }; // 旧实现"重新 Load 一遍拿句柄再 Release"是净 0 操作,什么都卸不掉。
if (_gameAudioHandle.IsValid())
Addressables.Release(_gameAudioHandle);
// 加载对应的句柄(只是为了拿到引用,之后释放) // 清空游戏音频字典(包卸载后 AudioClip 对象已失效,残留引用会导致播放 null)
AsyncOperationHandle<IList<AudioClip>> handle = Addressables.LoadAssetsAsync<AudioClip>(keys, null, MergeMode.Intersection); audioClipGameDic.Clear();
await handle.Task;
// 遍历卸载对应的音频 // 重建总字典:只保留默认音频
foreach (var audio in handle.Result)
{
if (name_IDDic.ContainsKey(audio.name))
{
int id = name_IDDic[audio.name];
if (audioClipGameDic.ContainsKey(id))
{
audioClipGameDic.Remove(id);
Debug.Log($"卸载音频: {audio.name} (ID={id})");
}
}
}
// 释放 Addressables 资源句柄
Addressables.Release(handle);
// 重建总字典:保留默认音频 + 剩余游戏音频
audioClipDic = new Dictionary<int, AudioClip>(audioClipDefaultDic); audioClipDic = new Dictionary<int, AudioClip>(audioClipDefaultDic);
foreach (var item in audioClipGameDic)
{
audioClipDic[item.Key] = item.Value;
}
Debug.Log("卸载完成,当前音频数量:" + audioClipDic.Count); Debug.Log("卸载完成,当前音频数量:" + audioClipDic.Count);
return Task.CompletedTask;
} }
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using static UnityEngine.AddressableAssets.Addressables; using static UnityEngine.AddressableAssets.Addressables;
[Serializable] [Serializable]
@@ -77,6 +78,14 @@ public class FishResourcesData
public static Dictionary<int, FishResourcesData> fishResourcesDatasDic;//id--FishResourcesData public static Dictionary<int, FishResourcesData> fishResourcesDatasDic;//id--FishResourcesData
public static Dictionary<int, Sprite> fishResourcesSpritesDic;//id--Sprite public static Dictionary<int, Sprite> fishResourcesSpritesDic;//id--Sprite
private static Dictionary<int, SkeletonDataAsset> diePrefabDic;//id--SkeletonDataAsset private static Dictionary<int, SkeletonDataAsset> diePrefabDic;//id--SkeletonDataAsset
// ⭐ 句柄显式管理(修复"包只增不减"的根源):
// 旧实现加载完立即 Release 句柄,但 fishPrefabDic 还持有 prefab 引用 ——
// 包没被卸载全靠 AudioResourcesData 泄漏的音频句柄"误打误撞"撑着(音频物理上就在鱼包里)。
// 现在:加载时保存句柄(prefab/dieSpine/sprite 三类),离房时 UnLoadFishAssetsData 统一 Release,
// 引用归零后鱼包真正从内存卸载 —— 依次进所有玩法不再累加内存。
private static readonly List<AsyncOperationHandle> _fishHandles = new List<AsyncOperationHandle>();
public static async Task LoadFishAssetsData() public static async Task LoadFishAssetsData()
{ {
// 清空对象池 // 清空对象池
@@ -86,6 +95,13 @@ public class FishResourcesData
diePrefabDic = new Dictionary<int, SkeletonDataAsset>(); diePrefabDic = new Dictionary<int, SkeletonDataAsset>();
fishResourcesSpritesDic = new Dictionary<int, Sprite>(); fishResourcesSpritesDic = new Dictionary<int, Sprite>();
// ⭐ 防御:上一轮句柄未释放(如离房流程被跳过)时先释放,避免引用计数累加
foreach (var h in _fishHandles)
{
if (h.IsValid()) Addressables.Release(h);
}
_fishHandles.Clear();
// ---------------- 1. 预下载所有依赖资源(关键) ---------------- // ---------------- 1. 预下载所有依赖资源(关键) ----------------
var labels = new List<string> var labels = new List<string>
{ {
@@ -120,7 +136,8 @@ public class FishResourcesData
}, MergeMode.Intersection); }, MergeMode.Intersection);
await prefabHandle.Task; await prefabHandle.Task;
Addressables.Release(prefabHandle); // ⭐ 不再立即 Release:保存句柄保持包存活,离房时统一卸载
_fishHandles.Add(prefabHandle);
Debug.Log($"Prefab 加载完成,共 {fishPrefabDic.Count} 个"); Debug.Log($"Prefab 加载完成,共 {fishPrefabDic.Count} 个");
// ---------------- 3. 加载死亡 Spine 动效 ---------------- // ---------------- 3. 加载死亡 Spine 动效 ----------------
@@ -138,7 +155,7 @@ public class FishResourcesData
}, MergeMode.Intersection); }, MergeMode.Intersection);
await dieHandle.Task; await dieHandle.Task;
Addressables.Release(dieHandle); _fishHandles.Add(dieHandle);
Debug.Log($"死亡 Spine 加载完成,共 {diePrefabDic.Count} 个"); Debug.Log($"死亡 Spine 加载完成,共 {diePrefabDic.Count} 个");
} }
catch (Exception e) catch (Exception e)
@@ -159,7 +176,7 @@ public class FishResourcesData
}, MergeMode.Intersection); }, MergeMode.Intersection);
await spriteHandle.Task; await spriteHandle.Task;
Addressables.Release(spriteHandle); _fishHandles.Add(spriteHandle);
Debug.Log($"Sprite 加载完成,共 {fishResourcesSpritesDic.Count} 个"); Debug.Log($"Sprite 加载完成,共 {fishResourcesSpritesDic.Count} 个");
} }
catch (Exception e) catch (Exception e)
@@ -171,81 +188,26 @@ public class FishResourcesData
} }
public static async Task UnLoadFishAssetsData() public static Task UnLoadFishAssetsData()
{ {
// ⭐ 真正的卸载:释放加载时保存的全部句柄(引用归零后鱼包才会真正卸载)。
// 旧实现"重新 Load 一遍拿句柄再 Release"是净 0 操作,什么都卸不掉。
Pool.Instance.Clear(); Pool.Instance.Clear();
// 卸载 Prefab foreach (var h in _fishHandles)
var keys = new List<string> { prefabFish_AALabel, GameManager.Instance.gameData.currGameType.ToString() };
var prefabHandle = Addressables.LoadAssetsAsync<GameObject>(keys, null, MergeMode.Intersection);
await prefabHandle.Task;
if (prefabHandle.Result != null)
{ {
foreach (var prefab in prefabHandle.Result) if (h.IsValid())
{ Addressables.Release(h);
if (FishResourcesPrefabsDic.TryGetValue(prefab.name, out int id))
{
if (fishPrefabDic.Remove(id))
{
Debug.Log($"卸载鱼Prefab: {prefab.name} (ID={id})");
}
}
}
} }
Addressables.Release(prefabHandle); _fishHandles.Clear();
// 卸载死亡 Spine 动效 // ⭐ 清空字典(包卸载后对象已失效,残留引用会指向已销毁资源)
keys = new List<string> { dieSpine_AALabel, GameManager.Instance.gameData.currGameType.ToString() }; fishPrefabDic?.Clear();
var dieHandle = Addressables.LoadAssetsAsync<SkeletonDataAsset>(keys, null, MergeMode.Intersection); diePrefabDic?.Clear();
await dieHandle.Task; fishResourcesSpritesDic?.Clear();
if (dieHandle.Result != null) Debug.Log($"[FishResourcesData] 鱼资源卸载完成");
{ return Task.CompletedTask;
foreach (var data in dieHandle.Result)
{
if (FishResourcesSpineDieDic.TryGetValue(data.name, out int id))
{
if (diePrefabDic.Remove(id))
{
Debug.Log($"卸载鱼死亡动效: {data.name} (ID={id})");
}
}
}
}
Addressables.Release(dieHandle);
// 卸载鱼 Sprite
// ⭐ try-catch 防护:Addressables 可能找不到 TuJian+游戏类型 交集的 Sprite,
// 抛 InvalidKeyException 导致整个 UnLoadFishAssetsData 中断,后续清理无法执行。
// Sprite 加载时已有 try-catch,卸载时也应对称处理
try
{
keys = new List<string> { Sprite_AALabel, GameManager.Instance.gameData.currGameType.ToString() };
var spriteHandle = Addressables.LoadAssetsAsync<Sprite>(keys, null, MergeMode.Intersection);
await spriteHandle.Task;
if (spriteHandle.Result != null)
{
foreach (var sprite in spriteHandle.Result)
{
if (FishResourcesSpriteDic.TryGetValue(sprite.name, out int id))
{
if (fishResourcesSpritesDic.Remove(id))
{
Debug.Log($"卸载鱼Sprite: {sprite.name} (ID={id})");
}
}
}
}
Addressables.Release(spriteHandle);
}
catch (Exception e)
{
Debug.LogWarning($"[FishResourcesData] Sprite 卸载失败(游戏类型={GameManager.Instance.gameData.currGameType}),跳过此项: {e.Message}");
}
Debug.Log($"卸载完成,剩余Prefab数量: {fishPrefabDic.Count}, 死亡动效数量: {diePrefabDic.Count}, Sprite数量: {fishResourcesSpritesDic.Count}");
} }
@@ -11994,7 +11994,7 @@ MonoBehaviour:
drawLineSegments: 60 drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
bossName: bossName:
isToUI: 1 isToUI: 0
tMP_CurrBossScore: {fileID: 0} tMP_CurrBossScore: {fileID: 0}
currBossScoreValue: 0 currBossScoreValue: 0
bossScoreValue: 0 bossScoreValue: 0
@@ -1035,6 +1035,11 @@ PrefabInstance:
propertyPath: m_Layer propertyPath: m_Layer
value: 6 value: 6
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1758318735507541674, guid: f11065bf68488f64e9d9b8acc8c4caff,
type: 3}
propertyPath: m_TagString
value: Fish
objectReference: {fileID: 0}
- target: {fileID: 1848564584791591582, guid: f11065bf68488f64e9d9b8acc8c4caff, - target: {fileID: 1848564584791591582, guid: f11065bf68488f64e9d9b8acc8c4caff,
type: 3} type: 3}
propertyPath: m_Layer propertyPath: m_Layer
@@ -14838,7 +14838,7 @@ MonoBehaviour:
drawLineSegments: 60 drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
bossName: bossName:
isToUI: 1 isToUI: 0
tMP_CurrBossScore: {fileID: 0} tMP_CurrBossScore: {fileID: 0}
currBossScoreValue: 0 currBossScoreValue: 0
bossScoreValue: 0 bossScoreValue: 0
@@ -192,7 +192,8 @@ public class RotatePan_XJMH : BJRedPacketEvent
// 内圈:可在选项范围内随机偏移(中心角度 ± 半角) // 内圈:可在选项范围内随机偏移(中心角度 ± 半角)
float neiCenterAngle = OptionIdToAngle((int)_innerOpt.optionId, innerCount); float neiCenterAngle = OptionIdToAngle((int)_innerOpt.optionId, innerCount);
float neiHalfAngle = (360f / innerCount) / 2f; float neiHalfAngle = (360f / innerCount) / 2f;
float neiTargetAngle = neiCenterAngle + Random.Range(-neiHalfAngle, neiHalfAngle); //float neiTargetAngle = neiCenterAngle + Random.Range(-neiHalfAngle, neiHalfAngle);
float neiTargetAngle = neiCenterAngle;
Debug.Log($"[XingYunFeiDie] 内圈目标角度: OptionId={_innerOpt.optionId} count={innerCount} 中心={neiCenterAngle}° 随机后={neiTargetAngle}°"); Debug.Log($"[XingYunFeiDie] 内圈目标角度: OptionId={_innerOpt.optionId} count={innerCount} 中心={neiCenterAngle}° 随机后={neiTargetAngle}°");
// 先转内圈 // 先转内圈
@@ -0,0 +1,108 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 84a92b25f83d49b9bc132d206b370281, type: 3}
m_Name: 592228afbce16145
m_EditorClassIdentifier:
hashCode: -1919871374
material: {fileID: 8548082164682178837}
materialHashCode: 0
m_Version: 1.1.0
m_FaceInfo:
m_FaceIndex: 0
m_FamilyName:
m_StyleName:
m_PointSize: 0
m_Scale: 0
m_UnitsPerEM: 0
m_LineHeight: 0
m_AscentLine: 0
m_CapLine: 0
m_MeanLine: 0
m_Baseline: 0
m_DescentLine: 0
m_SuperscriptOffset: 0
m_SuperscriptSize: 0
m_SubscriptOffset: 0
m_SubscriptSize: 0
m_UnderlineOffset: 0
m_UnderlineThickness: 0
m_StrikethroughOffset: 0
m_StrikethroughThickness: 0
m_TabWidth: 0
spriteSheet: {fileID: 2800000, guid: 1b70eadb887fad44abd4ece91ee38a6d, type: 3}
m_SpriteCharacterTable:
- m_ElementType: 2
m_Unicode: 65534
m_GlyphIndex: 0
m_Scale: 1
m_Name: 592228afbce16145
m_HashCode: -1919871374
m_SpriteGlyphTable:
- m_Index: 0
m_Metrics:
m_Width: 127
m_Height: 136
m_HorizontalBearingX: -63.5
m_HorizontalBearingY: 117.5
m_HorizontalAdvance: 127
m_GlyphRect:
m_X: 0
m_Y: 0
m_Width: 127
m_Height: 136
m_Scale: 1
m_AtlasIndex: 0
m_ClassDefinitionType: 0
sprite: {fileID: 21300000, guid: 1b70eadb887fad44abd4ece91ee38a6d, type: 3}
spriteInfoList: []
fallbackSpriteAssets: []
--- !u!21 &8548082164682178837
Material:
serializedVersion: 8
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: TextMeshPro/Sprite
m_Shader: {fileID: 4800000, guid: cf81c85f95fe47e1a27f6ae460cf182c, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 2800000, guid: 1b70eadb887fad44abd4ece91ee38a6d, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _ColorMask: 15
- _CullMode: 0
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UseUIAlphaClip: 0
m_Colors:
- _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767}
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ca36565f9fa2172499feaedffabf3ee8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

@@ -0,0 +1,153 @@
fileFormatVersion: 2
guid: 1b70eadb887fad44abd4ece91ee38a6d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -161,7 +161,7 @@ MonoBehaviour:
bottomLeft: {r: 1, g: 1, b: 1, a: 1} bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1} bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0} m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0} m_spriteAsset: {fileID: 11400000, guid: ca36565f9fa2172499feaedffabf3ee8, type: 2}
m_tintAllSprites: 0 m_tintAllSprites: 0
m_StyleSheet: {fileID: 0} m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901 m_TextStyleHashCode: -1183493901
@@ -103,6 +103,7 @@ using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.Playables; using UnityEngine.Playables;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using UnityEngine.UI; using UnityEngine.UI;
@@ -699,23 +700,38 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}); });
// ⭐ 大喇叭:开/关弹幕输入面板 // ⭐ 大喇叭:开/关弹幕输入面板
// ⭐ 绑定在 OnShow 里:先 RemoveAllListeners 再 Add,防止面板重复 OnShow 叠加监听器
// (叠加后点一次发送 = 发 N 次 API = 多扣 N 个道具)
if (dalaba != null) if (dalaba != null)
{
dalaba.onClick.RemoveAllListeners();
dalaba.onClick.AddListener(() => dalaba.onClick.AddListener(() =>
{ {
if (obj_DaLaBaPanel != null) if (obj_DaLaBaPanel != null)
obj_DaLaBaPanel.SetActive(!obj_DaLaBaPanel.activeSelf); obj_DaLaBaPanel.SetActive(!obj_DaLaBaPanel.activeSelf);
}); });
}
if (btn_DaLaBaSend != null) if (btn_DaLaBaSend != null)
{
btn_DaLaBaSend.onClick.RemoveAllListeners();
btn_DaLaBaSend.onClick.AddListener(OnClickDaLaBaSend); btn_DaLaBaSend.onClick.AddListener(OnClickDaLaBaSend);
}
if (btn_DaLaBaClose != null) if (btn_DaLaBaClose != null)
{
btn_DaLaBaClose.onClick.RemoveAllListeners();
btn_DaLaBaClose.onClick.AddListener(() => btn_DaLaBaClose.onClick.AddListener(() =>
{ {
if (obj_DaLaBaPanel != null) obj_DaLaBaPanel.SetActive(false); if (obj_DaLaBaPanel != null)
obj_DaLaBaPanel.SetActive(false);
}); });
}
// ⭐ 召唤卡:使用召唤卡召唤Boss // ⭐ 召唤卡:使用召唤卡召唤Boss(同样防重复绑定)
if (zhanhuanka != null) if (zhanhuanka != null)
{
zhanhuanka.onClick.RemoveAllListeners();
zhanhuanka.onClick.AddListener(OnClickZhanHuanKa); zhanhuanka.onClick.AddListener(OnClickZhanHuanKa);
}
// 服务器广播监听 // 服务器广播监听
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate); EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate);
@@ -1253,13 +1269,21 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
await AudioResourcesData.LoadAudioGame(); await AudioResourcesData.LoadAudioGame();
if (flow != _resourceFlowToken) return; if (flow != _resourceFlowToken) return;
await Addressables.LoadAssetsAsync<AudioClip>("Audio", clip => // ⭐ 开火音效 PW_Shoot 在 Default Local Group(Audio+PTCBZS 双标签交集检索):
{ // 只命中 Default 小包,不把其它玩法的巨型音频包拉进内存;句柄由 LoadBulletClip 静态保存,离房释放
if (flow != _resourceFlowToken) return; // 残留流程不再写面板字段 await LoadBulletClip();
if (clip.name == "PW_Shoot")
bulletClip = clip;
}).Task;
if (flow != _resourceFlowToken) return; if (flow != _resourceFlowToken) return;
if (_bulletClipHandle.IsValid() && _bulletClipHandle.Result != null)
{
foreach (var clip in _bulletClipHandle.Result)
{
if (clip.name == "PW_Shoot")
{
bulletClip = clip;
break;
}
}
}
SetLoadProgress(0.8f); SetLoadProgress(0.8f);
await Task.Delay(100); await Task.Delay(100);
@@ -1321,6 +1345,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (flow != _resourceFlowToken) return; // ⭐ 期间已有新流程(新面板加载),剩余卸载作废 if (flow != _resourceFlowToken) return; // ⭐ 期间已有新流程(新面板加载),剩余卸载作废
await AudioResourcesData.UnLoadAudioGame(); await AudioResourcesData.UnLoadAudioGame();
if (flow != _resourceFlowToken) return; if (flow != _resourceFlowToken) return;
UnLoadBulletClip(); // ⭐ 释放开火音效句柄(引用计数归零)
await MonsterMoveLine.UnLoadBezier(); await MonsterMoveLine.UnLoadBezier();
if (flow != _resourceFlowToken) return; if (flow != _resourceFlowToken) return;
// ⭐ 清空子弹池 + 特效池(鱼池已在 UnLoadFishAssetsData 内清理) // ⭐ 清空子弹池 + 特效池(鱼池已在 UnLoadFishAssetsData 内清理)
@@ -1332,6 +1357,32 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// <summary>资源是否已卸载(避免 OnHide 重复卸载)</summary> /// <summary>资源是否已卸载(避免 OnHide 重复卸载)</summary>
private bool _resourcesUnloaded = false; private bool _resourcesUnloaded = false;
/// <summary>
/// ⭐ 开火音效 PW_Shoot 的加载句柄(static:新老面板实例共享)。
/// PW_Shoot 在 Default Local Group(Audio+PTCBZS),该包因默认音频常驻本就不会卸载,
/// 但句柄仍要显式保存/释放,避免每次进房引用计数 +1 无限累加。
/// </summary>
private static AsyncOperationHandle<IList<AudioClip>> _bulletClipHandle;
/// <summary>加载开火音效(Audio+PTCBZS 交集,只命中 Default Local Group 里的 PW_Shoot)</summary>
private static async Task LoadBulletClip()
{
// 防御:上一轮句柄未释放时先释放
if (_bulletClipHandle.IsValid())
Addressables.Release(_bulletClipHandle);
_bulletClipHandle = Addressables.LoadAssetsAsync<AudioClip>(new List<string> { "Audio", "PTCBZS" }, null,
Addressables.MergeMode.Intersection);
await _bulletClipHandle.Task;
}
/// <summary>释放开火音效句柄(离房卸载流程调用)</summary>
private static void UnLoadBulletClip()
{
if (_bulletClipHandle.IsValid())
Addressables.Release(_bulletClipHandle);
}
/// <summary>离房 socket 响应等待信号</summary> /// <summary>离房 socket 响应等待信号</summary>
private TaskCompletionSource<bool> _leaveRoomResponseTcs; private TaskCompletionSource<bool> _leaveRoomResponseTcs;
/// <summary>正在离开的游戏面板类型(完成后关闭它切到 home)</summary> /// <summary>正在离开的游戏面板类型(完成后关闭它切到 home)</summary>
@@ -1383,11 +1434,20 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{ {
if (JinDuTiao != null) JinDuTiao.Show(); if (JinDuTiao != null) JinDuTiao.Show();
// ⭐ 先退房销毁场上活鱼,再卸载资源(与 UnLoadResources 顺序一致):
// 现在句柄真正会释放、包内资源(贴图/Spine)会被销毁,
// 若先卸载后销毁鱼,残留活鱼会在淡出期间丢失材质花屏
CreateFishManager.Instance.ExitRoom();
CreateFishManager.Instance.isResources = false;
BulletPool.Instance.ClearAll();
EffectPool.Instance.ClearAll();
SetUnloadProgress(0.2f); SetUnloadProgress(0.2f);
await FishResourcesData.UnLoadFishAssetsData(); await FishResourcesData.UnLoadFishAssetsData();
SetUnloadProgress(0.4f); SetUnloadProgress(0.4f);
await AudioResourcesData.UnLoadAudioGame(); await AudioResourcesData.UnLoadAudioGame();
UnLoadBulletClip(); // ⭐ 释放开火音效句柄(引用计数归零)
SetUnloadProgress(0.6f); SetUnloadProgress(0.6f);
await MonsterMoveLine.UnLoadBezier(); await MonsterMoveLine.UnLoadBezier();
@@ -1395,11 +1455,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
SetUnloadProgress(0.8f); SetUnloadProgress(0.8f);
await Task.Delay(100); await Task.Delay(100);
CreateFishManager.Instance.ExitRoom();
CreateFishManager.Instance.isResources = false;
// ⭐ 清空子弹池 + 特效池(鱼池已在 UnLoadFishAssetsData 内清理)
BulletPool.Instance.ClearAll();
EffectPool.Instance.ClearAll();
SetUnloadProgress(0.9f); SetUnloadProgress(0.9f);
// ⭐ 卡在 0.9 等待 socket 返回 LeaveRoomResult // ⭐ 卡在 0.9 等待 socket 返回 LeaveRoomResult
@@ -1862,8 +1917,13 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// 大喇叭发送:输入弹幕 → /api/Hall/SendBroadcast 全服广播 /// 大喇叭发送:输入弹幕 → /api/Hall/SendBroadcast 全服广播
/// (与背包大喇叭 OnClickFaSong 流程一致),发送成功后清空输入框并关闭面板。 /// (与背包大喇叭 OnClickFaSong 流程一致),发送成功后清空输入框并关闭面板。
/// </summary> /// </summary>
/// <summary>大喇叭发送中标志:防止回调返回前连点发送按钮导致多发多扣道具</summary>
private bool _isSendingDaLaBa;
private void OnClickDaLaBaSend() private void OnClickDaLaBaSend()
{ {
// ⭐ 防重入:上次请求回调还没回来时连点直接忽略(多发一次 = 多扣一个大喇叭)
if (_isSendingDaLaBa) return;
string msg = tmp_DaLaBaInput?.text?.Trim(); string msg = tmp_DaLaBaInput?.text?.Trim();
if (string.IsNullOrEmpty(msg)) if (string.IsNullOrEmpty(msg))
{ {
@@ -1871,8 +1931,13 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
return; return;
} }
APIRequestList.SendBroadcast("广播:" + msg, (result) => _isSendingDaLaBa = true;
// ⭐ 显示格式:"<sprite index=0>【小*鱼】:内容"(前面加喇叭图标 TMP 标签;名字打码:首尾保留、中间一个 *)
var userName = GameManager.Instance.user?.BaseInfo?.userName;
if (string.IsNullOrEmpty(userName)) userName = "玩家";
APIRequestList.SendBroadcast($"<sprite index=0>【{MaskBroadcastName(userName)}】:{msg}", (result) =>
{ {
_isSendingDaLaBa = false;
// 发送成功:清空输入框并关闭面板(广播内容会通过 LoginBroadcast 全服弹幕显示) // 发送成功:清空输入框并关闭面板(广播内容会通过 LoginBroadcast 全服弹幕显示)
if (tmp_DaLaBaInput != null) tmp_DaLaBaInput.text = ""; if (tmp_DaLaBaInput != null) tmp_DaLaBaInput.text = "";
if (obj_DaLaBaPanel != null) obj_DaLaBaPanel.SetActive(false); if (obj_DaLaBaPanel != null) obj_DaLaBaPanel.SetActive(false);
@@ -1887,6 +1952,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}); });
} }
/// <summary>
/// 玩家名打码(广播格式):保留首尾字符、中间一个 *("小金鱼"→"小*鱼"、"我是大帅哥"→"我*哥")。
/// 与召唤卡提示的 MaskUserName(三个星)区分,广播用单星。
/// </summary>
private static string MaskBroadcastName(string name)
{
if (string.IsNullOrEmpty(name)) return name;
name = name.Trim();
if (name.Length <= 1) return name;
return name[0] + "*" + name[name.Length - 1];
}
/// <summary> /// <summary>
/// 使用召唤卡:从背包找到召唤卡道具(type=11)拿道具编号 /// 使用召唤卡:从背包找到召唤卡道具(type=11)拿道具编号
/// → /api/SummonCard/use 召唤Boss(Boss鱼由服务端 OnFishSpawn 下发给房间所有人) /// → /api/SummonCard/use 召唤Boss(Boss鱼由服务端 OnFishSpawn 下发给房间所有人)
@@ -72,7 +72,11 @@ public class UIHome_BY_BackPackPanel : SubUIBase
if (DaLaBaPanel != null) if (DaLaBaPanel != null)
DaLaBaPanel.SetActive(false); DaLaBaPanel.SetActive(false);
if (btn_FaSong != null) if (btn_FaSong != null)
{
// ⭐ 先清再绑:防止面板重复 Show 叠加监听器(点一次发 N 次 = 多扣 N 个大喇叭)
btn_FaSong.onClick.RemoveAllListeners();
btn_FaSong.onClick.AddListener(OnClickFaSong); btn_FaSong.onClick.AddListener(OnClickFaSong);
}
// 数量选择面板(默认隐藏) // 数量选择面板(默认隐藏)
if (useObj != null) useObj.SetActive(false); if (useObj != null) useObj.SetActive(false);
@@ -147,23 +151,51 @@ public class UIHome_BY_BackPackPanel : SubUIBase
_curUseCountAction = null; _curUseCountAction = null;
} }
/// <summary>大喇叭发送中标志:防止回调返回前连点发送按钮导致多发多扣道具</summary>
private bool _isSendingFaSong;
private void OnClickFaSong() private void OnClickFaSong()
{ {
string msg = "广播:"+ tMPText_DaLaBaLabel?.text?.Trim(); // ⭐ 防重入:上次请求回调还没回来时连点直接忽略
if (string.IsNullOrEmpty(msg)) if (_isSendingFaSong) return;
// ⭐ 先取文本判空再拼前缀(原"广播:"+text 判空永远不成立,空内容也会发送扣道具)
string content = tMPText_DaLaBaLabel?.text?.Trim();
if (string.IsNullOrEmpty(content))
{ {
UIManager.Instance.ShowBoardError("请输入广播内容"); UIManager.Instance.ShowBoardError("请输入广播内容");
return; return;
} }
APIRequestList.SendBroadcast(msg, (result) => _isSendingFaSong = true;
// ⭐ 显示格式:"<sprite index=0>【小*鱼】:内容"(前面加喇叭图标 TMP 标签;名字打码与游戏内大喇叭同格式)
var userName = GameManager.Instance.user?.BaseInfo?.userName;
if (string.IsNullOrEmpty(userName)) userName = "玩家";
APIRequestList.SendBroadcast($"<sprite index=0>【{MaskBroadcastName(userName)}】:{content}", (result) =>
{ {
//UIManager.Instance.ShowBoardGongGao($"广播: {msg}"); _isSendingFaSong = false;
tMPText_DaLaBaLabel.text = ""; tMPText_DaLaBaLabel.text = "";
DaLaBaPanel.SetActive(false); DaLaBaPanel.SetActive(false);
// ⭐ 发送成功=服务端已扣大喇叭:拉最新用户数据刷新背包数量
APIRequestList.GetUser((user) =>
{
if (user != null)
GameManager.Instance.UpdataUserData(user);
});
}); });
} }
/// <summary>
/// 玩家名打码(广播格式):保留首尾字符、中间一个 *("小金鱼"→"小*鱼")。
/// 与 GameMainPanel_FishBase.MaskBroadcastName 同格式(背包面板独立副本)。
/// </summary>
private static string MaskBroadcastName(string name)
{
if (string.IsNullOrEmpty(name)) return name;
name = name.Trim();
if (name.Length <= 1) return name;
return name[0] + "*" + name[name.Length - 1];
}
private void OnClickConfirmChangeName() private void OnClickConfirmChangeName()
{ {
string newName = tMPText_NewName?.text?.Trim(); string newName = tMPText_NewName?.text?.Trim();