Files
test/Assets/PerfectWorld/Scripts/Chat/EmotionData/EmotionLibrarySpriteMap.cs
T
2026-04-06 13:47:47 +07:00

124 lines
4.8 KiB
C#

using System;
using TMPro;
using UnityEngine;
namespace BrewMonster.Scripts.Chat.EmotionData
{
/// <summary>
/// Ánh xạ (emotionSet, emotionIndex) từ <see cref="EmotionLibrarySO"/> sang tag TMP.
/// Gán asset này vào <see cref="CECUIManager"/> (field emotion) — <see cref="BrewMonster.UI.CECGameUIMan"/> không phải MonoBehaviour nên không kéo SO trên Inspector được.
///
/// Cách dùng:
/// 1) Tạo <see cref="EmotionLibrarySO"/> bằng Emotion Atlas Converter.
/// 2) Tạo <see cref="TMP_SpriteAsset"/> từ cùng atlas (Window → TextMeshPro → Sprite Importer),
/// đảm bảo tên sub-sprite khớp (vd. cell_0000) với atlas Multiple từ tool.
/// 3) Gán TMP_SpriteAsset vào ô chat <see cref="TMPro.TextMeshProUGUI"/> (Sprite Asset / Additional).
/// 4) Kéo SO này vào field trên GameObject có <see cref="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 (Entries + FrameSprites).")]
public EmotionLibrarySO Library;
[Tooltip("Sprite Asset dùng cho chat TMP — cùng tên sub-sprite với atlas (cell_XXXX). Bắt buộc cho emoji động (anim) hoặc khi không dùng name tag.")]
public TMP_SpriteAsset TmpSpriteAsset;
[Tooltip("Nếu true và chỉ 1 frame: thử <sprite name=\"...\"> (không cần tra index). Động (nhiều frame) vẫn cần TmpSpriteAsset để tra index.")]
public bool PreferSpriteNameTag = true;
[Tooltip("FPS mặc định cho <sprite anim> khi không suy ra được từ FrameTicks.")]
public int DefaultAnimFps = 10;
public bool TryGetSprite(int emotionSet, int emotionIndex, out EmotionSpriteInfo info)
{
info = default;
if (Library == null)
return false;
EmotionSetSnapshot set = Library.GetSetOrNull(emotionSet);
if (set == null || emotionIndex < 0 || emotionIndex >= set.Entries.Count)
return false;
EmotionEntryData entry = set.Entries[emotionIndex];
Sprite[] frames = entry.FrameSprites;
if (frames == null || frames.Length == 0)
return false;
if (frames.Length == 1)
{
Sprite s0 = frames[0];
if (s0 == null)
return false;
if (PreferSpriteNameTag && !string.IsNullOrEmpty(s0.name))
{
info = new EmotionSpriteInfo
{
UseSpriteName = true,
SpriteName = s0.name,
IsAnimated = false
};
return true;
}
int idx = FindSpriteIndexInTmpAsset(s0.name);
if (idx < 0)
return false;
info = new EmotionSpriteInfo { SpriteIndex = idx, IsAnimated = false };
return true;
}
if (TmpSpriteAsset == null)
return false;
int start = FindSpriteIndexInTmpAsset(frames[0]?.name);
int end = FindSpriteIndexInTmpAsset(frames[frames.Length - 1]?.name);
if (start < 0 || end < 0)
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;
}
private int FindSpriteIndexInTmpAsset(string spriteName)
{
if (TmpSpriteAsset == null || string.IsNullOrEmpty(spriteName))
return -1;
if (TmpSpriteAsset.spriteInfoList != null)
{
for (int i = 0; i < TmpSpriteAsset.spriteInfoList.Count; i++)
{
var s = TmpSpriteAsset.spriteInfoList[i];
if (s != null && s.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 last = frameTicks[numFrames - 1];
int first = frameTicks[0];
int span = last - first;
if (span <= 0)
return 0;
return Mathf.Max(1, Mathf.RoundToInt((numFrames - 1) * 60f / span));
}
}
}