【Unity】为GraphView中的Edge添加数据流效果

【Unity】为GraphView中的Edge添加数据流效果

Unity的Shader Graph和Visual Effect Graph的连图效果,都是通过 GraphView 实现的,但GraphView中默认的Edge并不带有数据流效果,这里提供了一个简单的带数据流效果的Edge实现。

主要实现过程就是在Edge元素下增加了一个Image元素,使Image在Edge的各个顶点之间游走,并根据游走的进度对Image颜色进行插值。

Flowing Edge

最初尝试实现这个效果的时候,发现EdgeControl类中有材质属性,所以想通过Shader来实现Edge的动态样式,但看了部分源码后发现EdgeControl中使用了大量的Internal接口,无法在自定义EdgeControl中调用,所以放弃了Shader方案。如果有大佬知道怎么使用Shader实现此效果,还请指教一下!

FlowingEdge的源代码:

using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

public class FlowingEdge : Edge
{
    
    
    public bool enableFlow
    {
    
    
        get => _isEnableFlow;
        set
        {
    
    
            if (_isEnableFlow == value)
            {
    
    
                return;
            }

            _isEnableFlow = value;
            if (_isEnableFlow)
            {
    
    
                Add(_flowImg);
            }
            else
            {
    
    
                Remove(_flowImg);
            }
        }
    }

    private bool _isEnableFlow;

    public float flowSize
    {
    
    
        get => _flowSize;
        set
        {
    
    
            _flowSize = value;
            _flowImg.style.width = new Length(_flowSize, LengthUnit.Pixel);
            _flowImg.style.height = new Length(_flowSize, LengthUnit.Pixel);
        }
    }

    private float _flowSize = 6f;

    public float flowSpeed {
    
     get; set; } = 150f;

    private readonly Image _flowImg;


    public FlowingEdge()
    {
    
    
        _flowImg = new Image
        {
    
    
            name = "flow-image",
            style =
            {
    
    
                width = new Length(flowSize, LengthUnit.Pixel),
                height = new Length(flowSize, LengthUnit.Pixel),
                borderTopLeftRadius = new Length(flowSize / 2, LengthUnit.Pixel),
                borderTopRightRadius = new Length(flowSize / 2, LengthUnit.Pixel),
                borderBottomLeftRadius = new Length(flowSize / 2, LengthUnit.Pixel),
                borderBottomRightRadius = new Length(flowSize / 2, LengthUnit.Pixel),
            }
        };
        // Add(_flowImg);

        edgeControl.RegisterCallback<GeometryChangedEvent>(OnEdgeControlGeometryChanged);

        enableFlow = true;
    }

    public override bool UpdateEdgeControl()
    {
    
    
        if (!base.UpdateEdgeControl())
        {
    
    
            return false;
        }

        UpdateFlow();
        return true;
    }


    #region Flow

    private float _totalEdgeLength;

    private float _passedEdgeLength;

    private int _flowPhaseIndex;

    private double _flowPhaseStartTime;

    private double _flowPhaseDuration;

    private float _currentPhaseLength;


    public void UpdateFlow()
    {
    
    
        if (!enableFlow)
        {
    
    
            return;
        }

        // Position
        var posProgress = (EditorApplication.timeSinceStartup - _flowPhaseStartTime) / _flowPhaseDuration;
        var flowStartPoint = edgeControl.controlPoints[_flowPhaseIndex];
        var flowEndPoint = edgeControl.controlPoints[_flowPhaseIndex + 1];
        var flowPos = Vector2.Lerp(flowStartPoint, flowEndPoint, (float)posProgress);
        _flowImg.transform.position = flowPos - Vector2.one * flowSize / 2;

        // Color
        var colorProgress = (_passedEdgeLength + _currentPhaseLength * posProgress) / _totalEdgeLength;
        var startColor = edgeControl.outputColor;
        var endColor = edgeControl.inputColor;
        var flowColor = Color.Lerp(startColor, endColor, (float)colorProgress);
        _flowImg.style.backgroundColor = flowColor;

        // Enter next phase
        if (posProgress >= 0.99999f)
        {
    
    
            _passedEdgeLength += _currentPhaseLength;

            _flowPhaseIndex++;
            if (_flowPhaseIndex >= edgeControl.controlPoints.Length - 1)
            {
    
    
                // Restart flow
                _flowPhaseIndex = 0;
                _passedEdgeLength = 0;
            }

            _flowPhaseStartTime = EditorApplication.timeSinceStartup;
            _currentPhaseLength = Vector2.Distance(edgeControl.controlPoints[_flowPhaseIndex],
                edgeControl.controlPoints[_flowPhaseIndex + 1]);
            _flowPhaseDuration = _currentPhaseLength / flowSpeed;
        }
    }

    private void OnEdgeControlGeometryChanged(GeometryChangedEvent evt)
    {
    
    
        // Restart flow
        _flowPhaseIndex = 0;
        _passedEdgeLength = 0;
        _flowPhaseStartTime = EditorApplication.timeSinceStartup;
        _currentPhaseLength = Vector2.Distance(edgeControl.controlPoints[_flowPhaseIndex],
            edgeControl.controlPoints[_flowPhaseIndex + 1]);
        _flowPhaseDuration = _currentPhaseLength / flowSpeed;

        // Calculate edge path length
        _totalEdgeLength = 0;
        for (int i = 0; i < edgeControl.controlPoints.Length - 1; i++)
        {
    
    
            var p = edgeControl.controlPoints[i];
            var pNext = edgeControl.controlPoints[i + 1];
            var phaseLen = Vector2.Distance(p, pNext);
            _totalEdgeLength += phaseLen;
        }
    }

    #endregion
}

在可以连接FlowingEdge的Node里,还要重写 InstantiatePort 方法,告知Port使用FlowingEdge:

public override Port InstantiatePort(Orientation orientation, Direction direction, Port.Capacity capacity, Type type)
{
    
    
    return Port.Create<FlowingEdge>(orientation, direction, capacity, type);
}

猜你喜欢

转载自blog.csdn.net/qq_21397217/article/details/126490788
今日推荐