using System.Collections.Generic; using UnityEngine; public class GhostTrailEmitter : MonoBehaviour { [Header("Referencias")] [SerializeField] private AntManScaleController scaleController; [SerializeField] private Material ghostMaterial; [SerializeField] private Transform ghostSourceTransform; [SerializeField] private MeshFilter sourceMeshFilter; [SerializeField] private MeshRenderer sourceMeshRenderer; [SerializeField] private SkinnedMeshRenderer sourceSkinnedMeshRenderer; [SerializeField] private bool useSkinnedMeshBake = true; [SerializeField] private bool useScaleControllerScaleOnSkinnedGhost = true; [Header("Spawn")] [SerializeField] [Min(0.01f)] private float spawnInterval = 0.05f; [SerializeField] [Min(1)] private int maxActiveGhosts = 32; [Header("Fade")] [SerializeField] [Min(0.01f)] private float ghostLifetime = 0.35f; [SerializeField] [Range(0f, 1f)] private float startAlpha = 0.45f; [SerializeField] [Range(0f, 1f)] private float endAlpha = 0f; [Header("Debug")] [SerializeField] private bool showGhostCountLogs; private readonly List _activeGhosts = new List(); private Material _fallbackMaterial; private bool _isEmitting; private float _spawnTimer; private void Awake() { if (scaleController == null) { scaleController = GetComponent(); } ResolveRenderSources(); if (ghostMaterial == null) { _fallbackMaterial = GetFallbackMaterial(); } } private void OnEnable() { if (scaleController != null) { scaleController.TransitionStarted += HandleTransitionStarted; scaleController.TransitionCompleted += HandleTransitionCompleted; } } private void OnDisable() { if (scaleController != null) { scaleController.TransitionStarted -= HandleTransitionStarted; scaleController.TransitionCompleted -= HandleTransitionCompleted; } } private void LateUpdate() { if (!_isEmitting) { return; } _spawnTimer += Time.deltaTime; while (_spawnTimer >= spawnInterval) { _spawnTimer -= spawnInterval; SpawnGhost(); } } private void HandleTransitionStarted(Vector3 from, Vector3 to) { _isEmitting = true; _spawnTimer = 0f; SpawnGhost(); } private void HandleTransitionCompleted(Vector3 scale) { _isEmitting = false; } private void SpawnGhost() { CleanupNullGhosts(); EnforceGhostLimit(); if (!TryGetGhostRenderData(out Mesh meshToUse, out Material materialToUse, out Transform sourceTransform, out bool ownsMesh, out bool fromSkinnedMesh)) { return; } GameObject ghostObject = new GameObject("GhostTrail"); ghostObject.transform.SetPositionAndRotation(sourceTransform.position, sourceTransform.rotation); ghostObject.transform.localScale = GetGhostScale(sourceTransform, fromSkinnedMesh); GhostTrailGhost ghost = ghostObject.AddComponent(); ghost.Initialize( meshToUse, materialToUse, ghostLifetime, startAlpha, endAlpha, this, ownsMesh ); _activeGhosts.Add(ghost); if (showGhostCountLogs) { Debug.Log($"[GhostTrailEmitter] Ghosts activos: {_activeGhosts.Count}", this); } } public void NotifyGhostDestroyed(GhostTrailGhost ghost) { _activeGhosts.Remove(ghost); } private void CleanupNullGhosts() { for (int i = _activeGhosts.Count - 1; i >= 0; i--) { if (_activeGhosts[i] == null) { _activeGhosts.RemoveAt(i); } } } private void EnforceGhostLimit() { while (_activeGhosts.Count >= maxActiveGhosts) { GhostTrailGhost oldestGhost = _activeGhosts[0]; _activeGhosts.RemoveAt(0); if (oldestGhost != null) { Destroy(oldestGhost.gameObject); } } } private void ResolveRenderSources() { if (ghostSourceTransform == null) { ghostSourceTransform = transform; } if (sourceSkinnedMeshRenderer == null) { sourceSkinnedMeshRenderer = ghostSourceTransform.GetComponentInChildren(true); } if (sourceMeshFilter == null) { sourceMeshFilter = ghostSourceTransform.GetComponentInChildren(true); } if (sourceMeshRenderer == null) { sourceMeshRenderer = ghostSourceTransform.GetComponentInChildren(true); } } private Material GetFallbackMaterial() { if (sourceSkinnedMeshRenderer != null) { return sourceSkinnedMeshRenderer.sharedMaterial; } if (sourceMeshRenderer != null) { return sourceMeshRenderer.sharedMaterial; } return null; } private bool TryGetGhostRenderData(out Mesh mesh, out Material material, out Transform sourceTransform, out bool ownsMesh, out bool fromSkinnedMesh) { ResolveRenderSources(); sourceTransform = ghostSourceTransform != null ? ghostSourceTransform : transform; material = ghostMaterial != null ? ghostMaterial : (_fallbackMaterial != null ? _fallbackMaterial : GetFallbackMaterial()); mesh = null; ownsMesh = false; fromSkinnedMesh = false; if (useSkinnedMeshBake && sourceSkinnedMeshRenderer != null) { Mesh bakedMesh = new Mesh(); sourceSkinnedMeshRenderer.BakeMesh(bakedMesh); mesh = bakedMesh; ownsMesh = true; fromSkinnedMesh = true; sourceTransform = sourceSkinnedMeshRenderer.transform; } else if (sourceMeshFilter != null) { mesh = sourceMeshFilter.sharedMesh; if (sourceMeshFilter.transform != null) { sourceTransform = sourceMeshFilter.transform; } } return mesh != null && material != null; } private Vector3 GetGhostScale(Transform sourceTransform, bool fromSkinnedMesh) { if (fromSkinnedMesh && useScaleControllerScaleOnSkinnedGhost && scaleController != null) { return scaleController.GetCurrentScale(); } return sourceTransform.lossyScale; } }