A component that renders a chained visual effect between two Units using a LineRenderer. It maintains three spline points (start, midpoint, end), updates their positions each frame, clamps minimum height, and controls lifetime-based alpha via a gradient. It also accepts RPCs to set an anchor position or anchor unit.
using System;
using Sandbox;
public class UnitChainedVfx : Component
{
[Property] public LineRenderer LineRenderer { get; set; }
public Unit ChainedUnit { get; set; }
public Unit AnchorUnit { get; set; }
private List<Vector3> _linePoints = new();
private const int NUM_POINTS = 3;
private Vector3 _anchorPos;
private TimeSince _timeSinceSpawn;
public float Lifetime { get; set; }
public float ChainLength { get; set; }
private const float MIN_HEIGHT = 5f;
protected override void OnAwake()
{
base.OnAwake();
_timeSinceSpawn = 0f;
for(int i = 0; i < NUM_POINTS; i++ )
{
_linePoints.Add( Vector3.Zero );
}
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( ChainedUnit.IsValid() )
_linePoints[0] = ChainedUnit.WorldPosition + new Vector3( 0f, 0f, ChainedUnit.Height * 0.5f );
if ( AnchorUnit.IsValid() )
_linePoints[NUM_POINTS - 1] = AnchorUnit.WorldPosition + new Vector3( 0f, 0f, AnchorUnit.Height * 0.75f );
else
_linePoints[NUM_POINTS - 1] = _anchorPos;
var a = _linePoints[0];
var b = _linePoints[NUM_POINTS - 1];
_linePoints[1] = a + (b - a) * 0.5f;
// prevent initial line flicker
if ( _timeSinceSpawn > 0.05f )
{
for ( int i = 0; i < NUM_POINTS; i++ )
{
LineRenderer.VectorPoints[i] = _linePoints[i].WithZ( Math.Max( _linePoints[i].z, MIN_HEIGHT ) );
}
var alpha = _timeSinceSpawn < 0.25f ? (_timeSinceSpawn / 0.25f) : (_timeSinceSpawn > Lifetime - 1f ? Utils.Map( _timeSinceSpawn, Lifetime - 1f, Lifetime, 1f, 0f ) : 1f);
var gradient = new Gradient();
gradient.AddAlpha( x: 0f, alpha: 0f );
gradient.AddAlpha( x: 0.025f, alpha: 1f );
gradient.AddAlpha( x: 0.975f, alpha: 1f );
gradient.AddAlpha( x: 1f, alpha: 0f );
var color = new Color( 0.8f, 0.8f, 1f );
gradient.AddColor( 0.5f, color.WithAlpha( alpha ) );
LineRenderer.Color = gradient;
}
}
[Rpc.Broadcast]
public void SetAnchorPos( Vector2 pos )
{
_anchorPos = new Vector3( pos.x, pos.y, 0f );
}
[Rpc.Broadcast]
public void SetAnchorUnit( Unit unit )
{
AnchorUnit = unit;
}
}