using System; using Cielonos.MainGame.Map; using TMPro; using UnityEngine; using UnityEngine.UI; namespace Cielonos.MainGame.UI { /// /// 地图上的单个节点 UI 元素。 /// 由 MapUIPage 动态实例化,定位在 RunMapNode.position 对应的 UI 坐标上。 /// 通过 ApplyDisplayState 控制可见性、交互性和边框颜色。 /// public class MapNodeElement : MonoBehaviour { // ================================================================ // 边框颜色常量 // ================================================================ /// Outline 偏移量,控制边框粗细。 private static readonly Vector2 OUTLINE_DISTANCE = new Vector2(3f, 3f); /// 已访问节点的边框颜色(白色)。 private static readonly Color OUTLINE_COLOR_VISITED = Color.white; /// 可前往但未访问节点的边框颜色(绿色)。 private static readonly Color OUTLINE_COLOR_SELECTABLE = new Color(0.2f, 0.9f, 0.2f, 1f); /// 不可前往节点的边框颜色(红色)。 private static readonly Color OUTLINE_COLOR_LOCKED = new Color(0.9f, 0.2f, 0.2f, 1f); /// 当前所在节点的边框颜色(金黄色)。 private static readonly Color OUTLINE_COLOR_CURRENT = new Color(1f, 0.9f, 0.3f, 1f); // ================================================================ // 序列化字段 // ================================================================ [Header("References")] [SerializeField] private Image iconImage; [SerializeField] private Image backgroundImage; [SerializeField] private TMP_Text labelText; [SerializeField] private Button button; // ================================================================ // 公共属性 // ================================================================ /// 关联的网格坐标。 public Vector2Int GridPosition { get; private set; } /// 关联的节点类型。 public MapNodeType NodeType { get; private set; } /// 当前显示状态。 public NodeDisplayState DisplayState { get; private set; } /// 节点被玩家点击时触发,参数为该节点的网格坐标。 public event Action OnClicked; // ================================================================ // 运行时字段 // ================================================================ /// 运行时自动创建的 Outline 组件,用于显示交互状态边框。 private Outline _outline; // ================================================================ // 初始化 // ================================================================ /// /// 初始化节点元素,设置网格坐标、类型、UI 位置,并绑定点击事件。 /// /// 对应的 RunMapNode 数据。 public void Setup(RunMapNode node) { GridPosition = node.gridPosition; NodeType = node.nodeType; // 将节点定位到 UI 坐标 RectTransform rt = GetComponent(); if (rt != null) { rt.anchoredPosition = node.position; } // 设置标签文本为节点类型简称 if (labelText != null) { labelText.text = GetNodeLabel(node.nodeType); } // 设置背景颜色 if (backgroundImage != null) { backgroundImage.color = GetNodeColor(node.nodeType); } // 确保 Outline 组件存在 EnsureOutline(); // 绑定按钮点击 → 触发 OnClicked 事件 if (button != null) { button.onClick.AddListener(HandleClick); } } // ================================================================ // 显示状态 // ================================================================ /// /// 更新节点的完整显示状态(可见性、交互性、使用状态、边框颜色)。 /// 由 MapUIPage.RefreshDisplayStates 批量调用。 /// public void ApplyDisplayState(NodeDisplayState state) { DisplayState = state; // --- 可见性 --- // 所有节点始终可见:Hidden 不再出现,但保留防御性处理 switch (state.visibility) { case MapNodeVisibility.Hidden: case MapNodeVisibility.Silhouette: gameObject.SetActive(true); if (iconImage != null) iconImage.enabled = false; if (labelText != null) labelText.text = "?"; if (backgroundImage != null) backgroundImage.color = new Color(0.4f, 0.4f, 0.4f, 0.6f); break; case MapNodeVisibility.Revealed: gameObject.SetActive(true); if (iconImage != null) iconImage.enabled = true; if (labelText != null) labelText.text = GetNodeLabel(NodeType); if (backgroundImage != null) backgroundImage.color = GetNodeColor(NodeType); break; } // --- 交互性(按钮是否可点击) --- if (button != null) { button.interactable = state.interaction == MapNodeInteraction.Selectable; } // --- 使用状态的视觉反馈(已访问/已用完的节点背景变暗) --- ApplyUsageVisual(state.usage, state.interaction); // --- 边框颜色(白/绿/红/金) --- ApplyOutlineVisual(state); } // ================================================================ // Outline 边框 // ================================================================ /// /// 确保 backgroundImage 上存在 Outline 组件。 /// 首次调用时自动添加。 /// private void EnsureOutline() { if (_outline != null) return; if (backgroundImage == null) return; _outline = backgroundImage.GetComponent(); if (_outline == null) { _outline = backgroundImage.gameObject.AddComponent(); } _outline.effectDistance = OUTLINE_DISTANCE; } /// /// 根据节点的显示状态设置 Outline 边框颜色。 /// 优先级从高到低: /// 1. 当前所在节点 → 金黄色 /// 2. 已访问/已用完 → 白色 /// 3. 可前往但未访问 → 绿色 /// 4. 不可前往 → 红色 /// Hidden 节点已被 SetActive(false),不在此处理。 /// private void ApplyOutlineVisual(NodeDisplayState state) { EnsureOutline(); if (_outline == null) return; _outline.enabled = true; // 优先级 1:当前所在节点 → 金黄色边框 if (state.interaction == MapNodeInteraction.Current) { _outline.effectColor = OUTLINE_COLOR_CURRENT; return; } // 优先级 2:已访问或已用完 → 白色边框 if (state.usage == MapNodeUsage.Visited || state.usage == MapNodeUsage.Exhausted) { _outline.effectColor = OUTLINE_COLOR_VISITED; return; } // 优先级 3:可前往但未曾去过 → 绿色边框 if (state.interaction == MapNodeInteraction.Selectable) { _outline.effectColor = OUTLINE_COLOR_SELECTABLE; return; } // 优先级 4:不可前往(Locked)→ 红色边框 _outline.effectColor = OUTLINE_COLOR_LOCKED; } // ================================================================ // 使用状态视觉 // ================================================================ /// /// 根据使用状态调整视觉效果(已访问/已用完的节点背景变暗)。 /// private void ApplyUsageVisual(MapNodeUsage usage, MapNodeInteraction interaction) { if (backgroundImage == null) return; Color baseColor = backgroundImage.color; switch (usage) { case MapNodeUsage.Visited: // 已访问节点稍微变暗 backgroundImage.color = new Color(baseColor.r * 0.7f, baseColor.g * 0.7f, baseColor.b * 0.7f, baseColor.a); break; case MapNodeUsage.Exhausted: // 已用完的节点明显变暗 backgroundImage.color = new Color(baseColor.r * 0.4f, baseColor.g * 0.4f, baseColor.b * 0.4f, baseColor.a * 0.6f); break; } // 当前所在节点高亮(覆盖变暗效果) if (interaction == MapNodeInteraction.Current) { backgroundImage.color = Color.white; } } // ================================================================ // 点击处理 // ================================================================ /// 按钮点击回调,转发为 OnClicked 事件。 private void HandleClick() { OnClicked?.Invoke(GridPosition); } // ================================================================ // 静态工具方法 // ================================================================ /// 获取节点类型的简称标签。 private static string GetNodeLabel(MapNodeType type) { return type switch { MapNodeType.Start => "S", MapNodeType.NormalBattle => "B", MapNodeType.EliteBattle => "E", MapNodeType.BossBattle => "BOSS", MapNodeType.MechanicalTable => "T", MapNodeType.LogisticsCenter => "$", MapNodeType.MedicalStation => "+", _ => "?" }; } /// 获取节点类型对应的背景颜色。 private static Color GetNodeColor(MapNodeType type) { return type switch { MapNodeType.Start => new Color(0.2f, 0.8f, 0.2f, 1f), // 绿色 MapNodeType.NormalBattle => new Color(0.6f, 0.6f, 0.6f, 1f), // 灰色 MapNodeType.EliteBattle => new Color(0.9f, 0.6f, 0.1f, 1f), // 橙色 MapNodeType.BossBattle => new Color(0.9f, 0.1f, 0.1f, 1f), // 红色 MapNodeType.MechanicalTable => new Color(0.3f, 0.5f, 0.9f, 1f), // 蓝色 MapNodeType.LogisticsCenter => new Color(0.9f, 0.85f, 0.2f, 1f),// 金色 MapNodeType.MedicalStation => new Color(0.3f, 0.9f, 0.6f, 1f), // 青绿 _ => Color.white }; } } }