2025-12-11 13:14:43 +01:00

165 lines
5.0 KiB
C#

// 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<Ghost> pool = new();
private readonly List<Ghost> 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<MeshFilter>();
var mr = GetComponent<MeshRenderer>();
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<MeshFilter>();
ghostMF.sharedMesh = sourceMesh;
var ghostMR = go.AddComponent<MeshRenderer>();
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);
}
}