using System.Collections; using System.Collections.Generic; using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// Implement a particle and/or sound effect when something is hit, damaged or destroyed. /// This can include multiple child particle systems and/or an audio source attached to the parent gameobject. /// [AddComponentMenu("Sci-Fi Ship Controller/Object Components/Effects Module")] [HelpURL("http://scsmmedia.com/ssc-documentation")] public class EffectsModule : MonoBehaviour { #region Public Variables and Properties /// /// Whether pooling is used when spawning effects objects of this type. /// Currently we don't support changing this at runtime. /// public bool usePooling = false; /// /// The starting size of the pool. /// public int minPoolSize = 5; /// /// The maximum allowed size of the pool. /// public int maxPoolSize = 100; /// /// The effects object will be automatically despawned after this amount of time (in seconds) has elapsed. /// public float despawnTime = 3f; /// /// If an audio source is included, the volume can be optionally set at runtime to the default volume. /// [Range(0f, 1f)] public float defaultVolume = 1f; /// /// Does this object get parented to another object when activated? If so, /// it will be reparented to the pool transform after use. /// public bool isReparented = false; /// /// Is the effects module enabled to emit particles and play an audio clip? /// See also EnableEffects() and DisableEffects(). /// public bool IsEffectsModuleEnabled { get { return isEffectsModuleEnabled; } } ///// ///// The ID number for this effects object prefab (as assigned by the Ship Controller Manager in the scene). ///// This is the index in the SSCManager effectsObjectTemplatesList ///// [INTERNAL USE ONLY] ///// //public int effectsObjectPrefabID; #endregion #region Private Variables private AudioSource audioSource = null; private AudioClip audioClip = null; private List shurikenParticlesList = null; private int numShurikenParticles = 0; private bool isEffectsModuleEnabled = true; private bool isAudioSourcePaused = false; /// /// [INTERNAL ONLY] /// Used to determine uniqueness /// [System.NonSerialized] internal uint itemSequenceNumber; /// /// [INTERNAL ONLY] /// If isReparented is true, this is the original parent /// transform used in the pooling system. /// [System.NonSerialized] internal Transform poolParentTrfm; #endregion #region Internal Static Variables internal static uint nextSequenceNumber = 1; internal readonly static string destroyMethodName = "DestroyEffectsObject"; #endregion #region Internal Methods /// /// [INTERNAL USE ONLY] /// Get a reference to the audio source and cache it, if it hasn't already been done. /// NOTE: For performance reasons, the audio source must be on the parent gameobject. /// /// internal AudioSource GetAudioSource() { if (audioSource == null) { audioSource = GetComponent(); } return audioSource; } /// /// [INTERNAL USE ONLY] /// Get a reference to the audio clip. Cache the audio source and audio clip if they /// are not already cached. /// /// internal AudioClip GetAudioClip() { if (audioSource == null) { audioSource = GetComponent(); } // If the audioClip is not defined, attempt to cache it (if it exists) if (audioClip == null && audioSource != null) { audioClip = audioSource.clip; } return audioClip; } /// /// [INTERNAL USE ONLY] /// Replace the existing clip with a new one /// /// internal void SetAudioClip(AudioClip newAudioClip) { if (audioSource != null) { audioSource.clip = newAudioClip; audioClip = audioSource.clip; } } #endregion #region Public Methods /// /// Check to see if the EffectsModule has an AudioSource attached. /// /// public bool HasAudioSource() { if (audioSource != null) { return true; } else if (itemSequenceNumber != 0u) { return false; } else { return TryGetComponent(out audioSource); } } /// /// Check to see if the EffectsModule has one or more ParticleSystems /// /// public bool HasParticleSystems() { if (numShurikenParticles > 0) { return true; } else if (itemSequenceNumber != 0u) { return false; } else { ParticleSystem[] particleSystems = GetComponentsInChildren(); return (particleSystems == null ? 0 : particleSystems.Length) > 0f; } } /// /// Initialises the effects object. Play the optional audio clip if there is one. /// Returns the unique identifier for this EffectsObject instance. /// public uint InitialiseEffectsObject () { // Check for an audiosource attached to the same gameobject in the prefab. // For performance reasons, don't search child objects. if (audioSource == null) { audioSource = GetComponent(); } // If the audioClip is not defined, attempt to cache it (if it exists) if (audioClip == null && audioSource != null) { audioClip = audioSource.clip; } // If there is a valid clip, play it. if (audioClip != null && audioSource.isActiveAndEnabled && !audioSource.isPlaying) { audioSource.Play(); } isAudioSourcePaused = false; IncrementSequenceNumber(); if (usePooling) { if (isReparented) { poolParentTrfm = transform.parent; } // If pooling, cache the list of particle systems for this effect if (shurikenParticlesList == null) { shurikenParticlesList = new List(); shurikenParticlesList.AddRange(GetComponentsInChildren()); numShurikenParticles = shurikenParticlesList == null ? 0 : shurikenParticlesList.Count; } ParticleSystem particleSystem = null; // Start all particle systems that haven't started automatically on awake for (int spIdx = 0; spIdx < numShurikenParticles; spIdx++) { particleSystem = shurikenParticlesList[spIdx]; if (particleSystem != null && !particleSystem.isPlaying) { particleSystem.Play(true); } } } // After a given amount of time, automatically destroy this effects object Invoke(destroyMethodName, despawnTime); return itemSequenceNumber; } #endregion #region Private Methods /// /// Makes this EffectsModule unique from all others that have gone before them. /// This is called every time the effect is Initialised. /// internal void IncrementSequenceNumber() { itemSequenceNumber = nextSequenceNumber++; // if sequence number needs to be wrapped, do so to a high-ish number that is unlikely to be in use if (nextSequenceNumber > uint.MaxValue - 100) { nextSequenceNumber = 100000; } } /// /// Removes the effects object. How this is done depends on what system is being used (i.e. pooling etc.). /// If pooling is enabled, and there is an audio clip still playing, stop it. /// internal void DestroyEffectsObject() { //Debug.Log("[DEBUG] EffectsModule.DestroyEffectsObject T: " + Time.time); if (usePooling) { // If there is a clip and it is still playing, stop it if (audioClip != null && audioSource.isActiveAndEnabled && audioSource.isPlaying) { audioSource.Stop(); isAudioSourcePaused = false; } ParticleSystem particleSystem = null; // Stop all particle systems for (int spIdx = 0; spIdx < numShurikenParticles; spIdx++) { particleSystem = shurikenParticlesList[spIdx]; if (particleSystem != null && particleSystem.isPlaying) { shurikenParticlesList[spIdx].Stop(true); } } // Deactivate the effects object gameObject.SetActive(false); if (isReparented && poolParentTrfm != null) { //Debug.Log("[DEBUG] EffectsModule.DestroyEffectsObject and reparent " + name + " T: " + Time.time); transform.SetParent(poolParentTrfm); } } else { // Destroy the effects object Destroy(gameObject); } } #endregion #region Public API Methods /// /// Re-enable an Effect after it has been disabled with /// DisableEffects(). See also SSCManager.ResumeEffectsObjects(). /// NOTE: Only takes action if IsEffectsModuleEnabled is false. /// public void EnableEffects() { if (!isEffectsModuleEnabled) { // If there is a clip and it is still playing, unpause it if (audioClip != null && audioSource.isActiveAndEnabled && isAudioSourcePaused) { audioSource.UnPause(); isAudioSourcePaused = false; } ParticleSystem particleSystem = null; // Play (resume) all paused particle systems for (int spIdx = 0; spIdx < numShurikenParticles; spIdx++) { particleSystem = shurikenParticlesList[spIdx]; if (particleSystem != null && particleSystem.isPaused) { shurikenParticlesList[spIdx].Play(true); } } isEffectsModuleEnabled = true; } } /// /// Useful when you want to pause the action in a game. /// Should always be called BEFORE setting Time.timeScale to 0. /// See also SSCManager.PauseEffectsObjects(). /// NOTE: Only takes action if IsEffectsModuleEnabled is true. /// public void DisableEffects() { if (isEffectsModuleEnabled) { // If there is a clip and it is still playing, pause it if (audioClip != null && audioSource.isActiveAndEnabled && audioSource.isPlaying) { audioSource.Pause(); isAudioSourcePaused = true; } ParticleSystem particleSystem = null; // Pause all particle systems for (int spIdx = 0; spIdx < numShurikenParticles; spIdx++) { particleSystem = shurikenParticlesList[spIdx]; if (particleSystem != null && particleSystem.isPlaying) { shurikenParticlesList[spIdx].Pause(true); } } isEffectsModuleEnabled = false; } } #endregion } }