Files
Cielonos/Assets/Scripts/MainGame/UI/PlayerUI/MainGamePages/Map/MapNodeElement.cs
2026-05-10 11:47:55 -04:00

302 lines
12 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;
using Cielonos.MainGame.Map;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 地图上的单个节点 UI 元素。
/// 由 MapUIPage 动态实例化,定位在 RunMapNode.position 对应的 UI 坐标上。
/// 通过 ApplyDisplayState 控制可见性、交互性和边框颜色。
/// </summary>
public class MapNodeElement : MonoBehaviour
{
// ================================================================
// 边框颜色常量
// ================================================================
/// <summary>Outline 偏移量,控制边框粗细。</summary>
private static readonly Vector2 OUTLINE_DISTANCE = new Vector2(3f, 3f);
/// <summary>已访问节点的边框颜色(白色)。</summary>
private static readonly Color OUTLINE_COLOR_VISITED = Color.white;
/// <summary>可前往但未访问节点的边框颜色(绿色)。</summary>
private static readonly Color OUTLINE_COLOR_SELECTABLE = new Color(0.2f, 0.9f, 0.2f, 1f);
/// <summary>不可前往节点的边框颜色(红色)。</summary>
private static readonly Color OUTLINE_COLOR_LOCKED = new Color(0.9f, 0.2f, 0.2f, 1f);
/// <summary>当前所在节点的边框颜色(金黄色)。</summary>
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;
// ================================================================
// 公共属性
// ================================================================
/// <summary>关联的网格坐标。</summary>
public Vector2Int GridPosition { get; private set; }
/// <summary>关联的节点类型。</summary>
public MapNodeType NodeType { get; private set; }
/// <summary>当前显示状态。</summary>
public NodeDisplayState DisplayState { get; private set; }
/// <summary>节点被玩家点击时触发,参数为该节点的网格坐标。</summary>
public event Action<Vector2Int> OnClicked;
// ================================================================
// 运行时字段
// ================================================================
/// <summary>运行时自动创建的 Outline 组件,用于显示交互状态边框。</summary>
private Outline _outline;
// ================================================================
// 初始化
// ================================================================
/// <summary>
/// 初始化节点元素设置网格坐标、类型、UI 位置,并绑定点击事件。
/// </summary>
/// <param name="node">对应的 RunMapNode 数据。</param>
public void Setup(RunMapNode node)
{
GridPosition = node.gridPosition;
NodeType = node.nodeType;
// 将节点定位到 UI 坐标
RectTransform rt = GetComponent<RectTransform>();
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);
}
}
// ================================================================
// 显示状态
// ================================================================
/// <summary>
/// 更新节点的完整显示状态(可见性、交互性、使用状态、边框颜色)。
/// 由 MapUIPage.RefreshDisplayStates 批量调用。
/// </summary>
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 边框
// ================================================================
/// <summary>
/// 确保 backgroundImage 上存在 Outline 组件。
/// 首次调用时自动添加。
/// </summary>
private void EnsureOutline()
{
if (_outline != null) return;
if (backgroundImage == null) return;
_outline = backgroundImage.GetComponent<Outline>();
if (_outline == null)
{
_outline = backgroundImage.gameObject.AddComponent<Outline>();
}
_outline.effectDistance = OUTLINE_DISTANCE;
}
/// <summary>
/// 根据节点的显示状态设置 Outline 边框颜色。
/// 优先级从高到低:
/// 1. 当前所在节点 → 金黄色
/// 2. 已访问/已用完 → 白色
/// 3. 可前往但未访问 → 绿色
/// 4. 不可前往 → 红色
/// Hidden 节点已被 SetActive(false),不在此处理。
/// </summary>
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;
}
// ================================================================
// 使用状态视觉
// ================================================================
/// <summary>
/// 根据使用状态调整视觉效果(已访问/已用完的节点背景变暗)。
/// </summary>
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;
}
}
// ================================================================
// 点击处理
// ================================================================
/// <summary>按钮点击回调,转发为 OnClicked 事件。</summary>
private void HandleClick()
{
OnClicked?.Invoke(GridPosition);
}
// ================================================================
// 静态工具方法
// ================================================================
/// <summary>获取节点类型的简称标签。</summary>
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 => "+",
_ => "?"
};
}
/// <summary>获取节点类型对应的背景颜色。</summary>
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
};
}
}
}