using System.Collections.Generic; using System; using UnityEngine; using UnityEngine.Events; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// A warp drive-like effect that works with a shipControlModule. /// [AddComponentMenu("Sci-Fi Ship Controller/Misc/Ship Warp Module")] [HelpURL("https://scsmmedia.com/ssc-documentation")] public class ShipWarpModule : MonoBehaviour { #region Enumerations public enum EnvAmbientSource { Colour = 0, Gradient = 1, Skybox = 2 } #endregion #region Public Variables public bool initialiseOnStart = false; /// /// Allow the user of Custom Player inputs during warp /// public bool allowCustomInputs = false; public Color nightSkyColour = new Color(21f / 255f, 21f / 255f, 21f / 255f, 1f); [Tooltip("By default the ambient sky colour will be set to the nightSkyColour")] public bool overrideAmbientColour = false; [Tooltip("If overriding the ambient colour, this is the ambient sky colour")] public Color ambientSkyColour = new Color(21f / 255f, 21f / 255f, 21f / 255f, 1f); /// /// These methods are called immediately after the camera settings have been changed /// public SSCCameraSettingsEvt1 onChangeCameraSettings = null; /// /// These methods get called immediately before Engage() is executed. /// public UnityEvent onPreEngageWarp = null; /// /// These methods get called immediately after Engage() is executed. /// public UnityEvent onPostEngageWarp = null; /// /// These methods get called immediately before Disengage() is executed. /// public UnityEvent onPreDisengageWarp = null; /// /// These methods get called immediately after Disengage() is executed. /// public UnityEvent onPostDisengageWarp = null; #endregion #region Public Properties - General /// /// Get or set the source of the ambient light. Colour, Gradient or Skybox. /// public EnvAmbientSource EnvironmentAmbientSource { get { return envAmbientSource; } set { SetEnvironmentAmbientSource(value); } } /// /// Is the module initialised? /// public bool IsInitialised { get { return isInitialised; } } /// /// Is warp currently engaged? /// public bool IsWarpEngaged { get { return isWarpEngaged; } } /// /// If greater than zero, the time, in seconds, that warp will automatically disengage. /// public float MaxWarpDuration { get { return maxWarpDuration; } set { SetMaxWarpDuration(value); } } #endregion #region Public Properties - Camera /// /// The zero-based index of the currently used ShipCameraSetttings. /// Will return - 1 if th module is not initialised, or there are no active camera settings applied. /// public int CurrentCameraSettingsIndex { get { return isInitialised ? currentCameraSettingsIndex : -1; } } /// /// If there are optional camera settings configured, apply the first one when warp is engaged. /// public bool IsApplyCameraSettingsOnEngage { get { return isApplyCameraSettingsOnEngage; } set { isApplyCameraSettingsOnEngage = value; } } #endregion #region Public Static Variables #endregion #region Protected Variables - General /// /// The source of the ambient light. Colour, Gradient or Skybox /// [SerializeField] protected EnvAmbientSource envAmbientSource = EnvAmbientSource.Colour; /// /// If greater than zero, the time, in seconds, that warp will automatically disengage. /// [SerializeField, Range(0f, 300f)] protected float maxWarpDuration = 0f; /// /// The offset, in local space, from warp fx is from the position of the ship /// [SerializeField] protected Vector3 offsetFromShip = new Vector3(0f, 0f, 40f); protected bool isInitialised = false; protected bool isSavedShipSettings = false; protected bool isWarpEngaged = false; protected SSCManager sscManager = null; protected float savedGravitationalAcceleration = 0f; protected bool savedIsPlayerInputEnabled = false; protected SSCRandom sscRandom; protected float warpEngagedTimer = 0f; #endregion #region Protected Variables - Ship /// /// The amount of proportional thrust to apply to forward thrusters when warp is engaged. /// [SerializeField, Range(0f, 1f)] protected float shipForwardThrust = 0.5f; /// /// The module used to control the player ship /// [SerializeField] protected ShipControlModule shipControlModule = null; /// /// The minimum interval, in seconds, between ship shake incidents /// [SerializeField, Range(0.1f, 10f)] protected float minShakeInterval = 0.5f; /// /// The maximum interval, in seconds, between ship shake incidents /// [SerializeField, Range(0.1f, 10f)] protected float maxShakeInterval = 3f; /// /// The maximum strength of the ship shake. Smaller numbers are better. /// This can be overridden by calling ShakeShip(duration,strength) /// [SerializeField, Range(0.005f, 0.5f)] protected float maxShakeStrength = 0.05f; /// /// The maximum duration, in seconds, the ship will shake per incident. /// This can be overridden by calling ShakeShip(duration,strength). /// [SerializeField, Range(0.1f, 5f)] protected float maxShakeDuration = 0.2f; /// /// The maximum angle, in degrees, the ship can pitch down. /// [SerializeField, Range(0f, 15f)] protected float maxShipPitchDown = 1f; /// /// The maximum time, in seconds, the ship will take to pitch up and down /// [SerializeField, Range(0f, 20f)] protected float maxShipPitchDuration = 5f; /// /// The maximum angle, in degrees, the ship can pitch up. /// [SerializeField, Range(0f, 15f)] protected float maxShipPitchUp = 1f; /// /// The curve used to evaluate the amount of pitch over the pitch duration of each pitch incident. /// [SerializeField] protected AnimationCurve shipPitchCurve = GetDefaultPitchCurve(); /// /// The maximum angle, in degrees, the ship can roll left or right. /// [SerializeField, Range(0f, 30f)] protected float maxShipRollAngle = 3f; /// /// The maximum time, in seconds, the ship will take to roll from left to right /// [SerializeField, Range(0f, 20f)] protected float maxShipRollDuration = 5f; /// /// The curve used to evaluate the amount of roll over the roll duration of each roll incident. /// [SerializeField] protected AnimationCurve shipRollCurve = GetDefaultRollCurve(); protected bool isPlayerShip = false; [NonSerialized] protected PlayerInputModule playerInputModule = null; [NonSerialized] protected Ship ship = null; [NonSerialized] protected Rigidbody shipRBody = null; protected Vector3 shipOriginPos = Vector3.zero; protected Quaternion shipOriginRot = Quaternion.identity; protected bool isShaking = false; protected float shakeStrength = 1f; protected float shakeDuration = 0f; protected float shakeShipTimer = 0f; protected float shakeIntervalTimer = 0f; protected float targetShipPitchAngle = 0f; protected float currentShipPitchAngle = 0f; protected float previousShipPitchAngle = 0f; protected float targetShipPitchDuration = 0f; protected float shipPitchTimer = 0f; protected float targetShipRollAngle = 0f; protected float currentShipRollAngle = 0f; protected float previousShipRollAngle = 0f; protected float targetShipRollDuration = 0f; protected float shipRollTimer = 0f; protected Vector3 currentShipPosOffset = Vector3.zero; protected Quaternion currentShipRot = Quaternion.identity; /// /// A list of forward thruster settings for isMinEffectsAlwaysOn /// [NonSerialized] protected readonly List isMinEffectsAlwaysOnList = new List(); protected int numThrusters = 0; protected int numFwdThrusters = 0; protected bool savedIsThrusterFXStationary = false; #endregion #region Protected Variables - Camera /// /// If there are optional camera settings configured, apply the first one when warp is engaged. /// [SerializeField] protected bool isApplyCameraSettingsOnEngage = false; /// /// The module used to control the player ship camera /// [SerializeField] protected ShipCameraModule shipCameraModule = null; /// /// A list of optional ShipCameraSettings for switching between camera settings when warp is engaged. /// [SerializeField] protected List shipCameraSettingsList = new List(); [NonSerialized] protected Camera camera1; protected int currentCameraSettingsIndex = -1; protected int currentCameraSettingsHash = 0; #endregion #region Protected Variables - FX /// /// The maximum interval, in seconds, between sound fx when warp is engaged /// [SerializeField, Range(0.1f, 30f)] protected float maxSoundInterval = 5f; [Tooltip("The child particle system used to generate the inner or centre particles for the FX")] [SerializeField] protected ParticleSystem innerParticleSystem = null; /// /// Is the sound effects currently paused. New new sounds will play until it is unpaused. /// [SerializeField] protected bool isSoundFXPaused = false; /// /// Is the volume randomised between 50 percent of the EffectsModule default volume, and the default volume? /// [SerializeField] protected bool isSoundIntervalRandomised = true; [Tooltip("The child particle system used to generate the outer particles for the FX")] [SerializeField] protected ParticleSystem outerParticleSystem = null; /// /// The local space relative offset from the ship used when instantiating Sound FX. /// [SerializeField] protected Vector3 soundFXOffset = Vector3.zero; /// /// A set of SoundFX that are randomly selected while warp is engaged. /// [SerializeField] protected SSCSoundFXSet sscSoundFXSet = null; protected int[] soundEffectsPrefabIDs = null; protected float soundFXIntervalTimer = 0; #endregion #region Protected and Public Variables - Editor [SerializeField] protected int selectedTabInt = 0; [HideInInspector] public bool allowRepaint = false; #endregion #region Public Delegates #endregion #region Protected Initialise Methods // Use this for initialization void Start() { if (initialiseOnStart) { Initialise(); } } #endregion #region Update Methods private void Update() { if (isWarpEngaged) { float dTime = Time.deltaTime; warpEngagedTimer += dTime; if (maxWarpDuration > 0f && warpEngagedTimer >= maxWarpDuration) { DisengageWarp(); } else { UpdateParticleSystems(dTime); UpdateSound(dTime); } } } private void FixedUpdate() { if (isWarpEngaged) { float dTime = Time.fixedDeltaTime; bool isMoveShip = false; UpdateRollShip(dTime, ref isMoveShip); UpdatePitchShip(dTime, ref isMoveShip); UpdateShakeShip(dTime, ref isMoveShip); if (isMoveShip) { MoveShip(); } } } #endregion #region Protected Virtual Methods - General protected virtual void UpdateRenderSettings() { // In the Unity Lighting editor for SRP, AmbientMode.Flat = Color and Trilight = Gradient RenderSettings.ambientMode = envAmbientSource == EnvAmbientSource.Gradient ? UnityEngine.Rendering.AmbientMode.Trilight : envAmbientSource == EnvAmbientSource.Colour ? UnityEngine.Rendering.AmbientMode.Flat : UnityEngine.Rendering.AmbientMode.Skybox; if (overrideAmbientColour) { RenderSettings.ambientSkyColor = ambientSkyColour; } else { RenderSettings.ambientSkyColor = nightSkyColour; } } #endregion #region Protected virtual Methods - Ship /// /// Attempt to configure the ship so that warp actions can be applied to it. /// protected virtual bool ConfigureShipForWarp() { bool isConfigured = false; if (shipControlModule == null) { #if UNITY_EDITOR Debug.LogWarning("[ERROR] SampleWarpFX.ConfigureShipForWarp - no ship defined"); #endif } else if (!shipControlModule.IsInitialised) { #if UNITY_EDITOR Debug.LogWarning("[ERROR] SampleWarpFX.ConfigureShipForWarp - " + shipControlModule.name + " is not initialised"); #endif } else { ship = shipControlModule.shipInstance; if (shipRBody == null) { shipRBody = shipControlModule.ShipRigidbody; } numThrusters = ship.thrusterList.Count; // Save and turn off gravity savedGravitationalAcceleration = ship.gravitationalAcceleration; ship.gravitationalAcceleration = 0f; // Save and enable thrusters if required savedIsThrusterFXStationary = ship.isThrusterFXStationary; ship.isThrusterFXStationary = shipForwardThrust > 0f; StopShipMovement(); numFwdThrusters = 0; if (shipForwardThrust > 0f) { // Count the forward thrusters and record some of their settings for (int thIdx = 0; thIdx < numThrusters; thIdx++) { Thruster thruster = ship.thrusterList[thIdx]; // Remember the setting so that it can be restored when disengaging warp. isMinEffectsAlwaysOnList.Add(thruster.isMinEffectsAlwaysOn); // forceUse: 1 = Forwards if (thruster.forceUse == 1) { numFwdThrusters++; // Enable the forward thruster fx to fire while the ship is not moving thruster.isMinEffectsAlwaysOn = true; thruster.currentInput = shipForwardThrust; } else { // Turn off non-forward thrusters thruster.isMinEffectsAlwaysOn = false; thruster.currentInput = 0f; } } } shipOriginPos = shipRBody.position; shipOriginRot = shipRBody.rotation; isSavedShipSettings = true; isConfigured = true; } return isConfigured; } /// /// Get a random shake interval, in seconds. /// /// protected virtual float GetShakeInterval() { if (isInitialised) { return sscRandom.Range(minShakeInterval, maxShakeInterval); } else { return 2f; } } /// /// Get a random pitch down angle in degrees. /// /// protected virtual float GetShipPitchDownAngle() { if (isInitialised && maxShipPitchDown > 0f) { return sscRandom.Range(maxShipPitchDown * 0.1f, maxShipPitchDown); } else { return 0f; } } /// /// Get a random pitch duration in seconds /// /// protected virtual float GetShipPitchTargetDuration() { if (isInitialised && maxShipPitchDuration > 0f) { return sscRandom.Range(maxShipPitchDuration * 0.5f, maxShipPitchDuration); } else { return 0f; } } /// /// Get a random pitch up angle in degrees. /// /// protected virtual float GetShipPitchUpAngle() { if (isInitialised && maxShipPitchUp > 0f) { return sscRandom.Range(maxShipPitchUp * 0.1f, maxShipPitchUp); } else { return 0f; } } /// /// Get a random roll angle in degrees. /// /// protected virtual float GetShipRollTargetAngle() { if (isInitialised && maxShipRollAngle > 0f) { return sscRandom.Range(maxShipRollAngle * 0.1f, maxShipRollAngle); } else { return 0f; } } /// /// Get a random roll duration in seconds /// /// protected virtual float GetShipRollTargetDuration() { if (isInitialised && maxShipRollDuration > 0f) { return sscRandom.Range(maxShipRollDuration * 0.5f, maxShipRollDuration); } else { return 0f; } } /// /// Attempt to update the ship's rigidbody position and rotation /// protected virtual void MoveShip() { if (isInitialised) { // When the ship is kinematic, we need to move the RigidBody. shipRBody.MovePosition(shipOriginPos + (currentShipRot * currentShipPosOffset)); shipRBody.MoveRotation(currentShipRot); } } /// /// Attempt to return ship settings to pre-warp values /// protected virtual void RestoreShipSettings() { if (isInitialised && isSavedShipSettings && shipControlModule != null) { ship.gravitationalAcceleration = savedGravitationalAcceleration; ship.isThrusterFXStationary = savedIsThrusterFXStationary; // Restore the thruster settings if (shipForwardThrust > 0f) { // Restore the original settings for the forward thrusters for (int thIdx = 0; thIdx < numThrusters; thIdx++) { Thruster thruster = ship.thrusterList[thIdx]; // Restore the original settings thruster.isMinEffectsAlwaysOn = isMinEffectsAlwaysOnList[thIdx]; thruster.currentInput = 0f; } } if (isPlayerShip && playerInputModule != null) { if (savedIsPlayerInputEnabled) { playerInputModule.EnableInput(); savedIsPlayerInputEnabled = false; } } // Return ship to original position shipRBody.MovePosition(shipOriginPos); shipRBody.MoveRotation(shipOriginRot); isSavedShipSettings = false; } } /// /// This is what moves the ship while it is shaking. /// /// protected virtual void ShipVibration(float deltaTime) { //Vector3 shipCurrentPos = shipRBody.position; //Quaternion shipCurrentRot = shipRBody.rotation; // We will always loose the first frame but that's ok as we // don't want to update the rigidbody just to change it again in the // same frame when StopShipShake() is called. float timeFactor = 1f; // Reduce strength over time of the shake timeFactor = (shakeDuration - (shakeDuration - shakeShipTimer)) / shakeDuration; // Set the minimum to be 50% of the shake strength timeFactor = 0.5f + (timeFactor * 0.5f); currentShipPosOffset = UnityEngine.Random.insideUnitCircle * (shakeStrength * timeFactor); } /// /// Attempt to start shaking the ship /// /// /// protected virtual void ShakeShip(float duration, float strength) { if (isWarpEngaged && duration > 0f && strength > 0f) { isShaking = true; shakeStrength = strength; shakeDuration = duration; shakeShipTimer = duration; shakeIntervalTimer = 0f; } else { StopShipShake(); } } /// /// Shake the ship after initial delay in seconds, with the /// current maxShakeDuration and maxShakeStrength. /// /// public void ShakeShipDelayed(float delayTime) { if (delayTime > 0f) { Invoke("ShakeShip", delayTime); } else { ShakeShip(maxShakeDuration, maxShakeStrength); } } /// /// Stop the ship from shaking /// protected virtual void StopShipShake() { isShaking = false; shakeShipTimer = 0f; } /// /// Pitch the ship up or down using the pitch timer. /// /// /// protected virtual void UpdatePitchShip(float dTime, ref bool isMoveShip) { if (targetShipPitchAngle != 0f && (maxShipPitchDown > 0f || maxShipPitchUp > 0f)) { currentShipPitchAngle = Mathf.Lerp(previousShipPitchAngle, targetShipPitchAngle, shipPitchCurve.Evaluate((targetShipPitchDuration - shipPitchTimer) / targetShipPitchDuration)); // Rotate around ship local right axis (pitch) currentShipRot = (isMoveShip ? currentShipRot : shipOriginRot) * Quaternion.AngleAxis(currentShipPitchAngle, Vector3.right); shipPitchTimer -= dTime; // Pitch Down if (targetShipPitchAngle > 0f) { // If pitching down and is almost at target, start pitching up if (currentShipPitchAngle > 0f && shipPitchTimer <= 0f) { // Get a -ve target angle, to start pitching up targetShipPitchAngle = maxShipPitchUp > 0f ? -GetShipPitchUpAngle() : 0f; targetShipPitchDuration = GetShipPitchTargetDuration(); shipPitchTimer = targetShipPitchDuration; previousShipPitchAngle = currentShipPitchAngle; } } // Pitch Up else { // If pitching up and is almost at target, start pitching down if (currentShipPitchAngle < 0f && shipPitchTimer <= 0f) { // Get a +ve target angle, to start pitching down targetShipPitchAngle = maxShipPitchDown > 0f ? GetShipPitchDownAngle() : 0f; targetShipPitchDuration = GetShipPitchTargetDuration(); shipPitchTimer = targetShipPitchDuration; previousShipPitchAngle = currentShipPitchAngle; } } isMoveShip = true; } } /// /// Roll the ship left or right using the roll timer /// /// /// protected virtual void UpdateRollShip(float dTime, ref bool isMoveShip) { if (maxShipRollAngle > 0f && targetShipRollAngle != 0f) { currentShipRollAngle = Mathf.Lerp(previousShipRollAngle, targetShipRollAngle, shipRollCurve.Evaluate((targetShipRollDuration - shipRollTimer) / targetShipRollDuration)); // Rotate around ship local forward axis (roll) currentShipRot = shipOriginRot * Quaternion.AngleAxis(currentShipRollAngle, Vector3.forward); shipRollTimer -= dTime; // Roll left if (targetShipRollAngle > 0f) { // If rolling left and is almost at target, start rolling right if (currentShipRollAngle > 0f && shipRollTimer <=0f) { // Get a -ve target angle, to start rolling right targetShipRollAngle = -GetShipRollTargetAngle(); targetShipRollDuration = GetShipRollTargetDuration(); shipRollTimer = targetShipRollDuration; previousShipRollAngle = currentShipRollAngle; } } // Roll right else { // If rolling right and is almost at target, start rolling left if (currentShipRollAngle < 0f && shipRollTimer <= 0f) { // Get a +ve target angle, to start rolling left targetShipRollAngle = GetShipRollTargetAngle(); targetShipRollDuration = GetShipRollTargetDuration(); shipRollTimer = targetShipRollDuration; previousShipRollAngle = currentShipRollAngle; } } isMoveShip = true; } } /// /// Shake the ship using the interval timer /// /// /// protected virtual void UpdateShakeShip(float dTime, ref bool isMoveShip) { if (isShaking) { // Check if shaking should stop shakeShipTimer -= dTime; if (shakeShipTimer <= 0f) { StopShipShake(); shakeIntervalTimer = GetShakeInterval(); } else { ShipVibration(dTime); isMoveShip = true; } } // Are we waiting for the next shake incident to occur? else if (shakeIntervalTimer > 0f) { shakeIntervalTimer -= dTime; if (shakeIntervalTimer <= 0f) { // Start the next shake incident shakeIntervalTimer = 0f; UpdateShakeDuration(); UpdateShakeStrength(); ShakeShip(); } } } /// /// Randomly set a new shake duration /// protected virtual void UpdateShakeDuration() { if (isInitialised) { shakeDuration = sscRandom.Range(maxShakeDuration * 0.1f, maxShakeDuration); } } /// /// Randomly set a new shake strength /// protected virtual void UpdateShakeStrength() { if (isInitialised) { shakeStrength = sscRandom.Range(maxShakeStrength * 0.1f, maxShakeStrength); } } #endregion #region Protected Virtual Methods - Camera protected virtual bool ConfigureCamera (Camera camera) { bool isSuccessful = false; if (camera != null) { camera.clearFlags = envAmbientSource == EnvAmbientSource.Skybox ? CameraClearFlags.Skybox : CameraClearFlags.SolidColor; camera.backgroundColor = nightSkyColour; camera1 = camera; isSuccessful = true; } return isSuccessful; } #endregion #region Protected Virtual Methods - FX /// /// Attempt to enable or disable the Particle FX /// /// protected virtual void EnableOrDisableParticleFX(bool isEnable) { if (isInitialised) { if (innerParticleSystem != null) { if (isEnable) { innerParticleSystem.gameObject.SetActive(true); } else if (innerParticleSystem.gameObject.activeSelf) { // Stop emmitting particles before disabling if (innerParticleSystem.IsAlive(true)) { innerParticleSystem.Stop(true); } innerParticleSystem.gameObject.SetActive(false); } } if (outerParticleSystem != null) { if (isEnable) { outerParticleSystem.gameObject.SetActive(true); } else if (outerParticleSystem.gameObject.activeSelf) { // Stop emmitting particles before disabling if (outerParticleSystem.IsAlive(true)) { outerParticleSystem.Stop(true); } outerParticleSystem.gameObject.SetActive(false); } } } } /// /// Get a random sound fx interval, in seconds. /// /// protected virtual float GetSoundFXInterval() { if (isInitialised) { return sscRandom.Range(maxSoundInterval * 0.2f, maxSoundInterval); } else { return 5f; } } /// /// Attempt to pause or unpause the Sound FX feature. /// Currently this does not stop any sound FX that have been instantiated. /// /// protected virtual void PauseOrUnPauseSoundFX(bool isPause) { if (isPause) { // A possible enhancement is to actual stop any current sound FX // from playing. if (!isSoundFXPaused) { isSoundFXPaused = true; } } else { isSoundFXPaused = false; } } /// /// Attempt to play a random sound and return the estimated duration for that sound. /// protected virtual float PlaySoundFX() { float estimatedDuration = 0f; if (isInitialised) { int numEffects = sscSoundFXSet != null ? sscSoundFXSet.NumberOfEffects : 0; if (numEffects > 0) { int prefabIDArrayIndex = sscRandom.Range(0, numEffects-1); int effectPrefabID = soundEffectsPrefabIDs[prefabIDArrayIndex]; if (effectPrefabID == SSCManager.NoPrefabID) { #if UNITY_EDITOR Debug.LogWarning("ShipWarpModule.PlaySoundFX() - item " + (prefabIDArrayIndex+1) + " in " + sscSoundFXSet.name + " does not have a template registered with SSCManager."); #endif } else { // Get the original prefab used to create the pool of sound fx EffectsModule effectsModule = sscManager.GetEffectsObjectPrefab(effectPrefabID); // Calculate the volume to use for this sound FX. float defaultVolume = effectsModule.defaultVolume; float soundFXVolume = isSoundIntervalRandomised ? sscRandom.Range(defaultVolume * 0.5f, defaultVolume) : defaultVolume; // As the sound should be pooled, assume the despawn time has been configured to match the audio clip length estimatedDuration = effectsModule.despawnTime; InstantiateSoundFXParameters sfxParms = new InstantiateSoundFXParameters() { effectsObjectPrefabID = effectPrefabID, position = shipOriginPos + (currentShipRot * (currentShipPosOffset + soundFXOffset)), volume = soundFXVolume }; sscManager.InstantiateSoundFX(sfxParms, null); } } } return estimatedDuration; } /// /// Override this method to update the particle systems at runtime during the Update() loop /// /// protected virtual void UpdateParticleSystems(float dTime) { } /// /// If required, count down until the next sound should be played, /// then play it. /// /// protected virtual void UpdateSound(float dTime) { if (!isSoundFXPaused && soundFXIntervalTimer > 0f) { soundFXIntervalTimer -= dTime; if (soundFXIntervalTimer <= 0f) { float soundDuration = PlaySoundFX(); soundFXIntervalTimer = GetSoundFXInterval() + soundDuration; } } } /// /// Ensure the particlesystem is a child of the warp module gameobject. /// Deactivate it when verified. /// /// /// protected virtual bool VerifyParticleSystem(ParticleSystem particleSystem) { bool isVerified = false; if (particleSystem != null && particleSystem.transform.IsChildOf(transform)) { particleSystem.gameObject.SetActive(false); isVerified = true; } return isVerified; } #endregion #region Protected and Internal Methods - General #endregion #region Protected and Internal Methods - Ship /// /// Get the number of forward thrusters on the ship /// /// protected int GetNumForwardThrusters() { int numFwdThrusters = 0; if (isInitialised && numThrusters > 0) { for (int thIdx = 0; thIdx < numThrusters; thIdx++) { // forceUse: 1 = Forwards if (ship.thrusterList[thIdx].forceUse == 1) { numFwdThrusters++; } } } return numFwdThrusters; } /// /// Shake the ship for maxShakeDuration seconds which maxShakeStrength or force. /// If the ship is not configured for warp or the duration and/or strength are 0 or less, /// StopShipShake() will be automatically called and the inputs ignored. /// protected void ShakeShip() { ShakeShip(maxShakeDuration, maxShakeStrength); } #endregion #region Protected and Internal Methods - Camera /// /// Attempt to get the next zero-based index in the list of ship camera set. /// NOTE: The slot might not contain a valid ShipCameraSettings ScriptableObject. /// /// protected int GetNextCameraSettingsIndex() { int nextSettingIndex = -1; if (isInitialised) { int numSettingSlots = shipCameraSettingsList.Count; if (numSettingSlots > 0) { // If not set yet, start in the first slot if (currentCameraSettingsIndex < 0) { nextSettingIndex = 0; } else { nextSettingIndex = (currentCameraSettingsIndex + 1) % numSettingSlots; } } } return nextSettingIndex; } #endregion #region Protected and Internal Methods - FX /// /// Attempt to create all (sound) EffectsModule pools with the SSCManager in the scene. /// protected void ReinitialiseSoundFX() { int numEffects = sscSoundFXSet != null ? sscSoundFXSet.NumberOfEffects : 0; if (numEffects > 0) { soundEffectsPrefabIDs = new int[numEffects]; if (sscManager == null) { Debug.Log("[DEBUG] ShipWarpModule.ReinitialiseSoundFX() - GetorCreateManager..."); sscManager = SSCManager.GetOrCreateManager(); } if (sscManager != null) { sscManager.CreateEffectsPools(sscSoundFXSet, soundEffectsPrefabIDs); } } else { soundEffectsPrefabIDs = null; } } #endregion #region Events #endregion #region Public API Methods - General /// /// Attempt to disengage warp fx for the ship /// public virtual void DisengageWarp() { if (isWarpEngaged) { if (onPreDisengageWarp != null) { onPreDisengageWarp.Invoke(); } warpEngagedTimer = 0f; CancelInvoke("ShakeShip"); EnableOrDisableParticleFX(false); RestoreShipSettings(); shipControlModule.StopThrusterEffects(); shakeIntervalTimer = 0f; shipRollTimer = 0f; soundFXIntervalTimer = 0f; isWarpEngaged = false; if (onPostDisengageWarp != null) { onPostDisengageWarp.Invoke(); } } } /// /// Attempt to engage warp fx for the ship /// public virtual void EngageWarp() { if (!isWarpEngaged) { if (onPreEngageWarp != null) { onPreEngageWarp.Invoke(); } if (ConfigureShipForWarp()) { // Update the position and rotation of the warp FX, based on the position of the ship. transform.SetPositionAndRotation(ship.TransformPosition + (ship.TransformRotation * offsetFromShip), ship.TransformRotation); EnableOrDisableParticleFX(true); UpdateShakeDuration(); UpdateShakeStrength(); shakeIntervalTimer = 0f; warpEngagedTimer = 0f; targetShipPitchAngle = maxShipPitchDown > 0f ? GetShipPitchDownAngle() : GetShipPitchUpAngle(); // Start pitching one way from level flight. If only up OR down is used, the duration will be full duration (rather than half) targetShipPitchDuration = GetShipPitchTargetDuration() * (maxShipPitchDown > 0f && maxShipPitchUp > 0f ? 0.5f : 1f); previousShipPitchAngle = 0f; shipPitchTimer = targetShipPitchDuration; targetShipRollAngle = GetShipRollTargetAngle(); currentShipRot = shipOriginRot; // Start by rolling one way from level flight. targetShipRollDuration = GetShipRollTargetDuration() * 0.5f; shipRollTimer = targetShipRollDuration; previousShipRollAngle = 0f; if (shipCameraModule != null) { shipCameraModule.EnableCamera(); } if (isApplyCameraSettingsOnEngage) { CycleCameraSettings(); } ShakeShipDelayed(minShakeInterval); soundFXIntervalTimer = GetSoundFXInterval(); isWarpEngaged = true; if (onPostEngageWarp != null) { onPostEngageWarp.Invoke(); } } } } /// /// Initialise the Warp Effect /// public virtual void Initialise() { if (isInitialised) { return; } else { UpdateRenderSettings(); if (shipCameraModule == null) { #if UNITY_EDITOR Debug.LogWarning("[ERROR] SampleWarpFX - ship camera module is not setup"); #endif } else if (ConfigureCamera(shipCameraModule.GetCamera1) && VerifyParticleSystem(innerParticleSystem) && VerifyParticleSystem(outerParticleSystem)) { DisableParticleFX(); // If the ship hasn't been initialised yet, do it now. if (shipControlModule != null && !shipControlModule.IsInitialised) { shipControlModule.InitialiseShip(); } sscRandom = new SSCRandom(); // Set the seed to an arbitary prime number (but it could be anything really) sscRandom.SetSeed(16729); ReinitialiseSoundFX(); isInitialised = true; } } } /// /// Update or create pooled sound effects with SSCManager in the scene. /// public virtual void RefreshManager() { ReinitialiseSoundFX(); } /// /// Attempt to set the environment ambient source (0: Colour, 1: Gradient, 2: Skybox) /// /// public void SetEnvironmentAmbientSource (int envAmbientSourceInt) { if (envAmbientSource >= 0 && envAmbientSourceInt < 3) { SetEnvironmentAmbientSource((EnvAmbientSource)envAmbientSourceInt); } } /// /// Set the environment ambient source (Colour, Gradient or Skybox). /// Will also refresh camera settings. /// /// public virtual void SetEnvironmentAmbientSource (EnvAmbientSource newEnvAmbientSource) { envAmbientSource = newEnvAmbientSource; UpdateRenderSettings(); if (camera1 != null) { ConfigureCamera(camera1); } } /// /// Set the time, in seconds, that warp will automatically disengage. If it is set to 0, /// it will not automatically disengage. /// /// public void SetMaxWarpDuration (float newMaxWarpDuration) { if (newMaxWarpDuration >= 0) { maxWarpDuration = newMaxWarpDuration; } } /// /// Attempt to stop the ship from moving. /// public virtual void StopShipMovement() { if (shipControlModule != null && shipControlModule.IsInitialised) { // If this is a player ship, prevent player input if (isPlayerShip || playerInputModule != null || shipControlModule.TryGetComponent(out playerInputModule)) { isPlayerShip = true; // Save and disable player input (optionally allow custom player inputs) // Check if it has already been saved when input is enabled. if (!savedIsPlayerInputEnabled) { savedIsPlayerInputEnabled = playerInputModule.IsInputEnabled; } if (savedIsPlayerInputEnabled) { playerInputModule.DisableInput(allowCustomInputs); } } shipControlModule.shipInstance.StopBoost(); shipControlModule.DisableShipMovement(); // Re-enable collision detection so that particles don't enter the ship shipControlModule.ShipRigidbody.detectCollisions = true; } } /// /// Attempt to toggle warp on and off. /// public void ToggleWarp() { if (isInitialised) { if (isWarpEngaged) { DisengageWarp(); } else { EngageWarp(); } } } #endregion #region Public API Methods - Ship /// /// Return the default pitch animation curve /// /// public static AnimationCurve GetDefaultPitchCurve() { return AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); } /// /// Return the default roll animation curve /// /// public static AnimationCurve GetDefaultRollCurve() { return AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); } /// /// Attempt to set the maxShakeInterval /// /// public void SetMaxShakeInterval (float newMaxShakeInterval) { if (newMaxShakeInterval >= 0.1f) { maxShakeInterval = newMaxShakeInterval; } } /// /// Attempt to set the minShakeInterval /// /// public void SetMinShakeInterval (float newMinShakeInterval) { if (newMinShakeInterval >= 0.1f) { minShakeInterval = newMinShakeInterval; } } /// /// Attempt to set the maxShakeStrength /// /// public void SetMaxShakeStrength (float newMaxShakeStrength) { if (newMaxShakeStrength >= 0.005f) { maxShakeStrength = newMaxShakeStrength; } } /// /// Attempt to set the maxShakeDuration /// /// public void SetMaxShakeDuration (float newMaxShakeDuration) { if (newMaxShakeDuration >= 0.1f) { maxShakeDuration = newMaxShakeDuration; } } /// /// Set the ship being used with the Ship Warp Module /// /// public void SetShipControlModule (ShipControlModule newShipControlModule) { shipControlModule = newShipControlModule; } /// /// Attempt to set the shipPitchCurve /// /// public void SetShipPitchCurve (AnimationCurve newAnimationCurve) { if (newAnimationCurve == null) { shipPitchCurve = GetDefaultPitchCurve(); } else { shipPitchCurve = newAnimationCurve; } } /// /// Attempt to set the maxShipPitchDown angle /// /// public void SetMaxShipPitchDown (float newMaxShipPitchDown) { if (newMaxShipPitchDown >= 0f && newMaxShipPitchDown < 90f) { maxShipPitchDown = newMaxShipPitchDown; } } /// /// Attempt to set the maximum time, in seconds, the ship will take to pitch up and down /// /// public void SetMaxShipPitchDuration (float newMaxShipPitchDuration) { if (newMaxShipPitchDuration >= 0f) { maxShipPitchDuration = newMaxShipPitchDuration; } } /// /// Attempt to set the maxShipPitchUp angle /// /// public void SetMaxShipPitchUp (float newMaxShipPitchUp) { if (newMaxShipPitchUp >= 0f && newMaxShipPitchUp < 90f) { maxShipPitchUp = newMaxShipPitchUp; } } /// /// Attempt to set the maxShipRollAngle /// /// public void SetMaxShipRollAngle (float newMaxShipRollAngle) { if (newMaxShipRollAngle >= 0f && newMaxShipRollAngle < 90f) { maxShipRollAngle = newMaxShipRollAngle; } } /// /// Attempt to set the maxShipDuration /// /// public void SetMaxShipRollDuration (float newMaxShipRollDuration) { if (newMaxShipRollDuration >= 0f) { maxShipRollDuration = newMaxShipRollDuration; } } /// /// Attempt to set the shipRollCurve /// /// public void SetShipRollCurve (AnimationCurve newAnimationCurve) { if (newAnimationCurve == null) { shipRollCurve = GetDefaultRollCurve(); } else { shipRollCurve = newAnimationCurve; } } #endregion #region Public API Methods - Camera /// /// Attempt to cycle through a list of camera settings. /// It will skip over any empty settings slots and will /// not apply the same settings twice in a row. /// public void CycleCameraSettings() { if (isInitialised && shipCameraModule != null) { int numCamSettings = shipCameraSettingsList.Count; int prevCameraSettingsIndex = currentCameraSettingsIndex; // Transverse the list of settings a maximum of once for (int iterations = 0; iterations < numCamSettings; iterations++) { int settingIdx = GetNextCameraSettingsIndex(); // Is the index valid for the list of camera settings? if (settingIdx >= 0 && settingIdx < numCamSettings) { currentCameraSettingsIndex = settingIdx; ShipCameraSettings camSettings = shipCameraSettingsList[settingIdx]; if (camSettings != null) { // Make sure we're not just trying to re-apply the same settings if (camSettings.GetHashCode() != currentCameraSettingsHash) { currentCameraSettingsHash = camSettings.GetHashCode(); shipCameraModule.ApplyCameraSettings(camSettings); if (onChangeCameraSettings != null) { onChangeCameraSettings.Invoke(prevCameraSettingsIndex+1, currentCameraSettingsIndex+1, false); } } break; } else { // This settings slot is empty, so look for another one continue; } } else { // No valid setting index found, so exit the loop break; } } #if UNITY_EDITOR if (numCamSettings == 0) { Debug.LogWarning("ShipWarpModule.CycleCameraSettings() - No Camera Settings found on the Camera Tab"); } #endif } } /// /// Get the current ship camera module (if any) /// /// public ShipCameraModule GetShipCameraModule() { return shipCameraModule; } /// /// Attempt to set the ship camera module used by the Warp FX /// /// public void SetShipCameraModule (ShipCameraModule newShipCameraModule) { shipCameraModule = newShipCameraModule; } #endregion #region Public API Methods - FX /// /// Attempt to disable the particle systems. /// public virtual void DisableParticleFX() { EnableOrDisableParticleFX(false); } /// /// Attempt to pause the sound FX feature. /// public void PauseSoundFX() { PauseOrUnPauseSoundFX(true); } /// /// Attempt to update the InnerParticleSystem with another ParticleSystem. /// /// public void SetInnerParticleSystem (ParticleSystem newParticleSystem) { if (VerifyParticleSystem(newParticleSystem)) { innerParticleSystem = newParticleSystem; } else { innerParticleSystem = null; } } /// /// Attempt to set the maxSoundInterval /// /// public void SetMaxSoundInterval (float newMaxSoundInterval) { if (newMaxSoundInterval >= 0.1f) { maxSoundInterval = newMaxSoundInterval; } } /// /// Attempt to update the OuterParticleSystem with another ParticleSystem. /// /// public void SetOuterParticleSystem (ParticleSystem newParticleSystem) { if (VerifyParticleSystem(newParticleSystem)) { outerParticleSystem = newParticleSystem; } else { outerParticleSystem = null; } } /// /// Attempt to update the Sound FX Set. /// /// public void SetSoundFXSet (SSCSoundFXSet newSoundFXSet) { if (newSoundFXSet != null) { sscSoundFXSet = newSoundFXSet; if (isInitialised) { ReinitialiseSoundFX(); } } else { sscSoundFXSet = null; } } /// /// Attempt to unpause the sound FX feature. /// public void UnpauseSoundFX() { PauseOrUnPauseSoundFX(false); } #endregion #region Public API Methods - Events /// /// Call this when you wish to remove any custom event listeners, like /// after creating them in code and then destroying the object. /// You could add this to your game play OnDestroy code. /// public virtual void RemoveListeners() { if (isInitialised) { if (onChangeCameraSettings != null) { onChangeCameraSettings.RemoveAllListeners(); } if (onPreEngageWarp != null) { onPreEngageWarp.RemoveAllListeners(); } if (onPostEngageWarp != null) { onPostEngageWarp.RemoveAllListeners(); } if (onPreDisengageWarp != null) { onPreDisengageWarp.RemoveAllListeners(); } if (onPostDisengageWarp != null) { onPostDisengageWarp.RemoveAllListeners(); } } } #endregion } }