using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
namespace Ichni.Story.Dialogue
{
///
/// 基于 的 Sprite 立绘渲染器。
///
/// 挂载在 VN 对话 UI 内的立绘槽位 GameObject 上(Left / Center / Right 等),
/// 每个槽位对应一个 实例。
/// 当前阶段仅实现静态显示(Show / Hide / SetEmotion / SetPosition / SetActiveVisual),
/// 后续阶段再叠加 DOTween 动画。
///
///
[RequireComponent(typeof(Image))]
public class SpritePortraitRenderer : MonoBehaviour, IPortraitRenderer
{
// ── 设置 ────────────────────────────────────────────────────────────────
/// 说话者的不透明度(满值)。
[SerializeField] private float _alphaActive = 1.0f;
/// 非说话者的压暗不透明度。
[SerializeField] private float _alphaInactive = 0.7f;
// ── 组件缓存 ─────────────────────────────────────────────────────────────
private Image _image;
private RectTransform _rectTransform;
// ── 状态 ─────────────────────────────────────────────────────────────────
private CharacterData _currentCharacter;
// ── IPortraitRenderer 实现 ───────────────────────────────────────────────
///
public RectTransform RectTransform => _rectTransform;
// ── 生命周期 ──────────────────────────────────────────────────────────────
private void Awake()
{
_image = GetComponent();
_rectTransform = GetComponent();
// 初始隐藏
SetGroupAlpha(0f);
}
// ── 接口方法 ─────────────────────────────────────────────────────────────
///
public void Show(CharacterData character, string emotion)
{
if (character == null)
{
Debug.LogWarning("[SpritePortraitRenderer] Show 传入了 null CharacterData。");
return;
}
_currentCharacter = character;
ApplySprite(emotion);
SetGroupAlpha(_alphaActive);
gameObject.SetActive(true);
}
///
public void Hide()
{
SetGroupAlpha(0f);
}
///
public void SetEmotion(string emotion)
{
if (_currentCharacter == null)
{
Debug.LogWarning("[SpritePortraitRenderer] SetEmotion 时尚未绑定角色数据。");
return;
}
ApplySprite(emotion);
}
///
public void SetPosition(Vector2 anchoredPos)
{
_rectTransform.anchoredPosition = anchoredPos;
}
///
public void SetActiveVisual(bool isSpeaking)
{
SetGroupAlpha(isSpeaking ? _alphaActive : _alphaInactive);
}
///
public void MoveTo(Vector2 anchoredPos, float duration)
{
_rectTransform.DOKill(complete: true);
_rectTransform.DOAnchorPos(anchoredPos, duration).SetEase(Ease.InOutQuad).Play();
}
///
public void Jump(float power, int numJumps, float duration)
{
// 对于跳跃,先停止并完成先前的动画,避免错乱(特别是被快进时)
_rectTransform.DOKill(complete: true);
// UI 的 DOJumpAnchorPos 非常适合这种表现,项目中要求手动调用 Play()
_rectTransform.DOJumpAnchorPos(_rectTransform.anchoredPosition, power, numJumps, duration).Play();
}
///
public void Shake(float duration, float strength, int vibrato)
{
_rectTransform.DOKill(complete: true);
// 左右震动(限制在 X 轴或以特定强度震动),项目中要求手动调用 Play()
_rectTransform.DOShakeAnchorPos(duration, new Vector2(strength, 0f), vibrato, 90, false, true).Play();
}
// ── 内部辅助 ─────────────────────────────────────────────────────────────
///
/// 从 中查找并赋值 Sprite。
/// 找不到指定情绪时回退到"normal",再找不到则使用字典中第一个条目,
/// 并在 Console 输出警告。
///
private void ApplySprite(string emotion)
{
if (_currentCharacter.emotionSprites == null || _currentCharacter.emotionSprites.Count == 0)
{
Debug.LogWarning($"[SpritePortraitRenderer] 角色 '{_currentCharacter.characterId}' 没有配置任何情绪 Sprite。");
return;
}
// 1. 精确匹配
if (!string.IsNullOrEmpty(emotion) &&
_currentCharacter.emotionSprites.TryGetValue(emotion, out Sprite target))
{
_image.sprite = target;
return;
}
// 2. 回退 normal
if (_currentCharacter.emotionSprites.TryGetValue("normal", out Sprite fallback))
{
Debug.LogWarning($"[SpritePortraitRenderer] 角色 '{_currentCharacter.characterId}' 没有情绪 '{emotion}',回退到 'normal'。");
_image.sprite = fallback;
return;
}
// 3. 使用第一个条目
foreach (Sprite first in _currentCharacter.emotionSprites.Values)
{
Debug.LogWarning($"[SpritePortraitRenderer] 角色 '{_currentCharacter.characterId}' 也没有 'normal',使用第一张 Sprite。");
_image.sprite = first;
return;
}
}
private void SetGroupAlpha(float alpha)
{
if (_image != null)
{
Color c = _image.color;
c.a = alpha;
_image.color = c;
}
}
}
}