From 8d04198f5b0e981da8dc29477baf8478424050b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=AE2003?= <2384017988@qq.com> Date: Wed, 29 Jul 2026 00:31:59 +0800 Subject: [PATCH] 1 --- .../Scripts/Net/WebSocketReponseList.cs | 30 +++++---- .../Scripts/fishResources/TurretResources.cs | 60 +++++++++--------- .../aa/UI/Game/GameMainPanel_FishBase.cs | 6 +- .../UI/Home/Sub/UIHome_BY_ItemListPanels.cs | 62 ++++++++++++++++--- 4 files changed, 106 insertions(+), 52 deletions(-) diff --git a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs index 2ecf1b9f9..549f7d24c 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs @@ -187,32 +187,40 @@ namespace Assets.MyGame.Scripts.Net case GameType.JQQD: break; case GameType.defaultPanel: - UIManager.Instance.Get(UIPanel.MainGamePanelDefault).currSitData.goldNum = gold; - + UpdateLocalPlayerGold(UIPanel.MainGamePanelDefault, gold); break; case GameType.FLZY: - UIManager.Instance.Get(UIPanel.MainGamePanelFLZY).currSitData.goldNum = gold; - + UpdateLocalPlayerGold(UIPanel.MainGamePanelFLZY, gold); break; case GameType.YYSC: - UIManager.Instance.Get(UIPanel.MainGamePanelYYSC).currSitData.goldNum = gold; - + UpdateLocalPlayerGold(UIPanel.MainGamePanelYYSC, gold); break; case GameType.ZGXQ: - break; case GameType.XJMH: - UIManager.Instance.Get(UIPanel.MainGamePanelXJMH).currSitData.goldNum = gold; - + UpdateLocalPlayerGold(UIPanel.MainGamePanelXJMH, gold); break; case GameType.HHWK: - UIManager.Instance.Get(UIPanel.MainGamePanelHHWK).currSitData.goldNum = gold; - + UpdateLocalPlayerGold(UIPanel.MainGamePanelHHWK, gold); break; default: break; } } + + /// + /// 后端推送 SyncPlayerGold 时同步本地玩家金币(底层值 + UI 显示)。 + /// 之前只更新 goldNum 不刷新 tMP_GoldNum.text,导致 UI 显示和后端 authoritative 值不同步, + /// 后续杀鱼累加基于修正值但 UI 显示旧值,差距越拉越大。 + /// + private static void UpdateLocalPlayerGold(UIPanel panelType, long gold) + { + var panel = UIManager.Instance.Get(panelType); + if (panel == null || panel.currSitData == null) return; + panel.currSitData.goldNum = gold; + if (panel.currSitData.tMP_GoldNum != null) + panel.currSitData.tMP_GoldNum.text = gold.ToString(); + } public static void GetRoomInfoResult(string msg) { //GameManager.Instance.StartGame();RoomChangeResponse diff --git a/Assets/MyGame/Scripts/fishResources/TurretResources.cs b/Assets/MyGame/Scripts/fishResources/TurretResources.cs index 97fe87cc5..827d55410 100644 --- a/Assets/MyGame/Scripts/fishResources/TurretResources.cs +++ b/Assets/MyGame/Scripts/fishResources/TurretResources.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; using static UnityEngine.AddressableAssets.Addressables; [Serializable] @@ -31,6 +32,9 @@ public class TurretResourcesData private static Dictionary turretPrefabDic; // id → prefab private static Dictionary turretSpriteDic; // id → sprite + // ---- 保留的 AA Handle(不释放,避免 Build 下资源被卸载)---- + private static readonly List _loadedHandles = new List(); + static TurretResourcesData() { Init(); @@ -93,18 +97,27 @@ public class TurretResourcesData /// /// 加载炮台相关 Addressables 资源(PT + Default 标签交集)。 + /// 注意:Handle 必须保留,不能 Release,否则 Build 下资源会被立即卸载, + /// 字典中存的就是已销毁对象的引用(Editor 下因 AssetDatabase 模式不会暴露此问题)。 /// public static async Task LoadTurretAssetsData() { turretPrefabDic = new Dictionary(); turretSpriteDic = new Dictionary(); + // 清理上一轮残留的 Handle + foreach (var h in _loadedHandles) + { + if (h.IsValid()) Addressables.Release(h); + } + _loadedHandles.Clear(); + var labels = new List { turret_AALabel, defaultSubLabel }; Debug.Log("[TurretResourcesData] 开始预下载炮台资源依赖 (PT + Default)..."); var depHandle = Addressables.DownloadDependenciesAsync(labels, MergeMode.Intersection); await depHandle.Task; - Addressables.Release(depHandle); + Addressables.Release(depHandle); // 预下载 Handle 可以立即释放,只影响下载缓存 Debug.Log("[TurretResourcesData] 炮台依赖资源预下载完成!"); // ---- 加载炮台 Prefab ---- @@ -118,7 +131,8 @@ public class TurretResourcesData }, MergeMode.Intersection); await prefabHandle.Task; - Addressables.Release(prefabHandle); + // 不释放!保留 Handle 以保持资源存活 + _loadedHandles.Add(prefabHandle); Debug.Log($"[TurretResourcesData] 炮台 Prefab 加载完成,共 {turretPrefabDic.Count} 个"); // ---- 加载炮台 Sprite ---- @@ -132,7 +146,8 @@ public class TurretResourcesData }, MergeMode.Intersection); await spriteHandle.Task; - Addressables.Release(spriteHandle); + // 不释放!保留 Handle 以保持资源存活 + _loadedHandles.Add(spriteHandle); Debug.Log($"[TurretResourcesData] 炮台 Sprite 加载完成,共 {turretSpriteDic.Count} 个"); Debug.Log("[TurretResourcesData] 所有炮台资源加载完毕!"); @@ -140,44 +155,25 @@ public class TurretResourcesData /// /// 卸载炮台 Addressables 资源。 + /// 释放 LoadTurretAssetsData 中保留的所有 Handle。 /// - public static async Task UnLoadTurretAssetsData() + public static Task UnLoadTurretAssetsData() { - var labels = new List { turret_AALabel, defaultSubLabel }; - - // 卸载 Prefab - var prefabHandle = Addressables.LoadAssetsAsync(labels, null, MergeMode.Intersection); - await prefabHandle.Task; - if (prefabHandle.Result != null) + foreach (var h in _loadedHandles) { - foreach (var prefab in prefabHandle.Result) + if (h.IsValid()) { - if (turretPrefabPathDic.TryGetValue(prefab.name, out int id)) - { - if (turretPrefabDic != null && turretPrefabDic.Remove(id)) - Debug.Log($"[TurretResourcesData] 卸载 Prefab: {prefab.name} (ID={id})"); - } + Addressables.Release(h); + Debug.Log($"[TurretResourcesData] 释放 Handle: {h.DebugName}"); } } - Addressables.Release(prefabHandle); + _loadedHandles.Clear(); - // 卸载 Sprite - var spriteHandle = Addressables.LoadAssetsAsync(labels, null, MergeMode.Intersection); - await spriteHandle.Task; - if (spriteHandle.Result != null) - { - foreach (var sprite in spriteHandle.Result) - { - if (turretSpritePathDic.TryGetValue(sprite.name, out int id)) - { - if (turretSpriteDic != null && turretSpriteDic.Remove(id)) - Debug.Log($"[TurretResourcesData] 卸载 Sprite: {sprite.name} (ID={id})"); - } - } - } - Addressables.Release(spriteHandle); + turretPrefabDic?.Clear(); + turretSpriteDic?.Clear(); Debug.Log($"[TurretResourcesData] 卸载完成,剩余 Prefab: {turretPrefabDic?.Count ?? 0}, Sprite: {turretSpriteDic?.Count ?? 0}"); + return Task.CompletedTask; } // ==================== 查询接口 ==================== diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index ada2dba33..968937a60 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -348,7 +348,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base RefreshSkillPropsCount(props); // 同步更新游戏内金币显示(刮刮卡领奖、邮件领取等都会触发) - if (currSitData != null && user?.assets != null) + // ⭐ 但游戏内金币由房间数据(RoomJoinNotice.gold / SyncPlayerGold)管理, + // user.assets.gold 是账户总金币,进房时后端推送 AssetUpdated 会触发此处, + // 如果用账户总金币覆盖房间金币,会导致显示与后端房间数据不一致。 + // 因此游戏内(isGame=true)不覆盖 goldNum,退房后由 SyncPlayerGold 或重新进房时设置。 + if (currSitData != null && user?.assets != null && !WebSocketManager.Instance.isGame) { currSitData.goldNum = user.assets.gold; if (currSitData.tMP_GoldNum != null) diff --git a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ItemListPanels.cs b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ItemListPanels.cs index 21b357e37..a0bf4528a 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ItemListPanels.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ItemListPanels.cs @@ -82,6 +82,13 @@ public class UIHome_BY_ItemListPanels : SubUIBase /// 预览区当前显示的炮台实例 private GameObject _currentPreviewObj; + /// + /// 面板自带的预览相机(TurretPanel.prefab 上 previewParent 的子节点)。 + /// 仅用于炮台/翅膀预览(这些 prefab 不带相机)。 + /// 战神 prefab 自带相机,预览战神时必须禁用此相机,避免两个相机渲染到同一 RenderTexture 造成冲突。 + /// + private Camera _panelPreviewCamera; + /// true=从游戏内打开(关闭时刷新游戏炮台),false=从大厅打开 private bool _isInGame; @@ -117,6 +124,10 @@ public class UIHome_BY_ItemListPanels : SubUIBase public void Start() { + // 缓存面板自带的预览相机(previewParent 下的 Camera 子节点) + if (previewParent != null) + _panelPreviewCamera = previewParent.GetComponentInChildren(true); + btn_Turret.onClick.AddListener(() => UpdateSelectType(ItemPanelSelect.Turrect)); btn_Wing.onClick.AddListener(() => UpdateSelectType(ItemPanelSelect.Wing)); btn_ZS.onClick.AddListener(() => UpdateSelectType(ItemPanelSelect.ZS)); @@ -344,7 +355,10 @@ public class UIHome_BY_ItemListPanels : SubUIBase // 设置展示 —— 根据分类从不同组件拿图标 if (toggleDisplay != null && resData != null) { + Sprite iconSprite = null; GameObject turretPrefab = TurretResourcesData.GetTurretPrefab(resData.turretNumber); + + // 优先从预制体直接引用拿图标;为空则回退到 AA 单独加载的 Sprite if (turretPrefab != null) { if (_currentSelectType == ItemPanelSelect.Wing) @@ -352,21 +366,24 @@ public class UIHome_BY_ItemListPanels : SubUIBase // 翅膀:从 WingItemInit.iconWing 拿图标 WingItemInit wingInit = turretPrefab.GetComponent(); if (wingInit != null && wingInit.iconWing != null) - { - toggleDisplay.SetSprite(wingInit.iconWing); - } + iconSprite = wingInit.iconWing; } else { // 炮台/战神:从 TurretData.uiIconSprite 拿图标 TurretData turretData = turretPrefab.GetComponent(); if (turretData != null && turretData.uiIconSprite != null) - { - toggleDisplay.SetSprite(turretData.uiIconSprite); - } + iconSprite = turretData.uiIconSprite; } } + // 兜底:预制体直引失效(如 AA 打包后依赖未打包进来)时,用 AA 单独加载的 Sprite + if (iconSprite == null) + iconSprite = TurretResourcesData.GetTurretSprite(resData.turretNumber); + + if (iconSprite != null) + toggleDisplay.SetSprite(iconSprite); + bool isOwned = IsItemOwned(resData.turretNumber); bool isEquipped = IsItemEquipped(resData.turretNumber); toggleDisplay.SetLockState(!isOwned); @@ -574,17 +591,25 @@ public class UIHome_BY_ItemListPanels : SubUIBase var toggle = toggleGo.GetComponent(); var toggleDisplay = toggleGo.GetComponent(); - // 设置图标(和炮台一样从 TurretData.uiIconSprite 获取) + // 设置图标(和炮台一样从 TurretData.uiIconSprite 获取,兜底用 AA 单独加载的 Sprite) if (toggleDisplay != null && resData != null) { + Sprite iconSprite = null; GameObject turretPrefab = TurretResourcesData.GetTurretPrefab(resData.turretNumber); if (turretPrefab != null) { TurretData turretData = turretPrefab.GetComponent(); if (turretData != null && turretData.uiIconSprite != null) - toggleDisplay.SetSprite(turretData.uiIconSprite); + iconSprite = turretData.uiIconSprite; } + // 兜底:预制体直引失效时,用 AA 单独加载的 Sprite + if (iconSprite == null) + iconSprite = TurretResourcesData.GetTurretSprite(resData.turretNumber); + + if (iconSprite != null) + toggleDisplay.SetSprite(iconSprite); + bool isOwned = IsItemOwned(resData.turretNumber); bool isEquipped = IsItemEquipped(resData.turretNumber); toggleDisplay.SetLockState(!isOwned); @@ -686,6 +711,11 @@ public class UIHome_BY_ItemListPanels : SubUIBase if (rawImg_DetailPreview != null) rawImg_DetailPreview.gameObject.SetActive(true); + // ---- 战神预览:禁用面板自带相机,避免与战神 prefab 自带相机冲突 ---- + // 战神 prefab 内部已有专门配置的 Camera(位置/旋转/CullingMask 都是为该战神模型定制的), + // 两个相机渲染到同一 RenderTexture 会导致打包后渲染顺序不一致而画面错乱。 + SetPanelCameraEnabled(false); + // ---- 预览(和翅膀一样实例化预制体到预览区)---- if (resData != null && previewParent != null && prefab != null) { @@ -694,6 +724,16 @@ public class UIHome_BY_ItemListPanels : SubUIBase } } + /// + /// 启用/禁用面板自带的预览相机。 + /// 炮台/翅膀预览时启用(它们不带相机);战神预览时禁用(战神自带相机)。 + /// + private void SetPanelCameraEnabled(bool enabled) + { + if (_panelPreviewCamera != null) + _panelPreviewCamera.enabled = enabled; + } + // ==================== 战神选择按钮 ==================== /// 选择按钮:装备选中的战神 @@ -858,6 +898,9 @@ public class UIHome_BY_ItemListPanels : SubUIBase if (rawImg_DetailPreview != null) rawImg_DetailPreview.gameObject.SetActive(_currentSelectType != ItemPanelSelect.Turrect); + // ---- 炮台/翅膀预览:启用面板自带相机(炮台/翅膀 prefab 不带相机)---- + SetPanelCameraEnabled(true); + // ---- 预览 ---- if (resData != null && previewParent != null && prefab != null) { @@ -886,6 +929,9 @@ public class UIHome_BY_ItemListPanels : SubUIBase Destroy(_currentPreviewObj); _currentPreviewObj = null; } + + // 清空预览时禁用面板相机,避免渲染空场景到 RenderTexture + SetPanelCameraEnabled(false); } /// 根据道具数据生成特殊效果描述。