UI/Player/ScannerSceneController.cs
using Sandbox;
public sealed class ScannerSceneController : Component
{
public sealed class BlipData
{
public Vector3 Position { get; set; }
public bool Above { get; set; }
public bool IsBot { get; set; }
}
private sealed class BlipVisual
{
public GameObject Arrow { get; set; }
public ModelRenderer ArrowRenderer { get; set; }
public GameObject Line { get; set; }
public LineRenderer LineRenderer { get; set; }
}
private readonly List<BlipVisual> _blipVisuals = new();
private IReadOnlyList<BlipData> _blips = Array.Empty<BlipData>();
private GameObject _playerMarker;
private ModelRenderer _playerMarkerRenderer;
private GameObject _radarBase;
private ModelRenderer _radarBaseRenderer;
public void SetBlips( IReadOnlyList<BlipData> blips )
{
_blips = blips ?? Array.Empty<BlipData>();
}
protected override void OnUpdate()
{
EnsureRadarBase();
EnsurePlayerMarker();
EnsureBlipObjects();
RenderBlips();
}
private void EnsurePlayerMarker()
{
if ( _playerMarker != null && _playerMarkerRenderer != null )
return;
_playerMarker = Scene.CreateObject();
_playerMarkerRenderer = _playerMarker.Components.Create<ModelRenderer>();
_playerMarkerRenderer.Model = Model.Load( "models/ui/radar_ship_01.vmdl" );
_playerMarkerRenderer.RenderOptions.Overlay = true;
_playerMarkerRenderer.Tint = new Color( 1f, 0.2f, 0.2f, 0.95f );
_playerMarker.WorldPosition = Vector3.Zero;
}
private void EnsureRadarBase()
{
if ( _radarBase != null && _radarBaseRenderer != null )
return;
_radarBase = Scene.CreateObject();
_radarBaseRenderer = _radarBase.Components.Create<ModelRenderer>();
_radarBaseRenderer.Model = Model.Load( "models/dev/plane.vmdl" );
_radarBaseRenderer.MaterialOverride = Material.Load( "materials/ui/radar_01.vmat" );
_radarBaseRenderer.SceneObject.Flags.IsTranslucent = true;
_radarBaseRenderer.RenderOptions.Overlay = true;
}
private void EnsureBlipObjects()
{
while ( _blipVisuals.Count < _blips.Count )
{
var arrow = Scene.CreateObject();
var arrowRenderer = arrow.Components.Create<ModelRenderer>();
arrowRenderer.Model = Model.Load( "models/ui/radar_arrow_01.vmdl" );
arrowRenderer.RenderOptions.Overlay = true;
var line = Scene.CreateObject();
var lineRenderer = line.Components.Create<LineRenderer>();
lineRenderer.RenderOptions.Overlay = true;
lineRenderer.UseVectorPoints = true;
lineRenderer.Width = 0.7f;
lineRenderer.Lighting = false;
lineRenderer.CastShadows = false;
lineRenderer.VectorPoints = new List<Vector3> { Vector3.Zero, Vector3.Zero };
_blipVisuals.Add( new BlipVisual
{
Arrow = arrow,
ArrowRenderer = arrowRenderer,
Line = line,
LineRenderer = lineRenderer
} );
}
}
private void RenderBlips()
{
for ( int i = 0; i < _blipVisuals.Count; i++ )
{
var v = _blipVisuals[i];
if ( v?.Arrow == null || v.Line == null || v.ArrowRenderer == null || v.LineRenderer == null )
continue;
if ( i >= _blips.Count )
{
v.ArrowRenderer.Enabled = false;
v.LineRenderer.Enabled = false;
continue;
}
var b = _blips[i];
v.ArrowRenderer.Enabled = true;
v.LineRenderer.Enabled = true;
v.Arrow.WorldPosition = b.Position;
// Keep all arrows in one constant orientation
v.Arrow.WorldRotation = Rotation.LookAt( b.Above ? Vector3.Up : Vector3.Down, Vector3.Left );
v.Arrow.WorldScale = b.Above ? Vector3.One * 0.12f : Vector3.One * 0.1f;
var color = b.Above
? new Color( 0.62f, 0.92f, 1f, 0.95f )
: new Color( 1f, 0.58f, 0.4f, 0.9f );
if ( b.IsBot )
{
color = b.Above
? new Color( 0.55f, 0.8f, 1f, 0.95f )
: new Color( 1f, 0.45f, 0.8f, 0.9f );
}
v.ArrowRenderer.Tint = color;
v.LineRenderer.Color = color.WithAlpha( 0.7f );
v.LineRenderer.VectorPoints ??= new List<Vector3>();
v.LineRenderer.VectorPoints.Clear();
v.LineRenderer.VectorPoints.Add( b.Position );
// Height line endpoint at z=0 (player plane in scanner coordinates)
v.LineRenderer.VectorPoints.Add( new Vector3( b.Position.x, b.Position.y, 0f ) );
}
}
protected override void OnDestroy()
{
foreach ( var v in _blipVisuals )
{
v?.Arrow?.Destroy();
v?.Line?.Destroy();
}
_playerMarker?.Destroy();
_playerMarker = null;
_playerMarkerRenderer = null;
_radarBase?.Destroy();
_radarBase = null;
_radarBaseRenderer = null;
_blipVisuals.Clear();
base.OnDestroy();
}
}