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
}
}