Files
5.24BuYu/Assets/MyGame/Scripts/fishResources/TurretResources.cs
T

597 lines
30 KiB
C#
Raw Normal View History

2026-05-22 19:30:02 +08:00
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
2026-07-29 00:31:59 +08:00
using UnityEngine.ResourceManagement.AsyncOperations;
2026-10-10 04:09:28 +08:00
using UnityEngine.ResourceManagement.ResourceLocations;
2026-10-09 22:06:36 +08:00
using UnityEngine.U2D;
2026-05-22 19:30:02 +08:00
using static UnityEngine.AddressableAssets.Addressables;
2026-08-28 19:42:54 +08:00
/// <summary>
/// 战神预制体用途:战神有两个预制体(展示/游戏内使用),名字相同,靠 Addressables 子标签区分。
/// - Display:Default 子标签(大厅/商城预览模型)
/// - GamePlay:ZSYX 子标签(座位上替代普通炮台的使用模型)
/// </summary>
public enum ZhanShenUsage
{
/// <summary>展示用:大厅/商城预览模型(Default 子标签加载)</summary>
Display = 0,
/// <summary>游戏内用:座位上替代普通炮台的模型(ZSYX 子标签加载)</summary>
GamePlay = 1
}
2026-05-22 19:30:02 +08:00
[Serializable]
public class TurretResourcesData
{
public int turretNumber;
2026-06-08 21:23:40 +08:00
public string turretName; // 中文名称(如"深海之眼")
public int turretType; // 1=普通炮台 2=战神 3=翅膀
public string prefabTurretResourcesPath; // 资源路径(prefab/spine/sprite 共用名)
public int animationType; // 1=spine 2=animator 3=animation
2026-07-12 22:30:37 +08:00
public int prefabType; // 预制体类型(战神分类:1=国色天香 2=西游传说 3=山海奇闻;炮台/翅膀=0)
2026-06-08 21:23:40 +08:00
public int attackAudioID; // 攻击音效 ID
public List<int> bulletID; // 子弹 ID 列表
2026-05-22 19:30:02 +08:00
2026-06-08 21:23:40 +08:00
// ---- Addressables 标签 ----
2026-08-28 19:42:54 +08:00
const string turret_AALabel = "PTCBZS"; // 炮台统一标签(主标签:炮台/翅膀/战神共用)
const string defaultSubLabel = "Default"; // 展示子标签(展示用预制体:普通炮台/翅膀/战神展示)
const string zhanShenGameSubLabel = "ZSYX"; // 战神游戏内子标签(战神的使用预制体,与展示同名靠标签区分)
2026-05-22 19:30:02 +08:00
2026-06-08 21:23:40 +08:00
// ---- 路径→ID 映射(用于 Addressables 加载时反查) ----
private static Dictionary<string, int> turretPrefabPathDic; // prefabPath → turretNumber
private static Dictionary<string, int> turretSpritePathDic; // spritePath → turretNumber
2026-05-22 19:30:02 +08:00
2026-06-08 21:23:40 +08:00
// ---- ID→数据/资源 映射 ----
public static Dictionary<int, TurretResourcesData> turretDataDic; // id → data
2026-08-28 19:42:54 +08:00
private static Dictionary<int, GameObject> turretPrefabDic; // id → 展示用预制体(Default 子标签)
private static Dictionary<int, GameObject> turretGamePrefabDic; // id → 战神游戏内使用预制体(ZSYX 子标签)
2026-06-08 21:23:40 +08:00
private static Dictionary<int, Sprite> turretSpriteDic; // id → sprite
2026-05-22 19:30:02 +08:00
2026-07-29 00:31:59 +08:00
// ---- 保留的 AA Handle(不释放,避免 Build 下资源被卸载)----
private static readonly List<AsyncOperationHandle> _loadedHandles = new List<AsyncOperationHandle>();
2026-10-09 22:06:36 +08:00
// ==================== 战神按需加载(散包:用哪个加载哪个) ====================
// 每个战神单独打标签 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>();
2026-10-10 04:09:28 +08:00
/// <summary>战神加载句柄:(编号, 用途) → 单资产 handle(卸引用归零时释放,只卸这一项)</summary>
private static readonly Dictionary<(int id, ZhanShenUsage usage), AsyncOperationHandle<GameObject>> _zsHandles
= new Dictionary<(int, ZhanShenUsage), AsyncOperationHandle<GameObject>>();
2026-10-09 22:06:36 +08:00
/// <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)
2026-10-12 15:32:36 +08:00
{
// ⭐ 资源加载超过 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)
2026-10-09 22:06:36 +08:00
{
var key = (turretNumber, usage);
_zsRefCount[key] = _zsRefCount.TryGetValue(key, out int c) ? c + 1 : 1;
if (_zsPrefabCache.TryGetValue(key, out var cached)) return cached;
2026-10-10 04:09:28 +08:00
// ⭐ 真·按需:位置表定位(只取元数据)→ LoadAssetAsync 只打开该战神自己那一个包
string pathName = GetPathName(turretNumber);
if (pathName != null)
2026-10-09 22:06:36 +08:00
{
2026-10-10 04:09:28 +08:00
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);
}
2026-10-09 22:06:36 +08:00
}
2026-10-10 04:09:28 +08:00
// 未找到:归还引用计数,不缓存
Debug.LogWarning($"[TurretResourcesData] 战神按需加载失败: id={turretNumber} name={pathName ?? "无"} usage={usage}(检查位置表)");
2026-10-09 22:06:36 +08:00
_zsRefCount[key] = c; // 还原为进入时的值
if (_zsRefCount[key] <= 0) _zsRefCount.Remove(key);
return null;
}
2026-10-10 04:09:28 +08:00
// ==================== 位置表(真·按需加载,绝不全量) ====================
// 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;
}
2026-10-09 22:06:36 +08:00
/// <summary>
/// ⭐ 按需获取炮台/翅膀 prefab(异步 + 引用计数,标签 PT{编号},如 PT11001)。
/// 散包模式下启动不再全量加载 prefab,座位装炮台/大厅预览时按需取。
/// 与 ReleaseTurretPrefab 配对(切换/关闭时 -1,归零卸包)。
/// </summary>
public static async Task<GameObject> AcquireTurretPrefabAsync(int turretNumber)
2026-10-12 15:32:36 +08:00
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(缓存命中瞬间完成,不会显示)
UIManager.BeginLoading();
try { return await AcquireTurretPrefabAsyncCore(turretNumber); }
finally { UIManager.EndLoading(); }
}
/// <summary>AcquireTurretPrefabAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task<GameObject> AcquireTurretPrefabAsyncCore(int turretNumber)
2026-10-09 22:06:36 +08:00
{
_ptRefCount[turretNumber] = _ptRefCount.TryGetValue(turretNumber, out int c) ? c + 1 : 1;
if (_ptPrefabCache.TryGetValue(turretNumber, out var cached)) return cached;
2026-10-10 04:09:28 +08:00
// ⭐ 真·按需:位置表定位(只取元数据)→ LoadAssetAsync 只打开这一个 prefab 的小包
string pathName = GetPathName(turretNumber);
if (pathName != null)
2026-10-09 22:06:36 +08:00
{
2026-10-10 04:09:28 +08:00
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);
}
2026-10-09 22:06:36 +08:00
}
2026-10-10 04:09:28 +08:00
Debug.LogWarning($"[TurretResourcesData] 炮台/翅膀按需加载失败: id={turretNumber} name={pathName ?? "无"}(检查 PTCBZS+Default 标签与位置表)");
2026-10-09 22:06:36 +08:00
_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);
}
2026-10-10 04:09:28 +08:00
/// <summary>按需缓存:炮台/翅膀 prefab(位置表按需加载)</summary>
2026-10-09 22:06:36 +08:00
private static readonly Dictionary<int, GameObject> _ptPrefabCache = new Dictionary<int, GameObject>();
2026-10-10 04:09:28 +08:00
private static readonly Dictionary<int, AsyncOperationHandle<GameObject>> _ptHandles = new Dictionary<int, AsyncOperationHandle<GameObject>>();
2026-10-09 22:06:36 +08:00
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() ?? "全部"})");
}
2026-05-22 19:30:02 +08:00
static TurretResourcesData()
{
Init();
}
2026-06-08 21:23:40 +08:00
/// <summary>
/// 从 Resources/tbturretresources.json 加载炮台元数据。
/// 表里没有的 turretNumber,调用方用 TurretData 自身的 Inspector 默认值,不报错。
/// </summary>
2026-05-22 19:30:02 +08:00
async static void Init()
{
2026-06-08 21:23:40 +08:00
turretPrefabPathDic = new Dictionary<string, int>();
turretSpritePathDic = new Dictionary<string, int>();
turretDataDic = new Dictionary<int, TurretResourcesData>();
2026-05-22 19:30:02 +08:00
2026-06-08 21:23:40 +08:00
TextAsset jsonAsset = Resources.Load<TextAsset>("tbturretresources");
if (jsonAsset == null)
2026-05-22 19:30:02 +08:00
{
2026-06-08 21:23:40 +08:00
Debug.LogWarning("[TurretResourcesData] 资源表 tbturretresources.json 未找到,炮台数据字典为空,将使用 TurretData 默认值");
return;
}
try
{
var list = JsonConvert.DeserializeObject<List<TurretResourcesData>>(jsonAsset.text);
if (list == null)
2026-05-22 19:30:02 +08:00
{
2026-06-08 21:23:40 +08:00
Debug.LogWarning("[TurretResourcesData] 资源表解析为空,将使用 TurretData 默认值");
return;
2026-05-22 19:30:02 +08:00
}
2026-06-08 21:23:40 +08:00
foreach (var item in list)
{
2026-07-12 22:30:37 +08:00
// 战神(turretType==2)的 prefabTurretResourcesPath 可能为空,不跳过
// 用 turretNumber 构造默认路径,以便 Addressables 按名匹配
2026-06-08 21:23:40 +08:00
if (string.IsNullOrEmpty(item.prefabTurretResourcesPath))
2026-07-12 22:30:37 +08:00
{
if (item.turretType == 2)
item.prefabTurretResourcesPath = item.turretNumber.ToString();
else
continue; // 炮台/翅膀路径为空则跳过
}
2026-06-08 21:23:40 +08:00
turretPrefabPathDic[item.prefabTurretResourcesPath] = item.turretNumber;
turretSpritePathDic[item.prefabTurretResourcesPath] = item.turretNumber;
turretDataDic[item.turretNumber] = item;
2026-07-12 22:30:37 +08:00
Debug.Log($"[TurretResourcesData] 加载炮台: ID={item.turretNumber}, Path={item.prefabTurretResourcesPath}, Type={item.turretType}, PrefabType={item.prefabType}, BulletIDs=[{string.Join(",", item.bulletID)}]");
2026-06-08 21:23:40 +08:00
}
Debug.Log($"[TurretResourcesData] 初始化完成,共 {turretDataDic.Count} 个炮台配置");
}
catch (System.Exception ex)
{
Debug.LogError($"[TurretResourcesData] JSON 解析失败: {ex.Message},炮台数据字典为空,将使用 TurretData 默认值");
2026-05-22 19:30:02 +08:00
}
}
2026-06-08 21:23:40 +08:00
// ==================== 资源加载 ====================
/// <summary>
2026-08-28 19:42:54 +08:00
/// 加载炮台相关 Addressables 资源(两组标签交集):
/// - PT + Default:普通炮台/翅膀/战神展示预制体 → turretPrefabDic;
/// - PT + ZSYX:战神游戏内使用预制体 → turretGamePrefabDic(与展示同名,靠标签区分)。
2026-07-29 00:31:59 +08:00
/// 注意:Handle 必须保留,不能 Release,否则 Build 下资源会被立即卸载,
/// 字典中存的就是已销毁对象的引用(Editor 下因 AssetDatabase 模式不会暴露此问题)。
2026-06-08 21:23:40 +08:00
/// </summary>
2026-05-22 19:30:02 +08:00
public static async Task LoadTurretAssetsData()
2026-10-12 15:32:36 +08:00
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadTurretAssetsDataCore(); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadTurretAssetsData 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadTurretAssetsDataCore()
2026-05-22 19:30:02 +08:00
{
turretPrefabDic = new Dictionary<int, GameObject>();
2026-08-28 19:42:54 +08:00
turretGamePrefabDic = new Dictionary<int, GameObject>();
2026-05-22 19:30:02 +08:00
turretSpriteDic = new Dictionary<int, Sprite>();
2026-07-29 00:31:59 +08:00
// 清理上一轮残留的 Handle
foreach (var h in _loadedHandles)
{
if (h.IsValid()) Addressables.Release(h);
}
_loadedHandles.Clear();
2026-08-28 19:42:54 +08:00
// 展示用标签组:PT + Default(普通炮台/翅膀/战神展示预制体)
2026-06-08 21:23:40 +08:00
var labels = new List<string> { turret_AALabel, defaultSubLabel };
2026-08-28 19:42:54 +08:00
// 战神游戏内标签组:PT + ZSYX(战神的使用预制体,名字与展示相同)
var zsGameLabels = new List<string> { turret_AALabel, zhanShenGameSubLabel };
2026-05-22 19:30:02 +08:00
2026-06-08 21:23:40 +08:00
Debug.Log("[TurretResourcesData] 开始预下载炮台资源依赖 (PT + Default)...");
2026-05-22 19:30:02 +08:00
var depHandle = Addressables.DownloadDependenciesAsync(labels, MergeMode.Intersection);
await depHandle.Task;
2026-07-29 00:31:59 +08:00
Addressables.Release(depHandle); // 预下载 Handle 可以立即释放,只影响下载缓存
2026-06-08 21:23:40 +08:00
Debug.Log("[TurretResourcesData] 炮台依赖资源预下载完成!");
2026-05-22 19:30:02 +08:00
2026-08-28 19:42:54 +08:00
Debug.Log("[TurretResourcesData] 开始预下载战神游戏内资源依赖 (PT + ZSYX)...");
var zsDepHandle = Addressables.DownloadDependenciesAsync(zsGameLabels, MergeMode.Intersection);
await zsDepHandle.Task;
Addressables.Release(zsDepHandle);
Debug.Log("[TurretResourcesData] 战神游戏内依赖资源预下载完成!");
2026-10-09 22:06:36 +08:00
// ⭐ 散包模式:炮台/翅膀/战神的 prefab 不再启动全量加载(每个资源一个小包,
// 全量会把上百个散包全部拉起 —— 刚登录 194 个包的根源)。
// prefab 全部按需:炮台/翅膀走 AcquireTurretPrefabAsync(PT{编号} 标签),
// 战神走 AcquireZhanShenAsync(ZS{编号}+Default/ZSYX 标签)。
// 启动只加载列表小图 Sprite(若 Sprite 与 prefab 同包仍会拉起散包,
// 应把列表小图合入图集 —— 见 SpriteAtlasBuilderWindow 工具)。
2026-05-22 19:30:02 +08:00
2026-10-10 04:09:28 +08:00
// ---- 加载炮台/翅膀/战神图标(图集方式)----
// ⭐ 图标已合入图集(PaoTaiSprite/chibangSprite/ZhanShenSprite,均在 AA 组内)。
// 按图集资产地址加载 → GetSprites 逐张按名填表;sprite 名 = 原文件名(paotai11001/chibang17005…)。
// 用地址加载(不用标签查询):不会把同组同标签的 prefab 一并拖进内存。
string[] atlasAddresses =
2026-05-22 19:30:02 +08:00
{
2026-10-10 04:09:28 +08:00
"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)
2026-10-09 22:06:36 +08:00
{
try
{
2026-10-10 04:09:28 +08:00
var atlasHandle = Addressables.LoadAssetAsync<SpriteAtlas>(addr);
2026-10-09 22:06:36 +08:00
await atlasHandle.Task;
2026-10-10 04:09:28 +08:00
if (atlasHandle.Status == AsyncOperationStatus.Succeeded && atlasHandle.Result != null)
2026-10-09 22:06:36 +08:00
{
_loadedHandles.Add(atlasHandle);
2026-10-10 04:09:28 +08:00
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}");
2026-10-09 22:06:36 +08:00
}
}
catch (Exception e)
{
2026-10-10 04:09:28 +08:00
Debug.LogWarning($"[TurretResourcesData] 图集加载跳过 {addr}: {e.Message}");
2026-10-09 22:06:36 +08:00
}
}
2026-10-10 04:09:28 +08:00
// ⭐ 编辑器兜底:SpriteAtlas 只在"打包时"才真正 Packed,编辑器 Play 下 GetSprites 返回空
// (所以编辑器里图集加载 0 张不是配置错)。编辑器直接用 AssetDatabase 按名字找 sprite,
// 仅编辑器编译(#if UNITY_EDITOR),真机走上面的图集路径。
#if UNITY_EDITOR
if (turretSpriteDic.Count == 0)
2026-10-09 22:06:36 +08:00
{
2026-10-10 04:09:28 +08:00
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} 张");
2026-10-09 22:06:36 +08:00
}
2026-10-10 04:09:28 +08:00
#endif
Debug.Log($"[TurretResourcesData] 图标加载完成: {turretSpriteDic.Count} 张(配置需要 {turretSpritePathDic.Count} 个)");
if (turretSpriteDic.Count == 0)
Debug.LogError("[TurretResourcesData] 图标一张都没加载到:编辑器请检查 sprite 名字与 tbturretresources 是否对得上;真机请确认三个图集 Inspector 的 Include in Build 已勾选");
2026-05-22 19:30:02 +08:00
}
2026-06-08 21:23:40 +08:00
/// <summary>
/// 卸载炮台 Addressables 资源。
2026-07-29 00:31:59 +08:00
/// 释放 LoadTurretAssetsData 中保留的所有 Handle。
2026-06-08 21:23:40 +08:00
/// </summary>
2026-07-29 00:31:59 +08:00
public static Task UnLoadTurretAssetsData()
2026-06-08 21:23:40 +08:00
{
2026-07-29 00:31:59 +08:00
foreach (var h in _loadedHandles)
2026-06-08 21:23:40 +08:00
{
2026-07-29 00:31:59 +08:00
if (h.IsValid())
2026-06-08 21:23:40 +08:00
{
2026-07-29 00:31:59 +08:00
Addressables.Release(h);
Debug.Log($"[TurretResourcesData] 释放 Handle: {h.DebugName}");
2026-06-08 21:23:40 +08:00
}
}
2026-07-29 00:31:59 +08:00
_loadedHandles.Clear();
2026-06-08 21:23:40 +08:00
2026-07-29 00:31:59 +08:00
turretPrefabDic?.Clear();
2026-08-28 19:42:54 +08:00
turretGamePrefabDic?.Clear();
2026-07-29 00:31:59 +08:00
turretSpriteDic?.Clear();
2026-06-08 21:23:40 +08:00
2026-08-28 19:42:54 +08:00
Debug.Log($"[TurretResourcesData] 卸载完成,剩余 Prefab: {turretPrefabDic?.Count ?? 0}, 战神游戏内: {turretGamePrefabDic?.Count ?? 0}, Sprite: {turretSpriteDic?.Count ?? 0}");
2026-07-29 00:31:59 +08:00
return Task.CompletedTask;
2026-06-08 21:23:40 +08:00
}
// ==================== 查询接口 ====================
/// <summary>根据 ID 获取炮台数据。</summary>
public static TurretResourcesData GetTurretData(int turretNumber)
{
return turretDataDic != null && turretDataDic.TryGetValue(turretNumber, out var data) ? data : null;
}
2026-10-09 22:06:36 +08:00
/// <summary>根据 ID 获取炮台 Prefab。⭐ 优先查按需缓存(PT{编号} 标签),回退全量字典(已不再填充,恒空)。</summary>
2026-05-22 19:30:02 +08:00
public static GameObject GetTurretPrefab(int turretNumber)
{
2026-10-09 22:06:36 +08:00
if (_ptPrefabCache.TryGetValue(turretNumber, out var prefab)) return prefab;
return turretPrefabDic != null && turretPrefabDic.TryGetValue(turretNumber, out var fullLoad) ? fullLoad : null;
2026-08-28 19:42:54 +08:00
}
/// <summary>
/// 根据战神编号获取战神预制体(两种情况二选一)。
/// - Display:展示用(大厅/商城预览,与 GetTurretPrefab 返回同一个展示模型);
/// - GamePlay:游戏内用(座位上替代普通炮台的使用模型)。
2026-10-09 22:06:36 +08:00
/// ⭐ 优先查按需加载缓存(散包);未加载时回退全量加载的字典(标签未拆时的平滑迁移)。
2026-08-28 19:42:54 +08:00
/// </summary>
public static GameObject GetZhanShenPrefab(int turretNumber, ZhanShenUsage usage = ZhanShenUsage.Display)
{
2026-10-09 22:06:36 +08:00
// 按需缓存优先
if (_zsPrefabCache.TryGetValue((turretNumber, usage), out var prefab))
return prefab;
// 回退:全量加载的字典(战神 prefab 未去 PTCBZS 标签时仍会被启动全量加载进来)
2026-08-28 19:42:54 +08:00
var dic = usage == ZhanShenUsage.GamePlay ? turretGamePrefabDic : turretPrefabDic;
2026-10-09 22:06:36 +08:00
return dic != null && dic.TryGetValue(turretNumber, out var fullLoad) ? fullLoad : null;
2026-05-22 19:30:02 +08:00
}
2026-06-08 21:23:40 +08:00
2026-10-10 04:09:28 +08:00
/// <summary>图标字典数量(诊断用)</summary>
public static int SpriteIconCount => turretSpriteDic != null ? turretSpriteDic.Count : 0;
2026-06-08 21:23:40 +08:00
/// <summary>根据 ID 获取炮台 Sprite。</summary>
2026-05-22 19:30:02 +08:00
public static Sprite GetTurretSprite(int turretNumber)
{
2026-06-08 21:23:40 +08:00
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;
2026-05-22 19:30:02 +08:00
}
}