Files
zeling_v2/Assets/Scripts/Dialogue/DialogueUI.cs
2026-05-13 09:19:54 +08:00

117 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Text;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Localization;
namespace BaseGames.Dialogue
{
/// <summary>
/// 对话框 UI 组件(架构 14_NarrativeModule §5
/// 挂载在 Canvas_Overlay 下的 DialogueBox 子对象。
/// 负责打字机效果、头像显示、继续提示。
/// </summary>
public class DialogueUI : MonoBehaviour
{
[SerializeField] private GameObject _rootPanel;
[SerializeField] private TMP_Text _speakerNameText;
[SerializeField] private TMP_Text _dialogueText;
[SerializeField] private GameObject _speakerNamePanel; // 无名称时隐藏整个名称框
[SerializeField] private GameObject _continuePrompt; // "▼" 图标,打字完成后显示
[SerializeField] private Image _speakerPortrait; // 角色头像框
[SerializeField] private AudioSource _voiceSource; // 语音播放源(可不配置)
private Coroutine _typingCoroutine;
private DialogueLine _currentLine;
private const float DefaultTypewriterDelay = 0.03f;
/// <summary>当前是否仍在执行打字机效果。</summary>
public bool IsTyping { get; private set; }
// ── 公开 API ──────────────────────────────────────────────────────
/// <summary>显示一行对话并开始打字机效果。</summary>
public void ShowLine(DialogueLine line)
{
_currentLine = line;
_rootPanel.SetActive(true);
_continuePrompt.SetActive(false);
// 说话人名称
bool hasSpeaker = !string.IsNullOrEmpty(line.speakerNameKey);
if (_speakerNamePanel != null) _speakerNamePanel.SetActive(hasSpeaker);
if (hasSpeaker && _speakerNameText != null)
_speakerNameText.text = LocalizationManager.Get(line.speakerNameKey, "Dialogue");
// 头像
if (_speakerPortrait != null)
{
_speakerPortrait.gameObject.SetActive(line.portraitSprite != null);
if (line.portraitSprite != null) _speakerPortrait.sprite = line.portraitSprite;
}
// 语音播放
if (_voiceSource != null)
{
_voiceSource.Stop();
if (line.voiceClip != null)
{
_voiceSource.clip = line.voiceClip;
_voiceSource.Play();
}
}
// 开始打字机协程
if (_typingCoroutine != null) StopCoroutine(_typingCoroutine);
_typingCoroutine = StartCoroutine(TypeLine(line));
}
/// <summary>立即显示全部文字(跳过打字机效果)。</summary>
public void SkipTyping()
{
if (!IsTyping) return;
if (_typingCoroutine != null)
{
StopCoroutine(_typingCoroutine);
_typingCoroutine = null;
}
_voiceSource?.Stop();
if (_dialogueText != null)
_dialogueText.text = LocalizationManager.Get(_currentLine.textKey ?? "", "Dialogue");
IsTyping = false;
if (_continuePrompt != null) _continuePrompt.SetActive(true);
}
/// <summary>隐藏对话框面板。</summary>
public void Hide()
{
if (_rootPanel != null) _rootPanel.SetActive(false);
}
// ── 内部协程 ──────────────────────────────────────────────────────
private IEnumerator TypeLine(DialogueLine line)
{
IsTyping = true;
float delay = line.typewriterDelay > 0f ? line.typewriterDelay : DefaultTypewriterDelay;
string text = LocalizationManager.Get(line.textKey ?? "", "Dialogue");
// 性能StringBuilder 避免每帧字符串分配O(n²) → O(n)
var sb = new StringBuilder(text.Length);
if (_dialogueText != null) _dialogueText.text = "";
foreach (char c in text)
{
sb.Append(c);
if (_dialogueText != null) _dialogueText.SetText(sb); // TMP SetText(StringBuilder) 零分配
yield return new WaitForSecondsRealtime(delay);
}
IsTyping = false;
if (_continuePrompt != null) _continuePrompt.SetActive(true);
_typingCoroutine = null;
}
}
}