302 lines
12 KiB
C#
302 lines
12 KiB
C#
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
|
||
};
|
||
}
|
||
}
|
||
}
|