using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// Class containing data for a thruster. /// [System.Serializable] public class Thruster { #region Public variables // IMPORTANT - when changing this section also update SetClassDefault() // Also update ClassName(ClassName className) Clone Constructor (if there is one) /// /// The name of the thruster. /// public string name; /// /// The maximum thrust force of this thruster in newtons. /// public float maxThrust; /// /// The amount of available power being supplied to the thruster. /// [Range(0f, 1f)] public float throttle; /// /// Position of the thruster in local space relative to the pivot point of the ship. This is the position where the thrust force will be applied at. /// public Vector3 relativePosition; /// /// The local space direction of thrust provided by the thruster. If you modify this, call Initialise(). /// public Vector3 thrustDirection; /// /// The current thruster input (from 0 to 1). /// public float currentInput; /// /// The use of this thruster in terms of force. This defines what inputs control the thruster. 0 - none. 1 - forwards thrust. 2 - backwards thrust. 3 - upwards thrust. 4 - downwards thrust. 5 - rightwards thrust. 6 - leftwards thrust. /// If you modify this, call ReinitialiseInputVariables() on the ship this thruster is attached to. /// public int forceUse; /// /// The primary use of this thruster in terms of a moment (turning force). This defines what inputs control the thruster. 0 - none. 1 - positive roll. 2 - negative roll. 3 - positive pitch. 4 - negative pitch. 5 - positive yaw. 6 - negative yaw. /// If you modify this, call ReinitialiseInputVariables() on the ship this thruster is attached to. /// public int primaryMomentUse; /// /// The secondary use of this thruster in terms of a moment (turning force). This defines what inputs control the thruster. 0 - none. 1 - positive roll. 2 - negative roll. 3 - positive pitch. 4 - negative pitch. 5 - positive yaw. 6 - negative yaw. /// If you modify this, call ReinitialiseInputVariables() on the ship this thruster is attached to. /// public int secondaryMomentUse; /// /// The index of the damage region this thruster is associated with. When the damage model of the ship is set to simple, this /// is irrelevant. A negative value means it is associated with no damage region (so the thruster's performance will not be /// affected by damage). When the damage model of the ship is set to progressive, a value of zero means it is /// associated with the main damage region. When the damage model of the ship is set to localised, a zero or positive value /// indicates which damage region it is associated with (using a zero-based indexing system). /// public int damageRegionIndex; /// /// When minEffectsRate > 0 and throttle > 0 the effects fire when thruster input is 0. /// The limitEffectsOnY and Z settings are still honoured when this is true. /// public bool isMinEffectsAlwaysOn; /// /// The minimum (i.e. when its health reaches zero) performance level of this thruster. The performance level affects how much /// thrust is produced by this thruster. At a performance level of one it produces the maxThrust value. At a performance level of /// zero it produces no thrust. /// [Range(0f, 1f)] public float minPerformance; /// /// The starting health value of this thruster. /// public float startingHealth; /// /// The 0.0-1.0 value that indicates the minimum normalised amount of any particle or sound effects that are /// applied when a non-zero thrustinput is received for this thruster. Default is 0. If the full /// particle emission rate should be applied when any input is received, set the value to 1.0. /// [Range(0f, 1f)] public float minEffectsRate; /// /// Does the amount of throttle available affect the minEffectsRate? /// public bool isThrottleMinEffects; /// /// Limit when the effects are used for this thruster based on the speed of the /// ship along the local Z axis (forward or backward) /// public bool limitEffectsOnZ; /// /// The minimum speed in m/s on the local z-axis the ship must be travelling at before /// the effects will activate /// [Range(-5000f, 5000f)] public float minEffectsOnZ; /// /// The maximum speed in m/s on the local z-axis the ship can be travelling for the /// the effects to be active /// [Range(-5000f, 5000f)] public float maxEffectsOnZ; /// /// Limit when the effects are used for this thruster based on the speed of the /// ship along the local Y axis (up or down) /// public bool limitEffectsOnY; /// /// The minimum speed in m/s on the local y-axis the ship must be travelling at before /// the effects will activate /// [Range(-5000f, 5000f)] public float minEffectsOnY; /// /// The maximum speed in m/s on the local y-axis the ship can be travelling for the /// the effects to be active /// [Range(-5000f, 5000f)] public float maxEffectsOnY; /// /// Whether the thruster is shown as expanded in the inspector window of the editor. /// public bool showInEditor; /// /// Whether the thruster node is shown as selected in the scene view of the editor. /// public bool selectedInSceneView; /// /// Whether the gizmos for this thruster are shown in the scene view of the editor /// public bool showGizmosInSceneView; /// /// The number of seconds it takes for this thruster to go from minimum to maximum power. /// Also known as Throttle Up Time. /// [Range(0f,30f)] public float rampUpDuration; /// /// The number of seconds it takes for this thruster to go from maximum to minimum power. /// [Range(0f, 30f)] public float rampDownDuration; /// /// The amount of fuel available - range 0.0 (empty) to 100.0 (full). /// At runtime call either thruster.SetFuelLevel(..) or shipInstance.SetFuelLevel(..) /// [Range(0f, 100f)] public float fuelLevel; /// /// The rate fuel is consumed per second. If rate is 0, fuel is unlimited /// [Range(0f, 20f)] public float fuelBurnRate; /// /// The heat of the thruster or engine - range 0.0 (starting temp) to 100.0 (max temp). /// At runtime call either thruster.SetHeatLevel(..) or shipInstance.SetHeatLevel(..) /// [Range(0f, 100f)] public float heatLevel; /// /// The rate heat is added per second. If rate is 0, heat level never changes. /// [Range(0f, 20f)] public float heatUpRate; /// /// The rate heat is removed per second. This is the rate the thruster cools when not in use. /// [Range(0f, 20f)] public float heatDownRate; /// /// The heat level that the thruster will begin to overheat and start producing less thrust. /// [Range(50f, 100f)] public float overHeatThreshold; /// /// When the thruster reaches max heat level of 100, will the thruster be inoperable /// until it is repaired? /// public bool isBurnoutOnMaxHeat; #endregion #region Public Properties /// /// The current performance level of this thruster (determined by the Health value). The performance level affects how much /// thrust is produced by this thruster. At a performance level of one it produces the maxThrust value. At a performance level /// of zero it produces no thrust. The value will zero if the fuelLevel is 0. /// public float CurrentPerformance { get; private set; } /// /// Current fuel level - range 0.0 (empty) to 100.0 (full) /// public float FuelLevel { get { return fuelLevel; } } /// /// Current heat level - range 0.0 (starting temp) to 100.0 (max temp). /// public float HeatLevel { get { return heatLevel; } } /// /// The current health value of this thruster. /// public float Health { get { return health; } set { // Update the health value health = value; // Update the current performance value UpdateThrusterPerformance(); } } /// /// The normalised local space direction of thrust provided by the thruster. /// public Vector3 thrustDirectionNormalised { get; private set; } #endregion #region Private variables private float health; private float previousInput; private float throttleDeltaAmount; // the amount to throttle down by private float startInput; // used with private float currentTargetInput; private float smoothTimer; private float rampTargetDuration; private bool isRampDown; #endregion #region Constructors // Class constructor public Thruster() { SetClassDefaults(); } // Copy constructor public Thruster(Thruster thruster) { if (thruster == null) { SetClassDefaults(); } else { this.name = thruster.name; this.maxThrust = thruster.maxThrust; this.throttle = thruster.throttle; this.relativePosition = thruster.relativePosition; this.thrustDirection = thruster.thrustDirection; this.currentInput = thruster.currentInput; this.forceUse = thruster.forceUse; this.primaryMomentUse = thruster.primaryMomentUse; this.secondaryMomentUse = thruster.secondaryMomentUse; this.damageRegionIndex = thruster.damageRegionIndex; this.minPerformance = thruster.minPerformance; this.startingHealth = thruster.startingHealth; this.Health = thruster.Health; this.showInEditor = thruster.showInEditor; this.selectedInSceneView = thruster.selectedInSceneView; this.showGizmosInSceneView = thruster.showGizmosInSceneView; this.minEffectsRate = thruster.minEffectsRate; this.isThrottleMinEffects = thruster.isThrottleMinEffects; this.isMinEffectsAlwaysOn = thruster.isMinEffectsAlwaysOn; this.limitEffectsOnZ = thruster.limitEffectsOnZ; this.minEffectsOnZ = thruster.minEffectsOnZ; this.maxEffectsOnZ = thruster.maxEffectsOnZ; this.limitEffectsOnY = thruster.limitEffectsOnY; this.minEffectsOnY = thruster.minEffectsOnY; this.maxEffectsOnY = thruster.maxEffectsOnY; this.rampUpDuration = thruster.rampUpDuration; this.rampDownDuration = thruster.rampDownDuration; this.fuelLevel = thruster.fuelLevel; this.fuelBurnRate = thruster.fuelBurnRate; this.heatLevel = thruster.heatLevel; this.heatUpRate = thruster.heatUpRate; this.heatDownRate = thruster.heatDownRate; this.overHeatThreshold = thruster.overHeatThreshold; this.isBurnoutOnMaxHeat = thruster.isBurnoutOnMaxHeat; this.Initialise(); } } #endregion #region Private or Internal Methods /// /// Reset thruster ramp up/down varibles. /// Used with SmoothThrusterInput(..) /// private void ResetThrusterDamping() { smoothTimer = 0f; rampTargetDuration = 0f; throttleDeltaAmount = 0f; currentTargetInput = 0f; previousInput = 0f; startInput = 0f; isRampDown = false; } /// /// Check if we need to burn fuel on this thruster. If there is no fuel /// available or the burn rate is 0, then do nothing. /// /// internal void BurnFuel(float dTime) { if (fuelLevel > 0f && fuelBurnRate > 0f && currentInput > 0f) { // Burn fuel independently to the health level. A damaged thruster will burn the same // amount of fuel but produce less thrust SetFuelLevel(fuelLevel - (currentInput * fuelBurnRate * dTime)); } } /// /// Check if we need to change the heat level on this thruster. If /// heat up rate is 0, then do nothing. /// /// internal void ManageHeat (float dTime) { if (heatUpRate > 0f) { // Heat or cool thruster independently to the health level. if (currentInput > 0f) { // Heating up if (heatLevel < 100f) { SetHeatLevel(heatLevel + (currentInput * heatUpRate * dTime)); } } // Only cool down if not burnt out else if (heatDownRate > 0f && (!isBurnoutOnMaxHeat || heatLevel < 100f)) { // Cooling down SetHeatLevel(heatLevel - (heatDownRate * dTime)); } } } #endregion #region Public Non-Static Methods public void SetClassDefaults() { this.name = "Thruster"; this.maxThrust = 100000f; this.throttle = 1f; this.relativePosition = Vector3.zero; this.thrustDirection = Vector3.forward; this.currentInput = 0f; this.forceUse = 1; this.primaryMomentUse = 0; this.secondaryMomentUse = 0; this.damageRegionIndex = -1; this.minPerformance = 0.25f; this.startingHealth = 100f; this.Health = 100f; this.showInEditor = true; this.selectedInSceneView = false; this.showGizmosInSceneView = true; this.minEffectsRate = 0f; this.isThrottleMinEffects = false; this.isMinEffectsAlwaysOn = false; this.limitEffectsOnZ = false; this.minEffectsOnZ = -5000f; this.maxEffectsOnZ = 5000f; this.limitEffectsOnY = false; this.minEffectsOnY = -5000f; this.maxEffectsOnY = 5000f; this.rampUpDuration = 0f; this.rampDownDuration = 0f; this.fuelLevel = 100f; this.fuelBurnRate = 0f; this.heatLevel = 0f; this.heatUpRate = 0f; this.heatDownRate = 2f; this.overHeatThreshold = 80f; this.isBurnoutOnMaxHeat = false; this.Initialise(); } /// /// Initialises data for the thruster. This does some precalculation to allow for performance improvements. /// Call after modifying thrustDirection. /// public void Initialise() { // Calculate normalised vectors thrustDirectionNormalised = thrustDirection.normalized; // Reset thruster ramp up/down varibles ResetThrusterDamping(); UpdateThrusterPerformance(); } /// /// Is the thruster heat level at or above the over heating threshold? /// /// public bool IsThrusterOverheating() { return heatLevel >= overHeatThreshold; } /// /// Reset the heat level to 0 and reset health /// public void Repair() { ResetThrusterDamping(); heatLevel = 0f; // This will also update the current performance Health = startingHealth; } /// /// Set the new fuel level available to this thruster. /// Range 0.0 (empty) to 100.0 (full). /// /// public void SetFuelLevel (float newFuelLevel) { if (newFuelLevel < 0f) { newFuelLevel = 0f; } else if (newFuelLevel > 100f) { newFuelLevel = 100f; } // Only update the thruster performance if we need to if (newFuelLevel != fuelLevel && (fuelLevel == 0f || newFuelLevel == 0f)) { fuelLevel = newFuelLevel; UpdateThrusterPerformance(); } else { fuelLevel = newFuelLevel; } if (fuelLevel == 0f) { ResetThrusterDamping(); } } /// /// Set the new heat level on this thruster. /// Range 0.0 (min) to 100.0 (max). /// /// public void SetHeatLevel(float newHeatLevel) { if (newHeatLevel < 0f) { newHeatLevel = 0f; } else if (newHeatLevel > 100f) { newHeatLevel = 100f; } // Only update the thruster performance if we need to // Update when: // a) heatLevel will be equal or above the overheat threshold // b) heatLevel will fallen below the overheat threshold // c) AND it has changed if (newHeatLevel != heatLevel && (newHeatLevel >= overHeatThreshold || (newHeatLevel < overHeatThreshold && heatLevel >= overHeatThreshold))) { heatLevel = newHeatLevel; UpdateThrusterPerformance(); } else { heatLevel = newHeatLevel; } if (heatLevel == 100f) { ResetThrusterDamping(); } } /// /// Smooth or dampen the thruster's currentInput value based on user settings. /// Throttle up and down time can be set independently for each thruster. /// /// public void SmoothThrusterInput(float lastFrameTime) { // Clamp 0.0-1.0 currentInput = currentInput < 0f ? 0f : currentInput > 1f ? 1f : currentInput; // Apply throttle level if (throttle < 1f && throttle >= 0f) { currentInput *= throttle; } // If the ramp up time is not set, simply return the target value if (rampUpDuration > 0f || rampDownDuration > 0f) { // Are we still trying to get to the same target value? if (currentInput != currentTargetInput) { // Target value has changed currentTargetInput = currentInput; startInput = previousInput; smoothTimer = 0f; // What is the delta between original value and target value? throttleDeltaAmount = currentTargetInput - previousInput; // Are we ramping up or down? isRampDown = throttleDeltaAmount < 0f; // What is the proportional ramp up duration between original value and target value? if (isRampDown) { throttleDeltaAmount *= -1f; rampTargetDuration = throttleDeltaAmount * rampDownDuration; } else { rampTargetDuration = throttleDeltaAmount * rampUpDuration; } } // Do we need to ramp up/down? if (smoothTimer < rampTargetDuration && rampTargetDuration >= 0f) { smoothTimer += lastFrameTime; // Get the point on the throttle up curve // y = (e ^ x/a) - 1, where a = duration (in seconds) and x = elapsed time float rampValue = (float)System.Math.Pow(System.Math.E, smoothTimer / rampTargetDuration) - 1f; // If close to the target value, set equal to the target value if (rampValue > 0.999f) { rampValue = 1f; smoothTimer = rampTargetDuration; } // When throttling up/down, increase/reduce the amount by adding/subtracting the proportion to be reduced from the // original throttle input amount when the timer started. currentInput = isRampDown ? startInput - throttleDeltaAmount * rampValue : startInput + throttleDeltaAmount * rampValue; previousInput = currentInput; } } } /// /// Update the CurrentPerformance value of this thruster. It gets automatically /// called when the Health is changed and when fuel or heat levels change. /// public void UpdateThrusterPerformance() { // Update the current performance value if (fuelLevel > 0f && heatLevel < 100f) { CurrentPerformance = health / startingHealth; if (heatUpRate > 0f && heatLevel >= overHeatThreshold) { CurrentPerformance *= 1f - SSCMath.Normalise(heatLevel, overHeatThreshold, 100f); } CurrentPerformance = CurrentPerformance > minPerformance ? CurrentPerformance : minPerformance; CurrentPerformance = CurrentPerformance < 1f ? CurrentPerformance : 1f; } else { CurrentPerformance = 0f; } } #endregion } }