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

392 lines
17 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>
/// 存储所有已加载的纹理按图像名称全局唯一的DefName索引。
/// </summary>
public Dictionary<string, Texture2D> packagesImages = new();
/// <summary>
/// 存储所有已创建的精灵按精灵名称全局唯一的DefName或其带索引后缀索引。
/// </summary>
public Dictionary<string, Sprite> sprites = new();
/// <summary>
/// 获取当前启动步骤的描述。
/// </summary>
public string StepDescription { get; } = "图像管理器正在加载中";
/// <summary>
/// 初始化图像管理器,加载默认精灵并处理所有 ImageDef 定义。
/// </summary>
/// <remarks>
/// <para>
/// **修正后的逻辑**:移除 <c>packagesImages.Count > 0</c> 的重复加载检查。
/// <c>Init</c> 方法现在将始终执行完整的初始化流程,确保所有组件都被正确加载。
/// 如果需要重新加载,应调用 <c>Reload()</c> 方法。
/// </para>
/// </remarks>
public void Init()
{
defaultSprite = Resources.Load<Sprite>("Default/DefaultImage");
if (defaultSprite == null)
{
Debug.LogWarning("无法加载默认精灵 'Resources/Default/DefaultImage'。请确保文件存在。");
}
InitImageDef();
}
/// <summary>
/// 根据 ImageDef 定义初始化并加载所有纹理和精灵。
/// </summary>
public void InitImageDef()
{
// 在每次 InitImageDef 调用前,清空旧数据,以确保重新初始化是干净的。
packagesImages.Clear();
sprites.Clear();
var textureCache = new Dictionary<string, Texture2D>();
var imageDef = Managers.DefineManager.Instance.QueryDefinesByType<ImageDef>();
if (imageDef == null || !imageDef.Any())
{
Debug.Log($"在定义管理器中未找到任何图像定义。({nameof(ImageDef)})");
return;
}
foreach (var ima in imageDef)
{
if (string.IsNullOrEmpty(ima.path) || string.IsNullOrEmpty(ima.packID))
{
Debug.LogWarning($"跳过图像定义 '{ima?.defName ?? ""}'因为它包含空路径或包ID。(路径: '{ima?.path ?? ""}', 包ID: '{ima?.packID ?? ""}')");
continue;
}
try
{
string cacheKey;
Texture2D texture;
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.defName}' (包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.defName}' (包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.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}')。" +
$"请验证文件路径、文件权限以及Unity Asset导入设置中的 'Read/Write Enabled' 选项。");
continue;
}
var processedTexture = texture;
// 如果需要翻转,创建翻转后的纹理
if (ima.flipX || ima.flipY)
{
var flippedTex = FlipTexture(texture, ima.flipX, ima.flipY);
if (flippedTex != null)
{
processedTexture = flippedTex;
}
else
{
Debug.LogError($"未能翻转图像定义关联的纹理: '{ima.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}'),将使用原始纹理。");
}
}
packagesImages[ima.defName] = processedTexture;
// 索引计算依赖这些参数来匹配游戏资源加载的预期。
SplitTextureIntoSprites(ima.defName, processedTexture, ima.hCount, ima.wCount, ima.pixelsPerUnit,
ima.flipX, ima.flipY);
}
catch (Exception ex)
{
// 捕获异常并打印详细错误信息
Debug.LogError(
$"处理图像定义时出错: '{ima.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}')。异常: {ex.GetType().Name}: {ex.Message}\n堆栈跟踪: {ex.StackTrace}");
}
}
}
/// <summary>
/// 将纹理按指定行数和列数分割成多个精灵,并存储起来。
/// </summary>
/// <param name="baseName">精灵的基础名称全局唯一的DefName。</param>
/// <param name="texture">要分割的 <see cref="Texture2D"/> 对象。</param>
/// <param name="rows">水平分割的行数。</param>
/// <param name="cols">垂直分割的列数。</param>
/// <param name="pixelsPerUnit">每个单元的像素数用于Sprite.Create。</param>
/// <param name="flipX">是否沿X轴翻转此参数用于调整索引。</param>
/// <param name="flipY">是否沿Y轴翻转此参数用于调整索引。</param>
/// <remarks>
/// <para>
/// **修正后的逻辑**:恢复 <c>flipX</c> 和 <c>flipY</c> 参数及其在索引计算中的使用。
/// 这确保了精灵的编号逻辑与原始设计保持一致,避免了游戏资源加载错误。
/// </para>
/// </remarks>
private void SplitTextureIntoSprites(
string baseName,
Texture2D texture,
int rows,
int cols,
int pixelsPerUnit,
bool flipX = false,
bool flipY = false)
{
if (!texture)
{
Debug.LogError($"SplitTextureIntoSprites: '{baseName}' 提供的纹理为空。无法分割。");
return;
}
rows = Mathf.Max(1, rows);
cols = Mathf.Max(1, cols);
var textureWidth = texture.width;
var textureHeight = texture.height;
var fullSpriteRect = new Rect(0, 0, textureWidth, textureHeight);
var fullSprite = Sprite.Create(texture, fullSpriteRect, new Vector2(0.5f, 0.5f), pixelsPerUnit);
fullSprite.name = baseName;
sprites[baseName] = fullSprite;
if (rows == 1 && cols == 1)
{
return;
}
if (textureWidth % cols != 0 || textureHeight % rows != 0)
{
Debug.LogWarning($"'{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);
// 此处的 flipX/Y 参数用于调整索引,以符合游戏或美术的特定约定。
var index = (flipY ? row : (rows - row - 1)) * cols + (flipX ? cols - 1 - col : col);
var spriteName = $"{baseName}_{index}";
sprite.name = spriteName;
sprites[spriteName] = sprite;
}
}
}
/// <summary>
/// 根据指定的翻转方向创建一个新的、翻转后的纹理。
/// </summary>
/// <param name="originalTexture">原始纹理。</param>
/// <param name="flipX">是否沿X轴翻转。</param>
/// <param name="flipY">是否沿Y轴翻转。</param>
/// <returns>一个新的、翻转后的Texture2D实例如果不需要翻转则返回原纹理如果纹理不可读返回null。</returns>
private Texture2D FlipTexture(Texture2D originalTexture, bool flipX, bool flipY)
{
if (!flipX && !flipY)
{
return originalTexture;
}
if (originalTexture == null)
{
Debug.LogError("FlipTexture: 原始纹理为null无法翻转。");
return null;
}
if (!originalTexture.isReadable)
{
Debug.LogError($"FlipTexture: 纹理 '{originalTexture.name}' (或其来源) 不可读。 " +
$"请确保在Unity导入设置中勾选 'Read/Write Enabled' 选项。无法进行翻转。");
return null;
}
var width = originalTexture.width;
var height = originalTexture.height;
var flippedTexture = new Texture2D(width, height, originalTexture.format, originalTexture.mipmapCount > 1);
flippedTexture.name = originalTexture.name + (flipX ? "_flippedX" : "") + (flipY ? "_flippedY" : "");
var originalPixels = originalTexture.GetPixels32();
var flippedPixels = new Color32[width * height];
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var originalIndex = y * width + x;
var targetX = flipX ? (width - 1 - x) : x;
var targetY = flipY ? (height - 1 - y) : y;
var flippedIndex = targetY * width + targetX;
if (originalIndex >= 0 && originalIndex < originalPixels.Length &&
flippedIndex >= 0 && flippedIndex < flippedPixels.Length)
{
flippedPixels[flippedIndex] = originalPixels[originalIndex];
}
else
{
Debug.LogWarning($"FlipTexture: 像素索引计算异常,原始: ({x},{y}) -> 索引 {originalIndex};翻转: ({targetX},{targetY}) -> 索引 {flippedIndex}. Texture: {originalTexture.name}");
}
}
}
flippedTexture.SetPixels32(flippedPixels);
flippedTexture.Apply();
return flippedTexture;
}
/// <summary>
/// 清理所有已加载的纹理和精灵数据。
/// </summary>
public void Clear()
{
packagesImages.Clear();
sprites.Clear();
}
/// <summary>
/// 重新加载所有图像数据。
/// </summary>
public void Reload()
{
Clear();
Init();
}
/// <summary>
/// 根据 <see cref="Data.ImageDef"/> 对象获取对应的精灵。
/// </summary>
/// <param name="ima">图像定义对象。</param>
/// <returns>对应的精灵,如果 <paramref name="ima"/> 为空,则返回默认精灵。</returns>
public Sprite GetSprite(ImageDef ima)
{
return ima == null ? defaultSprite : GetSprite(ima.defName);
}
/// <summary>
/// 根据精灵名称全局唯一的DefName获取对应的精灵。
/// </summary>
/// <param name="name">要获取的精灵名称。</param>
/// <returns>对应的精灵,如果名称为空或找不到,则返回默认精灵。</returns>
public Sprite GetSprite(string name)
{
if (string.IsNullOrEmpty(name))
{
Debug.LogWarning($"GetSprite: 尝试获取的精灵名称为空或null。返回默认精灵。");
return defaultSprite;
}
return sprites.GetValueOrDefault(name, defaultSprite);
}
/// <summary>
/// 根据基础名称全局唯一的DefName和索引获取被分割的子精灵。
/// </summary>
/// <param name="name">精灵的基础名称。</param>
/// <param name="index">子精灵的索引。</param>
/// <returns>对应的子精灵,如果找不到,则返回默认精灵。</returns>
public Sprite GetSprite(string name, int index)
{
var fullName = $"{name}_{index}";
return GetSprite(fullName);
}
/// <summary>
/// 根据精灵名称数组获取对应的精灵数组。
/// </summary>
/// <param name="names">精灵名称数组。</param>
/// <returns>对应的精灵数组,如果输入为空,则返回包含默认精灵的数组。</returns>
public Sprite[] GetSprites(string[] names)
{
if (names == null || names.Length == 0)
return new[] { defaultSprite };
return names.Select(name => GetSprite(name)).ToArray();
}
}
}