StorySystem

This commit is contained in:
SoulliesOfficial
2026-07-07 01:28:27 -04:00
parent d031afd075
commit 7b7d069b84
105 changed files with 4852 additions and 2734 deletions

View 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;
}
}
}
}