You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
6.5 KiB
C#

1 week ago
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Unity.BossRoom.VisualEffects
{
/// <summary>
/// Utility script attached to special-effects prefabs. These prefabs are
/// used by various ActionFX that need to show special short-lived graphics
/// such as "charging up" particles, ground path indicators, etc.
///
/// There are two different conceptual "modes":
/// - keep running until somebody explicitly calls Shutdown() (this is used by Actions with indeterminate durations; set m_AutoShutdownTime to -1)
/// - automatically call Shutdown() after a fixed amount of time (set m_AutoShutdownTime to the number of seconds)
///
/// Note that whichever mode is used, Shutdown() may be called prematurely by whoever owns this graphic
/// in the case of aborted actions.
///
/// Once Shutdown() is called (one way or another), the object self-destructs after the particles end
/// (or after a specific additional amount of time).
/// </summary>
///
/// <remarks>
/// When a particle system ends, it usually needs to stick around for a little while
/// to let the last remaining particles finish rendering. Shutdown() turns off particles,
/// and then self-destructs after the particles are all gone. ParticleSystems can technically
/// self-destruct on their own after being stopped: see the "Stop Action" field in the
/// ParticleSystem's inspector. But this script also acts as a way to self-destruct non-particle
/// graphics, and if you're implementing object pooling (for improved mobile performance), this
/// class can be refactored to move itself into an object pool instead of self-destructing.
/// </remarks>
public class SpecialFXGraphic : MonoBehaviour
{
[SerializeField]
[Tooltip("Particles that should be stopped on Shutdown")]
public List<ParticleSystem> m_ParticleSystemsToTurnOffOnShutdown;
[SerializeField]
[Tooltip("If this graphic should automatically Shutdown after a certain time, set it here (in seconds). -1 means no auto-shutdown.")]
private float m_AutoShutdownTime = -1;
[SerializeField]
[Tooltip("After Shutdown, how long before we self-destruct? 0 means no self destruct. -1 means self-destruct after ALL particles have disappeared")]
private float m_PostShutdownSelfDestructTime = -1;
[SerializeField]
[Tooltip("If this graphic should keep its spawn rotation during its lifetime.")]
bool m_StayAtSpawnRotation;
// track when Shutdown() is called so we don't try to do it twice
private bool m_IsShutdown = false;
// we keep a reference to our self-destruction coroutine in case we need to abort it prematurely
private Coroutine coroWaitForSelfDestruct = null;
Quaternion m_StartRotation;
private void Start()
{
m_StartRotation = transform.rotation;
if (m_AutoShutdownTime != -1)
{
coroWaitForSelfDestruct = StartCoroutine(CoroWaitForSelfDestruct());
}
}
public void Shutdown()
{
if (!m_IsShutdown)
{
foreach (var particleSystem in m_ParticleSystemsToTurnOffOnShutdown)
{
if (particleSystem)
{
particleSystem.Stop();
}
}
// now, when and how do we fully destroy ourselves?
if (m_PostShutdownSelfDestructTime >= 0)
{
// we have a fixed-time, so just destroy ourselves after that time
Destroy(gameObject, m_PostShutdownSelfDestructTime);
}
else if (m_PostShutdownSelfDestructTime == -1)
{
// special case! It means "keep checking the particles and self-destruct when they're all fully done"
StartCoroutine(CoroWaitForParticlesToEnd());
}
m_IsShutdown = true;
}
}
private IEnumerator CoroWaitForParticlesToEnd()
{
bool foundAliveParticles;
do
{
yield return new WaitForEndOfFrame();
foundAliveParticles = false;
foreach (var particleSystem in m_ParticleSystemsToTurnOffOnShutdown)
{
if (particleSystem.IsAlive())
{
foundAliveParticles = true;
}
}
} while (foundAliveParticles);
if (coroWaitForSelfDestruct != null)
{
StopCoroutine(coroWaitForSelfDestruct);
}
Destroy(gameObject);
yield break;
}
private IEnumerator CoroWaitForSelfDestruct()
{
yield return new WaitForSeconds(m_AutoShutdownTime);
coroWaitForSelfDestruct = null;
if (!m_IsShutdown)
{
Shutdown();
}
}
void Update()
{
if (m_StayAtSpawnRotation)
{
transform.rotation = m_StartRotation;
}
}
}
#if UNITY_EDITOR
/// <summary>
/// A custom editor that provides a button in the Inspector to auto-add all the
/// particle systems in a SpecialFXGraphic (so we don't have to manually maintain the list).
/// </summary>
[CustomEditor(typeof(SpecialFXGraphic))]
public class SpecialFXGraphicEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
if (GUILayout.Button("Auto-Add All Particle Systems"))
{
AddAllParticleSystems((SpecialFXGraphic)target);
}
}
private void AddAllParticleSystems(SpecialFXGraphic specialFxGraphic)
{
if (specialFxGraphic.m_ParticleSystemsToTurnOffOnShutdown == null)
{
specialFxGraphic.m_ParticleSystemsToTurnOffOnShutdown = new List<ParticleSystem>();
}
specialFxGraphic.m_ParticleSystemsToTurnOffOnShutdown.Clear();
foreach (var particleSystem in specialFxGraphic.GetComponentsInChildren<ParticleSystem>())
{
specialFxGraphic.m_ParticleSystemsToTurnOffOnShutdown.Add(particleSystem);
}
}
}
#endif
}