Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Managers/PackagesImageManager.cs

351 lines
15 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 Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
namespace Managers
{
/// <summary>
/// 包图像管理器,负责加载、管理和提供从定义数据中解析出的纹理和精灵。
/// </summary>
/// <remarks>
/// 该管理器是一个单例,并在启动过程中实现 ILaunchManager 接口,
/// 用于处理游戏或应用启动时图像资源的加载和初始化。
/// </remarks>
public class PackagesImageManager : Utils.Singleton<PackagesImageManager>, ILaunchManager
{
/// <summary>
/// 默认精灵,在找不到对应图像时返回。
/// </summary>
public Sprite defaultSprite;
/// <summary>
/// 存储所有已加载的纹理按包ID和图像名称索引。
/// </summary>
/// <remarks>
/// 外层字典键是包ID内层字典键是图像名称。
/// </remarks>
public Dictionary<string, Dictionary<string, Texture2D>> packagesImages = new();
/// <summary>
/// 存储所有已创建的精灵按包ID和精灵名称索引。
/// </summary>
/// <remarks>
/// 外层字典键是包ID内层字典键是精灵名称如果纹理被分割会包含索引后缀
/// </remarks>
public Dictionary<string, Dictionary<string, Sprite>> sprites = new();
/// <summary>
/// 获取当前启动步骤的描述。
/// </summary>
public string StepDescription { get; private set; } = "包图像管理器正在准备中...";
/// <summary>
/// 初始化图像管理器,加载默认精灵并处理所有 ImageDef 定义。
/// </summary>
public void Init()
{
if (packagesImages.Count > 0)
{
// 如果已经加载过,直接返回。
StepDescription = "包图像管理器已初始化。";
return;
}
StepDescription = "正在加载默认精灵..."; // 更新加载步骤描述
defaultSprite = Resources.Load<Sprite>("Default/DefaultImage");
if (defaultSprite == null)
{
Debug.LogWarning("无法加载默认精灵 'Resources/Default/DefaultImage'。请确保文件存在。");
}
StepDescription = "正在处理图像定义并创建精灵..."; // 更新加载步骤描述
InitImageDef();
StepDescription = "包图像管理器初始化完成。"; // 完成加载
}
/// <summary>
/// 根据 ImageDef 定义初始化并加载所有纹理和精灵。
/// </summary>
public void InitImageDef()
{
var textureCache = new Dictionary<string, Texture2D>();
var imageDef = Managers.DefineManager.Instance.QueryDefinesByType<ImageDef>();
if (imageDef == null || !imageDef.Any())
{
Debug.Log($"在 DefineManager 中未找到任何 ImageDef 定义。({typeof(ImageDef).Name})");
return;
}
foreach (var ima in imageDef)
{
if (string.IsNullOrEmpty(ima.path) || string.IsNullOrEmpty(ima.packID))
{
Debug.LogWarning($"跳过图像定义 '{ima?.name ?? ""}'因为它包含空路径或包ID。(路径: '{ima?.path ?? ""}', 包ID: '{ima?.packID ?? ""}')");
continue;
}
try
{
string cacheKey;
Texture2D texture = null;
if (ima.path.StartsWith("res:"))
{
// 处理 Resources 路径
var resPath = ima.path.Substring(4).Replace('\\', '/').TrimStart('/');
cacheKey = "res://" + resPath.ToLower(); // 缓存键使用小写路径
// 检查纹理缓存
if (!textureCache.TryGetValue(cacheKey, out texture))
{
var cleanPath = Path.ChangeExtension(resPath, null); // 去掉扩展名
texture = Resources.Load<Texture2D>(cleanPath);
if (texture)
textureCache[cacheKey] = texture;
}
}
else if (ima.path.Contains(':'))
{
// 处理其他包ID前缀如 "PackageID:Path"
var splitIndex = ima.path.IndexOf(':');
var packageID = ima.path.Substring(0, splitIndex);
var relativePath = ima.path.Substring(splitIndex + 1);
// 获取包根路径
var packageRoot = Managers.DefineManager.Instance.GetPackagePath(packageID);
if (string.IsNullOrEmpty(packageRoot))
{
Debug.LogWarning($"图像定义 '{ima.name}' (包ID: {ima.packID}): 引用的包ID '{packageID}' 未找到或没有根路径。跳过图像加载。");
continue;
}
var fullPath = Path.Combine(packageRoot, relativePath).Replace('\\', '/');
cacheKey = "file://" + fullPath.ToLower(); // 缓存键使用小写路径
// 检查纹理缓存
if (!textureCache.TryGetValue(cacheKey, out texture))
{
texture = Configs.ConfigProcessor.LoadTextureByIO(fullPath);
if (texture)
textureCache[cacheKey] = texture;
}
}
else
{
// 无前缀:使用当前定义所在包的路径
var pack = Managers.DefineManager.Instance.GetDefinePackage(ima);
if (pack == null)
{
Debug.LogError($"图像定义 '{ima.name}' (包ID: {ima.packID}): 源图像未找到对应的定义包。无法确定 '{ima.path}' 的完整路径。跳过。");
continue;
}
var fullPath = Path.Combine(pack.packRootPath, ima.path).Replace('\\', '/');
cacheKey = "file://" + fullPath.ToLower(); // 缓存键使用小写路径
// 检查纹理缓存
if (!textureCache.TryGetValue(cacheKey, out texture))
{
texture = Configs.ConfigProcessor.LoadTextureByIO(fullPath);
if (texture)
textureCache[cacheKey] = texture;
}
}
// 资源加载失败
if (!texture)
{
Debug.LogError($"未能加载图像定义所在的纹理: '{ima.name}' (路径: '{ima.path}', 包ID: '{ima.packID}')。请验证路径和文件是否存在。");
continue;
}
// 存储到包纹理字典(使用定义自身的 packID
var packId = ima.packID;
if (!packagesImages.ContainsKey(packId))
packagesImages[packId] = new Dictionary<string, Texture2D>();
// 警告:如果图片名重复,则覆盖
if (packagesImages[packId].ContainsKey(ima.name))
{
Debug.LogWarning($"包 '{packId}' 中名为 '{ima.name}' 的图像被多次定义。将覆盖之前的纹理引用。这可能表示配置错误。");
}
packagesImages[packId][ima.name] = texture; // 覆盖或添加
// 切分精灵
SplitTextureIntoSprites(packId, ima.name, texture, ima.hCount, ima.wCount, ima.pixelsPerUnit);
}
catch (Exception ex)
{
// 捕获异常并打印详细错误信息
Debug.LogError(
$"处理图像定义时出错: '{ima.name}' (路径: '{ima.path}', 包ID: '{ima.packID}')。异常: {ex.GetType().Name}: {ex.Message}\n堆栈跟踪: {ex.StackTrace}");
}
}
}
/// <summary>
/// 将纹理按指定行数和列数分割成多个精灵,并存储起来。
/// </summary>
/// <param name="packId">精灵所属的包ID。</param>
/// <param name="baseName">精灵的基础名称。</param>
/// <param name="texture">要分割的 <see cref="Texture2D"/> 对象。</param>
/// <param name="rows">水平分割的行数。</param>
/// <param name="cols">垂直分割的列数。</param>
/// <param name="pixelsPerUnit">每个单元的像素数用于Sprite.Create。</param>
private void SplitTextureIntoSprites(
string packId,
string baseName,
Texture2D texture,
int rows,
int cols,
int pixelsPerUnit)
{
if (!texture)
{
Debug.LogError($"SplitTextureIntoSprites: 包 '{packId}' 中 '{baseName}' 提供的纹理为空。无法分割。");
return;
}
// 如果行数或列数小于1则设为1不分割
rows = Mathf.Max(1, rows);
cols = Mathf.Max(1, cols);
var textureWidth = texture.width;
var textureHeight = texture.height;
if (!sprites.ContainsKey(packId))
sprites[packId] = new Dictionary<string, Sprite>();
// 创建未分割的完整精灵,使用原始名称
var fullSpriteRect = new Rect(0, 0, textureWidth, textureHeight);
// 警告:如果精灵名重复,则覆盖
if (sprites[packId].ContainsKey(baseName))
{
Debug.LogWarning($"包 '{packId}' 中名为 '{baseName}' 的精灵已存在。将覆盖之前的完整精灵定义。");
}
var fullSprite = Sprite.Create(texture, fullSpriteRect, new Vector2(0.5f, 0.5f), pixelsPerUnit);
fullSprite.name = baseName; // 确保 Sprite.name 被设置
sprites[packId][baseName] = fullSprite;
// 如果不分割rows和cols都为1提前返回
if (rows == 1 && cols == 1)
{
return;
}
// 检查纹理尺寸是否可被分割数整除
if (textureWidth % cols != 0 || textureHeight % rows != 0)
{
Debug.LogError($"包 '{packId}' 中 '{baseName}' 的纹理尺寸 ({textureWidth}x{textureHeight}) 不能被指定的行数 ({rows}) 和列数 ({cols}) 完美整除。子精灵将不会生成或可能不正确。仅显示完整精灵。");
return; // 终止子精灵分割,只保留完整的精灵
}
var tileWidth = textureWidth / cols;
var tileHeight = textureHeight / rows;
for (var row = 0; row < rows; row++)
{
for (var col = 0; col < cols; col++)
{
Rect spriteRect = new(col * tileWidth, row * tileHeight, tileWidth, tileHeight);
var sprite = Sprite.Create(texture, spriteRect, new Vector2(0.5f, 0.5f), pixelsPerUnit);
// 精灵索引计算方式
var index = (rows - row - 1) * cols + col;
var spriteName = $"{baseName}_{index}";
sprite.name = spriteName;
// 警告:如果子精灵名重复,则覆盖
if (sprites[packId].ContainsKey(spriteName))
{
Debug.LogWarning($"包 '{packId}' 中名为 '{spriteName}' 的精灵已存在。将覆盖之前的子精灵定义。这可能表示配置错误。");
}
sprites[packId][spriteName] = sprite;
}
}
}
/// <summary>
/// 清理所有已加载的纹理和精灵数据。
/// </summary>
/// <remarks>
/// 此方法会清空 <see cref="packagesImages"/> 和 <see cref="sprites"/> 字典,
/// 但不会卸载 <see cref="defaultSprite"/>,因为它通常通过 Resources.Load 加载,由 Unity 管理其生命周期。
/// </remarks>
public void Clear()
{
packagesImages.Clear();
sprites.Clear();
StepDescription = "包图像管理器数据已清理。"; // 更新状态
}
/// <summary>
/// 重新加载所有图像数据。
/// </summary>
/// <remarks>
/// 此方法会首先调用 <see cref="Clear()"/> 清理所有数据,然后调用 <see cref="Init()"/> 重新初始化。
/// </remarks>
public void Reload()
{
Clear();
Init();
}
/// <summary>
/// 根据 <see cref="ImageDef"/> 对象获取对应的精灵。
/// </summary>
/// <param name="ima">包含精灵包ID和名称的 <see cref="ImageDef"/> 对象。</param>
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
public Sprite GetSprite(ImageDef ima)
{
if (ima == null) return defaultSprite;
return GetSprite(ima.packID, ima.name);
}
/// <summary>
/// 根据包ID和精灵名称获取对应的精灵。
/// </summary>
/// <param name="packID">精灵所属的包ID。如果为空则会遍历所有包查找。</param>
/// <param name="name">精灵的名称。</param>
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
public Sprite GetSprite(string packID, string name)
{
if (string.IsNullOrEmpty(packID))
{
// 如果packID为空遍历所有包以name查找
foreach (var kvp in sprites)
{
if (kvp.Value.TryGetValue(name, out var sprite))
return sprite;
}
}
else if (sprites.TryGetValue(packID, out var dict))
{
if (dict.TryGetValue(name, out var sprite))
return sprite;
}
// 如果未找到,返回默认精灵
return defaultSprite;
}
/// <summary>
/// 根据包ID、基础名称和索引获取被分割的子精灵。
/// </summary>
/// <param name="packID">精灵所属的包ID。</param>
/// <param name="name">精灵的基础名称。</param>
/// <param name="index">子精灵的索引。</param>
/// <returns>如果找到对应的子精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
public Sprite GetSprite(string packID, string name, int index)
{
var fullName = $"{name}_{index}";
return GetSprite(packID, fullName);
}
}
}