Files
5.24BuYu/Assets/MyGame/Scripts/fishResources/TurretResources.cs
T
2026-10-12 15:32:36 +08:00

597 lines
30 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.ResourceManagement.ResourceLocations;
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(卸引用归零时释放,只卸这一项)</summary>
private static readonly Dictionary<(int id, ZhanShenUsage usage), AsyncOperationHandle<GameObject>> _zsHandles
= new Dictionary<(int, ZhanShenUsage), AsyncOperationHandle<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)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(缓存命中瞬间完成,不会显示)
UIManager.BeginLoading();
try { return await AcquireZhanShenAsyncCore(turretNumber, usage); }
finally { UIManager.EndLoading(); }
}
/// <summary>AcquireZhanShenAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task<GameObject> AcquireZhanShenAsyncCore(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;
// ⭐ 真·按需:位置表定位(只取元数据)→ LoadAssetAsync 只打开该战神自己那一个包
string pathName = GetPathName(turretNumber);
if (pathName != null)
{
var map = await (usage == ZhanShenUsage.GamePlay ? GetZsGameLocMapAsync() : GetTurretLocMapAsync());
if (map.TryGetValue(pathName, out var loc))
{
var h = Addressables.LoadAssetAsync<GameObject>(loc);
await h.Task;
if (h.Status == AsyncOperationStatus.Succeeded && h.Result != null)
{
// 竞态1:加载期间引用已被释放(已切走)→ 归还句柄,不缓存
if (!_zsRefCount.ContainsKey(key))
{
if (h.IsValid()) Addressables.Release(h);
return null;
}
// 竞态2:加载期间别人已装入同一个 → 归还多余句柄,复用已有的
if (_zsPrefabCache.TryGetValue(key, out var raced))
{
if (h.IsValid()) Addressables.Release(h);
return raced;
}
_zsPrefabCache[key] = h.Result;
_zsHandles[key] = h;
return h.Result;
}
if (h.IsValid()) Addressables.Release(h);
}
}
// 未找到:归还引用计数,不缓存
Debug.LogWarning($"[TurretResourcesData] 战神按需加载失败: id={turretNumber} name={pathName ?? "无"} usage={usage}(检查位置表)");
_zsRefCount[key] = c; // 还原为进入时的值
if (_zsRefCount[key] <= 0) _zsRefCount.Remove(key);
return null;
}
// ==================== 位置表(真·按需加载,绝不全量) ====================
// LoadResourceLocationsAsync 只取"资源位置"元数据:不加载资产、不解压任何包,一次构表缓存;
// 之后每个炮台/翅膀/战神 按编号 → 名字 → 位置 → LoadAssetAsync(location),
// 只打开它自己那一个包(组是 PackSeparately,每个 prefab 独立小包)。切换/离房时随引用归零释放。
private static Task<Dictionary<string, IResourceLocation>> _ptLocMapTask; // PTCBZS+Default(炮台/翅膀/战神展示)
private static Task<Dictionary<string, IResourceLocation>> _zsGameLocMapTask; // PTCBZS+ZSYX(战神游戏内)
private static Dictionary<int, string> _idToPathName;
/// <summary>编号 → 资源名(如 11001 → paotai11001;13002 → display_wargod_14902_0_dev)</summary>
private static string GetPathName(int id)
{
if (_idToPathName == null)
{
_idToPathName = new Dictionary<int, string>();
if (turretPrefabPathDic != null)
foreach (var kv in turretPrefabPathDic)
_idToPathName[kv.Value] = kv.Key;
}
return _idToPathName.TryGetValue(id, out var n) ? n : null;
}
private static Task<Dictionary<string, IResourceLocation>> GetTurretLocMapAsync()
=> _ptLocMapTask ??= BuildLocMapAsync(new List<string> { turret_AALabel, defaultSubLabel });
private static Task<Dictionary<string, IResourceLocation>> GetZsGameLocMapAsync()
=> _zsGameLocMapTask ??= BuildLocMapAsync(new List<string> { turret_AALabel, zhanShenGameSubLabel });
/// <summary>按标签交集构建"资源名 → 位置"表(仅元数据,不动资产)</summary>
private static async Task<Dictionary<string, IResourceLocation>> BuildLocMapAsync(List<string> labels)
{
var map = new Dictionary<string, IResourceLocation>();
try
{
var h = Addressables.LoadResourceLocationsAsync(labels, MergeMode.Intersection, typeof(GameObject));
await h.Task;
if (h.Status == AsyncOperationStatus.Succeeded && h.Result != null)
{
foreach (var loc in h.Result)
{
var name = System.IO.Path.GetFileNameWithoutExtension(loc.PrimaryKey);
if (!string.IsNullOrEmpty(name) && !map.ContainsKey(name))
map[name] = loc;
}
}
if (h.IsValid()) Addressables.Release(h);
}
catch (Exception e)
{
Debug.LogWarning($"[TurretResourcesData] 位置表构建失败: {e.Message}");
}
Debug.Log($"[TurretResourcesData] 位置表构建完成: {map.Count} 项(仅元数据,未加载资源)");
return map;
}
/// <summary>
/// ⭐ 按需获取炮台/翅膀 prefab(异步 + 引用计数,标签 PT{编号},如 PT11001)。
/// 散包模式下启动不再全量加载 prefab,座位装炮台/大厅预览时按需取。
/// 与 ReleaseTurretPrefab 配对(切换/关闭时 -1,归零卸包)。
/// </summary>
public static async Task<GameObject> AcquireTurretPrefabAsync(int turretNumber)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(缓存命中瞬间完成,不会显示)
UIManager.BeginLoading();
try { return await AcquireTurretPrefabAsyncCore(turretNumber); }
finally { UIManager.EndLoading(); }
}
/// <summary>AcquireTurretPrefabAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task<GameObject> AcquireTurretPrefabAsyncCore(int turretNumber)
{
_ptRefCount[turretNumber] = _ptRefCount.TryGetValue(turretNumber, out int c) ? c + 1 : 1;
if (_ptPrefabCache.TryGetValue(turretNumber, out var cached)) return cached;
// ⭐ 真·按需:位置表定位(只取元数据)→ LoadAssetAsync 只打开这一个 prefab 的小包
string pathName = GetPathName(turretNumber);
if (pathName != null)
{
var map = await GetTurretLocMapAsync();
if (map.TryGetValue(pathName, out var loc))
{
var h = Addressables.LoadAssetAsync<GameObject>(loc);
await h.Task;
if (h.Status == AsyncOperationStatus.Succeeded && h.Result != null)
{
// 竞态1:加载期间引用已被释放(已切走)→ 归还句柄,不缓存
if (!_ptRefCount.ContainsKey(turretNumber))
{
if (h.IsValid()) Addressables.Release(h);
return null;
}
// 竞态2:加载期间别人已装入同一个 → 归还多余句柄,复用已有的
if (_ptPrefabCache.TryGetValue(turretNumber, out var raced))
{
if (h.IsValid()) Addressables.Release(h);
return raced;
}
_ptPrefabCache[turretNumber] = h.Result;
_ptHandles[turretNumber] = h;
return h.Result;
}
if (h.IsValid()) Addressables.Release(h);
}
}
Debug.LogWarning($"[TurretResourcesData] 炮台/翅膀按需加载失败: id={turretNumber} name={pathName ?? "无"}(检查 PTCBZS+Default 标签与位置表)");
_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(位置表按需加载)</summary>
private static readonly Dictionary<int, GameObject> _ptPrefabCache = new Dictionary<int, GameObject>();
private static readonly Dictionary<int, AsyncOperationHandle<GameObject>> _ptHandles = new Dictionary<int, AsyncOperationHandle<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()
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadTurretAssetsDataCore(); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadTurretAssetsData 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadTurretAssetsDataCore()
{
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 工具)。
// ---- 加载炮台/翅膀/战神图标(图集方式)----
// ⭐ 图标已合入图集(PaoTaiSprite/chibangSprite/ZhanShenSprite,均在 AA 组内)。
// 按图集资产地址加载 → GetSprites 逐张按名填表;sprite 名 = 原文件名(paotai11001/chibang17005…)。
// 用地址加载(不用标签查询):不会把同组同标签的 prefab 一并拖进内存。
string[] atlasAddresses =
{
"Assets/MyGame/aa/UI/Atlases/PaoTaiSprite.spriteatlasv2",
"Assets/MyGame/aa/UI/Atlases/chibangSprite.spriteatlasv2",
"Assets/MyGame/aa/UI/Atlases/ZhanShenSprite.spriteatlasv2",
};
var buf = new Sprite[1024];
foreach (var addr in atlasAddresses)
{
try
{
var atlasHandle = Addressables.LoadAssetAsync<SpriteAtlas>(addr);
await atlasHandle.Task;
if (atlasHandle.Status == AsyncOperationStatus.Succeeded && atlasHandle.Result != null)
{
_loadedHandles.Add(atlasHandle);
var atlasObj = atlasHandle.Result;
int n = atlasObj.GetSprites(buf);
int hit = 0;
for (int i = 0; i < n; i++)
{
var sp = buf[i];
if (sp == null) continue;
// 规范化名字:去掉同级后缀(如 " (Atlas)"),兼容 V2 打包改名
string key = System.Text.RegularExpressions.Regex.Replace(sp.name, @"\s*\(.*\)\s*$", "").Trim();
if (turretSpritePathDic.TryGetValue(key, out int id))
{
turretSpriteDic[id] = sp;
hit++;
}
// 只保留精确匹配:前缀匹配可能把 sprite 串到别的编号上(串图),已移除
}
Debug.Log($"[TurretResourcesData] 图集 {System.IO.Path.GetFileName(addr)}: 打包 {n} 张, 命中 {hit} 张");
}
else
{
Debug.LogWarning($"[TurretResourcesData] 图集加载失败 {addr}: status={atlasHandle.Status}, err={atlasHandle.OperationException?.Message}");
}
}
catch (Exception e)
{
Debug.LogWarning($"[TurretResourcesData] 图集加载跳过 {addr}: {e.Message}");
}
}
// ⭐ 编辑器兜底:SpriteAtlas 只在"打包时"才真正 Packed,编辑器 Play 下 GetSprites 返回空
// (所以编辑器里图集加载 0 张不是配置错)。编辑器直接用 AssetDatabase 按名字找 sprite,
// 仅编辑器编译(#if UNITY_EDITOR),真机走上面的图集路径。
#if UNITY_EDITOR
if (turretSpriteDic.Count == 0)
{
var byName = new Dictionary<string, string>();
foreach (var guid in UnityEditor.AssetDatabase.FindAssets("t:Sprite"))
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var baseName = System.IO.Path.GetFileNameWithoutExtension(path);
if (!byName.ContainsKey(baseName)) byName[baseName] = path;
}
foreach (var kv in turretSpritePathDic)
{
if (turretSpriteDic.ContainsKey(kv.Value)) continue;
if (byName.TryGetValue(kv.Key, out var path))
{
var sp = UnityEditor.AssetDatabase.LoadAssetAtPath<Sprite>(path);
if (sp != null) turretSpriteDic[kv.Value] = sp;
}
}
Debug.Log($"[TurretResourcesData] 编辑器兜底加载图标(AssetDatabase): {turretSpriteDic.Count} 张");
}
#endif
Debug.Log($"[TurretResourcesData] 图标加载完成: {turretSpriteDic.Count} 张(配置需要 {turretSpritePathDic.Count} 个)");
if (turretSpriteDic.Count == 0)
Debug.LogError("[TurretResourcesData] 图标一张都没加载到:编辑器请检查 sprite 名字与 tbturretresources 是否对得上;真机请确认三个图集 Inspector 的 Include in Build 已勾选");
}
/// <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>图标字典数量(诊断用)</summary>
public static int SpriteIconCount => turretSpriteDic != null ? turretSpriteDic.Count : 0;
/// <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;
}
}