Files
5.24BuYu/Assets/MyGame/Scripts/fishResources/TurretResources.cs
T
2026-10-09 22:06:36 +08:00

436 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.U2D;
using static UnityEngine.AddressableAssets.Addressables;
/// <summary>
/// 战神预制体用途:战神有两个预制体(展示/游戏内使用),名字相同,靠 Addressables 子标签区分。
/// - Display:Default 子标签(大厅/商城预览模型)
/// - GamePlay:ZSYX 子标签(座位上替代普通炮台的使用模型)
/// </summary>
public enum ZhanShenUsage
{
/// <summary>展示用:大厅/商城预览模型(Default 子标签加载)</summary>
Display = 0,
/// <summary>游戏内用:座位上替代普通炮台的模型(ZSYX 子标签加载)</summary>
GamePlay = 1
}
[Serializable]
public class TurretResourcesData
{
public int turretNumber;
public string turretName; // 中文名称(如"深海之眼")
public int turretType; // 1=普通炮台 2=战神 3=翅膀
public string prefabTurretResourcesPath; // 资源路径(prefab/spine/sprite 共用名)
public int animationType; // 1=spine 2=animator 3=animation
public int prefabType; // 预制体类型(战神分类:1=国色天香 2=西游传说 3=山海奇闻;炮台/翅膀=0)
public int attackAudioID; // 攻击音效 ID
public List<int> bulletID; // 子弹 ID 列表
// ---- Addressables 标签 ----
const string turret_AALabel = "PTCBZS"; // 炮台统一标签(主标签:炮台/翅膀/战神共用)
const string defaultSubLabel = "Default"; // 展示子标签(展示用预制体:普通炮台/翅膀/战神展示)
const string zhanShenGameSubLabel = "ZSYX"; // 战神游戏内子标签(战神的使用预制体,与展示同名靠标签区分)
// ---- 路径→ID 映射(用于 Addressables 加载时反查) ----
private static Dictionary<string, int> turretPrefabPathDic; // prefabPath → turretNumber
private static Dictionary<string, int> turretSpritePathDic; // spritePath → turretNumber
// ---- ID→数据/资源 映射 ----
public static Dictionary<int, TurretResourcesData> turretDataDic; // id → data
private static Dictionary<int, GameObject> turretPrefabDic; // id → 展示用预制体(Default 子标签)
private static Dictionary<int, GameObject> turretGamePrefabDic; // id → 战神游戏内使用预制体(ZSYX 子标签)
private static Dictionary<int, Sprite> turretSpriteDic; // id → sprite
// ---- 保留的 AA Handle(不释放,避免 Build 下资源被卸载)----
private static readonly List<AsyncOperationHandle> _loadedHandles = new List<AsyncOperationHandle>();
// ==================== 战神按需加载(散包:用哪个加载哪个) ====================
// 每个战神单独打标签 ZS{编号}(如 ZS14901),展示 prefab = ZS{id}+Default,游戏内 = ZS{id}+ZSYX。
// 战神 prefab 不再带 PTCBZS 标签 → 启动全量加载(PT+Default/PT+ZSYX)不再拉出战神,
// turretchibangzhanshenres 包只剩炮台/翅膀/sprite,战神包按需单独加载。
/// <summary>战神 prefab 缓存:(编号, 用途) → prefab</summary>
private static readonly Dictionary<(int id, ZhanShenUsage usage), GameObject> _zsPrefabCache
= new Dictionary<(int, ZhanShenUsage), GameObject>();
/// <summary>战神加载句柄:(编号, 用途) → handle(卸载用;LoadAssetsAsync 返回 IList 句柄)</summary>
private static readonly Dictionary<(int id, ZhanShenUsage usage), AsyncOperationHandle<IList<GameObject>>> _zsHandles
= new Dictionary<(int, ZhanShenUsage), AsyncOperationHandle<IList<GameObject>>>();
/// <summary>战神引用计数:(编号, 用途) → 使用中的座位/预览数(归零才真正卸载,防止多座位共用误卸)</summary>
private static readonly Dictionary<(int id, ZhanShenUsage usage), int> _zsRefCount
= new Dictionary<(int, ZhanShenUsage), int>();
/// <summary>
/// ⭐ 按需获取单个战神 prefab(异步 + 引用计数)。
/// 用途:进房/换战神(GamePlay)、大厅预览(Display)。
/// 每次调用引用 +1;切换/关闭时配对调用 ReleaseZhanShen(-1),
/// 引用归零才真正卸载战神包 —— 多个座位用同一个战神不会互相误卸。
/// 标签:ZS{编号} + Default(展示)/ZSYX(游戏内) 交集;标签未配置时返回 null
/// (调用方可回退 GetZhanShenPrefab 查全量字典,平滑迁移)。
/// </summary>
public static async Task<GameObject> AcquireZhanShenAsync(int turretNumber, ZhanShenUsage usage)
{
var key = (turretNumber, usage);
_zsRefCount[key] = _zsRefCount.TryGetValue(key, out int c) ? c + 1 : 1;
if (_zsPrefabCache.TryGetValue(key, out var cached)) return cached;
var keys = new List<string> { "ZS" + turretNumber, usage == ZhanShenUsage.GamePlay ? zhanShenGameSubLabel : defaultSubLabel };
var handle = Addressables.LoadAssetsAsync<GameObject>(keys, null, MergeMode.Intersection);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null && handle.Result.Count > 0)
{
// 展示/游戏内同名靠标签区分,交集结果取第一个
_zsPrefabCache[key] = handle.Result[0];
_zsHandles[key] = handle;
return handle.Result[0];
}
// 加载失败(标签未配置等):归还引用计数,不缓存句柄
if (handle.IsValid()) Addressables.Release(handle);
_zsRefCount[key] = c; // 还原为进入时的值
if (_zsRefCount[key] <= 0) _zsRefCount.Remove(key);
return null;
}
/// <summary>
/// ⭐ 按需获取炮台/翅膀 prefab(异步 + 引用计数,标签 PT{编号},如 PT11001)。
/// 散包模式下启动不再全量加载 prefab,座位装炮台/大厅预览时按需取。
/// 与 ReleaseTurretPrefab 配对(切换/关闭时 -1,归零卸包)。
/// </summary>
public static async Task<GameObject> AcquireTurretPrefabAsync(int turretNumber)
{
_ptRefCount[turretNumber] = _ptRefCount.TryGetValue(turretNumber, out int c) ? c + 1 : 1;
if (_ptPrefabCache.TryGetValue(turretNumber, out var cached)) return cached;
var handle = Addressables.LoadAssetsAsync<GameObject>(new List<string> { "PT" + turretNumber }, null, MergeMode.Intersection);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null && handle.Result.Count > 0)
{
_ptPrefabCache[turretNumber] = handle.Result[0];
_ptHandles[turretNumber] = handle;
return handle.Result[0];
}
if (handle.IsValid()) Addressables.Release(handle);
_ptRefCount[turretNumber] = c;
if (_ptRefCount[turretNumber] <= 0) _ptRefCount.Remove(turretNumber);
return null;
}
/// <summary>释放炮台/翅膀 prefab 引用(与 AcquireTurretPrefabAsync 配对,归零卸包)</summary>
public static void ReleaseTurretPrefab(int turretNumber)
{
if (!_ptRefCount.TryGetValue(turretNumber, out int c) || c <= 0) return;
if (c > 1) { _ptRefCount[turretNumber] = c - 1; return; }
_ptRefCount.Remove(turretNumber);
if (_ptHandles.TryGetValue(turretNumber, out var h) && h.IsValid())
Addressables.Release(h);
_ptHandles.Remove(turretNumber);
_ptPrefabCache.Remove(turretNumber);
}
/// <summary>按需缓存:炮台/翅膀 prefab(PT{编号} 标签)</summary>
private static readonly Dictionary<int, GameObject> _ptPrefabCache = new Dictionary<int, GameObject>();
private static readonly Dictionary<int, AsyncOperationHandle<IList<GameObject>>> _ptHandles = new Dictionary<int, AsyncOperationHandle<IList<GameObject>>>();
private static readonly Dictionary<int, int> _ptRefCount = new Dictionary<int, int>();
/// <summary>
/// 强制卸载按需加载的炮台/翅膀 prefab(离房时调用,忽略引用计数)。
/// </summary>
public static void UnloadAllTurretPrefabs()
{
var keys = new List<int>(_ptHandles.Keys);
foreach (var k in keys)
{
if (_ptHandles[k].IsValid())
Addressables.Release(_ptHandles[k]);
}
_ptHandles.Clear();
_ptPrefabCache.Clear();
_ptRefCount.Clear();
if (keys.Count > 0)
Debug.Log($"[TurretResourcesData] 卸载炮台/翅膀按需 prefab {keys.Count} 项");
}
/// <summary>
/// 释放战神引用(切换战神/关闭预览时调用,与 AcquireZhanShenAsync 配对)。
/// 引用归零才真正卸载包;标签未配置走全量回退时此调用无害(没有计数项直接跳过)。
/// </summary>
public static void ReleaseZhanShen(int turretNumber, ZhanShenUsage usage)
{
var key = (turretNumber, usage);
if (!_zsRefCount.TryGetValue(key, out int c) || c <= 0) return;
if (c > 1)
{
_zsRefCount[key] = c - 1;
return;
}
// 归零:真正卸载
_zsRefCount.Remove(key);
if (_zsHandles.TryGetValue(key, out var h) && h.IsValid())
Addressables.Release(h);
_zsHandles.Remove(key);
_zsPrefabCache.Remove(key);
}
/// <summary>
/// 强制卸载按需加载的战神资源(离房时清游戏内的;面板关闭可清展示的)。
/// usage 传 null 清全部。忽略引用计数(离房场景所有引用者都已销毁)。
/// </summary>
public static void UnloadAllZhanShen(ZhanShenUsage? usage = null)
{
var toRemove = new List<(int, ZhanShenUsage)>();
foreach (var kv in _zsHandles)
{
if (usage == null || kv.Key.usage == usage.Value)
toRemove.Add(kv.Key);
}
foreach (var k in toRemove)
{
if (_zsHandles[k].IsValid())
Addressables.Release(_zsHandles[k]);
_zsHandles.Remove(k);
_zsPrefabCache.Remove(k);
_zsRefCount.Remove(k);
}
if (toRemove.Count > 0)
Debug.Log($"[TurretResourcesData] 卸载战神按需资源 {toRemove.Count} 项(usage={usage?.ToString() ?? "全部"})");
}
static TurretResourcesData()
{
Init();
}
/// <summary>
/// 从 Resources/tbturretresources.json 加载炮台元数据。
/// 表里没有的 turretNumber,调用方用 TurretData 自身的 Inspector 默认值,不报错。
/// </summary>
async static void Init()
{
turretPrefabPathDic = new Dictionary<string, int>();
turretSpritePathDic = new Dictionary<string, int>();
turretDataDic = new Dictionary<int, TurretResourcesData>();
TextAsset jsonAsset = Resources.Load<TextAsset>("tbturretresources");
if (jsonAsset == null)
{
Debug.LogWarning("[TurretResourcesData] 资源表 tbturretresources.json 未找到,炮台数据字典为空,将使用 TurretData 默认值");
return;
}
try
{
var list = JsonConvert.DeserializeObject<List<TurretResourcesData>>(jsonAsset.text);
if (list == null)
{
Debug.LogWarning("[TurretResourcesData] 资源表解析为空,将使用 TurretData 默认值");
return;
}
foreach (var item in list)
{
// 战神(turretType==2)的 prefabTurretResourcesPath 可能为空,不跳过
// 用 turretNumber 构造默认路径,以便 Addressables 按名匹配
if (string.IsNullOrEmpty(item.prefabTurretResourcesPath))
{
if (item.turretType == 2)
item.prefabTurretResourcesPath = item.turretNumber.ToString();
else
continue; // 炮台/翅膀路径为空则跳过
}
turretPrefabPathDic[item.prefabTurretResourcesPath] = item.turretNumber;
turretSpritePathDic[item.prefabTurretResourcesPath] = item.turretNumber;
turretDataDic[item.turretNumber] = item;
Debug.Log($"[TurretResourcesData] 加载炮台: ID={item.turretNumber}, Path={item.prefabTurretResourcesPath}, Type={item.turretType}, PrefabType={item.prefabType}, BulletIDs=[{string.Join(",", item.bulletID)}]");
}
Debug.Log($"[TurretResourcesData] 初始化完成,共 {turretDataDic.Count} 个炮台配置");
}
catch (System.Exception ex)
{
Debug.LogError($"[TurretResourcesData] JSON 解析失败: {ex.Message},炮台数据字典为空,将使用 TurretData 默认值");
}
}
// ==================== 资源加载 ====================
/// <summary>
/// 加载炮台相关 Addressables 资源(两组标签交集):
/// - PT + Default:普通炮台/翅膀/战神展示预制体 → turretPrefabDic;
/// - PT + ZSYX:战神游戏内使用预制体 → turretGamePrefabDic(与展示同名,靠标签区分)。
/// 注意:Handle 必须保留,不能 Release,否则 Build 下资源会被立即卸载,
/// 字典中存的就是已销毁对象的引用(Editor 下因 AssetDatabase 模式不会暴露此问题)。
/// </summary>
public static async Task LoadTurretAssetsData()
{
turretPrefabDic = new Dictionary<int, GameObject>();
turretGamePrefabDic = new Dictionary<int, GameObject>();
turretSpriteDic = new Dictionary<int, Sprite>();
// 清理上一轮残留的 Handle
foreach (var h in _loadedHandles)
{
if (h.IsValid()) Addressables.Release(h);
}
_loadedHandles.Clear();
// 展示用标签组:PT + Default(普通炮台/翅膀/战神展示预制体)
var labels = new List<string> { turret_AALabel, defaultSubLabel };
// 战神游戏内标签组:PT + ZSYX(战神的使用预制体,名字与展示相同)
var zsGameLabels = new List<string> { turret_AALabel, zhanShenGameSubLabel };
Debug.Log("[TurretResourcesData] 开始预下载炮台资源依赖 (PT + Default)...");
var depHandle = Addressables.DownloadDependenciesAsync(labels, MergeMode.Intersection);
await depHandle.Task;
Addressables.Release(depHandle); // 预下载 Handle 可以立即释放,只影响下载缓存
Debug.Log("[TurretResourcesData] 炮台依赖资源预下载完成!");
Debug.Log("[TurretResourcesData] 开始预下载战神游戏内资源依赖 (PT + ZSYX)...");
var zsDepHandle = Addressables.DownloadDependenciesAsync(zsGameLabels, MergeMode.Intersection);
await zsDepHandle.Task;
Addressables.Release(zsDepHandle);
Debug.Log("[TurretResourcesData] 战神游戏内依赖资源预下载完成!");
// ⭐ 散包模式:炮台/翅膀/战神的 prefab 不再启动全量加载(每个资源一个小包,
// 全量会把上百个散包全部拉起 —— 刚登录 194 个包的根源)。
// prefab 全部按需:炮台/翅膀走 AcquireTurretPrefabAsync(PT{编号} 标签),
// 战神走 AcquireZhanShenAsync(ZS{编号}+Default/ZSYX 标签)。
// 启动只加载列表小图 Sprite(若 Sprite 与 prefab 同包仍会拉起散包,
// 应把列表小图合入图集 —— 见 SpriteAtlasBuilderWindow 工具)。
// ---- 加载炮台 Sprite(列表图标,全量保留)----
var spriteHandle = Addressables.LoadAssetsAsync<Sprite>(labels, data =>
{
if (turretSpritePathDic.TryGetValue(data.name, out int id))
{
turretSpriteDic[id] = data;
}
}, MergeMode.Intersection);
await spriteHandle.Task;
// 不释放!保留 Handle 以保持资源存活
_loadedHandles.Add(spriteHandle);
// ⭐ 图集兜底:列表小图已合入 SpriteAtlas 且 sprite 从组里移除(标签查不到)时,
// 按图集标签 "TurretAtlas" 加载图集,用 GetSprite(名) 填表。
// (图集方式下 sprite 仍在图集里保留原名 = prefabTurretResourcesPath)
if (turretSpriteDic.Count == 0)
{
try
{
var atlasHandle = Addressables.LoadAssetsAsync<SpriteAtlas>("TurretAtlas", null);
await atlasHandle.Task;
if (atlasHandle.Status == AsyncOperationStatus.Succeeded
&& atlasHandle.Result != null && atlasHandle.Result.Count > 0)
{
var atlas = atlasHandle.Result[0];
foreach (var kv in turretSpritePathDic)
{
var sp = atlas.GetSprite(kv.Key);
if (sp != null)
turretSpriteDic[kv.Value] = sp;
}
_loadedHandles.Add(atlasHandle);
Debug.Log($"[TurretResourcesData] 图集方式加载 Sprite 完成,共 {turretSpriteDic.Count} 个(来自 {atlas.name})");
}
}
catch (Exception e)
{
Debug.LogWarning($"[TurretResourcesData] 图集兜底加载失败(未找到 TurretAtlas 标签的图集?): {e.Message}");
}
}
else
{
Debug.Log($"[TurretResourcesData] 炮台 Sprite 加载完成,共 {turretSpriteDic.Count} 个(prefab 已改按需加载)");
}
}
/// <summary>
/// 卸载炮台 Addressables 资源。
/// 释放 LoadTurretAssetsData 中保留的所有 Handle。
/// </summary>
public static Task UnLoadTurretAssetsData()
{
foreach (var h in _loadedHandles)
{
if (h.IsValid())
{
Addressables.Release(h);
Debug.Log($"[TurretResourcesData] 释放 Handle: {h.DebugName}");
}
}
_loadedHandles.Clear();
turretPrefabDic?.Clear();
turretGamePrefabDic?.Clear();
turretSpriteDic?.Clear();
Debug.Log($"[TurretResourcesData] 卸载完成,剩余 Prefab: {turretPrefabDic?.Count ?? 0}, 战神游戏内: {turretGamePrefabDic?.Count ?? 0}, Sprite: {turretSpriteDic?.Count ?? 0}");
return Task.CompletedTask;
}
// ==================== 查询接口 ====================
/// <summary>根据 ID 获取炮台数据。</summary>
public static TurretResourcesData GetTurretData(int turretNumber)
{
return turretDataDic != null && turretDataDic.TryGetValue(turretNumber, out var data) ? data : null;
}
/// <summary>根据 ID 获取炮台 Prefab。⭐ 优先查按需缓存(PT{编号} 标签),回退全量字典(已不再填充,恒空)。</summary>
public static GameObject GetTurretPrefab(int turretNumber)
{
if (_ptPrefabCache.TryGetValue(turretNumber, out var prefab)) return prefab;
return turretPrefabDic != null && turretPrefabDic.TryGetValue(turretNumber, out var fullLoad) ? fullLoad : null;
}
/// <summary>
/// 根据战神编号获取战神预制体(两种情况二选一)。
/// - Display:展示用(大厅/商城预览,与 GetTurretPrefab 返回同一个展示模型);
/// - GamePlay:游戏内用(座位上替代普通炮台的使用模型)。
/// ⭐ 优先查按需加载缓存(散包);未加载时回退全量加载的字典(标签未拆时的平滑迁移)。
/// </summary>
public static GameObject GetZhanShenPrefab(int turretNumber, ZhanShenUsage usage = ZhanShenUsage.Display)
{
// 按需缓存优先
if (_zsPrefabCache.TryGetValue((turretNumber, usage), out var prefab))
return prefab;
// 回退:全量加载的字典(战神 prefab 未去 PTCBZS 标签时仍会被启动全量加载进来)
var dic = usage == ZhanShenUsage.GamePlay ? turretGamePrefabDic : turretPrefabDic;
return dic != null && dic.TryGetValue(turretNumber, out var fullLoad) ? fullLoad : null;
}
/// <summary>根据 ID 获取炮台 Sprite。</summary>
public static Sprite GetTurretSprite(int turretNumber)
{
return turretSpriteDic != null && turretSpriteDic.TryGetValue(turretNumber, out var sprite) ? sprite : null;
}
/// <summary>获取所有炮台数据列表。</summary>
public static List<TurretResourcesData> GetAllTurretData()
{
return turretDataDic != null ? new List<TurretResourcesData>(turretDataDic.Values) : new List<TurretResourcesData>();
}
/// <summary>根据类型获取炮台数据(1=普通炮台, 2=战神, 3=翅膀)。</summary>
public static List<TurretResourcesData> GetTurretDataByType(int turretType)
{
var result = new List<TurretResourcesData>();
if (turretDataDic == null) return result;
foreach (var kv in turretDataDic)
{
if (kv.Value.turretType == turretType)
result.Add(kv.Value);
}
return result;
}
}