ScopeLens.cs

Component that renders a scope lens. It creates a 1024x1024 render target, assigns it to a CameraComponent and sets that texture into the renderer's material. Optionally aligns the scope camera to a muzzle transform at a configured zeroing range.

File Access
using Sandbox;
using System;

public sealed class ScopeLens : Component
{
	[RequireComponent] public ModelRenderer Renderer { get; set; }
	/// <summary>Scope camera that renders into the lens texture.</summary>
	[Property] public CameraComponent Camera { get; set; }

	/// <summary>Aim the scope camera at the muzzle zero distance on start.</summary>
	[Property, Group( "Zeroing" )] public bool ApplyZeroing { get; set; }

	/// <summary>Zero distance in meters; camera looks at that point along the muzzle.</summary>
	[Property, Group( "Zeroing" ), ShowIf( nameof( ApplyZeroing ), true )] public float Range { get; set; } = 100f;

	/// <summary>Muzzle transform used for zeroing direction and roll alignment.</summary>
	[Property, Group( "Zeroing" ), ShowIf( nameof( ApplyZeroing ), true )] public GameObject Muzzle;

	private Texture RenderTarget;

	protected override void OnStart()
	{
		SetupRenderTarget();

		if ( ApplyZeroing )
		{
			ApplyZero();
		}
	}

	private void SetupRenderTarget()
	{
		RenderTarget = Texture.CreateRenderTarget().WithDynamicUsage().WithSize( 1024, 1024 ).Create();
		Camera.RenderTarget = RenderTarget;
		var material = Renderer.MaterialOverride;
		material.Set( "Color", RenderTarget );
		Renderer.MaterialOverride = material;
	}

	private void ApplyZero()
	{
		Vector3 zeroPoint = Muzzle.WorldPosition +
					Muzzle.WorldRotation.Forward * MathX.MeterToInch( Range );

		// Use muzzle up so roll matches the weapon even if it starts on its side.
		// World up would bake a twisted local rotation that stays crooked when picked up.
		Camera.WorldRotation = Rotation.LookAt(
			zeroPoint - Camera.WorldPosition,
			Muzzle.WorldRotation.Up
		);
	}
}