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