117 lines
4.6 KiB
C#
117 lines
4.6 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|