using System.Collections; using System.Collections.Generic; using System.Text; using TMPro; using UnityEngine; using UnityEngine.UI; using BaseGames.Localization; namespace BaseGames.Dialogue { /// /// 对话框 UI 组件(架构 14_NarrativeModule §5)。 /// 挂载在 Canvas_Overlay 下的 DialogueBox 子对象。 /// 负责打字机效果、头像显示、继续提示。 /// 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 Image _speakerNameBackground; // 说话人名称框背景,用于应用 accentColor(可选) [SerializeField] private AudioSource _voiceSource; // 语音播放源(可不配置) [Header("选项系统(可选)")] [Tooltip("选项按钮的父节点容器。ShowChoices 通过对象池激活/停用按钮,HideChoices 停用全部。\n留空则分支选项功能静默禁用。")] [SerializeField] private Transform _choicesContainer; [Tooltip("选项按钮预制体(需包含 Button 组件和 TMP_Text 子组件)。\n首次使用时预热 PoolInitialSize 个到对象池,后续零 GC。")] [SerializeField] private GameObject _choiceButtonPrefab; [Tooltip("选项按钮池初始大小。设为预期最大选项数,默认 8 覆盖绝大多数情况。")] [SerializeField] [Range(2, 16)] private int _choicePoolSize = 8; // 说话人名称框背景的默认色(Awake 时记录,切换角色后可还原) private Color _defaultNameBgColor = Color.white; // 缓存名称框 RectTransform,避免 ShowLine 每次调用 GetComponent(零堆分配) private RectTransform _speakerNamePanelRT; // 选项按钮对象池:Awake 时按 _choicePoolSize 预热,ShowChoices/HideChoices 零 GC private readonly List<(GameObject go, Button btn, TMP_Text lbl)> _choicePool = new(); private Coroutine _typingCoroutine; private DialogueLine _currentLine; private const float DefaultTypewriterDelay = 0.03f; // 缓存 WaitForSecondsRealtime:delay 值不变时直接复用,避免每行 new 分配。 private WaitForSecondsRealtime _cachedTypeDelay; private float _cachedTypeDelayValue = -1f; // 缓存 StringBuilder:每行 Clear() 复用,避免每行 new StringBuilder(n) 的堆分配。 // 初始容量 256,足以容纳绝大多数对话行,超长时会自动扩容(扩容极少发生)。 private readonly StringBuilder _typingSB = new(256); /// 当前是否仍在执行打字机效果。 public bool IsTyping { get; private set; } private void Awake() { if (_speakerNameBackground != null) _defaultNameBgColor = _speakerNameBackground.color; if (_speakerNamePanel != null) _speakerNamePanelRT = _speakerNamePanel.GetComponent(); // 预热选项按钮对象池:在此时创建可避免首次对话时的 Instantiate 停顿 if (_choicesContainer != null && _choiceButtonPrefab != null) { for (int i = 0; i < _choicePoolSize; i++) { var go = Instantiate(_choiceButtonPrefab, _choicesContainer); var btn = go.GetComponent