239 lines
6.7 KiB
C#
239 lines
6.7 KiB
C#
|
|
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<GhostTrailGhost> _activeGhosts = new List<GhostTrailGhost>();
|
||
|
|
|
||
|
|
private Material _fallbackMaterial;
|
||
|
|
private bool _isEmitting;
|
||
|
|
private float _spawnTimer;
|
||
|
|
|
||
|
|
private void Awake()
|
||
|
|
{
|
||
|
|
if (scaleController == null)
|
||
|
|
{
|
||
|
|
scaleController = GetComponent<AntManScaleController>();
|
||
|
|
}
|
||
|
|
|
||
|
|
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<GhostTrailGhost>();
|
||
|
|
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<SkinnedMeshRenderer>(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sourceMeshFilter == null)
|
||
|
|
{
|
||
|
|
sourceMeshFilter = ghostSourceTransform.GetComponentInChildren<MeshFilter>(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sourceMeshRenderer == null)
|
||
|
|
{
|
||
|
|
sourceMeshRenderer = ghostSourceTransform.GetComponentInChildren<MeshRenderer>(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;
|
||
|
|
}
|
||
|
|
}
|