添加图片素材读取流程

This commit is contained in:
huangjun
2026-08-31 10:22:59 +08:00
parent 816335b894
commit fce331da84
42 changed files with 19608 additions and 4003 deletions
@@ -18,6 +18,7 @@ namespace SepCore.Definition
public const int DataTableAsset = 100;
public const int DictionaryAsset = 100;
public const int FontAsset = 50;
public const int SpriteAsset = 40;
public const int MusicAsset = 20;
public const int SceneAsset = 0;
public const int SoundAsset = 30;
@@ -28,6 +28,8 @@ public sealed partial class CharacterConfig : Luban.BeanBase
SkillActionId = _buf.ReadInt();
WeaponItemId = _buf.ReadInt();
ArmorItemId = _buf.ReadInt();
Icon = _buf.ReadString();
Icon_Ref = null;
}
public static CharacterConfig DeserializeCharacterConfig(ByteBuf _buf)
@@ -83,6 +85,11 @@ public sealed partial class CharacterConfig : Luban.BeanBase
/// 初始防具ID,0为空
/// </summary>
public readonly int ArmorItemId;
/// <summary>
/// 图标资源配置
/// </summary>
public readonly string Icon;
public SpriteConfig Icon_Ref;
public const int __ID__ = 676994987;
public override int GetTypeId() => __ID__;
@@ -101,6 +108,7 @@ public sealed partial class CharacterConfig : Luban.BeanBase
Icon_Ref = tables.TbSpriteConfig.GetOrDefault(Icon);
}
public override string ToString()
@@ -118,6 +126,7 @@ public sealed partial class CharacterConfig : Luban.BeanBase
+ "skillActionId:" + SkillActionId + ","
+ "weaponItemId:" + WeaponItemId + ","
+ "armorItemId:" + ArmorItemId + ","
+ "icon:" + Icon + ","
+ "}";
}
}
@@ -27,6 +27,8 @@ public sealed partial class ItemConfig : Luban.BeanBase
AtkBonus = _buf.ReadInt();
MatBonus = _buf.ReadInt();
SpeedBonus = _buf.ReadInt();
Icon = _buf.ReadString();
Icon_Ref = null;
}
public static ItemConfig DeserializeItemConfig(ByteBuf _buf)
@@ -78,6 +80,11 @@ public sealed partial class ItemConfig : Luban.BeanBase
/// SPD修正
/// </summary>
public readonly int SpeedBonus;
/// <summary>
/// 图标资源配置
/// </summary>
public readonly string Icon;
public SpriteConfig Icon_Ref;
public const int __ID__ = -764023723;
public override int GetTypeId() => __ID__;
@@ -95,6 +102,7 @@ public sealed partial class ItemConfig : Luban.BeanBase
Icon_Ref = tables.TbSpriteConfig.GetOrDefault(Icon);
}
public override string ToString()
@@ -111,6 +119,7 @@ public sealed partial class ItemConfig : Luban.BeanBase
+ "atkBonus:" + AtkBonus + ","
+ "matBonus:" + MatBonus + ","
+ "speedBonus:" + SpeedBonus + ","
+ "icon:" + Icon + ","
+ "}";
}
}
@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using Luban;
namespace SepCore.Definition
{
public sealed partial class SpriteConfig : Luban.BeanBase
{
public SpriteConfig(ByteBuf _buf)
{
Key = _buf.ReadString();
AtlasPath = _buf.ReadString();
SpriteIndex = _buf.ReadInt();
}
public static SpriteConfig DeserializeSpriteConfig(ByteBuf _buf)
{
return new SpriteConfig(_buf);
}
/// <summary>
/// 资源键
/// </summary>
public readonly string Key;
/// <summary>
/// 图集路径(相对于 Assets/GameMain/Textures)
/// </summary>
public readonly string AtlasPath;
/// <summary>
/// Sprite 索引(-1 表示整张图)
/// </summary>
public readonly int SpriteIndex;
public const int __ID__ = -1527849337;
public override int GetTypeId() => __ID__;
public void ResolveRef(Tables tables)
{
}
public override string ToString()
{
return "{ "
+ "key:" + Key + ","
+ "atlasPath:" + AtlasPath + ","
+ "spriteIndex:" + SpriteIndex + ","
+ "}";
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3c1026f4da3a46e4af39d3feb237024
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -82,6 +82,10 @@ public partial class Tables
/// 界面音效配置
/// </summary>
public TbUISoundConfig TbUISoundConfig {get; }
/// <summary>
/// 图片资源配置
/// </summary>
public TbSpriteConfig TbSpriteConfig {get; }
public Tables(System.Func<string, ByteBuf> loader)
{
@@ -103,6 +107,7 @@ public partial class Tables
TbSoundConfig = new TbSoundConfig(loader("tbsoundconfig"));
TbUIFormConfig = new TbUIFormConfig(loader("tbuiformconfig"));
TbUISoundConfig = new TbUISoundConfig(loader("tbuisoundconfig"));
TbSpriteConfig = new TbSpriteConfig(loader("tbspriteconfig"));
ResolveRef();
}
@@ -126,6 +131,7 @@ public partial class Tables
TbSoundConfig.ResolveRef(this);
TbUIFormConfig.ResolveRef(this);
TbUISoundConfig.ResolveRef(this);
TbSpriteConfig.ResolveRef(this);
}
}
@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using Luban;
namespace SepCore.Definition
{
/// <summary>
/// 图片资源配置
/// </summary>
public partial class TbSpriteConfig
{
private readonly System.Collections.Generic.Dictionary<string, SpriteConfig> _dataMap;
private readonly System.Collections.Generic.List<SpriteConfig> _dataList;
public TbSpriteConfig(ByteBuf _buf)
{
_dataMap = new System.Collections.Generic.Dictionary<string, SpriteConfig>();
_dataList = new System.Collections.Generic.List<SpriteConfig>();
for(int n = _buf.ReadSize() ; n > 0 ; --n)
{
SpriteConfig _v;
_v = SpriteConfig.DeserializeSpriteConfig(_buf);
_dataList.Add(_v);
_dataMap.Add(_v.Key, _v);
}
}
public System.Collections.Generic.Dictionary<string, SpriteConfig> DataMap => _dataMap;
public System.Collections.Generic.List<SpriteConfig> DataList => _dataList;
public SpriteConfig GetOrDefault(string key) => _dataMap.TryGetValue(key, out var v) ? v : null;
public SpriteConfig Get(string key) => _dataMap[key];
public SpriteConfig this[string key] => _dataMap[key];
public void ResolveRef(Tables tables)
{
foreach(var _v in _dataList)
{
_v.ResolveRef(tables);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96ef8dd7c7cf44d7b8a3b64f80723105
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -61,5 +61,13 @@ namespace SepCore.Utility
{
return GameFramework.Utility.Text.Format("Assets/GameMain/UI/UISounds/{0}.wav", assetName);
}
/// <summary>
/// 获取图片资源路径,assetName 为相对 Assets/GameMain/Textures 的路径,需包含扩展名
/// </summary>
public static string GetSpriteAsset(string assetName)
{
return GameFramework.Utility.Text.Format("Assets/GameMain/Textures/{0}", assetName);
}
}
}
@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using SepCore.Definition;
using SepCore.Utility;
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
/// </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);
}
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;
return Sprite.Create(texture, new Rect(x, y, tileSize, tileSize), new Vector2(0.5f, 0.5f), pixelsPerUnit);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b27858426f6b44b718b0279bdff6bc1e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -35,12 +35,14 @@ namespace SepCore.CustomComponent
"tbsoundconfig",
"tbuiformconfig",
"tbuisoundconfig",
"tbformattext"
"tbformattext",
"tbspriteconfig"
};
private sealed class TableAccessor
{
public Func<Tables, int, object> RowGetter;
public Func<Tables, string, object> StringRowGetter;
public Func<Tables, object> ListGetter;
}
@@ -127,6 +129,11 @@ namespace SepCore.CustomComponent
Accessor(tables => id => tables.TbUISoundConfig.GetOrDefault(id),
tables => tables.TbUISoundConfig.DataList)
},
{
typeof(SpriteConfig),
StringAccessor(tables => key => tables.TbSpriteConfig.GetOrDefault(key),
tables => tables.TbSpriteConfig.DataList)
},
};
private static TableAccessor Accessor<TValue>(Func<Tables, Func<int, object>> rowGetterFactory,
@@ -139,6 +146,16 @@ namespace SepCore.CustomComponent
};
}
private static TableAccessor StringAccessor<TValue>(Func<Tables, Func<string, object>> rowGetterFactory,
Func<Tables, List<TValue>> listGetter) where TValue : BeanBase
{
return new TableAccessor
{
StringRowGetter = (tables, key) => rowGetterFactory(tables)(key),
ListGetter = tables => listGetter(tables)
};
}
private Dictionary<string, byte[]> _tableBytes = null;
private int _remainCount = 0;
private Action _onLoadSuccess = null;
@@ -178,6 +195,33 @@ namespace SepCore.CustomComponent
return (T)accessor.RowGetter(Tables, id);
}
/// <summary>
/// 按字符串主键获取数据行,未找到时返回 null。
/// </summary>
/// <typeparam name="T">数据行类型(对应生成的 xxxConfig 类)。</typeparam>
/// <param name="key">数据行字符串主键。</param>
/// <returns>数据行,未找到时返回 null。</returns>
public T Get<T>(string key) where T : class
{
if (Tables == null)
{
throw new InvalidOperationException("Luban data tables are not loaded yet.");
}
TableAccessor accessor;
if (!TableAccessors.TryGetValue(typeof(T), out accessor))
{
throw new NotSupportedException("No table getter is registered for type '" + typeof(T).FullName + "'.");
}
if (accessor.StringRowGetter == null)
{
throw new NotSupportedException("Table for type '" + typeof(T).FullName + "' does not use a string key.");
}
return (T)accessor.StringRowGetter(Tables, key);
}
/// <summary>
/// 获取整个表的所有数据行。
/// </summary>
@@ -1,3 +1,5 @@
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Definition;
using TMPro;
using UnityEngine;
@@ -17,6 +19,8 @@ namespace SepCore.UI
[SerializeField] private FormatTextUI atkText;
[SerializeField] private FormatTextUI matText;
private int _iconVersion = 0;
/// <summary>
/// 用存档角色填充格子;角色配置不存在时按空格子显示。
/// </summary>
@@ -36,7 +40,8 @@ namespace SepCore.UI
speedText.Set(config.Speed);
atkText.Set(config.Atk);
matText.Set(config.Mat);
HideIcon();
_iconVersion++;
ShowIconAsync(config.Icon_Ref, _iconVersion).Forget();
}
/// <summary>
@@ -44,6 +49,7 @@ namespace SepCore.UI
/// </summary>
public void SetEmpty()
{
_iconVersion++;
characterName.text = string.Empty;
hpText.Clear();
mpText.Clear();
@@ -63,5 +69,25 @@ namespace SepCore.UI
icon.sprite = null;
icon.gameObject.SetActive(false);
}
/// <summary>
/// 异步加载角色图标;iconVersion 用于防止复用格子时旧加载结果覆盖新内容。
/// </summary>
private async UniTaskVoid ShowIconAsync(SpriteConfig iconConfig, int iconVersion)
{
if (iconConfig == null || icon == null)
{
return;
}
Sprite sprite = await SpriteLoader.LoadSpriteAsync(iconConfig);
if (sprite == null || _iconVersion != iconVersion)
{
return;
}
icon.sprite = sprite;
icon.gameObject.SetActive(true);
}
}
}
@@ -1,3 +1,5 @@
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Base;
using SepCore.Definition;
using TMPro;
@@ -26,6 +28,7 @@ namespace SepCore.UI
private int _slotId = 0;
private bool _filled = false;
private bool _clickBound = false;
private int _iconVersion = 0;
/// <summary>
/// 设置格子在固定网格中的索引,点击事件以此作为唯一标识。
@@ -49,9 +52,10 @@ namespace SepCore.UI
}
_filled = true;
_iconVersion++;
rarity.color = GetRarityColor(config.Rarity);
quantityText.text = stack.count.ToString();
HideIcon();
ShowIconAsync(config.Icon_Ref, _iconVersion).Forget();
BindClick();
if (button != null)
{
@@ -65,6 +69,7 @@ namespace SepCore.UI
public void SetEmpty()
{
_filled = false;
_iconVersion++;
rarity.color = RarityEmpty;
quantityText.text = string.Empty;
HideIcon();
@@ -117,6 +122,26 @@ namespace SepCore.UI
icon.gameObject.SetActive(false);
}
/// <summary>
/// 异步加载物品图标;iconVersion 用于防止复用格子时旧加载结果覆盖新内容。
/// </summary>
private async UniTaskVoid ShowIconAsync(SpriteConfig iconConfig, int iconVersion)
{
if (iconConfig == null || icon == null)
{
return;
}
Sprite sprite = await SpriteLoader.LoadSpriteAsync(iconConfig);
if (sprite == null || _iconVersion != iconVersion)
{
return;
}
icon.sprite = sprite;
icon.gameObject.SetActive(true);
}
private static Color GetRarityColor(Rarity rarity)
{
switch (rarity)
@@ -1,3 +1,5 @@
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
@@ -10,11 +12,14 @@ namespace SepCore.UI
/// </summary>
public partial class ItemDetailsForm : UGuiForm
{
private int _iconVersion = 0;
/// <summary>
/// 按物品 ID 刷新详情界面;配置不存在时清空显示。
/// </summary>
public void Refresh(int itemId)
{
_iconVersion++;
ItemDetailsView view = View;
if (view == null)
{
@@ -79,6 +84,8 @@ namespace SepCore.UI
{
view.itemDetailIcon.gameObject.SetActive(false);
}
ShowIconAsync(config.Icon_Ref, _iconVersion).Forget();
}
private static void ClearDetails(ItemDetailsView view)
@@ -97,6 +104,31 @@ namespace SepCore.UI
{
view.itemDetailDescriptionText.text = string.Empty;
}
if (view.itemDetailIcon != null)
{
view.itemDetailIcon.gameObject.SetActive(false);
}
}
/// <summary>
/// 异步加载物品图标;iconVersion 用于防止连续刷新时旧加载结果覆盖新内容。
/// </summary>
private async UniTaskVoid ShowIconAsync(SpriteConfig iconConfig, int iconVersion)
{
if (iconConfig == null || View == null || View.itemDetailIcon == null)
{
return;
}
Sprite sprite = await SpriteLoader.LoadSpriteAsync(iconConfig);
if (sprite == null || _iconVersion != iconVersion)
{
return;
}
View.itemDetailIcon.sprite = sprite;
View.itemDetailIcon.gameObject.SetActive(true);
}
private static string GetItemTypeName(ItemType itemType)