169 lines
8.4 KiB
C#
169 lines
8.4 KiB
C#
using TMPro;
|
|
using UnityEngine;
|
|
|
|
namespace BrewMonster.Scripts.Chat.EmotionData
|
|
{
|
|
/// <summary>
|
|
/// Ánh xạ (emotionSet, emotionIndex) từ <see cref="EmotionLibrarySO"/> sang tag TMP.
|
|
/// Maps (emotionSet, emotionIndex) from EmotionLibrarySO to TMP rich-text tag.
|
|
///
|
|
/// TMP_SpriteAsset của mỗi bộ giờ nằm trực tiếp trong EmotionSetSnapshot.TmpSpriteAsset —
|
|
/// chỉ cần gán Library vào SO này là đủ.
|
|
/// Each set's TMP_SpriteAsset now lives directly in EmotionSetSnapshot.TmpSpriteAsset —
|
|
/// only the Library reference is needed here.
|
|
///
|
|
/// Cách dùng:
|
|
/// 1) Tạo EmotionLibrarySO bằng Emotion Atlas Converter.
|
|
/// 2) Tạo TMP_SpriteAsset cho từng atlas, gán vào EmotionSetSnapshot.TmpSpriteAsset trong Library.
|
|
/// 3) Kéo SO này vào field trên GameObject có CECUIManager (Awake gọi SetEmotionSpriteMap).
|
|
/// </summary>
|
|
[CreateAssetMenu(fileName = "EmotionLibrarySpriteMap", menuName = "Perfect World/Chat/Emotion Library Sprite Map", order = 2)]
|
|
public class EmotionLibrarySpriteMap : ScriptableObject, IEmotionSpriteMap
|
|
{
|
|
[Tooltip("Dữ liệu emotion đã convert — TmpSpriteAsset của từng bộ nằm trong EmotionSetSnapshot. " +
|
|
"Converted emotion data — each set's TmpSpriteAsset lives inside EmotionSetSnapshot.")]
|
|
public EmotionLibrarySO Library;
|
|
|
|
[Tooltip("Nếu true và emoji chỉ 1 frame: dùng <sprite name=\"…\"> (không cần tra index). " +
|
|
"Emoji động (nhiều frame) vẫn cần TmpSpriteAsset trong EmotionSetSnapshot.")]
|
|
public bool PreferSpriteNameTag = true;
|
|
|
|
[Tooltip("FPS mặc định cho <sprite anim> khi không suy ra được từ FrameTicks.")]
|
|
public int DefaultAnimFps = 10;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
// IEmotionSpriteMap
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
|
|
public bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info)
|
|
{
|
|
info = default;
|
|
|
|
if (Library == null)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: Library == null. Gán EmotionLibrarySO vào field Library.");
|
|
return false;
|
|
}
|
|
|
|
EmotionSetSnapshot set = Library.GetSetOrNull(emotionSet);
|
|
if (set == null)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: Không tìm thấy set index={emotionSet}. " +
|
|
$"Sets hiện có: [{string.Join(", ", Library.Sets?.ConvertAll(s => s?.EmotionSetIndex.ToString() ?? "null") ?? new System.Collections.Generic.List<string>())}]");
|
|
return false;
|
|
}
|
|
|
|
if (emotionIndex < 0 || emotionIndex >= set.Entries.Count)
|
|
{
|
|
Debug.LogWarning($"[EmotionLibrarySpriteMap] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: emotionIndex={emotionIndex} ngoài range [0, {set.Entries.Count - 1}].");
|
|
return false;
|
|
}
|
|
|
|
EmotionEntryData entry = set.Entries[emotionIndex];
|
|
Sprite[] frames = entry.FrameSprites;
|
|
if (frames == null || frames.Length == 0)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: entry.FrameSprites null/empty. Chạy lại Emotion Atlas Converter để populate FrameSprites.");
|
|
return false;
|
|
}
|
|
|
|
if (frames.Length == 1)
|
|
{
|
|
Sprite s0 = frames[0];
|
|
if (s0 == null)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: frames[0] == null.");
|
|
return false;
|
|
}
|
|
|
|
if (PreferSpriteNameTag && !string.IsNullOrEmpty(s0.name))
|
|
{
|
|
info = new EmotionSpriteInfo { UseSpriteName = true, SpriteName = s0.name, IsAnimated = false };
|
|
return true;
|
|
}
|
|
|
|
// PreferSpriteNameTag=false → tra index trong TmpSpriteAsset của set
|
|
int idx = FindSpriteIndex(set.TmpSpriteAsset, s0.name);
|
|
if (idx < 0)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: không tìm thấy sprite \"{s0.name}\" trong TMP asset " +
|
|
(set.TmpSpriteAsset == null ? "(null — gán TmpSpriteAsset vào EmotionSetSnapshot trong Library)." : $"\"{set.TmpSpriteAsset.name}\"."));
|
|
return false;
|
|
}
|
|
|
|
info = new EmotionSpriteInfo { SpriteIndex = idx, IsAnimated = false };
|
|
return true;
|
|
}
|
|
|
|
// Multi-frame (animated) — bắt buộc phải có TmpSpriteAsset trong set để tra index
|
|
TMP_SpriteAsset animAsset = set.TmpSpriteAsset;
|
|
if (animAsset == null)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: EmotionSetSnapshot[{emotionSet}].TmpSpriteAsset == null " +
|
|
$"(emoji động {frames.Length} frames cần TmpSpriteAsset để tra index). Gán TmpSpriteAsset vào EmotionSetSnapshot trong Library.");
|
|
return false;
|
|
}
|
|
|
|
int start = FindSpriteIndex(animAsset, frames[0]?.name);
|
|
int end = FindSpriteIndex(animAsset, frames[frames.Length - 1]?.name);
|
|
if (start < 0 || end < 0)
|
|
{
|
|
Debug.LogWarning($"[Cuong] TryGetSprite({emotionSet},{emotionIndex}) → FAIL: frame không tìm thấy trong \"{animAsset.name}\". " +
|
|
$"start=\"{frames[0]?.name}\"→{start}, end=\"{frames[frames.Length - 1]?.name}\"→{end}.");
|
|
return false;
|
|
}
|
|
|
|
int fps = EstimateFps(entry.FrameTicks, frames.Length);
|
|
info = new EmotionSpriteInfo
|
|
{
|
|
SpriteIndex = start,
|
|
IsAnimated = true,
|
|
AnimEndFrame = end,
|
|
AnimFPS = fps > 0 ? fps : DefaultAnimFps
|
|
};
|
|
return true;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
// Helpers
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Tra tên sprite trong spriteCharacterTable của asset, trả về index hoặc -1.
|
|
/// Look up sprite name in the asset's spriteCharacterTable; returns index or -1.
|
|
/// </summary>
|
|
private static int FindSpriteIndex(TMP_SpriteAsset asset, string spriteName)
|
|
{
|
|
if (asset == null || string.IsNullOrEmpty(spriteName))
|
|
return -1;
|
|
|
|
// TMP 3.x: dùng spriteCharacterTable (spriteInfoList đã deprecated)
|
|
// TMP 3.x: use spriteCharacterTable (spriteInfoList is deprecated)
|
|
var charTable = asset.spriteCharacterTable;
|
|
if (charTable == null)
|
|
{
|
|
BMLogger.LogError("[Cuong] charTable == null");
|
|
return -1;
|
|
}
|
|
|
|
BMLogger.Log($"[Cuong] {spriteName}");
|
|
for (int i = 0; i < charTable.Count; i++)
|
|
{
|
|
if (charTable[i] != null && charTable[i].name == spriteName)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private static int EstimateFps(int[] frameTicks, int numFrames)
|
|
{
|
|
if (frameTicks == null || numFrames < 2 || frameTicks.Length < numFrames)
|
|
return 0;
|
|
int span = frameTicks[numFrames - 1] - frameTicks[0];
|
|
if (span <= 0)
|
|
return 0;
|
|
return Mathf.Max(1, Mathf.RoundToInt((numFrames - 1) * 60f / span));
|
|
}
|
|
}
|
|
}
|