Files
ichni_Official/Assets/Scripts/NewStorySystem/Tree/BlockConnectorView.cs
2026-07-05 16:08:23 -04:00

82 lines
3.6 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 UnityEngine;
using UnityEngine.UI.Extensions;
namespace Ichni.Story.UI
{
/// <summary>
/// 故事树中两个 block 之间的连接线视图,使用 UILineRenderer 绘制 S 形曲线。
/// </summary>
public class BlockConnectorView : MonoBehaviour
{
// 判定两端口坐标是否塌缩为同一点的阈值(像素)。
private const float DegenerateDistanceThreshold = 1f;
public UILineRenderer curve;
public StoryBlockView startBlock;
public StoryBlockView endBlock;
/// <summary>
/// 设置连接线的起止 block 并重算曲线点。传入 null 时保留已有引用。
/// 注意:必须在 block 的 RectTransform 完成布局Canvas 已刷新)后调用,
/// 否则端口世界坐标尚未生效,会导致所有顶点塌缩到同一点。
/// </summary>
public void SetCurve(StoryBlockView start = null, StoryBlockView end = null)
{
if (start != null) startBlock = start;
if (end != null) endBlock = end;
if (startBlock == null || endBlock == null)
{
Debug.LogWarning("[BlockConnectorView] 起点或终点 block 未设置,无法绘制曲线。");
return;
}
RectTransform selfRect = (RectTransform)transform;
// ScreenSpaceCamera / WorldSpace 画布下的世界→屏幕转换需要对应相机Overlay 传 null。
Camera uiCamera = ResolveUICamera();
Vector2 startPosition = GetLocalPoint(startBlock.outPort, selfRect, uiCamera);
Vector2 endPosition = GetLocalPoint(endBlock.inPort, selfRect, uiCamera);
if (Vector2.Distance(startPosition, endPosition) < DegenerateDistanceThreshold)
{
Debug.LogWarning(
$"[BlockConnectorView] '{startBlock.blockId}' -> '{endBlock.blockId}' 端口坐标塌缩为同一点 " +
$"(start={startPosition}, end={endPosition}),通常是在 block 布局完成前调用了 SetCurve。");
}
// 两个控制点构成 S 形:先水平离开起点,再水平进入终点。
float midX = (startPosition.x + endPosition.x) / 2f;
Vector2 mid1 = new Vector2(midX, startPosition.y);
Vector2 mid2 = new Vector2(midX, endPosition.y);
curve.Points = new[] { startPosition, mid1, mid2, endPosition };
curve.SetVerticesDirty();
}
/// <summary>
/// 使用 <see cref="RectTransformUtility"/> 将 <paramref name="port"/> 的世界位置
/// 转换到 <paramref name="space"/> 的局部坐标(相对 pivot即 UILineRenderer 顶点所用的坐标系。
/// </summary>
private static Vector2 GetLocalPoint(RectTransform port, RectTransform space, Camera uiCamera)
{
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, port.position);
RectTransformUtility.ScreenPointToLocalPointInRectangle(space, screenPoint, uiCamera, out Vector2 localPoint);
return localPoint;
}
/// <summary>
/// 解析用于坐标转换的相机Overlay 画布返回 null其余ScreenSpaceCamera / WorldSpace返回画布相机。
/// </summary>
private Camera ResolveUICamera()
{
Canvas canvas = curve != null ? curve.canvas : GetComponentInParent<Canvas>();
if (canvas == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
return null;
return canvas.worldCamera;
}
}
}