1
This commit is contained in:
@@ -1427,6 +1427,10 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
currentTurret = null;
|
||||
}
|
||||
|
||||
// ⭐ 换炮台:释放上一个炮台的按需引用(引用归零才真正卸包;同编号不释放)
|
||||
if (currentTurretNumber > 0 && currentTurretNumber != turretNumber)
|
||||
TurretResourcesData.ReleaseTurretPrefab(currentTurretNumber);
|
||||
|
||||
currentTurretNumber = turretNumber;
|
||||
|
||||
// ⭐ 散包按需加载:缓存没有时异步取(PT{编号} 标签),完成后实例化
|
||||
@@ -1435,12 +1439,14 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
|
||||
/// <summary>炮台加载序号(防加载期间再次切换的竞态)</summary>
|
||||
private int _turretLoadSeq = 0;
|
||||
/// <summary>当前按需加载的翅膀编号(换翅膀/卸下时释放引用)</summary>
|
||||
private int _currentWingNumber = 0;
|
||||
|
||||
/// <summary>炮台异步按需加载协程:AcquireTurretPrefabAsync 完成后实例化(原同步逻辑后半段)</summary>
|
||||
private IEnumerator LoadTurretRoutine(int turretNumber, int seq)
|
||||
{
|
||||
GameObject prefab = TurretResourcesData.GetTurretPrefab(turretNumber);
|
||||
if (prefab == null)
|
||||
// ⭐ 每次都走 Acquire(缓存命中立即返回并正确 +1 引用,多座位共用同一炮台不会互相误卸)
|
||||
GameObject prefab = null;
|
||||
{
|
||||
var task = TurretResourcesData.AcquireTurretPrefabAsync(turretNumber);
|
||||
while (!task.IsCompleted) yield return null;
|
||||
@@ -1608,10 +1614,21 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
// turretNumber<=0 = 卸下(与战神同一约定):只清理不加载,不打警告
|
||||
if (turretNumber <= 0)
|
||||
{
|
||||
// ⭐ 卸下翅膀:释放按需引用
|
||||
if (_currentWingNumber > 0)
|
||||
{
|
||||
TurretResourcesData.ReleaseTurretPrefab(_currentWingNumber);
|
||||
_currentWingNumber = 0;
|
||||
}
|
||||
ClearWing();
|
||||
return;
|
||||
}
|
||||
|
||||
// ⭐ 换翅膀:释放上一个的按需引用(引用归零才真正卸包)
|
||||
if (_currentWingNumber > 0 && _currentWingNumber != turretNumber)
|
||||
TurretResourcesData.ReleaseTurretPrefab(_currentWingNumber);
|
||||
_currentWingNumber = turretNumber;
|
||||
|
||||
// 先清理已有翅膀
|
||||
ClearWing();
|
||||
|
||||
@@ -1622,8 +1639,8 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
/// <summary>翅膀异步按需加载协程(模式同炮台:seq 防竞态)</summary>
|
||||
private IEnumerator LoadWingRoutine(int turretNumber, int seq)
|
||||
{
|
||||
GameObject prefab = TurretResourcesData.GetTurretPrefab(turretNumber);
|
||||
if (prefab == null)
|
||||
// ⭐ 每次都走 Acquire(缓存命中立即返回并正确 +1 引用)
|
||||
GameObject prefab = null;
|
||||
{
|
||||
var task = TurretResourcesData.AcquireTurretPrefabAsync(turretNumber);
|
||||
while (!task.IsCompleted) yield return null;
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
|
||||
@@ -59,9 +60,9 @@ public class TurretResourcesData
|
||||
/// <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>战神加载句柄:(编号, 用途) → 单资产 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>();
|
||||
@@ -80,23 +81,98 @@ public class TurretResourcesData
|
||||
_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)
|
||||
// ⭐ 真·按需:位置表定位(只取元数据)→ LoadAssetAsync 只打开该战神自己那一个包
|
||||
string pathName = GetPathName(turretNumber);
|
||||
if (pathName != null)
|
||||
{
|
||||
// 展示/游戏内同名靠标签区分,交集结果取第一个
|
||||
_zsPrefabCache[key] = handle.Result[0];
|
||||
_zsHandles[key] = handle;
|
||||
return handle.Result[0];
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 加载失败(标签未配置等):归还引用计数,不缓存句柄
|
||||
if (handle.IsValid()) Addressables.Release(handle);
|
||||
|
||||
// 未找到:归还引用计数,不缓存
|
||||
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,座位装炮台/大厅预览时按需取。
|
||||
@@ -107,15 +183,38 @@ public class TurretResourcesData
|
||||
_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)
|
||||
// ⭐ 真·按需:位置表定位(只取元数据)→ LoadAssetAsync 只打开这一个 prefab 的小包
|
||||
string pathName = GetPathName(turretNumber);
|
||||
if (pathName != null)
|
||||
{
|
||||
_ptPrefabCache[turretNumber] = handle.Result[0];
|
||||
_ptHandles[turretNumber] = handle;
|
||||
return handle.Result[0];
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (handle.IsValid()) Addressables.Release(handle);
|
||||
|
||||
Debug.LogWarning($"[TurretResourcesData] 炮台/翅膀按需加载失败: id={turretNumber} name={pathName ?? "无"}(检查 PTCBZS+Default 标签与位置表)");
|
||||
_ptRefCount[turretNumber] = c;
|
||||
if (_ptRefCount[turretNumber] <= 0) _ptRefCount.Remove(turretNumber);
|
||||
return null;
|
||||
@@ -133,9 +232,9 @@ public class TurretResourcesData
|
||||
_ptPrefabCache.Remove(turretNumber);
|
||||
}
|
||||
|
||||
/// <summary>按需缓存:炮台/翅膀 prefab(PT{编号} 标签)</summary>
|
||||
/// <summary>按需缓存:炮台/翅膀 prefab(位置表按需加载)</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, AsyncOperationHandle<GameObject>> _ptHandles = new Dictionary<int, AsyncOperationHandle<GameObject>>();
|
||||
private static readonly Dictionary<int, int> _ptRefCount = new Dictionary<int, int>();
|
||||
|
||||
/// <summary>
|
||||
@@ -305,51 +404,83 @@ public class TurretResourcesData
|
||||
// 启动只加载列表小图 Sprite(若 Sprite 与 prefab 同包仍会拉起散包,
|
||||
// 应把列表小图合入图集 —— 见 SpriteAtlasBuilderWindow 工具)。
|
||||
|
||||
// ---- 加载炮台 Sprite(列表图标,全量保留)----
|
||||
var spriteHandle = Addressables.LoadAssetsAsync<Sprite>(labels, data =>
|
||||
// ---- 加载炮台/翅膀/战神图标(图集方式)----
|
||||
// ⭐ 图标已合入图集(PaoTaiSprite/chibangSprite/ZhanShenSprite,均在 AA 组内)。
|
||||
// 按图集资产地址加载 → GetSprites 逐张按名填表;sprite 名 = 原文件名(paotai11001/chibang17005…)。
|
||||
// 用地址加载(不用标签查询):不会把同组同标签的 prefab 一并拖进内存。
|
||||
string[] atlasAddresses =
|
||||
{
|
||||
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)
|
||||
"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.LoadAssetsAsync<SpriteAtlas>("TurretAtlas", null);
|
||||
var atlasHandle = Addressables.LoadAssetAsync<SpriteAtlas>(addr);
|
||||
await atlasHandle.Task;
|
||||
if (atlasHandle.Status == AsyncOperationStatus.Succeeded
|
||||
&& atlasHandle.Result != null && atlasHandle.Result.Count > 0)
|
||||
if (atlasHandle.Status == AsyncOperationStatus.Succeeded && atlasHandle.Result != null)
|
||||
{
|
||||
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})");
|
||||
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] 图集兜底加载失败(未找到 TurretAtlas 标签的图集?): {e.Message}");
|
||||
Debug.LogWarning($"[TurretResourcesData] 图集加载跳过 {addr}: {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
// ⭐ 编辑器兜底:SpriteAtlas 只在"打包时"才真正 Packed,编辑器 Play 下 GetSprites 返回空
|
||||
// (所以编辑器里图集加载 0 张不是配置错)。编辑器直接用 AssetDatabase 按名字找 sprite,
|
||||
// 仅编辑器编译(#if UNITY_EDITOR),真机走上面的图集路径。
|
||||
#if UNITY_EDITOR
|
||||
if (turretSpriteDic.Count == 0)
|
||||
{
|
||||
Debug.Log($"[TurretResourcesData] 炮台 Sprite 加载完成,共 {turretSpriteDic.Count} 个(prefab 已改按需加载)");
|
||||
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>
|
||||
@@ -408,6 +539,9 @@ public class TurretResourcesData
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user