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 { public class ThrusterEffects : MonoBehaviour { #region Public variables /// /// The amount of input required before the effect will have any effect. /// This can be useful with controllers which can produce small values /// when the user is not moving them (simulates a dead-zone). /// It can also avoid thruster effects "firing" for very small inputs. /// [Range(0f, 0.1f)] public static float inputThreshold = 0.005f; #endregion #region Private variables - General private bool initialised = false; private int index = 0; private float currentThrusterInput = 0f; #endregion #region Private shuriken particle variables // Arrays of shuriken particle systems and their initial rate over time multipliers private ParticleSystem[] shurikenParticles; private float[] shurikenParticleInitialRateMultipliers; private float[] shurikenParticleInitialSpeedMultipliers; private int numShurikenParticles = 0; private ParticleSystem.EmissionModule shurikenEmissionModule; private ParticleSystem.MainModule shurikenMainModule; #endregion #region Private AudioSource variables // Audio sources for clips private AudioSource[] audioSources; private int numAudioSources = 0; private AudioSource audioSource; private float maxVolume = 1f; #endregion #region Private or Internal Methods /// /// [INTERNAL ONLY] /// Clear thruster effects data /// internal void Clear() { shurikenParticles = null; shurikenParticleInitialRateMultipliers = null; shurikenParticleInitialSpeedMultipliers = null; audioSources = null; numAudioSources = 0; numShurikenParticles = 0; } /// /// If initialised restore the ParticleSystems to their original values. This is typically /// used when we want to reinitialise the system. /// protected virtual void RestoreInitialValues() { if (initialised) { // Restore Particle Systems to original values numShurikenParticles = shurikenParticles == null ? 0 : shurikenParticles.Length; for (index = 0; index < numShurikenParticles; index++) { if (shurikenParticles[index] != null) { shurikenEmissionModule = shurikenParticles[index].emission; shurikenMainModule = shurikenParticles[index].main; shurikenEmissionModule.rateOverTimeMultiplier = shurikenParticleInitialRateMultipliers[index]; shurikenMainModule.startSpeedMultiplier = shurikenParticleInitialSpeedMultipliers[index]; } } // Restore Audio Sources to original volumes numAudioSources = audioSources == null ? 0 : audioSources.Length; for (index = 0; index < numAudioSources; index++) { audioSources[index].volume = maxVolume; } } } #endregion #region Public Virtual Member Methods /// /// Uses the given thruster input value to update all thruster effects systems i.e. particle systems. /// minEffectsRate must be between 0.0 and 1.0. This is the minimum particle system emission rate /// proportional to the maximum emission rate. /// Assumes thruster is not null. /// /// /// public virtual void UpdateThrusterInput (Thruster thruster, Vector3 shipLocalVelocity) { if (initialised) { // We only want to update the thruster effect systems when we receive input from this function // This way we can update this less frequently etc. to improve performance // Check for maxThrust > 0. If no thrust, then no FX or audio. float thrusterInput = thruster.maxThrust * thruster.throttle > 0f ? thruster.currentInput * thruster.CurrentPerformance : 0f; bool isFXLimited = false; // Check for inputThreshold currentThrusterInput = thrusterInput < inputThreshold ? 0f : thrusterInput; // Check if ship is travelling too fast or two slow on Z-axis for the thruster to be active if (currentThrusterInput > 0f && thruster.limitEffectsOnZ) { if (shipLocalVelocity.z < thruster.minEffectsOnZ || shipLocalVelocity.z > thruster.maxEffectsOnZ) { currentThrusterInput = 0f; isFXLimited = true; } } // Check if ship is travelling too fast or two slow on Y-axis for the thruster to be active if (currentThrusterInput > 0f && thruster.limitEffectsOnY) { if (shipLocalVelocity.y < thruster.minEffectsOnY || shipLocalVelocity.y > thruster.maxEffectsOnY) { currentThrusterInput = 0f; isFXLimited = true; } } if (currentThrusterInput > 0f || (!isFXLimited && thruster.isMinEffectsAlwaysOn)) { // If user wants a minimum particle emission or audio volume, update accordingly. currentThrusterInput = currentThrusterInput < thruster.minEffectsRate ? thruster.minEffectsRate * (thruster.isThrottleMinEffects ? thruster.throttle : 1f) : currentThrusterInput; // Clamp to 1.0 if (currentThrusterInput > 1f) { currentThrusterInput = 1f; } } // Update shuriken particle systems numShurikenParticles = shurikenParticles == null ? 0 : shurikenParticles.Length; for (index = 0; index < numShurikenParticles; index++) { ParticleSystem pSystem = shurikenParticles[index]; shurikenEmissionModule = pSystem.emission; shurikenMainModule = pSystem.main; if (currentThrusterInput > 0f) { // +SMS v1.3.6 Beta 1j. Used with isMinEffectsAlwaysOn and ship.isThrusterFXStationary if (thruster.isMinEffectsAlwaysOn && pSystem.isPaused) { pSystem.Play(true); } shurikenEmissionModule.rateOverTimeMultiplier = shurikenParticleInitialRateMultipliers[index] * currentThrusterInput; shurikenMainModule.startSpeedMultiplier = shurikenParticleInitialSpeedMultipliers[index] * currentThrusterInput; } else { shurikenEmissionModule.rateOverTimeMultiplier = 0f; shurikenMainModule.startSpeedMultiplier = 0f; } } // Update Audio Sources // numAudioSources is set in Initialise() for (index = 0; index < numAudioSources; index++) { audioSource = audioSources[index]; if (currentThrusterInput > 0f) { // +SMS v1.3.6 Beta 1j. Used with isMinEffectsAlwaysOn and ship.isThrusterFXStationary if (thruster.isMinEffectsAlwaysOn && audioSource.mute) { audioSource.mute = false; } audioSource.volume = currentThrusterInput * maxVolume; if (!audioSource.isPlaying && audioSource.isActiveAndEnabled) { audioSource.Play(); } } else { audioSource.volume = 0f; } } } } /// /// Pauses the thruster effects. /// public virtual void Pause() { // Update shuriken particle systems numShurikenParticles = shurikenParticles == null ? 0 : shurikenParticles.Length; for (index = 0; index < numShurikenParticles; index++) { shurikenParticles[index].Pause(); } Mute(true); } /// /// Stop thruster effects and set sound volume to 0. /// public virtual void Stop() { // Update shuriken particle systems numShurikenParticles = shurikenParticles == null ? 0 : shurikenParticles.Length; for (index = 0; index < numShurikenParticles; index++) { // Cannot just do Stop() as previous state could have been Pause() shurikenParticles[index].Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } // Turn off volume. // numAudioSources is set in Initialise() for (index = 0; index < numAudioSources; index++) { audioSource = audioSources[index]; audioSource.volume = 0f; if (audioSource.isActiveAndEnabled && audioSource.isPlaying) { audioSource.Stop(); } } } /// /// Plays the thruster effects (resuming them if they were previously paused). /// public virtual void Play() { // Update shuriken particle systems numShurikenParticles = shurikenParticles == null ? 0 : shurikenParticles.Length; for (index = 0; index < numShurikenParticles; index++) { shurikenParticles[index].Play(); } UnMute(true); } /// /// Set the max volume of the thruster effects audio. /// /// public virtual void SetMaxVolume(float newMaxVolume) { maxVolume = newMaxVolume < 0f ? 0f : newMaxVolume > 1f ? 1f : newMaxVolume; } /// /// Mutes the sound on all active audio sources on the thruster. This is automatically /// called in Pause(). /// public virtual void Mute(bool isPauseSound) { // numAudioSources is set in Initialise() for (index = 0; index < numAudioSources; index++) { audioSource = audioSources[index]; audioSource.mute = true; if (isPauseSound && audioSource.isPlaying && audioSource.isActiveAndEnabled) { audioSource.Pause(); } } } /// /// UnMutes the sound on all active audio sources on the thruster. This is automatically /// called in Play(). /// /// public virtual void UnMute(bool isUnPauseSound) { // numAudioSources is set in Initialise() for (index = 0; index < numAudioSources; index++) { audioSource = audioSources[index]; if (isUnPauseSound && !audioSource.isPlaying && audioSource.isActiveAndEnabled) { audioSource.Play(); } audioSource.mute = false; } } /// /// Initialise thruster effects. /// NOTE: This will increase Garbage Collection, so use sparingly. /// Ignores disabled AudioSources /// The audio max volume is set to the current volume of the audio source. /// public virtual void Initialise () { // If this is being re-initialised, like when effects have been added, removed or disabled, // we need to reset the Particle Systems and Audio Sources to original values first. if (initialised) { RestoreInitialValues(); } // Find all the attached shuriken particle systems List shurikenParticlesList = new List(); shurikenParticlesList.AddRange(GetComponentsInChildren()); shurikenParticles = shurikenParticlesList.ToArray(); // Get the initial rate over time / speed multipliers - this is necessary as we want to always set the multiplier // to this initial value multiplied by the current thruster input numShurikenParticles = shurikenParticles == null ? 0 : shurikenParticles.Length; shurikenParticleInitialRateMultipliers = new float[numShurikenParticles]; shurikenParticleInitialSpeedMultipliers = new float[numShurikenParticles]; for (index = 0; index < numShurikenParticles; index++) { if (shurikenParticles[index] != null) { shurikenParticleInitialRateMultipliers[index] = shurikenParticles[index].emission.rateOverTimeMultiplier; shurikenParticleInitialSpeedMultipliers[index] = shurikenParticles[index].main.startSpeedMultiplier; } } // Find all the attached enabled audiosources List audioSourceList = new List(2); // For some reason when includeInactive is false, it still returns them. GetComponentsInChildren(false, audioSourceList); // As an optimisation remove any invalid or non-configured sources // We need to loop through the list and potentially have to count the items twice, but it will be faster when updating. numAudioSources = audioSourceList == null ? 0 : audioSourceList.Count; for (index = numAudioSources- 1; index >= 0; index--) { if (audioSourceList[index].clip == null || !audioSourceList[index].gameObject.activeSelf) { audioSourceList.RemoveAt(index); } else { maxVolume = audioSourceList[index].volume; } } audioSources = audioSourceList.ToArray(); numAudioSources = audioSources == null ? 0 : audioSources.Length; initialised = true; } #endregion } }