Sweep over 48 files (Editor/ excluded): remove comments that restate the code, condense class summaries, fix three stale doc statements (HsvColorPicker alpha bar, PaintHud layoutPrefab, MasterVolume FallbackDefault). Inline two single-use wrappers (IsPointerOverUi, BuildModes), drop an unused using, replace a magic literal with the existing ParallaxBase constant, and convert ~10 UnityEngine.Object null checks to the implicit-bool convention. No serialized fields, signatures, RPC/NetworkVariable names, defaults, or #if blocks touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
135 lines
4.9 KiB
C#
135 lines
4.9 KiB
C#
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
/// <summary>
|
|
/// Momentary world-space laser beam for a hunter's shot. Purely visual (no networking): a LineRenderer
|
|
/// from muzzle to hit point that fades IN fast then OUT, animating opacity (per-vertex colour alpha, read
|
|
/// by the Chameleon/Laser shader) and thickness (widthMultiplier). Self-contained: builds its own
|
|
/// LineRenderer child + additive material at runtime, so it can be added to an avatar purely in code with
|
|
/// no prefab authoring. Call <see cref="Fire"/> to trigger; the beam disables its renderer once expired.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public sealed class HunterLaserBeam : MonoBehaviour
|
|
{
|
|
[Header("Look")]
|
|
[ColorUsage(true, true)]
|
|
[Tooltip("HDR beam tint. Its alpha is ignored - the animated opacity envelope drives visibility.")]
|
|
[SerializeField] private Color color = new Color(1f, 0.25f, 0.25f, 1f);
|
|
|
|
[Tooltip("HDR emission multiplier applied to the tint. >1 pushes the beam past LDR into bloom.")]
|
|
[Min(0f)]
|
|
[SerializeField] private float emission = 6f;
|
|
|
|
[Tooltip("Peak beam thickness in world units (the width envelope scales this).")]
|
|
[Min(0f)]
|
|
[SerializeField] private float maxWidth = 0.06f;
|
|
|
|
[Tooltip("Optional material override. Empty -> a built-in additive laser material (Chameleon/Laser) is created at runtime.")]
|
|
[SerializeField] private Material materialOverride;
|
|
|
|
[Header("Timing")]
|
|
[Tooltip("Total beam lifetime in seconds (fade-in + fade-out).")]
|
|
[Min(0.01f)]
|
|
[SerializeField] private float lifetime = 0.5f;
|
|
|
|
[Tooltip("Fraction of the lifetime spent fading IN (the rest fades out).")]
|
|
[Range(0f, 1f)]
|
|
[SerializeField] private float fadeInFraction = 0.2f;
|
|
|
|
[Tooltip("Thickness floor as a fraction of maxWidth at the envelope's ends, so the beam never pinches to a dot.")]
|
|
[Range(0f, 1f)]
|
|
[SerializeField] private float minWidthFraction = 0.35f;
|
|
|
|
private LineRenderer _line;
|
|
private Material _material;
|
|
private bool _ownsMaterial;
|
|
private float _age;
|
|
private bool _active;
|
|
|
|
private static readonly int ColorId = Shader.PropertyToID("_Color");
|
|
|
|
private void Awake()
|
|
{
|
|
var go = new GameObject("LaserBeamLine");
|
|
go.transform.SetParent(transform, false);
|
|
|
|
_line = go.AddComponent<LineRenderer>();
|
|
_line.useWorldSpace = true; // endpoints are fixed world points; the beam ignores avatar motion
|
|
_line.positionCount = 2;
|
|
_line.numCapVertices = 2;
|
|
_line.numCornerVertices = 0;
|
|
_line.alignment = LineAlignment.View;
|
|
_line.textureMode = LineTextureMode.Stretch;
|
|
_line.shadowCastingMode = ShadowCastingMode.Off;
|
|
_line.receiveShadows = false;
|
|
_line.lightProbeUsage = LightProbeUsage.Off;
|
|
_line.reflectionProbeUsage = ReflectionProbeUsage.Off;
|
|
|
|
if (materialOverride)
|
|
{
|
|
_material = materialOverride;
|
|
}
|
|
else
|
|
{
|
|
_material = new Material(Shader.Find("Chameleon/Laser"));
|
|
_ownsMaterial = true;
|
|
}
|
|
_line.material = _material;
|
|
if (_material && _material.HasProperty(ColorId))
|
|
{
|
|
Color hdr = color * emission; // push the tint into HDR so bloom picks it up
|
|
hdr.a = 1f; // alpha is unused by the additive shader
|
|
_material.SetColor(ColorId, hdr);
|
|
}
|
|
|
|
_line.enabled = false;
|
|
}
|
|
|
|
/// <summary>Fires a one-shot beam along the given world-space segment; restarts the animation if already running.</summary>
|
|
public void Fire(Vector3 start, Vector3 end)
|
|
{
|
|
if (!_line) return;
|
|
_line.SetPosition(0, start);
|
|
_line.SetPosition(1, end);
|
|
_age = 0f;
|
|
_active = true;
|
|
_line.enabled = true;
|
|
ApplyEnvelope(0f);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!_active) return;
|
|
_age += Time.deltaTime;
|
|
float t = lifetime > 0f ? _age / lifetime : 1f;
|
|
if (t >= 1f)
|
|
{
|
|
_active = false;
|
|
_line.enabled = false;
|
|
return;
|
|
}
|
|
ApplyEnvelope(t);
|
|
}
|
|
|
|
// Maps normalized life t (0..1) to opacity + thickness: rises over fadeInFraction, then eases back down.
|
|
private void ApplyEnvelope(float t)
|
|
{
|
|
float inFrac = Mathf.Clamp01(fadeInFraction);
|
|
float e = t < inFrac
|
|
? (inFrac > 0f ? t / inFrac : 1f)
|
|
: (inFrac < 1f ? 1f - (t - inFrac) / (1f - inFrac) : 1f);
|
|
e = Mathf.Clamp01(e);
|
|
e = e * e * (3f - 2f * e); // smoothstep for a softer ramp
|
|
|
|
_line.widthMultiplier = maxWidth * Mathf.Lerp(minWidthFraction, 1f, e);
|
|
var vc = new Color(1f, 1f, 1f, e); // hue from the material _Color; alpha carries the fade envelope
|
|
_line.startColor = vc;
|
|
_line.endColor = vc;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_ownsMaterial && _material) Destroy(_material);
|
|
}
|
|
}
|