// Assets/_Project/Scripts/VFX/TowerDropReticle.cs using UnityEngine; using UnityEngine.Rendering.Universal; namespace TD.VFX { /// /// Local visual for a tower-drop targeting reticle — the ground decal, the vertical /// energy beam, and the rising motes, bundled under one root. It is spawned as a child /// of a BuildSiteVisual, which feeds it construction progress via /// each frame. The decal (and optionally the beam) shrink/fade /// as progress climbs toward 1 through the shader's _Progress property. /// /// /// Pure visualization — no networking. It runs on every peer because the owning /// BuildSiteVisual spawns on every peer, and it reads the build site's already- /// replicated progress. It is destroyed automatically when the build site despawns at /// construction-complete or cancel (it's a child of that NetworkObject). /// /// Per-instance materials. Several towers can build at once, so each reticle /// needs its own material — a shared material would make every reticle share one /// _Progress value and shrink in lockstep. The decal's material is cloned here; /// beam renderers use , which auto-instantiates. /// public class TowerDropReticle : MonoBehaviour { [Tooltip("Ground decal projector. Its material's _Progress drives the reticle shrink/fade. " + "Auto-found in children if left empty.")] [SerializeField] private DecalProjector decal; [Tooltip("Optional extra renderers (e.g. the beam) whose material also exposes _Progress " + "so they fade with construction. Renderers without the property are skipped.")] [SerializeField] private Renderer[] progressRenderers; [Tooltip("Shader float property that drives the shrink: 0 = full size, 1 = complete/gone.")] [SerializeField] private string progressProperty = "_Progress"; private int progressId; private Material decalMaterialInstance; private Material[] rendererMaterialInstances; private void Awake() { progressId = Shader.PropertyToID(progressProperty); if (decal == null) decal = GetComponentInChildren(); // Clone the decal material so this reticle animates independently of any other // build site sharing the same source material asset. if (decal != null && decal.material != null) { decalMaterialInstance = new Material(decal.material); decal.material = decalMaterialInstance; } // Renderer.material returns a per-renderer instance, so these are already unique. if (progressRenderers != null && progressRenderers.Length > 0) { rendererMaterialInstances = new Material[progressRenderers.Length]; for (int i = 0; i < progressRenderers.Length; i++) if (progressRenderers[i] != null) rendererMaterialInstances[i] = progressRenderers[i].material; } } /// /// Feed normalized construction progress. 0 = build just started (reticle full size); /// 1 = complete (reticle shrunk away). Safe to call every frame on any peer. /// public void SetProgress(float t01) { t01 = Mathf.Clamp01(t01); if (decalMaterialInstance != null) decalMaterialInstance.SetFloat(progressId, t01); if (rendererMaterialInstances != null) { for (int i = 0; i < rendererMaterialInstances.Length; i++) { var mat = rendererMaterialInstances[i]; if (mat != null && mat.HasProperty(progressId)) mat.SetFloat(progressId, t01); } } } private void OnDestroy() { // The cloned decal material is ours to free. Renderer material instances are // owned by their renderers and cleaned up when those GameObjects are destroyed. if (decalMaterialInstance != null) Destroy(decalMaterialInstance); } } }