调整部分数据表配置,优化 SpriteCache 功能

This commit is contained in:
2026-09-13 20:36:21 +08:00
parent de53890de6
commit 7dc529a840
520 changed files with 24231 additions and 967 deletions
@@ -5,7 +5,6 @@ using Cysharp.Threading.Tasks;
using SepCore.CustomUtility;
using SepCore.Definition;
using SepCore.Entity;
using SepCore.Entity.Weapon;
using UnityGameFramework.Runtime;
using HideEntityCompleteEventArgs = UnityGameFramework.Runtime.HideEntityCompleteEventArgs;
using ShowEntityFailureEventArgs = UnityGameFramework.Runtime.ShowEntityFailureEventArgs;
@@ -162,22 +161,11 @@ namespace SepCore.AsyncTask
float timeout = 30f,
CancellationToken cancellationToken = default)
{
if (data == null)
{
Log.Warning("Enemy data is invalid.");
return UniTask.FromResult<EnemyBase>(null);
}
Type enemyType = TryGetType(data.EnemyType.ToString());
Type enemyType = GetLogicType(data.LogicType);
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
if (enemyType == null || entityConfig == null)
{
Log.Warning("Can not load enemy logic type or entity config for assetName '{0}'.", data.AssetName);
return UniTask.FromResult<EnemyBase>(null);
}
return entityComponent
.ShowEntityAsync(data.Id, enemyType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Enemy",
return entityComponent.ShowEntityAsync(
data.Id, enemyType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Enemy",
Constant.AssetPriority.EnemyAsset, data, timeout, cancellationToken)
.ContinueWith(entity => entity?.Logic as EnemyBase);
}
@@ -190,22 +178,11 @@ namespace SepCore.AsyncTask
float timeout = 30f,
CancellationToken cancellationToken = default)
{
if (data == null)
{
Log.Warning("Weapon data is invalid.");
return UniTask.FromResult<WeaponBase>(null);
}
Type weaponType = TryGetType("Weapon." + data.WeaponType);
Type weaponType = GetLogicType(data.LogicType);
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
if (weaponType == null || entityConfig == null)
{
Log.Warning("Can not load weapon logic type or entity config for assetName '{0}'.", data.AssetName);
return UniTask.FromResult<WeaponBase>(null);
}
return entityComponent
.ShowEntityAsync(data.Id, weaponType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Weapon",
return entityComponent.ShowEntityAsync(
data.Id, weaponType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Weapon",
Constant.AssetPriority.WeaponAsset, data, timeout, cancellationToken)
.ContinueWith(entity => entity?.Logic as WeaponBase);
}
@@ -298,24 +275,13 @@ namespace SepCore.AsyncTask
float timeout,
CancellationToken cancellationToken) where T : EntityLogic
{
if (data == null)
{
Log.Warning("Data is invalid.");
return UniTask.FromResult<T>(null);
}
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
if (entityConfig == null)
{
Log.Warning("Can not load entity assetName '{0}' from data table.", data.AssetName);
return UniTask.FromResult<T>(null);
}
return entityComponent.ShowEntityAsync<T>(data.Id, AssetUtility.GetEntityAsset(entityConfig.PrefabPath),
entityGroupName, priority, data, timeout, cancellationToken);
}
private static Type TryGetType(string rawTypeName)
private static Type GetLogicType(string rawTypeName)
{
string typeName = EntityNamespace + rawTypeName;
if (!_typeDict.TryGetValue(typeName, out Type type))
@@ -323,8 +289,7 @@ namespace SepCore.AsyncTask
type = Type.GetType(typeName);
if (type == null)
{
Log.Warning("Can not load entity type '{0}'.", typeName);
return null;
throw new InvalidOperationException($"Can not load entity type '{typeName}'.");
}
_typeDict.Add(typeName, type);
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using GameFramework.Resource;
using UnityGameFramework.Runtime;
@@ -20,14 +21,33 @@ namespace SepCore.AsyncTask
/// <param name="resourceComponent">资源组件</param>
/// <param name="assetName">资源名称</param>
/// <param name="priority">加载优先级</param>
/// <returns>加载完成的资源</returns>
public static UniTask<T> LoadAssetAsync<T>(this ResourceComponent resourceComponent, string assetName, int priority = 0)
/// <param name="userData">用户自定义数据</param>
/// <param name="progress">加载进度</param>
/// <param name="timeout">超时时间(秒),0表示不超时</param>
/// <returns>加载的资源</returns>
public static UniTask<T> LoadAssetAsync<T>(this ResourceComponent resourceComponent,
string assetName,
int priority = 0,
object userData = null,
IProgress<float> progress = null,
float timeout = 0f) where T : class
{
var tcs = AutoResetUniTaskCompletionSource<T>.Create();
resourceComponent.LoadAsset(assetName, priority, new LoadAssetCallbacks(
(_, asset, _, _) => tcs.TrySetResult((T)asset),
(_, _, errorMessage, _) => tcs.TrySetException(new Exception(errorMessage))
));
var tcs = new UniTaskCompletionSource<T>();
var callbacks = new LoadAssetCallbacks(
(name, asset, duration, ud) => tcs.TrySetResult((T)asset),
(name, status, errorMessage, ud) => tcs.TrySetException(new Exception($"加载资源失败: {name}, status: {status}, error: {errorMessage}")),
(name, p, ud) => progress?.Report(p)
);
resourceComponent.LoadAsset(assetName, typeof(T), priority, callbacks, userData);
if (timeout > 0f)
{
UniTask.Delay(TimeSpan.FromSeconds(timeout), cancellationToken: CancellationToken.None)
.ContinueWith(() => tcs.TrySetException(new TimeoutException($"加载资源超时: {assetName}, {timeout}秒")));
}
return tcs.Task;
}
@@ -0,0 +1,202 @@
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
{
/// <summary>
/// 图集 Sprite 加载器,通过图集名 + 序号定位 Sprite。
/// 序号 -1 表示图集本身就是一张完整 Sprite,不需要切分。
/// </summary>
public static class SpriteLoader
{
private const float DefaultPixelsPerUnit = 32f;
private static readonly Dictionary<string, Texture2D> _textureCache = new();
private static readonly Dictionary<string, Task<Texture2D>> _loadingTextures = new();
private static readonly Dictionary<string, Dictionary<int, Sprite>> _spriteCache = new();
/// <summary>
/// 异步加载图集 Sprite
/// </summary>
/// <param name="config">Sprite 配置</param>
/// <param name="tileSize">切分块尺寸(像素),默认 32</param>
/// <param name="pixelsPerUnit">Sprite 像素单位比,默认 32</param>
/// <param name="timeout">超时时间(秒),0 表示不超时</param>
/// <returns>加载的 Sprite,失败时返回 null</returns>
public static UniTask<Sprite> LoadSpriteAsync(SpriteConfig config, int tileSize = 32,
float pixelsPerUnit = DefaultPixelsPerUnit, float timeout = 0f)
{
return LoadSpriteAsync(config.AtlasPath, config.SpriteIndex, tileSize, pixelsPerUnit, timeout);
}
/// <summary>
/// 异步加载图集 Sprite,按资源键查 tbspriteconfig 表定位
/// </summary>
/// <param name="key">tbspriteconfig 的资源键</param>
/// <param name="tileSize">切分块尺寸(像素),默认 32</param>
/// <param name="pixelsPerUnit">Sprite 像素单位比,默认 32</param>
/// <param name="timeout">超时时间(秒),0 表示不超时</param>
/// <returns>加载的 Sprite,失败时返回 null</returns>
public static UniTask<Sprite> LoadSpriteByKeyAsync(string key, int tileSize = 32,
float pixelsPerUnit = DefaultPixelsPerUnit, float timeout = 0f)
{
SpriteConfig config = global::GameEntry.Luban.Get<SpriteConfig>(key);
if (config == null)
{
Log.Error("Sprite config not found: {0}", key);
return default;
}
return LoadSpriteAsync(config, tileSize, pixelsPerUnit, timeout);
}
/// <summary>
/// 异步加载图集 Sprite
/// </summary>
/// <param name="atlasName">图集名称(相对 Assets/GameMain/Textures 的路径,含扩展名,如 32rogues/monsters.png)</param>
/// <param name="index">序号,-1 表示整张图集作为一张 Sprite</param>
/// <param name="tileSize">切分块尺寸(像素),默认 32</param>
/// <param name="pixelsPerUnit">Sprite 像素单位比,默认 32</param>
/// <param name="timeout">超时时间(秒),0 表示不超时</param>
/// <returns>加载的 Sprite,失败时返回 null</returns>
public static async UniTask<Sprite> 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<int, Sprite> sprites))
{
sprites = new Dictionary<int, Sprite>();
_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;
}
/// <summary>
/// 释放指定图集的所有 Sprite 和纹理引用
/// </summary>
public static void Release(string atlasName)
{
if (_spriteCache.TryGetValue(atlasName, out Dictionary<int, Sprite> 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);
}
}
/// <summary>
/// 释放所有图集的 Sprite 和纹理引用
/// </summary>
public static void ReleaseAll()
{
List<string> atlasNames = new List<string>(_textureCache.Keys);
foreach (string atlasName in atlasNames)
{
Release(atlasName);
}
}
private static async UniTask<Texture2D> GetTextureAsync(string atlasName, float timeout)
{
if (_textureCache.TryGetValue(atlasName, out Texture2D cached))
{
return cached;
}
if (_loadingTextures.TryGetValue(atlasName, out Task<Texture2D> loading))
{
return await loading;
}
Task<Texture2D> 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<Texture2D> LoadTextureAsync(string atlasName, float timeout)
{
try
{
return await global::GameEntry.Resource.LoadAssetAsync<Texture2D>(
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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7c896f5a3874033ab7cb99a06dbc0b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,23 +1,10 @@
using System;
using System.Collections.Generic;
using SepCore.Definition;
using UnityGameFramework.Runtime;
using SepCore.CustomUtility;
namespace SepCore.Entity
{
public static class EntityExtension
{
private static int s_SerialId = -10;
private static int _serialId = 0;
private const string EntityNamespace = "SepCore.Entity.";
private static readonly Dictionary<string, Type> _typeDict;
static EntityExtension()
{
_typeDict = new Dictionary<string, Type>();
}
public static EntityBase GetGameEntity(this EntityComponent entityComponent, int entityId)
{
@@ -41,116 +28,9 @@ namespace SepCore.Entity
entityComponent.AttachEntity(entityBase.Entity, ownerId, parentTransformPath, userData);
}
public static void ShowEntity<T>(this EntityComponent entityComponent, EntityDataBase data, string group,
int priority) where T : EntityBase
{
if (data == null)
{
Log.Warning("Data is invalid.");
return;
}
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
if (entityConfig == null)
{
Log.Warning("Can not load entity assetName '{0}' from data table.", data.AssetName);
return;
}
entityComponent.ShowEntity(data.Id, typeof(T), AssetUtility.GetEntityAsset(entityConfig.PrefabPath), group,
priority, data);
}
public static void ShowPlayer(this EntityComponent entityComponent, PlayerData data)
{
entityComponent.ShowEntity(typeof(Player), "Player", Constant.AssetPriority.PlayerAsset, data);
}
public static void ShowEnemy(this EntityComponent entityComponent, EnemyData data)
{
if (data == null)
{
Log.Warning("Enemy data is invalid.");
return;
}
var enemyType = TryGetType(data.EnemyType.ToString());
entityComponent.ShowEntity(enemyType, "Enemy", Constant.AssetPriority.EnemyAsset, data);
}
public static void ShowWeapon(this EntityComponent entityComponent, WeaponData data)
{
if (data == null)
{
Log.Warning("Weapon data is invalid.");
return;
}
var weaponType = TryGetType("Weapon." + data.WeaponType);
entityComponent.ShowEntity(weaponType, "Weapon", Constant.AssetPriority.WeaponAsset, data);
}
public static void ShowEffect(this EntityComponent entityComponent, EffectData data)
{
entityComponent.ShowEntity(typeof(Effect), "Effect", Constant.AssetPriority.EffectAsset, data);
}
public static void ShowCoin(this EntityComponent entityComponent, CoinData data)
{
entityComponent.ShowEntity(typeof(CoinEntity), "Drop", Constant.AssetPriority.EffectAsset, data);
}
public static void ShowExp(this EntityComponent entityComponent, ExpData data)
{
entityComponent.ShowEntity(typeof(ExpEntity), "Drop", Constant.AssetPriority.EffectAsset, data);
}
private static void ShowEntity(this EntityComponent entityComponent, Type logicType, string entityGroup,
int priority, EntityDataBase data)
{
if (data == null)
{
Log.Warning("Data is invalid.");
return;
}
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
if (entityConfig == null)
{
Log.Warning("Can not load entity assetName '{0}' from data table.", data.AssetName);
return;
}
entityComponent.ShowEntity(data.Id, logicType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), entityGroup,
priority, data);
}
private static Type TryGetType(string rawTypeName)
{
string typeName = EntityNamespace + rawTypeName;
if (!_typeDict.TryGetValue(typeName, out Type type))
{
type = Type.GetType(typeName);
if (type == null)
{
Log.Warning("Can not load entity type '{0}'.", typeName);
return null;
}
_typeDict.Add(typeName, type);
}
return type;
}
public static int NextId(this EntityComponent entityComponent)
{
return --s_SerialId;
}
public static int SerialId(this EntityComponent entityComponent)
{
return ++_serialId;
}
}
}