// RingGateEffect.cs // Zweck: Erzeugt „Geister“-Kopien (Meshes), die aufsteigen + verblassen (Holo-Effekt um die Gates). using System.Collections.Generic; using UnityEngine; public class RingGateEffect : MonoBehaviour { [Header("Material/Color")] [Tooltip("Transparent/Unlit empfohlen. Falls leer: benutze Material dieses Objekts.")] public Material hologramMaterial; [ColorUsage(true, true)] public Color baseColor = new Color(0.3f, 0.9f, 1f, 0.9f); [Header("Emission")] [Min(0f)] public float spawnRate = 0.8f; [Range(1, 64)] public int maxGhosts = 12; public bool emitting = true; public bool burstOnEnable = true; [Header("Motion & Life")] [Min(0.1f)] public float lifetime = 2.2f; public float riseDistance = 0.6f; public float yawDegPerSec = 35f; public float startScale = 1f; public float endScale = 1.12f; [Header("Curves")] public AnimationCurve heightOverLife = AnimationCurve.EaseInOut(0, 0, 1, 1); public AnimationCurve alphaOverLife = new AnimationCurve(new Keyframe(0, 0.9f), new Keyframe(1, 0f)); public AnimationCurve scaleOverLife = new AnimationCurve(new Keyframe(0, 0f), new Keyframe(1, 1f)); private struct Ghost { public Transform tf; public float age; public Renderer rend; public MaterialPropertyBlock mpb; } private readonly List pool = new(); private readonly List alive = new(); private float spawnAccum; private Mesh sourceMesh; private Material sourceMat; private Transform container; private static readonly int _BaseColor = Shader.PropertyToID("_BaseColor"); private static readonly int _Color = Shader.PropertyToID("_Color"); private void Awake() { var mf = GetComponent(); var mr = GetComponent(); if (!mf || !mr || !mf.sharedMesh) { Debug.LogError($"[{name}] Benötigt MeshFilter+MeshRenderer mit Mesh."); enabled = false; return; } sourceMesh = mf.sharedMesh; sourceMat = hologramMaterial ? hologramMaterial : mr.sharedMaterial; container = new GameObject($"{name}_Ghosts").transform; container.SetParent(transform, false); // Pool vorwärmen for (int i = 0; i < maxGhosts; i++) { var go = new GameObject($"Ghost_{i}"); go.transform.SetParent(container, false); var ghostMF = go.AddComponent(); ghostMF.sharedMesh = sourceMesh; var ghostMR = go.AddComponent(); ghostMR.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; ghostMR.receiveShadows = false; ghostMR.sharedMaterial = sourceMat; var g = new Ghost { tf = go.transform, age = lifetime + 1f, rend = ghostMR, mpb = new MaterialPropertyBlock() }; go.SetActive(false); pool.Add(g); } } private void OnEnable() { if (burstOnEnable) { for (int i = 0; i < Mathf.Min(4, maxGhosts); i++) Spawn(); } } private void Update() { float dt = Time.deltaTime; if (emitting && spawnRate > 0f) { spawnAccum += spawnRate * dt; while (spawnAccum >= 1f) { spawnAccum -= 1f; Spawn(); } } // Simulation/Render for (int i = alive.Count - 1; i >= 0; i--) { var g = alive[i]; g.age += dt; if (g.age >= lifetime) { g.tf.gameObject.SetActive(false); alive.RemoveAt(i); pool.Add(g); continue; } float t = Mathf.Clamp01(g.age / lifetime); // Bewegung float h01 = Mathf.Clamp01(heightOverLife.Evaluate(t)); float y = riseDistance * h01; float s01 = Mathf.Clamp01(scaleOverLife.Evaluate(t)); float s = Mathf.Lerp(startScale, endScale, s01); g.tf.localPosition = new Vector3(0f, y, 0f); g.tf.localRotation = Quaternion.Euler(0f, yawDegPerSec * g.age, 0f); g.tf.localScale = Vector3.one * s; // Farbe/Alpha float a = Mathf.Clamp01(alphaOverLife.Evaluate(t)); var c = baseColor; c.a *= a; g.mpb.Clear(); g.mpb.SetColor(_BaseColor, c); g.mpb.SetColor(_Color, c); g.rend.SetPropertyBlock(g.mpb); alive[i] = g; } } private void Spawn() { if (pool.Count == 0) return; var g = pool[pool.Count - 1]; pool.RemoveAt(pool.Count - 1); g.age = 0f; g.tf.localPosition = Vector3.zero; g.tf.localRotation = Quaternion.identity; g.tf.localScale = Vector3.one * startScale; g.tf.gameObject.SetActive(true); alive.Add(g); } }