using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using SepCore.CustomUtility;
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.AsyncTask
{
///
/// 图集 Sprite 加载器,通过图集名 + 序号定位 Sprite。
/// 序号 -1 表示图集本身就是一张完整 Sprite,不需要切分。
///
public static class SpriteLoader
{
private const float DefaultPixelsPerUnit = 32f;
private static readonly Dictionary _textureCache = new();
private static readonly Dictionary> _loadingTextures = new();
private static readonly Dictionary> _spriteCache = new();
///
/// 异步加载图集 Sprite
///
/// Sprite 配置
/// 切分块尺寸(像素),默认 32
/// Sprite 像素单位比,默认 32
/// 超时时间(秒),0 表示不超时
/// 加载的 Sprite,失败时返回 null
public static UniTask LoadSpriteAsync(SpriteConfig config, int tileSize = 32,
float pixelsPerUnit = DefaultPixelsPerUnit, float timeout = 0f)
{
return LoadSpriteAsync(config.AtlasPath, config.SpriteIndex, tileSize, pixelsPerUnit, timeout);
}
///
/// 异步加载图集 Sprite,按资源键查 tbspriteconfig 表定位
///
/// tbspriteconfig 的资源键
/// 切分块尺寸(像素),默认 32
/// Sprite 像素单位比,默认 32
/// 超时时间(秒),0 表示不超时
/// 加载的 Sprite,失败时返回 null
public static UniTask LoadSpriteByKeyAsync(string key, int tileSize = 32,
float pixelsPerUnit = DefaultPixelsPerUnit, float timeout = 0f)
{
SpriteConfig config = global::GameEntry.Luban.Get(key);
if (config == null)
{
Log.Error("Sprite config not found: {0}", key);
return default;
}
return LoadSpriteAsync(config, tileSize, pixelsPerUnit, timeout);
}
///
/// 异步加载图集 Sprite
///
/// 图集名称(相对 Assets/GameMain/Textures 的路径,含扩展名,如 32rogues/monsters.png)
/// 序号,-1 表示整张图集作为一张 Sprite
/// 切分块尺寸(像素),默认 32
/// Sprite 像素单位比,默认 32
/// 超时时间(秒),0 表示不超时
/// 加载的 Sprite,失败时返回 null
public static async UniTask LoadSpriteAsync(string atlasName, int index, int tileSize = 32,
float pixelsPerUnit = DefaultPixelsPerUnit, float timeout = 0f)
{
Texture2D texture = await GetTextureAsync(atlasName, timeout);
if (texture == null)
{
return null;
}
int cacheKey = index < 0 ? -1 : index;
if (!_spriteCache.TryGetValue(atlasName, out Dictionary sprites))
{
sprites = new Dictionary();
_spriteCache[atlasName] = sprites;
}
if (sprites.TryGetValue(cacheKey, out Sprite cached))
{
return cached;
}
Sprite sprite = CreateSprite(texture, cacheKey, tileSize, pixelsPerUnit);
if (sprite != null)
{
sprites[cacheKey] = sprite;
}
return sprite;
}
///
/// 释放指定图集的所有 Sprite 和纹理引用
///
public static void Release(string atlasName)
{
if (_spriteCache.TryGetValue(atlasName, out Dictionary sprites))
{
foreach (Sprite sprite in sprites.Values)
{
if (sprite != null)
{
UnityEngine.Object.Destroy(sprite);
}
}
sprites.Clear();
_spriteCache.Remove(atlasName);
}
if (_textureCache.TryGetValue(atlasName, out Texture2D texture))
{
global::GameEntry.Resource.UnloadAsset(texture);
_textureCache.Remove(atlasName);
}
}
///
/// 释放所有图集的 Sprite 和纹理引用
///
public static void ReleaseAll()
{
List atlasNames = new List(_textureCache.Keys);
foreach (string atlasName in atlasNames)
{
Release(atlasName);
}
}
private static async UniTask GetTextureAsync(string atlasName, float timeout)
{
if (_textureCache.TryGetValue(atlasName, out Texture2D cached))
{
return cached;
}
if (_loadingTextures.TryGetValue(atlasName, out Task loading))
{
return await loading;
}
Task task = LoadTextureAsync(atlasName, timeout).AsTask();
_loadingTextures[atlasName] = task;
try
{
Texture2D texture = await task;
if (texture != null)
{
_textureCache[atlasName] = texture;
}
return texture;
}
finally
{
_loadingTextures.Remove(atlasName);
}
}
private static async UniTask LoadTextureAsync(string atlasName, float timeout)
{
try
{
return await global::GameEntry.Resource.LoadAssetAsync(
AssetUtility.GetSpriteAsset(atlasName), Constant.AssetPriority.SpriteAsset, timeout: timeout);
}
catch (Exception e)
{
Log.Error("加载图集失败: {0}, error: {1}", atlasName, e);
return null;
}
}
private static Sprite CreateSprite(Texture2D texture, int index, int tileSize, float pixelsPerUnit)
{
if (index < 0)
{
return Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height),
new Vector2(0.5f, 0.5f), pixelsPerUnit, 0, SpriteMeshType.FullRect);
}
int columns = Mathf.Max(1, texture.width / tileSize);
int rows = Mathf.Max(1, texture.height / tileSize);
int column = index % columns;
int row = index / columns;
if (row >= rows)
{
Log.Error("图集 {0} 序号 {1} 超出范围 (cols: {2}, rows: {3})", texture.name, index, columns, rows);
return null;
}
float x = column * tileSize;
float y = texture.height - (row + 1) * tileSize;
// 交互描边需要完整矩形网格,才能在 Shader 中为图案四周扩展留白。
return Sprite.Create(texture, new Rect(x, y, tileSize, tileSize),
new Vector2(0.5f, 0.5f), pixelsPerUnit, 0, SpriteMeshType.FullRect);
}
}
}