StorySystem
This commit is contained in:
175
Assets/Scripts/NewStorySystem/Dialogue/SpritePortraitRenderer.cs
Normal file
175
Assets/Scripts/NewStorySystem/Dialogue/SpritePortraitRenderer.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using DG.Tweening;
|
||||
|
||||
namespace Ichni.Story.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于 <see cref="UnityEngine.UI.Image"/> 的 Sprite 立绘渲染器。
|
||||
/// <para>
|
||||
/// 挂载在 VN 对话 UI 内的立绘槽位 GameObject 上(Left / Center / Right 等),
|
||||
/// 每个槽位对应一个 <see cref="SpritePortraitRenderer"/> 实例。
|
||||
/// 当前阶段仅实现静态显示(Show / Hide / SetEmotion / SetPosition / SetActiveVisual),
|
||||
/// 后续阶段再叠加 DOTween 动画。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Image))]
|
||||
public class SpritePortraitRenderer : MonoBehaviour, IPortraitRenderer
|
||||
{
|
||||
// ── 设置 ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>说话者的不透明度(满值)。</summary>
|
||||
[SerializeField] private float _alphaActive = 1.0f;
|
||||
|
||||
/// <summary>非说话者的压暗不透明度。</summary>
|
||||
[SerializeField] private float _alphaInactive = 0.7f;
|
||||
|
||||
// ── 组件缓存 ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Image _image;
|
||||
private RectTransform _rectTransform;
|
||||
|
||||
// ── 状态 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private CharacterData _currentCharacter;
|
||||
|
||||
// ── IPortraitRenderer 实现 ───────────────────────────────────────────────
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RectTransform RectTransform => _rectTransform;
|
||||
|
||||
// ── 生命周期 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_image = GetComponent<Image>();
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
|
||||
// 初始隐藏
|
||||
SetGroupAlpha(0f);
|
||||
}
|
||||
|
||||
// ── 接口方法 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Hide()
|
||||
{
|
||||
SetGroupAlpha(0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetEmotion(string emotion)
|
||||
{
|
||||
if (_currentCharacter == null)
|
||||
{
|
||||
Debug.LogWarning("[SpritePortraitRenderer] SetEmotion 时尚未绑定角色数据。");
|
||||
return;
|
||||
}
|
||||
|
||||
ApplySprite(emotion);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetPosition(Vector2 anchoredPos)
|
||||
{
|
||||
_rectTransform.anchoredPosition = anchoredPos;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetActiveVisual(bool isSpeaking)
|
||||
{
|
||||
SetGroupAlpha(isSpeaking ? _alphaActive : _alphaInactive);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MoveTo(Vector2 anchoredPos, float duration)
|
||||
{
|
||||
_rectTransform.DOKill(complete: true);
|
||||
_rectTransform.DOAnchorPos(anchoredPos, duration).SetEase(Ease.InOutQuad).Play();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Jump(float power, int numJumps, float duration)
|
||||
{
|
||||
// 对于跳跃,先停止并完成先前的动画,避免错乱(特别是被快进时)
|
||||
_rectTransform.DOKill(complete: true);
|
||||
|
||||
// UI 的 DOJumpAnchorPos 非常适合这种表现,项目中要求手动调用 Play()
|
||||
_rectTransform.DOJumpAnchorPos(_rectTransform.anchoredPosition, power, numJumps, duration).Play();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
}
|
||||
|
||||
// ── 内部辅助 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 从 <see cref="CharacterData.emotionSprites"/> 中查找并赋值 Sprite。
|
||||
/// 找不到指定情绪时回退到"normal",再找不到则使用字典中第一个条目,
|
||||
/// 并在 Console 输出警告。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user