using System.Collections.Generic; using Ichni.Story.Dialogue; using TMPro; using UnityEngine; using UnityEngine.UI; namespace Ichni.Story.UI { /// /// 文本块视图。点击后通过 Yarn Spinner 进入对应节点的对话。 /// public class TextBlockView : StoryBlockView { [Header("Text Block")] public TMP_Text storyIdText; public TMP_Text titleText; [Tooltip("各状态对应的背景预制体。未配置 Forbidden 时会自动回退到 Locked 背景。")] public Dictionary blockVisuals = new Dictionary(); /// 该文本块对应的 Yarn 节点名。 public string YarnNodeName { get; private set; } // 当前已实例化的状态背景对象;状态切换时销毁并替换 private GameObject _currentVisual; public override void Initialize(StoryBlockDefinition def, Vector2 position, StoryBlockState blockState) { // 注意:base.Initialize 末尾会调用 ApplyState(state),届时已生成对应背景 YarnNodeName = def.yarnNodeName; base.Initialize(def, position, blockState); if (storyIdText != null) storyIdText.text = def.blockId; // 当前仍直接显示 titleKey;正式内容接入时需要由 Unity Localization 解析玩家可见标题。 if (titleText != null) titleText.text = def.titleKey; } /// /// 应用状态:在基类逻辑(按钮可交互性)之外,切换到对应状态的背景视觉。 /// public override void ApplyState(StoryBlockState newState) { base.ApplyState(newState); UpdateVisual(newState); } /// /// 按当前状态实例化对应的背景预制体,作为第一个子物体(位于文本/端口/按钮之下), /// 并拉伸铺满整个 block。状态切换时先销毁旧背景。 /// private void UpdateVisual(StoryBlockState newState) { if (_currentVisual != null) { // 旧背景中的 Button 即将被销毁,先主动解绑,防止基类继续持有失效引用。 BindInteractionButton(null); Destroy(_currentVisual); _currentVisual = null; } if (blockVisuals == null) { Debug.LogWarning($"[TextBlockView] block '{blockId}' 缺少状态 {newState} 的背景预制体(blockVisuals 未配置)。"); return; } // Forbidden 专用美术尚未配置前沿用 Locked 背景,保证互斥路线 Block 始终可见且不可点击。 // 后续只需在 Inspector 中补充 Forbidden 键,即可自动覆盖此回退表现。 if ((!blockVisuals.TryGetValue(newState, out GameObject prefab) || prefab == null) && newState == StoryBlockState.Forbidden) { blockVisuals.TryGetValue(StoryBlockState.Locked, out prefab); } if (prefab == null) { Debug.LogWarning($"[TextBlockView] block '{blockId}' 缺少状态 {newState} 的背景预制体。"); return; } _currentVisual = Instantiate(prefab, blockRect); _currentVisual.transform.SetAsFirstSibling(); // 状态视觉可自由调整层级;不再依赖“第一个子物体就是 Button”的脆弱约定。 // 通过基类重新绑定,确保每一次状态背景替换后 TextBlock 仍能响应点击。 Button interactionButton = _currentVisual.GetComponentInChildren