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

385 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>
public void Init()
{
if (packagesImages.Count > 0)
{
// 如果已经有数据,则跳过初始化,防止重复加载。
return;
}
defaultSprite = Resources.Load<Sprite>("Default/DefaultImage");
if (defaultSprite == null)
{
Debug.LogWarning("无法加载默认精灵 'Resources/Default/DefaultImage'。请确保文件存在。");
}
InitImageDef();
}
/// <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($"在定义管理器中未找到任何图像定义。({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 = 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.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}')。请验证路径和文件是否存在。");
continue;
}
// --- 修改开始 ---
Texture2D processedTexture = texture;
// 如果需要翻转,创建翻转后的纹理
if (ima.flipX || ima.flipY)
{
Texture2D 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>
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;
}
// 如果行数或列数小于1则设为1不分割
rows = Mathf.Max(1, rows);
cols = Mathf.Max(1, cols);
var textureWidth = texture.width;
var textureHeight = texture.height;
// 创建未分割的完整精灵,使用原始名称 (baseName即 ImageDef.name)
var fullSpriteRect = new Rect(0, 0, textureWidth, textureHeight);
var fullSprite = Sprite.Create(texture, fullSpriteRect, new Vector2(0.5f, 0.5f), pixelsPerUnit);
fullSprite.name = baseName; // 确保 Sprite.name 被设置
sprites[baseName] = fullSprite;
// 如果不分割rows和cols都为1提前返回
if (rows == 1 && cols == 1)
{
return;
}
// 检查纹理尺寸是否可被分割数整除
if (textureWidth % cols != 0 || textureHeight % rows != 0)
{
Debug.LogError($"'{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 = (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实例如果不需要翻转则返回原纹理。</returns>
private Texture2D FlipTexture(Texture2D originalTexture, bool flipX, bool flipY)
{
if (!flipX && !flipY)
{
return originalTexture; // 无需翻转,返回原始纹理
}
if (originalTexture == null)
{
Debug.LogError("FlipTexture: 原始纹理为null无法翻转。");
return null;
}
// Unity Texture2D 的 GetPixels/SetPixels 操作要求纹理是可读写的。
// 确保纹理的可读写性,或创建一个可读写的副本。
// Configs.ConfigProcessor.LoadTextureByIO 和 Resources.Load的纹理
// 它们的"Read/Write Enabled"导入设置必须开启。
// 这里为了确保纹理处理成功,我们将像素复制到一个新的可读写纹理中。
int width = originalTexture.width;
int height = originalTexture.height;
// 创建一个全新的Texture2D来承载翻转后的像素。
// 使用Color32代替Color效率更高且通常直接对应原始图像数据。
Texture2D flippedTexture = new Texture2D(width, height, originalTexture.format, originalTexture.mipmapCount > 1);
// 给翻转后的纹理一个名字,方便调试
flippedTexture.name = originalTexture.name + (flipX ? "_flippedX" : "") + (flipY ? "_flippedY" : "");
Color32[] originalPixels = originalTexture.GetPixels32();
Color32[] flippedPixels = new Color32[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// 计算原始像素的索引
int originalIndex = y * width + x;
// 计算翻转后像素的目标坐标
int targetX = flipX ? (width - 1 - x) : x;
int targetY = flipY ? (height - 1 - y) : y;
// 计算翻转后像素的目标索引
int 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(); // 应用像素更改到GPU
return flippedTexture;
}
/// <summary>
/// 清理所有已加载的纹理和精灵数据。
/// </summary>
/// <remarks>
/// 此方法会清空 <see cref="packagesImages"/> 和 <see cref="sprites"/> 字典,
/// 但不会卸载 <see cref="defaultSprite"/>,因为它通常通过 Resources.Load 加载,由 Unity 管理其生命周期。
/// </remarks>
public void Clear()
{
packagesImages.Clear();
sprites.Clear();
}
/// <summary>
/// 重新加载所有图像数据。
/// </summary>
/// <remarks>
/// 此方法会首先调用 <see cref="Clear()"/> 清理所有数据,然后调用 <see cref="Init()"/> 重新初始化。
/// </remarks>
public void Reload()
{
Clear();
Init();
}
/// <summary>
/// 根据 <see cref="ImageDef"/> 对象获取对应的精灵。
/// </summary>
/// <param name="ima">包含精灵名称的 <see cref="ImageDef"/> 对象。</param>
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
public Sprite GetSprite(ImageDef ima)
{
return ima == null ? defaultSprite : GetSprite(ima.defName);
}
/// <summary>
/// 根据精灵名称全局唯一的DefName获取对应的精灵。
/// </summary>
/// <param name="name">精灵的名称。</param>
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
public Sprite GetSprite(string name)
{
return sprites.GetValueOrDefault(name, defaultSprite);
}
/// <summary>
/// 根据基础名称全局唯一的DefName和索引获取被分割的子精灵。
/// </summary>
/// <param name="name">精灵的基础名称。</param>
/// <param name="index">子精灵的索引。</param>
/// <returns>如果找到对应的子精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
public Sprite GetSprite(string name, int index)
{
var fullName = $"{name}_{index}";
return GetSprite(fullName);
}
public Sprite[] GetSprites(string[] names)
{
if (names == null || names.Length == 0)
return new[] { defaultSprite };
return names.Select(name => GetSprite(name)).ToArray();
}
}
}