5522 lines
283 KiB
C#
5522 lines
283 KiB
C#
|
#define _SSC_SHIP_DEBUG
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
// For Math functions instead of Mathf
|
|||
|
using System;
|
|||
|
|
|||
|
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
|
|||
|
namespace SciFiShipController
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Class containing data for a ship.
|
|||
|
/// </summary>
|
|||
|
[System.Serializable]
|
|||
|
public class Ship
|
|||
|
{
|
|||
|
#region Enumerations
|
|||
|
|
|||
|
public enum ShipPhysicsModel
|
|||
|
{
|
|||
|
PhysicsBased = 10,
|
|||
|
Arcade = 20
|
|||
|
}
|
|||
|
|
|||
|
public enum GroundNormalCalculationMode
|
|||
|
{
|
|||
|
SingleNormal = 10,
|
|||
|
SmoothedNormal = 20
|
|||
|
//AverageHeight = 30
|
|||
|
}
|
|||
|
|
|||
|
public enum RollControlMode
|
|||
|
{
|
|||
|
YawInput = 10,
|
|||
|
StrafeInput = 20
|
|||
|
}
|
|||
|
|
|||
|
public enum ShipDamageModel
|
|||
|
{
|
|||
|
Simple = 10,
|
|||
|
Progressive = 20,
|
|||
|
Localised = 30
|
|||
|
}
|
|||
|
|
|||
|
public enum RespawningMode
|
|||
|
{
|
|||
|
DontRespawn = 10,
|
|||
|
RespawnAtOriginalPosition = 20,
|
|||
|
RespawnAtLastPosition = 30,
|
|||
|
RespawnAtSpecifiedPosition = 40,
|
|||
|
RespawnOnPath = 50
|
|||
|
}
|
|||
|
|
|||
|
public enum StuckAction
|
|||
|
{
|
|||
|
DoNothing = 10,
|
|||
|
InvokeCallback = 20,
|
|||
|
RespawnOnPath = 30,
|
|||
|
SameAsRespawningMode = 50
|
|||
|
}
|
|||
|
|
|||
|
public enum InputControlAxis
|
|||
|
{
|
|||
|
None = 0,
|
|||
|
X = 1,
|
|||
|
Y = 2
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Variables and Properties
|
|||
|
|
|||
|
// IMPORTANT - when changing this section also update SetClassDefault()
|
|||
|
// Also update ClassName(ClassName className) Clone Constructor (if there is one)
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// If enabled, the InitialiseShip() will be called as soon as Awake() runs for the ship. This should be disabled if you are
|
|||
|
/// instantiating the ship through code.
|
|||
|
/// </summary>
|
|||
|
public bool initialiseOnAwake;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// What physics model the ship uses. If you modify this, call ReinitialiseShipPhysicsModel().
|
|||
|
/// Physics Based: Physics-based model. Physically realistic thrusters and aerodynamic devices are used to control the ship's motion.
|
|||
|
/// Arcade: Simplified physics model. Some physical effects are simplified and unrealistic effects can be used to enhance gameplay.
|
|||
|
/// </summary>
|
|||
|
public ShipPhysicsModel shipPhysicsModel;
|
|||
|
|
|||
|
// Physical characteristics
|
|||
|
/// <summary>
|
|||
|
/// Mass of the ship in kilograms. If you modify this, call ReinitialiseMass() on the ShipControlModule.
|
|||
|
/// </summary>
|
|||
|
public float mass;
|
|||
|
/// <summary>
|
|||
|
/// Centre of mass of the ship in local space. If you modify this, call ReinitialiseMass() on the ShipControlModule.
|
|||
|
/// </summary>
|
|||
|
public Vector3 centreOfMass;
|
|||
|
/// <summary>
|
|||
|
/// If set to true, the centre of mass will be set to the value of centreOfMass.
|
|||
|
/// Otherwise the centre of mass will be set to the unity default specified by the rigdbody.
|
|||
|
/// NOTE: This value will not affect anything if changed at runtime.
|
|||
|
/// </summary>
|
|||
|
public bool setCentreOfMassManually;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Show the centre of mass gizmos in the scene view.
|
|||
|
/// </summary>
|
|||
|
public bool showCOMGizmosInSceneView;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Show the centre of lift and lift direction gizmos in the scene view.
|
|||
|
/// </summary>
|
|||
|
public bool showCOLGizmosInSceneView;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Show the centre of forwards thrust gizmos in the scene view.
|
|||
|
/// </summary>
|
|||
|
public bool showCOTGizmosInSceneView;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// List of all thruster components for this ship. If you modify this, call ReinitialiseThrusterVariables() and ReinitialiseInputVariables().
|
|||
|
/// </summary>
|
|||
|
public List<Thruster> thrusterList;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When the ship is enabled, but ship movement is disabled, should thrusters fire
|
|||
|
/// if they have isMinEffectsAlwaysOn enabled?
|
|||
|
/// </summary>
|
|||
|
public bool isThrusterFXStationary;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Are the thruster systems online or in the process of coming online?
|
|||
|
/// At runtime call shipInstance.StartupThrusterSystems() or ShutdownThrusterSystems()
|
|||
|
/// </summary>
|
|||
|
public bool isThrusterSystemsStarted;
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, it takes for the thrusters to fully come online
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 60f)] public float thrusterSystemStartupDuration;
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, it takes for the thrusters to fully shutdown
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 60f)] public float thrusterSystemShutdownDuration;
|
|||
|
/// <summary>
|
|||
|
/// For thrusters, use a central fuel level, rather than fuel level per thruster.
|
|||
|
/// </summary>
|
|||
|
public bool useCentralFuel;
|
|||
|
/// <summary>
|
|||
|
/// The amount of fuel available when useCentralFuel is true - range 0.0 (empty) to 100.0 (full).
|
|||
|
/// At runtime call shipInstance.SetFuelLevel(..)
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 100f)] public float centralFuelLevel;
|
|||
|
/// <summary>
|
|||
|
/// List of all wing components for this ship. If you modify this, call ReinitialiseWingVariables().
|
|||
|
/// </summary>
|
|||
|
public List<Wing> wingList;
|
|||
|
/// <summary>
|
|||
|
/// List of all control surface components for this ship. If you modify this, call ReinitialiseInputVariables().
|
|||
|
/// </summary>
|
|||
|
public List<ControlSurface> controlSurfaceList;
|
|||
|
/// <summary>
|
|||
|
/// List of all weapon components for this ship. If you modify this, call ReinitialiseWeaponVariables().
|
|||
|
/// </summary>
|
|||
|
public List<Weapon> weaponList;
|
|||
|
/// <summary>
|
|||
|
/// When the ship is enabled, but movement is disabled, weapons and damage are updated
|
|||
|
/// </summary>
|
|||
|
public bool useWeaponsWhenMovementDisabled = false;
|
|||
|
/// <summary>
|
|||
|
/// [READ ONLY] The number of weapons on this ship
|
|||
|
/// </summary>
|
|||
|
public int NumberOfWeapons { get { return weaponList == null ? 0 : weaponList.Count; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the thruster systems section expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isThrusterSystemsExpanded;
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the thruster list expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isThrusterListExpanded;
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the wing list expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isWingListExpanded;
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the control surface list expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isControlSurfaceListExpanded;
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the weapon list expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isWeaponListExpanded;
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the main damage expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isMainDamageExpanded;
|
|||
|
/// <summary>
|
|||
|
/// [Internal Use] Is the local damage list expanded in the SCM Editor?
|
|||
|
/// </summary>
|
|||
|
public bool isDamageListExpanded;
|
|||
|
/// <summary>
|
|||
|
/// Whether the Damge is shown as expanded in the inspector window of the editor.
|
|||
|
/// </summary>
|
|||
|
public bool showDamageInEditor;
|
|||
|
|
|||
|
// Aerodynamic properties
|
|||
|
/// <summary>
|
|||
|
/// The drag coefficients in the local x, y and z directions. Increasing the drag coefficients will increase drag.
|
|||
|
/// </summary>
|
|||
|
public Vector3 dragCoefficients;
|
|||
|
/// <summary>
|
|||
|
/// The projected areas (in square metres) of the ship in the local x, y and z planes.
|
|||
|
/// </summary>
|
|||
|
public Vector3 dragReferenceAreas;
|
|||
|
/// <summary>
|
|||
|
/// The centre of drag for moments causing rotation along the local x-axis.
|
|||
|
/// </summary>
|
|||
|
public Vector3 centreOfDragXMoment;
|
|||
|
/// <summary>
|
|||
|
/// The centre of drag for moments causing rotation along the local y-axis.
|
|||
|
/// </summary>
|
|||
|
public Vector3 centreOfDragYMoment;
|
|||
|
/// <summary>
|
|||
|
/// The centre of drag for moments causing rotation along the local z-axis.
|
|||
|
/// </summary>
|
|||
|
public Vector3 centreOfDragZMoment;
|
|||
|
/// <summary>
|
|||
|
/// Multiplier of the effect of angular drag. Only relevant when not in physics-based mode (when in this mode it is set to the physically realistic value of 1).
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 4f)] public float angularDragFactor;
|
|||
|
/// <summary>
|
|||
|
/// When this is enabled, drag moments have no effect (i.e. drag cannot cause the ship to rotate). Only relevant when not in physics-based mode (when in this mode drag moments are always enabled).
|
|||
|
/// </summary>
|
|||
|
public bool disableDragMoments;
|
|||
|
/// <summary>
|
|||
|
/// A multiplier for drag moments causing rotation along the local (pitch) x-axis. Decreasing this will make these moments weaker.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float dragXMomentMultiplier;
|
|||
|
/// <summary>
|
|||
|
/// A multiplier for drag moments causing rotation along the local (yaw) y-axis. Decreasing this will make these moments weaker.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float dragYMomentMultiplier;
|
|||
|
/// <summary>
|
|||
|
/// A multiplier for drag moments causing rotation along the local (roll) z-axis. Decreasing this will make these moments weaker.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float dragZMomentMultiplier;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// How strong the effect of stalling is on wings (between 0 and 1). Higher values make the effect of stalling more prominent.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float wingStallEffect;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether a braking component is enabled in arcade mode.
|
|||
|
/// </summary>
|
|||
|
public bool arcadeUseBrakeComponent;
|
|||
|
/// <summary>
|
|||
|
/// The strength of the brake force in arcade mode. Only relevant when not in physics-based mode and when
|
|||
|
/// arcadeUseBrakeComponent is enabled.
|
|||
|
/// </summary>
|
|||
|
public float arcadeBrakeStrength;
|
|||
|
/// <summary>
|
|||
|
/// Whether the strength of the brake force ignores the density of the medium the ship is in (assuming it to be a constant
|
|||
|
/// value of one kilogram per cubic metre). Only relevant when not in physics-based mode and when arcadeUseBrakeComponent
|
|||
|
/// is enabled.
|
|||
|
/// </summary>
|
|||
|
public bool arcadeBrakeIgnoreMediumDensity;
|
|||
|
/// <summary>
|
|||
|
/// The minimum braking acceleration (in metres per second) caused by the brake when the brake is fully engaged. Increase this
|
|||
|
/// value to make the ship come to a stop more quickly at low speeds. Only relevant when not in physics-based mode and when
|
|||
|
/// arcadeUseBrakeComponent is enabled.
|
|||
|
/// </summary>
|
|||
|
public float arcadeBrakeMinAcceleration;
|
|||
|
|
|||
|
// Input modulation
|
|||
|
/// <summary>
|
|||
|
/// Strength of the rotational flight assist. Set to zero to disable.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 10f)] public float rotationalFlightAssistStrength;
|
|||
|
/// <summary>
|
|||
|
/// Strength of the translational flight assist. Set to zero to disable.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 10f)] public float translationalFlightAssistStrength;
|
|||
|
/// <summary>
|
|||
|
/// Strength of the stability flight assist. Set to zero to disable.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 10f)] public float stabilityFlightAssistStrength;
|
|||
|
/// <summary>
|
|||
|
/// Strength of the brake flight assist. Set to zero to disable [Default = 0]
|
|||
|
/// Operates on Forward and Backward movements when there is no ship input.
|
|||
|
/// Is overridden in forwards direction when AutoCruise is enabled on PlayerInputModule.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 10f)] public float brakeFlightAssistStrength;
|
|||
|
/// <summary>
|
|||
|
/// The effective minimum speed in m/s that the brake flight assist will operate
|
|||
|
/// </summary>
|
|||
|
[Range(-1000f, 1000f)] public float brakeFlightAssistMinSpeed;
|
|||
|
/// <summary>
|
|||
|
/// The effective maximum speed in m/s that the brake flight assist will operate
|
|||
|
/// </summary>
|
|||
|
[Range(-1000f, 1000f)] public float brakeFlightAssistMaxSpeed;
|
|||
|
/// <summary>
|
|||
|
/// How much power of the ship's roll thrusters is used to execute roll maneuvers.
|
|||
|
/// Increasing this value will increase the roll speed and responsiveness of the ship.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float rollPower;
|
|||
|
/// <summary>
|
|||
|
/// How much power of the ship's roll thrusters is used to execute pitching maneuvers.
|
|||
|
/// Increasing this value will increase the pitch speed and responsiveness of the ship.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float pitchPower;
|
|||
|
/// <summary>
|
|||
|
/// How much power of the ship's roll thrusters is used to execute yaw maneuvers.
|
|||
|
/// Increasing this value will increase the yaw speed and responsiveness of the ship.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float yawPower;
|
|||
|
/// <summary>
|
|||
|
/// How much steering inputs are prioritised over lateral inputs for thrusters.
|
|||
|
/// A value of 0 means no prioritisation takes place, while a value of 1 will almost completely deactivate relevant lateral
|
|||
|
/// thrusters whenever any opposing steering input at all is applied.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float steeringThrusterPriorityLevel;
|
|||
|
/// <summary>
|
|||
|
/// Whether pitch and roll is limited within a certain range.
|
|||
|
/// </summary>
|
|||
|
public bool limitPitchAndRoll;
|
|||
|
/// <summary>
|
|||
|
/// The maximum allowed pitch (in degrees). Only relevant when limitPitchAndRoll is set to true.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 75f)] public float maxPitch;
|
|||
|
/// <summary>
|
|||
|
/// The maximum allowed roll (in degrees) when turning. Only relevant when limitPitchAndRoll is set to true.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 75f)] public float maxTurnRoll;
|
|||
|
/// <summary>
|
|||
|
/// How quickly the ship pitches (in degrees per second) when pitch and roll is limited to a certain range. Only relevant when limitPitchAndRoll is set to true.
|
|||
|
/// </summary>
|
|||
|
[Range(10f, 90f)] public float pitchSpeed;
|
|||
|
/// <summary>
|
|||
|
/// How quickly the ship rolls (in degrees per second) when pitch and roll is limited to a certain range. Only relevant when limitPitchAndRoll is set to true.
|
|||
|
/// </summary>
|
|||
|
[Range(10f, 90f)] public float turnRollSpeed;
|
|||
|
/// <summary>
|
|||
|
/// The mode currently being used for controlling roll. Only relevant when limitPitchAndRoll is set to true.
|
|||
|
/// Yaw Input: The ship's roll is dependent on the yaw input.
|
|||
|
/// Strafe Input: The ship's roll is dependent on the left-right strafe input.
|
|||
|
/// </summary>
|
|||
|
public RollControlMode rollControlMode;
|
|||
|
/// <summary>
|
|||
|
/// How quickly the ship pitches and rolls to match the target pitch and roll. Only relevant when limitPitchAndRoll is set to true.
|
|||
|
/// </summary>
|
|||
|
[Range(1f, 10f)] public float pitchRollMatchResponsiveness;
|
|||
|
/// <summary>
|
|||
|
/// Whether the ship will attempt to maintain a constant distance from the detectable ground surface.
|
|||
|
/// </summary>
|
|||
|
public bool stickToGroundSurface;
|
|||
|
/// <summary>
|
|||
|
/// Helps the ship to avoid crashing into the ground below it
|
|||
|
/// </summary>
|
|||
|
public bool avoidGroundSurface;
|
|||
|
/// <summary>
|
|||
|
/// Whether the ship will orient itself upwards when a ground surface is not detectable.
|
|||
|
/// </summary>
|
|||
|
public bool orientUpInAir;
|
|||
|
/// <summary>
|
|||
|
/// The distance from the ground the ship will attempt to maintain. Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// </summary>
|
|||
|
public float targetGroundDistance;
|
|||
|
/// <summary>
|
|||
|
/// Whether the movement used for matching the Target Distance from the ground is smoothed. Enable this to prevent the ship
|
|||
|
/// from accelerating too quickly when near the Target Distance. Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// If you modify this, call ReinitialiseGroundMatchVariables().
|
|||
|
/// </summary>
|
|||
|
public bool useGroundMatchSmoothing;
|
|||
|
/// <summary>
|
|||
|
/// Whether the ground match algorithm "looks ahead" to detect obstacles ahead of the ship.
|
|||
|
/// Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// </summary>
|
|||
|
public bool useGroundMatchLookAhead;
|
|||
|
/// <summary>
|
|||
|
/// The minimum distance from the ground the ship will attempt to maintain. Only relevant when stickToGroundSurface and
|
|||
|
/// useGroundMatchSmoothing is set to true.
|
|||
|
/// </summary>
|
|||
|
public float minGroundDistance;
|
|||
|
/// <summary>
|
|||
|
/// The minimum distance from the ground the ship will attempt to maintain. Only relevant when stickToGroundSurface and
|
|||
|
/// useGroundMatchSmoothing is set to true.
|
|||
|
/// </summary>
|
|||
|
public float maxGroundDistance;
|
|||
|
/// <summary>
|
|||
|
/// The maximum distance to check for the ground from the bottom of the ship via raycast.
|
|||
|
/// Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 100f)] public float maxGroundCheckDistance;
|
|||
|
/// <summary>
|
|||
|
/// How responsive the ship is to sudden changes in the distance to the ground.
|
|||
|
/// Increasing this value will allow the ship to match the Target Distance more closely but may lead to a juddering effect
|
|||
|
/// if increased too much. Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// If you modify this, call ReinitialiseGroundMatchVariables().
|
|||
|
/// </summary>
|
|||
|
[Range(1f, 100f)] public float groundMatchResponsiveness;
|
|||
|
/// <summary>
|
|||
|
/// How much the up/down motion of the ship is damped when attempting to match the Target Distance.
|
|||
|
/// Increasing this value will reduce overshoot but may make the movement too rigid if increased too much.
|
|||
|
/// Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// If you modify this, call ReinitialiseGroundMatchVariables().
|
|||
|
/// </summary>
|
|||
|
[Range(1f, 100f)] public float groundMatchDamping;
|
|||
|
/// <summary>
|
|||
|
/// The limit to how quickly the ship can accelerate to maintain the Target Distance to the ground.
|
|||
|
/// Increasing this value will allow the ship to match the Target Distance more closely but may look less natural.
|
|||
|
/// If you modify this, call ReinitialiseGroundMatchVariables().
|
|||
|
/// </summary>
|
|||
|
[Range(1f, 4f)] public float maxGroundMatchAccelerationFactor;
|
|||
|
/// <summary>
|
|||
|
/// The limit (when at the Target Distance from the ground) to how quickly the ship can accelerate to maintain the Target
|
|||
|
/// Distance to the ground. Increasing this value will allow the ship to match the Target Distance more closely but may
|
|||
|
/// look less natural. Only relevant when stickToGroundSurface and useGroundMatchSmoothing is set to true.
|
|||
|
/// If you modify this, call ReinitialiseGroundMatchVariables().
|
|||
|
/// </summary>
|
|||
|
public float centralMaxGroundMatchAccelerationFactor;
|
|||
|
/// <summary>
|
|||
|
/// How the normal direction (orientation) is determined.
|
|||
|
/// When Single Normal is selected, the single normal of each face of the ground geometry is used.
|
|||
|
/// When Smoothed Normal is selected, the normals on each vertex of the face of the ground geometry are blended together to give a smoothed normal, which is used instead.
|
|||
|
/// Smoothed Normal is more computationally expensive.
|
|||
|
/// </summary>
|
|||
|
public GroundNormalCalculationMode groundNormalCalculationMode;
|
|||
|
/// <summary>
|
|||
|
/// The number of past frames (including this frame) used to average ground normals over. Increase this value to make pitch and roll
|
|||
|
/// movement smoother. Decrease this value to make pitch and roll movement more responsive to changes in the ground
|
|||
|
/// surface. Only relevant when stickToGroundSurface is set to true.
|
|||
|
/// If you modify this, call ReinitialiseGroundMatchVariables().
|
|||
|
/// </summary>
|
|||
|
[Range(1, 50)] public int groundNormalHistoryLength;
|
|||
|
/// <summary>
|
|||
|
/// Layermask determining which layers will be detected as part of the ground surface when raycasted against.
|
|||
|
/// </summary>
|
|||
|
public LayerMask groundLayerMask;
|
|||
|
/// <summary>
|
|||
|
/// The input axis to be controlled or limited. This is used to simulate 2.5D flight.
|
|||
|
/// </summary>
|
|||
|
public InputControlAxis inputControlAxis;
|
|||
|
/// <summary>
|
|||
|
/// The target value of the inputControlAxis.
|
|||
|
/// </summary>
|
|||
|
public float inputControlLimit;
|
|||
|
/// <summary>
|
|||
|
/// The rate at which force is applied to limit control
|
|||
|
/// </summary>
|
|||
|
[Range(0.1f, 100f)] public float inputControlMovingRigidness;
|
|||
|
/// <summary>
|
|||
|
/// The rate at which the ship turns to limit or correct the rotation
|
|||
|
/// </summary>
|
|||
|
[Range(0.1f, 100f)] public float inputControlTurningRigidness;
|
|||
|
/// <summary>
|
|||
|
/// Forward X angle for the plane the ship will fly in
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 180f)] public float inputControlForwardAngle;
|
|||
|
|
|||
|
// Arcade physics properties
|
|||
|
/// <summary>
|
|||
|
/// How fast the ship accelerates when pitching up and down in degrees per second squared.
|
|||
|
/// Increasing this value will increase how fast the ship can be pitched up and down by pilot and rotational flight assist inputs.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 500f)] public float arcadePitchAcceleration;
|
|||
|
/// <summary>
|
|||
|
/// How fast the ship accelerates when turning left and right in degrees per second squared.
|
|||
|
/// Increasing this value will increase how fast the ship can be turned left and right by pilot and rotational flight assist inputs.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 500f)] public float arcadeYawAcceleration;
|
|||
|
/// <summary>
|
|||
|
/// How fast the ship accelerates when rolling left and right in degrees per second squared.
|
|||
|
/// Increasing this value will increase how fast the ship can be rolled left and right by pilot and rotational flight assist inputs.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 500f)] public float arcadeRollAcceleration;
|
|||
|
/// <summary>
|
|||
|
/// How quickly the ship accelerates in metres per second squared while in the air to move in the direction the ship is facing.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1000f)] public float arcadeMaxFlightTurningAcceleration;
|
|||
|
/// <summary>
|
|||
|
/// How quickly the ship accelerates in metres per second squared while near the ground to move in the direction the ship is facing.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1000f)] public float arcadeMaxGroundTurningAcceleration;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
|
|||
|
/// </summary>
|
|||
|
public Vector3 pilotForceInput { private get; set; }
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
|
|||
|
/// </summary>
|
|||
|
public Vector3 pilotMomentInput { private get; set; }
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
|
|||
|
/// </summary>
|
|||
|
public bool pilotPrimaryFireInput { private get; set; }
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY] shouldn't be called directly, call SendInput in the ship control module instead
|
|||
|
/// </summary>
|
|||
|
public bool pilotSecondaryFireInput { private get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The density (in kilograms per cubic metre) of the fluid the ship is travelling through (usually air).
|
|||
|
/// </summary>
|
|||
|
public float mediumDensity;
|
|||
|
/// <summary>
|
|||
|
/// The magnitude of the acceleration (in metres per second squared) due to gravity.
|
|||
|
/// </summary>
|
|||
|
public float gravitationalAcceleration;
|
|||
|
/// <summary>
|
|||
|
/// The direction in world space in which gravity acts upon the ship.
|
|||
|
/// </summary>
|
|||
|
public Vector3 gravityDirection;
|
|||
|
|
|||
|
// TODO: Update comment when adding more damage models
|
|||
|
/// <summary>
|
|||
|
/// What damage model the ship uses.
|
|||
|
/// Simple: Simplistic damage model with a single health value. Effects of damage are only visual until the ship is destroyed.
|
|||
|
/// Progressive: More complex damage model. As the ship takes damage the performance of parts is affected.
|
|||
|
/// Localised: More complex damage model. Similar to progressive, but different parts can be damaged independently of each other.
|
|||
|
/// If you modify this, call ReinitialiseDamageRegionVariables().
|
|||
|
/// </summary>
|
|||
|
public ShipDamageModel shipDamageModel;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Damage Region used in the simple and progressive damage models.
|
|||
|
/// If you modify this, call ReinitialiseDamageRegionVariables().
|
|||
|
/// NOTE: Not required if just changing the Health or ShieldHealth
|
|||
|
/// </summary>
|
|||
|
public DamageRegion mainDamageRegion;
|
|||
|
/// <summary>
|
|||
|
/// List of Damage Regions used in the localised damage model.
|
|||
|
/// If you modify this, call ReinitialiseDamageRegionVariables().
|
|||
|
/// NOTE: Not required if just changing the Health or ShieldHealth
|
|||
|
/// </summary>
|
|||
|
public List<DamageRegion> localisedDamageRegionList;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether damage multipliers are used when calculating damage from projectiles.
|
|||
|
/// </summary>
|
|||
|
public bool useDamageMultipliers;
|
|||
|
/// <summary>
|
|||
|
/// Whether damage multipliers are localised (i.e. there are different sets of damage multipliers for each damage region
|
|||
|
/// of the ship). Only relevant when useDamageMultipliers is set to true and shipDamageModel is set to Localised.
|
|||
|
/// </summary>
|
|||
|
public bool useLocalisedDamageMultipliers;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY]
|
|||
|
/// Normalised (0.0 – 1.0) value of the overall health of the ship. To get the actual health values, see the damageRegions of the ship.
|
|||
|
/// </summary>
|
|||
|
public float HealthNormalised
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
float _healthN = mainDamageRegion.startingHealth == 0f ? 0f : mainDamageRegion.Health / mainDamageRegion.startingHealth;
|
|||
|
if (_healthN > 1f) { return 1f; }
|
|||
|
else if (_healthN < 0f) { return 0f; }
|
|||
|
else { return _healthN; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// How respawning happens. If you change this to RespawningMode.RespawnAtLastPosition, call ReinitialiseRespawnVariables().
|
|||
|
/// </summary>
|
|||
|
public RespawningMode respawningMode;
|
|||
|
/// <summary>
|
|||
|
/// How long the respawning process takes (in seconds). Only relevant when respawningMode is not set to DontRespawn.
|
|||
|
/// </summary>
|
|||
|
public float respawnTime;
|
|||
|
/// <summary>
|
|||
|
/// The time (in seconds) between updates of the collision respawn position. Hence when the ship is destroyed by colliding with
|
|||
|
/// something, the ship respawn position will be where the ship was between this time ago and twice this time ago. Only
|
|||
|
/// relevant when respawningMode is set to RespawnAtLastPosition.
|
|||
|
/// </summary>
|
|||
|
public float collisionRespawnPositionDelay;
|
|||
|
/// <summary>
|
|||
|
/// Where the ship respawns from in world space when respawningMode is set to RespawnFromSpecifiedPosition.
|
|||
|
/// </summary>
|
|||
|
public Vector3 customRespawnPosition;
|
|||
|
/// <summary>
|
|||
|
/// The rotation the ship respawns from in world space when respawningMode is set to RespawnFromSpecifiedPosition.
|
|||
|
/// </summary>
|
|||
|
public Vector3 customRespawnRotation;
|
|||
|
/// <summary>
|
|||
|
/// The velocity the ship respawns with in local space. Only relevant when respawningMode is not set to DontRespawn.
|
|||
|
/// </summary>
|
|||
|
public Vector3 respawnVelocity;
|
|||
|
/// <summary>
|
|||
|
/// guidHash code of the Path which the ship will be respawned on when respawningMode is RespawnOnPath.
|
|||
|
/// </summary>
|
|||
|
public int respawningPathGUIDHash;
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The current respawn position
|
|||
|
/// </summary>
|
|||
|
public Vector3 RespawnPosition { get { return currentRespawnPosition; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Whether controller rumble is applied to the ship by the ship control module.
|
|||
|
/// </summary>
|
|||
|
public bool applyControllerRumble = false;
|
|||
|
/// <summary>
|
|||
|
/// The minimum amount of damage that will cause controller rumble.
|
|||
|
/// </summary>
|
|||
|
public float minRumbleDamage = 0f;
|
|||
|
/// <summary>
|
|||
|
/// The amount of damage corresponding to maximum controller rumble.
|
|||
|
/// </summary>
|
|||
|
public float maxRumbleDamage = 10f;
|
|||
|
/// <summary>
|
|||
|
/// The time (in seconds) that a controller rumble event lasts for.
|
|||
|
/// </summary>
|
|||
|
[Range(0.1f, 5f)] public float damageRumbleTime = 0.5f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The amount of time that needs to elapse before a stationary ship is considered stuck.
|
|||
|
/// When the value is 0, a stationary ship is never considered stuck.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 300)] public float stuckTime;
|
|||
|
/// <summary>
|
|||
|
/// The maximum speed in m/sec the ship can be moving before it can be considered stuck
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 1f)] public float stuckSpeedThreshold;
|
|||
|
/// <summary>
|
|||
|
/// The action to take when the ship is deemed stationary or stuck.
|
|||
|
/// </summary>
|
|||
|
public StuckAction stuckAction;
|
|||
|
/// <summary>
|
|||
|
/// guidHash code of the Path which the ship will be respawned on when it is stuck
|
|||
|
/// and the StuckAction is RespawnOnPath.
|
|||
|
/// </summary>
|
|||
|
public int stuckActionPathGUIDHash;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The world velocity of the ship as a vector. Derived from the velocity of the rigidbody.
|
|||
|
/// </summary>
|
|||
|
public Vector3 WorldVelocity { get { return worldVelocity; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The local velocity of the ship as a vector. Derived from the velocity of the rigidbody.
|
|||
|
/// </summary>
|
|||
|
public Vector3 LocalVelocity { get { return localVelocity; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The world angular velocity of the ship as a vector. Derived from the angular velocity of the rigidbody.
|
|||
|
/// </summary>
|
|||
|
public Vector3 WorldAngularVelocity { get { return worldAngularVelocity; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The local angular velocity of the ship as a vector. Derived from the angular velocity of the rigidbody.
|
|||
|
/// </summary>
|
|||
|
public Vector3 LocalAngularVelocity { get { return localAngularVelocity; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The pitch angle, in degrees, above or below the artificial horizon
|
|||
|
/// </summary>
|
|||
|
public float PitchAngle { get { return Vector3.SignedAngle(new Vector3(trfmFwd.x, 0f, trfmFwd.z), trfmFwd, Vector3.right) * Mathf.Sign(-Vector3.Dot(Vector3.forward, trfmFwd)); } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The roll angle, in degrees, left (-ve) or right (+ve)
|
|||
|
/// </summary>
|
|||
|
public float RollAngle { get { Vector3 _localUpNormal = trfmInvRot * Vector3.up; return -Mathf.Atan2(_localUpNormal.x, _localUpNormal.y) * Mathf.Rad2Deg; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rigidbody position of the ship as a vector. Derived from the position of the rigidbody. This is
|
|||
|
/// where the physics engine says the ship is.
|
|||
|
/// </summary>
|
|||
|
public Vector3 RigidbodyPosition { get { return rbodyPos; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rigidbody rotation of the ship as a vector. Derived from the rotation of the rigidbody. This is
|
|||
|
/// where the physics engine says the ship is rotated.
|
|||
|
/// </summary>
|
|||
|
public Quaternion RigidbodyRotation { get { return rbodyRot; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rigidbody inverse rotation of the ship as a vector. Derived from the rotation of the rigidbody. This is
|
|||
|
/// the inverse of where the physics engine says the ship is rotated.
|
|||
|
/// </summary>
|
|||
|
public Quaternion RigidbodyInverseRotation { get { return rbodyInvRot; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rigidbody forward direction of the ship as a vector. Derived from the rotation of the rigidbody. This is
|
|||
|
/// the direction the physics engine says the ship is facing.
|
|||
|
/// </summary>
|
|||
|
public Vector3 RigidbodyForward { get { return rbodyFwd; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rigidbody right direction of the ship as a vector. Derived from the rotation of the rigidbody. This is
|
|||
|
/// the direction the physics engine says the ship's right direction is facing.
|
|||
|
/// </summary>
|
|||
|
public Vector3 RigidbodyRight { get { return rbodyRight; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rigidbody up direction of the ship as a vector. Derived from the rotation of the rigidbody. This is
|
|||
|
/// the direction the physics engine says the ship's up direction is facing.
|
|||
|
/// </summary>
|
|||
|
public Vector3 RigidbodyUp { get { return rbodyUp; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The position of the ship as a vector. Derived from the position of the transform. You should use
|
|||
|
/// RigidbodyPosition instead if you are using the data for physics calculations.
|
|||
|
/// </summary>
|
|||
|
public Vector3 TransformPosition { get { return trfmPos; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The forward direction of the ship in world space as a vector. Derived from the forward direction of the transform.
|
|||
|
/// You should use RigidbodyForward instead if you are using the data for physics calculations.
|
|||
|
/// </summary>
|
|||
|
public Vector3 TransformForward { get { return trfmFwd; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The right direction of the ship in world space as a vector. Derived from the right direction of the transform.
|
|||
|
/// You should use RigidbodyRight instead if you are using the data for physics calculations.
|
|||
|
/// </summary>
|
|||
|
public Vector3 TransformRight { get { return trfmRight; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The up direction of the ship in world space as a vector. Derived from the up direction of the transform.
|
|||
|
/// You should use RigidbodyUp instead if you are using the data for physics calculations.
|
|||
|
/// </summary>
|
|||
|
public Vector3 TransformUp { get { return trfmUp; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The rotation of the ship in world space as a quaternion. Derived from the rotation of the transform.
|
|||
|
/// You should use RigidbodyRotation instead if you are using the data for physics calculations.
|
|||
|
/// </summary>
|
|||
|
public Quaternion TransformRotation { get { return trfmRot; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The inverse rotation of the ship in world space as a quaternion. Derived from the rotation of the transform.
|
|||
|
/// You should use RigidbodyInverseRotation instead if you are using the data for physics calculations.
|
|||
|
/// </summary>
|
|||
|
public Quaternion TransformInverseRotation { get { return trfmInvRot; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] Whether the ship is currently sticking to a ground surface.
|
|||
|
/// </summary>
|
|||
|
public bool IsGrounded { get { return stickToGroundSurfaceEnabled; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The current normal of the target plane in world space. This is the upwards direction the ship will attempt to
|
|||
|
/// orient itself to if limit pitch and roll is enabled.
|
|||
|
/// </summary>
|
|||
|
public Vector3 WorldTargetPlaneNormal { get { return worldTargetPlaneNormal; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The faction or alliance the ship belongs to. This can be used to identify if a ship is friend or foe.
|
|||
|
/// Default (neutral) is 0.
|
|||
|
/// </summary>
|
|||
|
public int factionId;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The squadron this ship is a member of.
|
|||
|
/// </summary>
|
|||
|
public int squadronId;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The relative size of the blip on the radar mini-map
|
|||
|
/// </summary>
|
|||
|
[Range(1, 5)] public byte radarBlipSize;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY] Instead, call shipControlModule.EnableRadar() or DisableRadar().
|
|||
|
/// </summary>
|
|||
|
public bool isRadarEnabled;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READONLY] The number used by the SSCRadar system to identify this ship at a point in time.
|
|||
|
/// This should not be stored across frames and is updated as required by the system.
|
|||
|
/// </summary>
|
|||
|
public int RadarId { get { return radarItemIndex; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] public SSCRadarPacket sscRadarPacket;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Session-only transform InstanceID
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] public int shipId;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Session-only estimated maximum range of turret weapons
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] public float estimatedMaxTurretRange;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Session-only estimated maximum range of all weapons with isAutoTargetingEnabled = true
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] public float estimatedMaxAutoTargetRange;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Delegates
|
|||
|
|
|||
|
public delegate void CallbackOnDamage(float mainDamageRegionHealth);
|
|||
|
public delegate void CallbackOnCameraShake(float shakeAmountNormalised);
|
|||
|
public delegate void CallbackOnWeaponFired(Ship ship, Weapon weapon);
|
|||
|
public delegate void CallbackOnWeaponNoAmmo(Ship ship, Weapon weapon);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The name of the custom method that is called immediately
|
|||
|
/// after damage has changed. Your method must take 1 float
|
|||
|
/// parameter. This should be a lightweight method to avoid
|
|||
|
/// performance issues. It could be used to update a HUD or
|
|||
|
/// take some other action.
|
|||
|
/// </summary>
|
|||
|
[NonSerialized] public CallbackOnDamage callbackOnDamage = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Generally reserved for internal use by ShipCameraModule. If you use your own
|
|||
|
/// camera scripts, you can create a lightweight custom method and assign it at
|
|||
|
/// runtime so that this is called whenever camera shake data is available.
|
|||
|
/// </summary>
|
|||
|
[NonSerialized] public CallbackOnCameraShake callbackOnCameraShake = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The name of the custom method that is called immediately after
|
|||
|
/// a weapon has fired.
|
|||
|
/// This should be a lightweight method to avoid performance issues that
|
|||
|
/// doesn't hold references the ship or weapon past the end of the current frame.
|
|||
|
/// </summary>
|
|||
|
[NonSerialized] public CallbackOnWeaponFired callbackOnWeaponFired = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The name of the custom method that is called immediately after the weapon
|
|||
|
/// runs out of ammunition. Your method must take a ship and weapon parameter.
|
|||
|
/// This should be a lightweight method to avoid performance issues that
|
|||
|
/// doesn't hold references the ship or weapon past the end of the current frame.
|
|||
|
/// </summary>
|
|||
|
[NonSerialized] public CallbackOnWeaponNoAmmo callbackOnWeaponNoAmmo = null;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private and Internal Variables
|
|||
|
|
|||
|
#region Private Variables - Cached
|
|||
|
private Vector3 worldVelocity;
|
|||
|
private Vector3 localVelocity;
|
|||
|
private Vector3 absLocalVelocity;
|
|||
|
private Vector3 worldAngularVelocity;
|
|||
|
private Vector3 localAngularVelocity;
|
|||
|
private Vector3 trfmUp;
|
|||
|
private Vector3 trfmFwd;
|
|||
|
private Vector3 trfmRight;
|
|||
|
private Vector3 trfmPos;
|
|||
|
private Vector3 rbodyPos;
|
|||
|
private Vector3 rbodyUp;
|
|||
|
private Vector3 rbodyFwd;
|
|||
|
private Vector3 rbodyRight;
|
|||
|
private Quaternion trfmRot;
|
|||
|
private Quaternion trfmInvRot;
|
|||
|
private Quaternion rbodyRot;
|
|||
|
private Quaternion rbodyInvRot;
|
|||
|
private Vector3 rBodyInertiaTensor;
|
|||
|
private bool shipPhysicsModelPhysicsBased;
|
|||
|
private bool shipDamageModelIsSimple;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Timing related
|
|||
|
private float lastFixedUpdateTime = 0f;
|
|||
|
private Vector3 previousFrameWorldVelocity = Vector3.zero;
|
|||
|
internal float thrusterSystemStartupTimer = 0f;
|
|||
|
internal float thrusterSystemShutdownTimer = 0f;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Cached input
|
|||
|
// Is each input being used on the ship?
|
|||
|
private bool longitudinalThrustInputUsed;
|
|||
|
private bool horizontalThrustInputUsed;
|
|||
|
private bool verticalThrustInputUsed;
|
|||
|
private bool pitchMomentInputUsed;
|
|||
|
private bool yawMomentInputUsed;
|
|||
|
private bool rollMomentInputUsed;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Input
|
|||
|
private bool limitPitchAndRollEnabled = false;
|
|||
|
private bool stickToGroundSurfaceEnabled = false;
|
|||
|
private Vector3 forceInput = Vector3.zero;
|
|||
|
private Vector3 momentInput = Vector3.zero;
|
|||
|
private float pitchAmountInput = 0f, rollAmountInput = 0f;
|
|||
|
private float pitchAmount = 0f, rollAmount = 0f;
|
|||
|
private float maxFramePitchDelta, maxFrameRollDelta;
|
|||
|
private float targetLocalPitch, targetLocalRoll;
|
|||
|
private float yawVelocity;
|
|||
|
private float flightAssistValue;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Thruster
|
|||
|
private Thruster thruster;
|
|||
|
private Vector3 thrustForce = Vector3.zero;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Aerodynamics
|
|||
|
private float dragLength, dragWidth, dragHeight;
|
|||
|
private float quarticLengthXProj, quarticLengthYProj;
|
|||
|
private float quarticWidthYProj, quarticWidthZProj;
|
|||
|
private float quarticHeightXProj, quarticHeightZProj;
|
|||
|
private Vector3 localDragForce;
|
|||
|
private Vector3 localLiftForce;
|
|||
|
private Vector3 localInducedDragForce;
|
|||
|
private float liftForceMagnitude;
|
|||
|
private Wing wing;
|
|||
|
private Vector3 wingPlaneNormal = Vector3.right;
|
|||
|
private Vector3 localVeloInWingPlane = Vector3.zero;
|
|||
|
private float wingAngleOfAttack = 0f;
|
|||
|
private float angularDragMultiplier;
|
|||
|
private ControlSurface controlSurface;
|
|||
|
private float aerodynamicTrueAirspeed;
|
|||
|
private float liftCoefficient = 0f, phi0, phi1, phi2, phi3;
|
|||
|
private Vector3 controlSurfaceMovementAxis = Vector3.up;
|
|||
|
private float controlSurfaceInput = 0f, controlSurfaceAngle;
|
|||
|
private float controlSurfaceLiftDelta, controlSurfaceDragDelta;
|
|||
|
private Vector3 controlSurfaceRelativePosition;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Gravity
|
|||
|
private Vector3 localGravityAcceleration;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Arcade physics
|
|||
|
/// <summary>
|
|||
|
/// Note that this is actually an acceleration.
|
|||
|
/// </summary>
|
|||
|
private Vector3 arcadeMomentVector = Vector3.zero;
|
|||
|
/// <summary>
|
|||
|
/// Note that this is actually an acceleration.
|
|||
|
/// </summary>
|
|||
|
private Vector3 arcadeForceVector = Vector3.zero;
|
|||
|
private float arcadeBrakeForceMagnitude = 0f;
|
|||
|
private float arcadeBrakeForceMinMagnitude = 0f;
|
|||
|
private float maxGroundMatchAcceleration = 0f;
|
|||
|
private float centralMaxGroundMatchAcceleration = 0f;
|
|||
|
// Temp ground distance input required
|
|||
|
private float groundDistInput = 0f;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Combat variables
|
|||
|
private Weapon weapon;
|
|||
|
private Vector3 weaponRelativeFirePosition = Vector3.zero;
|
|||
|
private Vector3 weaponRelativeFireDirection = Vector3.zero;
|
|||
|
private Vector3 weaponWorldBasePosition = Vector3.zero;
|
|||
|
private Vector3 weaponWorldFirePosition = Vector3.zero;
|
|||
|
private Vector3 weaponWorldFireDirection = Vector3.zero;
|
|||
|
private Vector3 currentRespawnPosition = Vector3.zero;
|
|||
|
private Quaternion currentRespawnRotation = Quaternion.identity;
|
|||
|
private Vector3 currentCollisionRespawnPosition = Vector3.zero;
|
|||
|
private Vector3 nextCollisionRespawnPosition = Vector3.zero;
|
|||
|
private Quaternion currentCollisionRespawnRotation = Quaternion.identity;
|
|||
|
private Quaternion nextCollisionRespawnRotation = Quaternion.identity;
|
|||
|
private float collisionRespawnPositionTimer = 0f;
|
|||
|
private bool lastDamageWasCollisionDamage = false;
|
|||
|
private int weaponFiringButtonInt;
|
|||
|
private int weaponPrimaryFiringInt, weaponSecondaryFiringInt, weaponAutoFiringInt;
|
|||
|
private int weaponTypeInt;
|
|||
|
private int lastDamageEventIndex = -1;
|
|||
|
private float damageRumbleAmountRequired = 0f;
|
|||
|
private float damageCameraShakeAmountRequired = 0f;
|
|||
|
|
|||
|
// Damage Regions
|
|||
|
[System.NonSerialized] internal int numLocalisedDamageRegions;
|
|||
|
|
|||
|
// Radar variables
|
|||
|
[System.NonSerialized] internal int radarItemIndex = -1;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Collision
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Unordered unique set of colliders on objects that have been attached to this ship.
|
|||
|
/// These are ignored during collision events
|
|||
|
/// </summary>
|
|||
|
[NonSerialized] private HashSet<int> attachedCollisionColliders = new HashSet<int>();
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - PID Controllers
|
|||
|
private PIDController pitchPIDController;
|
|||
|
private PIDController rollPIDController;
|
|||
|
private PIDController groundDistPIDController;
|
|||
|
private PIDController rFlightAssistPitchPIDController;
|
|||
|
private PIDController rFlightAssistYawPIDController;
|
|||
|
private PIDController rFlightAssistRollPIDController;
|
|||
|
private PIDController inputAxisForcePIDController;
|
|||
|
private PIDController inputAxisMomentPIDController;
|
|||
|
private PIDController sFlightAssistPitchPIDController;
|
|||
|
private PIDController sFlightAssistYawPIDController;
|
|||
|
private PIDController sFlightAssistRollPIDController;
|
|||
|
private float sFlightAssistTargetPitch = 0f;
|
|||
|
private float sFlightAssistTargetYaw = 0f;
|
|||
|
private float sFlightAssistTargetRoll = 0f;
|
|||
|
private bool sFlightAssistRotatingPitch = false;
|
|||
|
private bool sFlightAssistRotatingYaw = false;
|
|||
|
private bool sFlightAssistRotatingRoll = false;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Variables - Normals
|
|||
|
private Vector3 worldTargetPlaneNormal = Vector3.up;
|
|||
|
private Vector3 localTargetPlaneNormal = Vector3.up;
|
|||
|
private float currentPerpendicularGroundDist = -1f;
|
|||
|
private MeshCollider groundMeshCollider;
|
|||
|
private MeshCollider cachedGroundMeshCollider;
|
|||
|
private Mesh cachedGroundMesh;
|
|||
|
private Vector3[] cachedGroundMeshNormals;
|
|||
|
private int[] cachedGroundMeshTriangles;
|
|||
|
private Vector3 groundNormal0;
|
|||
|
private Vector3 groundNormal1;
|
|||
|
private Vector3 groundNormal2;
|
|||
|
private Vector3 barycentricCoord;
|
|||
|
private Vector3[] groundNormalHistory;
|
|||
|
private int groundNormalHistoryIdx = 0;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private and Internal Variables - Misc
|
|||
|
|
|||
|
private int componentIndex;
|
|||
|
private int componentListSize;
|
|||
|
|
|||
|
private Ray raycastRay;
|
|||
|
private RaycastHit raycastHitInfo;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL ONLY]
|
|||
|
/// Added in 1.3.3 to support muzzle FX parenting.
|
|||
|
/// MAY NEED TO BE REMOVED IN THE FUTURE.
|
|||
|
/// WARNING: Not fully tested with destroying ships
|
|||
|
/// in the scene. Need to be careful it doesn't prevent
|
|||
|
/// garabage collection of ShipControlModule.
|
|||
|
/// Where possible - use the cached trfmXXX itema above.
|
|||
|
/// </summary>
|
|||
|
[System.NonSerialized] internal Transform shipTransform;
|
|||
|
|
|||
|
private SSCManager sscManager = null;
|
|||
|
|
|||
|
// Reusable lists - typically used with GetComponents or GetComponentsInChildren to avoid GC.
|
|||
|
//private List<ShipControlModule> tempShipControlModuleList;
|
|||
|
|
|||
|
// Add additional force or boost
|
|||
|
private Vector3 boostDirection;
|
|||
|
private float boostTimer;
|
|||
|
private float boostForce; // in newtons
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Constructors
|
|||
|
|
|||
|
// Class constructor
|
|||
|
public Ship()
|
|||
|
{
|
|||
|
SetClassDefaults();
|
|||
|
}
|
|||
|
|
|||
|
// Copy constructor
|
|||
|
public Ship (Ship ship)
|
|||
|
{
|
|||
|
if (ship == null) { SetClassDefaults(); }
|
|||
|
else
|
|||
|
{
|
|||
|
this.initialiseOnAwake = ship.initialiseOnAwake;
|
|||
|
this.shipPhysicsModel = ship.shipPhysicsModel;
|
|||
|
this.mass = ship.mass;
|
|||
|
this.centreOfMass = ship.centreOfMass;
|
|||
|
this.setCentreOfMassManually = ship.setCentreOfMassManually;
|
|||
|
this.showCOMGizmosInSceneView = ship.showCOMGizmosInSceneView;
|
|||
|
this.showCOLGizmosInSceneView = ship.showCOLGizmosInSceneView;
|
|||
|
this.showCOTGizmosInSceneView = ship.showCOTGizmosInSceneView;
|
|||
|
|
|||
|
if (ship.thrusterList == null)
|
|||
|
{
|
|||
|
this.thrusterList = new List<Thruster>(25);
|
|||
|
|
|||
|
// Create a default forwards-facing thruster
|
|||
|
Thruster thruster = new Thruster();
|
|||
|
if (thruster != null)
|
|||
|
{
|
|||
|
thruster.name = "Forward Thruster";
|
|||
|
thruster.forceUse = 1;
|
|||
|
thruster.primaryMomentUse = 0;
|
|||
|
thruster.secondaryMomentUse = 0;
|
|||
|
thruster.maxThrust = 100000;
|
|||
|
thruster.thrustDirection = Vector3.forward;
|
|||
|
thruster.relativePosition = Vector3.zero;
|
|||
|
this.thrusterList.Add(thruster);
|
|||
|
}
|
|||
|
}
|
|||
|
else { this.thrusterList = ship.thrusterList.ConvertAll(th => new Thruster(th)); }
|
|||
|
|
|||
|
if (ship.wingList == null) { this.wingList = new List<Wing>(5); }
|
|||
|
else { this.wingList = ship.wingList.ConvertAll(wg => new Wing(wg)); }
|
|||
|
|
|||
|
if (ship.controlSurfaceList == null) { this.controlSurfaceList = new List<ControlSurface>(10); }
|
|||
|
else { this.controlSurfaceList = ship.controlSurfaceList.ConvertAll(cs => new ControlSurface(cs)); }
|
|||
|
|
|||
|
if (ship.weaponList == null) { this.weaponList = new List<Weapon>(10); }
|
|||
|
else { this.weaponList = ship.weaponList.ConvertAll(wp => new Weapon(wp)); }
|
|||
|
|
|||
|
this.isThrusterSystemsExpanded = ship.isThrusterSystemsExpanded;
|
|||
|
this.isThrusterListExpanded = ship.isThrusterListExpanded;
|
|||
|
this.isWingListExpanded = ship.isWingListExpanded;
|
|||
|
this.isControlSurfaceListExpanded = ship.isControlSurfaceListExpanded;
|
|||
|
this.isWeaponListExpanded = ship.isWeaponListExpanded;
|
|||
|
this.isMainDamageExpanded = ship.isMainDamageExpanded;
|
|||
|
this.isDamageListExpanded = ship.isDamageListExpanded;
|
|||
|
this.showDamageInEditor = ship.showDamageInEditor;
|
|||
|
|
|||
|
this.isThrusterFXStationary = ship.isThrusterFXStationary;
|
|||
|
this.isThrusterSystemsStarted = ship.isThrusterSystemsStarted;
|
|||
|
this.thrusterSystemStartupDuration = ship.thrusterSystemStartupDuration;
|
|||
|
this.thrusterSystemShutdownDuration = ship.thrusterSystemShutdownDuration;
|
|||
|
this.useCentralFuel = ship.useCentralFuel;
|
|||
|
this.centralFuelLevel = ship.centralFuelLevel;
|
|||
|
|
|||
|
this.dragCoefficients = ship.dragCoefficients;
|
|||
|
this.dragReferenceAreas = ship.dragReferenceAreas;
|
|||
|
this.centreOfDragXMoment = ship.centreOfDragXMoment;
|
|||
|
this.centreOfDragYMoment = ship.centreOfDragYMoment;
|
|||
|
this.centreOfDragZMoment = ship.centreOfDragZMoment;
|
|||
|
this.angularDragFactor = ship.angularDragFactor;
|
|||
|
this.disableDragMoments = ship.disableDragMoments;
|
|||
|
this.dragXMomentMultiplier = ship.dragXMomentMultiplier;
|
|||
|
this.dragYMomentMultiplier = ship.dragYMomentMultiplier;
|
|||
|
this.dragZMomentMultiplier = ship.dragZMomentMultiplier;
|
|||
|
this.wingStallEffect = ship.wingStallEffect;
|
|||
|
|
|||
|
this.arcadeUseBrakeComponent = ship.arcadeUseBrakeComponent;
|
|||
|
this.arcadeBrakeStrength = ship.arcadeBrakeStrength;
|
|||
|
this.arcadeBrakeIgnoreMediumDensity = ship.arcadeBrakeIgnoreMediumDensity;
|
|||
|
this.arcadeBrakeMinAcceleration = ship.arcadeBrakeMinAcceleration;
|
|||
|
|
|||
|
this.rotationalFlightAssistStrength = ship.rotationalFlightAssistStrength;
|
|||
|
this.translationalFlightAssistStrength = ship.translationalFlightAssistStrength;
|
|||
|
this.brakeFlightAssistStrength = ship.brakeFlightAssistStrength;
|
|||
|
this.brakeFlightAssistMaxSpeed = ship.brakeFlightAssistMinSpeed;
|
|||
|
this.brakeFlightAssistMaxSpeed = ship.brakeFlightAssistMaxSpeed;
|
|||
|
|
|||
|
this.limitPitchAndRoll = ship.limitPitchAndRoll;
|
|||
|
this.maxPitch = ship.maxPitch;
|
|||
|
this.maxTurnRoll = ship.maxTurnRoll;
|
|||
|
this.pitchSpeed = ship.pitchSpeed;
|
|||
|
this.turnRollSpeed = ship.turnRollSpeed;
|
|||
|
this.rollControlMode = ship.rollControlMode;
|
|||
|
this.pitchRollMatchResponsiveness = ship.pitchRollMatchResponsiveness;
|
|||
|
this.stickToGroundSurface = ship.stickToGroundSurface;
|
|||
|
this.avoidGroundSurface = ship.avoidGroundSurface;
|
|||
|
this.useGroundMatchSmoothing = ship.useGroundMatchSmoothing;
|
|||
|
this.useGroundMatchLookAhead = ship.useGroundMatchLookAhead;
|
|||
|
this.orientUpInAir = ship.orientUpInAir;
|
|||
|
this.targetGroundDistance = ship.targetGroundDistance;
|
|||
|
this.minGroundDistance = ship.minGroundDistance;
|
|||
|
this.maxGroundDistance = ship.maxGroundDistance;
|
|||
|
this.maxGroundCheckDistance = ship.maxGroundCheckDistance;
|
|||
|
this.groundMatchResponsiveness = ship.groundMatchResponsiveness;
|
|||
|
this.groundMatchDamping = ship.groundMatchDamping;
|
|||
|
this.maxGroundMatchAccelerationFactor = ship.maxGroundMatchAccelerationFactor;
|
|||
|
this.centralMaxGroundMatchAccelerationFactor = ship.centralMaxGroundMatchAccelerationFactor;
|
|||
|
this.groundNormalCalculationMode = ship.groundNormalCalculationMode;
|
|||
|
this.groundNormalHistoryLength = ship.groundNormalHistoryLength;
|
|||
|
this.groundLayerMask = ship.groundLayerMask;
|
|||
|
|
|||
|
this.inputControlAxis = ship.inputControlAxis;
|
|||
|
this.inputControlLimit = ship.inputControlLimit;
|
|||
|
this.inputControlForwardAngle = ship.inputControlForwardAngle;
|
|||
|
this.inputControlMovingRigidness = ship.inputControlMovingRigidness;
|
|||
|
this.inputControlTurningRigidness = ship.inputControlTurningRigidness;
|
|||
|
|
|||
|
this.rollPower = ship.rollPower;
|
|||
|
this.pitchPower = ship.pitchPower;
|
|||
|
this.yawPower = ship.yawPower;
|
|||
|
this.steeringThrusterPriorityLevel = ship.steeringThrusterPriorityLevel;
|
|||
|
|
|||
|
this.pilotMomentInput = ship.pilotMomentInput;
|
|||
|
this.pilotForceInput = ship.pilotForceInput;
|
|||
|
this.arcadePitchAcceleration = ship.arcadePitchAcceleration;
|
|||
|
this.arcadeYawAcceleration = ship.arcadeYawAcceleration;
|
|||
|
this.arcadeRollAcceleration = ship.arcadeRollAcceleration;
|
|||
|
this.arcadeMaxFlightTurningAcceleration = ship.arcadeMaxFlightTurningAcceleration;
|
|||
|
this.arcadeMaxGroundTurningAcceleration = ship.arcadeMaxGroundTurningAcceleration;
|
|||
|
|
|||
|
this.mediumDensity = ship.mediumDensity;
|
|||
|
this.gravitationalAcceleration = ship.gravitationalAcceleration;
|
|||
|
this.gravityDirection = ship.gravityDirection;
|
|||
|
|
|||
|
this.shipDamageModel = ship.shipDamageModel;
|
|||
|
if (ship.mainDamageRegion == null)
|
|||
|
{
|
|||
|
this.mainDamageRegion = new DamageRegion();
|
|||
|
this.mainDamageRegion.name = "Main Damage Region";
|
|||
|
}
|
|||
|
else { this.mainDamageRegion = new DamageRegion(ship.mainDamageRegion); }
|
|||
|
|
|||
|
if (ship.localisedDamageRegionList == null) { this.localisedDamageRegionList = new List<DamageRegion>(10); }
|
|||
|
else { this.localisedDamageRegionList = ship.localisedDamageRegionList.ConvertAll(dr => new DamageRegion(dr)); }
|
|||
|
|
|||
|
this.useDamageMultipliers = ship.useDamageMultipliers;
|
|||
|
this.useLocalisedDamageMultipliers = ship.useLocalisedDamageMultipliers;
|
|||
|
|
|||
|
this.respawnTime = ship.respawnTime;
|
|||
|
this.collisionRespawnPositionDelay = ship.collisionRespawnPositionDelay;
|
|||
|
this.respawningMode = ship.respawningMode;
|
|||
|
this.customRespawnPosition = ship.customRespawnPosition;
|
|||
|
this.customRespawnRotation = ship.customRespawnRotation;
|
|||
|
this.respawnVelocity = ship.respawnVelocity;
|
|||
|
this.respawningPathGUIDHash = ship.respawningPathGUIDHash;
|
|||
|
this.minRumbleDamage = ship.minRumbleDamage;
|
|||
|
this.maxRumbleDamage = ship.maxRumbleDamage;
|
|||
|
this.stuckTime = ship.stuckTime;
|
|||
|
this.stuckSpeedThreshold = ship.stuckSpeedThreshold;
|
|||
|
this.stuckAction = ship.stuckAction;
|
|||
|
this.stuckActionPathGUIDHash = ship.stuckActionPathGUIDHash;
|
|||
|
this.factionId = ship.factionId;
|
|||
|
this.squadronId = ship.squadronId;
|
|||
|
|
|||
|
this.isRadarEnabled = ship.isRadarEnabled;
|
|||
|
this.radarBlipSize = ship.radarBlipSize;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private and Internal Non-Static Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL ONLY]
|
|||
|
/// Check if the thruster systems are starting up or shutting down.
|
|||
|
/// If the state has changed in this frame, like has started or shutdown, the method will return true.
|
|||
|
/// </summary>
|
|||
|
/// <param name="deltaTime"></param>
|
|||
|
/// <param name="hasShutdown"></param>
|
|||
|
/// <param name="hasStarted"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal bool CheckThrusterSystems(float deltaTime, out bool hasStarted, out bool hasShutdown)
|
|||
|
{
|
|||
|
bool hasStateChanged = false;
|
|||
|
hasShutdown = false;
|
|||
|
hasStarted = false;
|
|||
|
|
|||
|
// Is the system shutting down?
|
|||
|
if (thrusterSystemShutdownTimer > 0f)
|
|||
|
{
|
|||
|
thrusterSystemShutdownTimer += deltaTime;
|
|||
|
|
|||
|
//Debug.Log("[DEBUG] Thruster System shutting down: " + thrusterSystemShutdownTimer);
|
|||
|
|
|||
|
if (thrusterSystemShutdownTimer > thrusterSystemShutdownDuration)
|
|||
|
{
|
|||
|
ShutdownThrusterSystems(true);
|
|||
|
hasStateChanged = true;
|
|||
|
hasShutdown = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SetThrottleAllThrusters(1f - thrusterSystemShutdownTimer / thrusterSystemShutdownDuration);
|
|||
|
}
|
|||
|
}
|
|||
|
else if (thrusterSystemStartupTimer > 0f)
|
|||
|
{
|
|||
|
thrusterSystemStartupTimer += deltaTime;
|
|||
|
|
|||
|
//Debug.Log("[DEBUG] Thruster System starting up: " + thrusterSystemStartupTimer);
|
|||
|
|
|||
|
if (thrusterSystemStartupTimer > thrusterSystemStartupDuration)
|
|||
|
{
|
|||
|
StartupThrusterSystems(true);
|
|||
|
hasStateChanged = true;
|
|||
|
hasStarted = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SetThrottleAllThrusters(thrusterSystemStartupTimer / thrusterSystemStartupDuration);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return hasStateChanged;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reset the thruster systems variables and thruster throttle values.
|
|||
|
/// The systems might have been starting or shutting down when the ship
|
|||
|
/// was destroyed.
|
|||
|
/// Typically used when a ship is respawned.
|
|||
|
/// </summary>
|
|||
|
internal void ResetThrusterSystems()
|
|||
|
{
|
|||
|
// If the systems are starting or shutting down, reset all the
|
|||
|
// thruster throttle values.
|
|||
|
if (thrusterSystemStartupTimer > 0f || thrusterSystemShutdownTimer > 0f)
|
|||
|
{
|
|||
|
SetThrottleAllThrusters(isThrusterSystemsStarted ? 1f : 0f);
|
|||
|
}
|
|||
|
|
|||
|
thrusterSystemShutdownTimer = 0f;
|
|||
|
thrusterSystemStartupTimer = 0f;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the angle between two vectors projected into a specified plane, with a sign indicating the direction.
|
|||
|
/// </summary>
|
|||
|
/// <param name="from"></param>
|
|||
|
/// <param name="to"></param>
|
|||
|
/// <param name="planeNormal"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private float SignedAngleInPlane(Vector3 from, Vector3 to, Vector3 planeNormal)
|
|||
|
{
|
|||
|
return Vector3.SignedAngle(Vector3.ProjectOnPlane(from, planeNormal), Vector3.ProjectOnPlane(to, planeNormal), planeNormal);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns a float number raised to an integer power.
|
|||
|
/// </summary>
|
|||
|
/// <param name="baseNum"></param>
|
|||
|
/// <param name="indexNum"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private float IntegerPower(float baseNum, int powerNum)
|
|||
|
{
|
|||
|
// Start power calculation at power of zero instead of power of one, so that anything to the power of zero is one
|
|||
|
float resultNum = 1f;
|
|||
|
// Iteratively mutiply by the base number
|
|||
|
for (int i = 0; i < powerNum; i++) { resultNum *= baseNum; }
|
|||
|
return resultNum;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the damped (averaged) normal.
|
|||
|
/// </summary>
|
|||
|
/// <param name="currentNormal"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private Vector3 GetDampedGroundNormal()
|
|||
|
{
|
|||
|
Vector3 dampedNormal = Vector3.zero;
|
|||
|
|
|||
|
// Loop through ground normal
|
|||
|
for (int hIdx = 0; hIdx < groundNormalHistoryLength; hIdx++)
|
|||
|
{
|
|||
|
// Optimise by setting each axis rather than creating new vectors
|
|||
|
dampedNormal.x += groundNormalHistory[hIdx].x;
|
|||
|
dampedNormal.y += groundNormalHistory[hIdx].y;
|
|||
|
dampedNormal.z += groundNormalHistory[hIdx].z;
|
|||
|
}
|
|||
|
|
|||
|
dampedNormal.x *= 1f / groundNormalHistoryLength;
|
|||
|
dampedNormal.y *= 1f / groundNormalHistoryLength;
|
|||
|
dampedNormal.z *= 1f / groundNormalHistoryLength;
|
|||
|
|
|||
|
return dampedNormal.normalized;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update the normal history.
|
|||
|
/// </summary>
|
|||
|
/// <param name="currentNormal"></param>
|
|||
|
private void UpdateGroundNormalHistory(Vector3 currentNormal)
|
|||
|
{
|
|||
|
// Update the current element of the history with the current normal
|
|||
|
if (groundNormalHistoryLength > groundNormalHistoryIdx) { groundNormalHistory[groundNormalHistoryIdx] = currentNormal; }
|
|||
|
// Keeps track of which array position to replace. Avoids having to shuffle elements
|
|||
|
groundNormalHistoryIdx++;
|
|||
|
if (groundNormalHistoryIdx >= groundNormalHistoryLength) { groundNormalHistoryIdx = 0; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns a damped max acceleration input.
|
|||
|
/// </summary>
|
|||
|
/// <param name="currentDisplacement"></param>
|
|||
|
/// <param name="targetDisplacement"></param>
|
|||
|
/// <param name="minDisplacement"></param>
|
|||
|
/// <param name="maxDisplacement"></param>
|
|||
|
/// <param name="minAcceleration"></param>
|
|||
|
/// <param name="maxAcceleration"></param>
|
|||
|
/// <param name="inputScalingFactor"></param>
|
|||
|
/// <param name="inputPowerFactor"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private float DampedMaxAccelerationInput(float currentDisplacement, float targetDisplacement, float minDisplacement,
|
|||
|
float maxDisplacement, float minAcceleration, float maxAcceleration, float inputScalingFactor, float inputPowerFactor)
|
|||
|
{
|
|||
|
// Highest possible max acceleration input is max acceleration
|
|||
|
float maxAccelerationInput = maxAcceleration;
|
|||
|
|
|||
|
// The max acceleration is determined by how close we are to the ground
|
|||
|
// At the min/max allowed distance from the ground the limit is infinity
|
|||
|
// At the target distance from the ground the limit is some given value
|
|||
|
|
|||
|
if (currentDisplacement < targetDisplacement && currentDisplacement > minDisplacement)
|
|||
|
{
|
|||
|
// Reciprocal
|
|||
|
float xVal = (currentDisplacement - minDisplacement) / (targetDisplacement - minDisplacement);
|
|||
|
maxAccelerationInput = minAcceleration + (inputScalingFactor * ((1f / Mathf.Pow(xVal, inputPowerFactor)) - 1f));
|
|||
|
}
|
|||
|
else if (currentDisplacement > targetDisplacement && currentDisplacement < maxDisplacement)
|
|||
|
{
|
|||
|
// Reciprocal
|
|||
|
float xVal = (currentDisplacement - maxDisplacement) / (targetDisplacement - maxDisplacement);
|
|||
|
maxAccelerationInput = minAcceleration + (inputScalingFactor * ((1f / Mathf.Pow(xVal, inputPowerFactor)) - 1f));
|
|||
|
}
|
|||
|
|
|||
|
// Highest possible max acceleration input is max acceleration
|
|||
|
return maxAccelerationInput < maxAcceleration ? maxAccelerationInput : maxAcceleration;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Applies a specified amount of damage to the ship at a specified position.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageAmount"></param>
|
|||
|
/// <param name="damagePosition"></param>
|
|||
|
/// <param name="isCollisionDamage"></param>
|
|||
|
private void ApplyDamage(float damageAmount, ProjectileModule.DamageType damageType, Vector3 damagePosition, bool adjustForCollisionResistance)
|
|||
|
{
|
|||
|
// Determine which damage region was hit
|
|||
|
bool[] regionDamagedArray = new bool[1];
|
|||
|
float[] actualDamageArray = new float[1];
|
|||
|
int damageArrayLengths = 1;
|
|||
|
if (shipDamageModel == ShipDamageModel.Localised)
|
|||
|
{
|
|||
|
damageArrayLengths = localisedDamageRegionList.Count + 1;
|
|||
|
regionDamagedArray = new bool[damageArrayLengths];
|
|||
|
actualDamageArray = new float[damageArrayLengths];
|
|||
|
}
|
|||
|
|
|||
|
// Loop through all damage regions
|
|||
|
DamageRegion thisDamageRegion;
|
|||
|
float actualDamage = 0f;
|
|||
|
bool regionDamaged = false;
|
|||
|
bool isShipInvincible = mainDamageRegion.isInvincible;
|
|||
|
|
|||
|
// Get damage position in local space (calc once outside the loop)
|
|||
|
Vector3 localDamagePosition = trfmInvRot * (damagePosition - trfmPos);
|
|||
|
|
|||
|
for (int i = 0; i < damageArrayLengths; i++)
|
|||
|
{
|
|||
|
// Get the current damage region we are iterating over, and work out whether it has been hit
|
|||
|
if (i == 0)
|
|||
|
{
|
|||
|
// Main damage region
|
|||
|
thisDamageRegion = mainDamageRegion;
|
|||
|
// Main damage region is always hit unless it is invincible (which would make the whole ship invincible)
|
|||
|
regionDamaged = !isShipInvincible;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Localised damage region
|
|||
|
thisDamageRegion = localisedDamageRegionList[i - 1];
|
|||
|
|
|||
|
// If the main damage region is invincible, so are all the damage regions
|
|||
|
if (isShipInvincible || thisDamageRegion.isInvincible) { regionDamaged = false; }
|
|||
|
else
|
|||
|
{
|
|||
|
// Calculate whether the hit point is within the volume, and hence populate regionDamaged variable
|
|||
|
// First, get damage position in local space
|
|||
|
//localDamagePosition = trfmInvRot * (damagePosition - trfmPos);
|
|||
|
|
|||
|
// Then check whether the position is within the bounds of this damage region
|
|||
|
regionDamaged = thisDamageRegion.IsHit(localDamagePosition);
|
|||
|
|
|||
|
// For some reason we need to slightly expand the damage region when the collider is exactly the same size.
|
|||
|
//regionDamaged = localDamagePosition.x >= thisDamageRegion.relativePosition.x - (thisDamageRegion.size.x / 2) - 0.0001f &&
|
|||
|
// localDamagePosition.x <= thisDamageRegion.relativePosition.x + (thisDamageRegion.size.x / 2) + 0.0001f &&
|
|||
|
// localDamagePosition.y >= thisDamageRegion.relativePosition.y - (thisDamageRegion.size.y / 2) - 0.0001f &&
|
|||
|
// localDamagePosition.y <= thisDamageRegion.relativePosition.y + (thisDamageRegion.size.y / 2) + 0.0001f &&
|
|||
|
// localDamagePosition.z >= thisDamageRegion.relativePosition.z - (thisDamageRegion.size.z / 2) - 0.0001f &&
|
|||
|
// localDamagePosition.z <= thisDamageRegion.relativePosition.z + (thisDamageRegion.size.z / 2) + 0.0001f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Don't do any further calculations if the region wasn't hit
|
|||
|
if (regionDamaged)
|
|||
|
{
|
|||
|
// Adjust for collision resistance - value passed in is just an impulse magnitude
|
|||
|
if (adjustForCollisionResistance)
|
|||
|
{
|
|||
|
if (thisDamageRegion.collisionDamageResistance < 0.01f) { actualDamage = Mathf.Infinity; }
|
|||
|
else { actualDamage = damageAmount / thisDamageRegion.collisionDamageResistance * 4f / mass; }
|
|||
|
}
|
|||
|
else { actualDamage = damageAmount; }
|
|||
|
|
|||
|
// Modify damage dealt based on relevant damage multipliers
|
|||
|
if (useDamageMultipliers)
|
|||
|
{
|
|||
|
if (useLocalisedDamageMultipliers)
|
|||
|
{
|
|||
|
actualDamage *= thisDamageRegion.GetDamageMultiplier(damageType);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
actualDamage *= mainDamageRegion.GetDamageMultiplier(damageType);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Determine whether shielding is active for this damage region
|
|||
|
if (thisDamageRegion.useShielding && thisDamageRegion.ShieldHealth > 0f)
|
|||
|
{
|
|||
|
// Set the shielding to active
|
|||
|
regionDamaged = false;
|
|||
|
// Only do damage to the shield if the damage amount is above the shielding threshold
|
|||
|
if (actualDamage >= thisDamageRegion.shieldingDamageThreshold)
|
|||
|
{
|
|||
|
thisDamageRegion.ShieldHealth -= actualDamage;
|
|||
|
thisDamageRegion.shieldRechargeDelayTimer = 0f;
|
|||
|
// If this damage destroys the shielding entirely...
|
|||
|
if (thisDamageRegion.ShieldHealth <= 0f)
|
|||
|
{
|
|||
|
// Get the residual damage value
|
|||
|
actualDamage = -thisDamageRegion.ShieldHealth;
|
|||
|
// Set the shielding to inactive
|
|||
|
regionDamaged = true;
|
|||
|
thisDamageRegion.ShieldHealth = -0.01f;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (regionDamaged)
|
|||
|
{
|
|||
|
// Reduce health of damage region itself
|
|||
|
thisDamageRegion.Health -= actualDamage;
|
|||
|
}
|
|||
|
}
|
|||
|
// Added v1.2.7 - regions not damaged should have 0 damage
|
|||
|
else { actualDamage = 0f; }
|
|||
|
|
|||
|
// Set calculated array variables
|
|||
|
actualDamageArray[i] = actualDamage;
|
|||
|
regionDamagedArray[i] = regionDamaged;
|
|||
|
}
|
|||
|
|
|||
|
// Need to re-check this variable, as damage can destroy shielding and then use residual damage to affect
|
|||
|
// the damage region as well
|
|||
|
if (!shipDamageModelIsSimple && !isShipInvincible)
|
|||
|
{
|
|||
|
// Loop through each part list and reduce health of all parts in the regions that have been damaged
|
|||
|
|
|||
|
// Compute an index shift for localised damage regions
|
|||
|
// Main damage region is index 0, so localised damage regions are a one-based indexing system
|
|||
|
// as opposed to the standard zero-based indexing system
|
|||
|
int drIndexShift = 0, drShiftedIndex, drIndex;
|
|||
|
if (shipDamageModel == ShipDamageModel.Localised) { drIndexShift = 1; }
|
|||
|
|
|||
|
componentListSize = thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
drIndex = thrusterList[componentIndex].damageRegionIndex;
|
|||
|
drShiftedIndex = drIndex + drIndexShift;
|
|||
|
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
|
|||
|
{
|
|||
|
thrusterList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
componentListSize = wingList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
drIndex = wingList[componentIndex].damageRegionIndex;
|
|||
|
drShiftedIndex = drIndex + drIndexShift;
|
|||
|
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
|
|||
|
{
|
|||
|
wingList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
componentListSize = controlSurfaceList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
drIndex = controlSurfaceList[componentIndex].damageRegionIndex;
|
|||
|
drShiftedIndex = drIndex + drIndexShift;
|
|||
|
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
|
|||
|
{
|
|||
|
controlSurfaceList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
componentListSize = weaponList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
drIndex = weaponList[componentIndex].damageRegionIndex;
|
|||
|
drShiftedIndex = drIndex + drIndexShift;
|
|||
|
if (drIndex > -1 && drShiftedIndex < damageArrayLengths && regionDamagedArray[drShiftedIndex])
|
|||
|
{
|
|||
|
weaponList[componentIndex].Health -= actualDamageArray[drShiftedIndex];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Damage callback
|
|||
|
if (callbackOnDamage != null) { callbackOnDamage(mainDamageRegion.Health); }
|
|||
|
|
|||
|
// Add a damage event
|
|||
|
lastDamageEventIndex++;
|
|||
|
// Calculate rumble and camera shake required
|
|||
|
float rumbleDamageAmount = damageAmount;
|
|||
|
if (adjustForCollisionResistance)
|
|||
|
{
|
|||
|
rumbleDamageAmount /= mainDamageRegion.collisionDamageResistance * 2500f;
|
|||
|
}
|
|||
|
|
|||
|
// Start camera shake as damageAmount with adjusted collision amount if applicable
|
|||
|
damageCameraShakeAmountRequired = rumbleDamageAmount;
|
|||
|
|
|||
|
if (rumbleDamageAmount <= minRumbleDamage) { damageRumbleAmountRequired = 0f; }
|
|||
|
else if (rumbleDamageAmount >= maxRumbleDamage) { damageRumbleAmountRequired = 1f; }
|
|||
|
else { damageRumbleAmountRequired = (rumbleDamageAmount - minRumbleDamage) / (maxRumbleDamage - minRumbleDamage); }
|
|||
|
|
|||
|
// Calculate camera shake required
|
|||
|
if (damageCameraShakeAmountRequired < 0f) { damageCameraShakeAmountRequired = 0f; }
|
|||
|
else if (damageCameraShakeAmountRequired > 1f) { damageCameraShakeAmountRequired = 1f; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the index or 0-based position in the list of damage regions.
|
|||
|
/// Although typically used to return the index in the localisedDamageRegionList,
|
|||
|
/// if passed the mainDamageRegion, it will always return 0.
|
|||
|
/// If no matching regions are found, it will return -1.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageRegion"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private int GetDamageRegionIndex(DamageRegion damageRegion)
|
|||
|
{
|
|||
|
int damageRegionIndex = -1;
|
|||
|
|
|||
|
if (damageRegion.guidHash == mainDamageRegion.guidHash) { return 0; }
|
|||
|
else
|
|||
|
{
|
|||
|
int localisedDamageRegionListCount = localisedDamageRegionList == null ? 0 : localisedDamageRegionList.Count;
|
|||
|
|
|||
|
for (int drIdx = 0; drIdx < localisedDamageRegionListCount; drIdx++)
|
|||
|
{
|
|||
|
if (localisedDamageRegionList[drIdx].guidHash == damageRegion.guidHash) { damageRegionIndex = drIdx; break; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return damageRegionIndex;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update the maximum estimated range for all turrets with auto targeting enabled
|
|||
|
/// </summary>
|
|||
|
internal void UpdateMaxTurretRange()
|
|||
|
{
|
|||
|
int numWeapons = weaponList == null ? 0 : weaponList.Count;
|
|||
|
|
|||
|
float maxRange = 0f;
|
|||
|
|
|||
|
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
|
|||
|
{
|
|||
|
Weapon weapon = weaponList[wpIdx];
|
|||
|
|
|||
|
// Only process turrets with Auto Targeting enabled
|
|||
|
if (weapon != null && weapon.isAutoTargetingEnabled && (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt))
|
|||
|
{
|
|||
|
if (weapon.estimatedRange > maxRange) { maxRange = weapon.estimatedRange; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
estimatedMaxTurretRange = maxRange;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update the maximum estimated range for all weapons with auto targeting enabled
|
|||
|
/// </summary>
|
|||
|
internal void UpdateMaxAutoTargetRange()
|
|||
|
{
|
|||
|
int numWeapons = weaponList == null ? 0 : weaponList.Count;
|
|||
|
|
|||
|
float maxRange = 0f;
|
|||
|
|
|||
|
for (int wpIdx = 0; wpIdx < numWeapons; wpIdx++)
|
|||
|
{
|
|||
|
Weapon weapon = weaponList[wpIdx];
|
|||
|
|
|||
|
// Only process weapons with Auto Targeting enabled
|
|||
|
if (weapon != null && weapon.isAutoTargetingEnabled)
|
|||
|
{
|
|||
|
if (weapon.estimatedRange > maxRange) { maxRange = weapon.estimatedRange; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
estimatedMaxAutoTargetRange = maxRange;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This gets called from FixedUpdate in BeamModule. It performs the following:
|
|||
|
/// 1. Checks if the beam should be despawned
|
|||
|
/// 2. Moves the beam
|
|||
|
/// 3. Changes the length
|
|||
|
/// 4. Checks if it hits anything
|
|||
|
/// 5. Updates damage on what it hits
|
|||
|
/// 6. Instantiate effects object at hit point
|
|||
|
/// 7. Consumes weapon power
|
|||
|
/// It needs to be a member of the ship instance as it requires both ship and beam data.
|
|||
|
/// Assumes the beam linerenderer has useWorldspace enabled.
|
|||
|
/// </summary>
|
|||
|
/// <param name="beamModule"></param>
|
|||
|
internal void MoveBeam(BeamModule beamModule)
|
|||
|
{
|
|||
|
if (beamModule.isInitialised && beamModule.isBeamEnabled && beamModule.weaponIndex >= 0 && beamModule.firePositionIndex >= 0)
|
|||
|
{
|
|||
|
Weapon weapon = weaponList[beamModule.weaponIndex];
|
|||
|
SSCBeamItemKey beamItemKey = weapon.beamItemKeyList[beamModule.firePositionIndex];
|
|||
|
|
|||
|
// Read once from the deltaTime property getter
|
|||
|
float _deltaTime = Time.deltaTime;
|
|||
|
|
|||
|
if ((weapon.weaponTypeInt == Weapon.FixedBeamInt || weapon.weaponTypeInt == Weapon.TurretBeamInt) && beamItemKey.beamSequenceNumber == beamModule.itemSequenceNumber)
|
|||
|
{
|
|||
|
weaponFiringButtonInt = (int)weapon.firingButton;
|
|||
|
|
|||
|
// For autofiring weapons, this will return false.
|
|||
|
bool isReadyToFire = (pilotPrimaryFireInput && weaponFiringButtonInt == weaponPrimaryFiringInt) || (pilotSecondaryFireInput && weaponFiringButtonInt == weaponSecondaryFiringInt);
|
|||
|
|
|||
|
// If this is auto-firing turret check if it is ready to fire
|
|||
|
if (weaponFiringButtonInt == weaponAutoFiringInt && weapon.weaponTypeInt == Weapon.TurretBeamInt)
|
|||
|
{
|
|||
|
// Is turret locked on target and in direct line of sight?
|
|||
|
// NOTE: If check LoS is enabled, it will still fire if another enemy is between the turret and the weapon.target.
|
|||
|
isReadyToFire = weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon));
|
|||
|
|
|||
|
// BUILD TEST
|
|||
|
//isReadyToFire = !weapon.checkLineOfSight || WeaponHasLineOfSight(weapon);
|
|||
|
// TODO - Is target in range?
|
|||
|
}
|
|||
|
|
|||
|
// Should this beam be despawned or returned to the pool?
|
|||
|
// a) No charge in weapon
|
|||
|
// b) Weapon has no health
|
|||
|
// c) Weapon has no performance
|
|||
|
// d) user stopped firing and has fired for min time permitted
|
|||
|
// e) has exceeded the maximum firing duration
|
|||
|
if (weapon.chargeAmount <= 0f || weapon.health <= 0f || weapon.currentPerformance == 0f || (!isReadyToFire && beamModule.burstDuration > beamModule.minBurstDuration) || beamModule.burstDuration > beamModule.maxBurstDuration)
|
|||
|
{
|
|||
|
// Unassign the beam from this weapon's fire position
|
|||
|
weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);
|
|||
|
|
|||
|
beamModule.DestroyBeam();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Move the beam start
|
|||
|
// Calculate the World-space Fire Position (like when weapon is first fired)
|
|||
|
// Note: That the ship could have moved and rotated, so we recalc here.
|
|||
|
Vector3 _weaponWSBasePos = GetWeaponWorldBasePosition(weapon);
|
|||
|
Vector3 _weaponWSFireDir = GetWeaponWorldFireDirection(weapon);
|
|||
|
Vector3 _weaponWSFirePos = GetWeaponWorldFirePosition(weapon, _weaponWSBasePos, beamModule.firePositionIndex);
|
|||
|
|
|||
|
// Move the beam transform but keep the first LineRenderer position at 0,0,0
|
|||
|
beamModule.transform.position = _weaponWSFirePos;
|
|||
|
|
|||
|
beamModule.transform.SetPositionAndRotation(_weaponWSFirePos, Quaternion.LookRotation(_weaponWSFireDir, rbodyUp));
|
|||
|
|
|||
|
// Calc length and end position
|
|||
|
float _desiredLength = beamModule.burstDuration * beamModule.speed;
|
|||
|
|
|||
|
if (_desiredLength > weapon.maxRange) { _desiredLength = weapon.maxRange; }
|
|||
|
|
|||
|
Vector3 _endPosition = _weaponWSFirePos + (_weaponWSFireDir * _desiredLength);
|
|||
|
|
|||
|
// Check if it hit anything
|
|||
|
if (Physics.Linecast(_weaponWSFirePos, _endPosition, out raycastHitInfo, ~0, QueryTriggerInteraction.Ignore))
|
|||
|
{
|
|||
|
// Adjust the end position to the hit point
|
|||
|
_endPosition = raycastHitInfo.point;
|
|||
|
|
|||
|
// Update damage if it hit a ship or an object with a damage receiver
|
|||
|
if (!BeamModule.CheckShipHit(raycastHitInfo, beamModule, _deltaTime))
|
|||
|
{
|
|||
|
// If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
|
|||
|
BeamModule.CheckObjectHit(raycastHitInfo, beamModule, _deltaTime);
|
|||
|
}
|
|||
|
|
|||
|
// ISSUES - the effect may not be visible while firing if:
|
|||
|
// 1) if the effect despawn time is less than the beam max burst duration (We have a warning in the BeamModule editor)
|
|||
|
// 2) if the effect does not have looping enabled (this could be more expensive to check)
|
|||
|
|
|||
|
// Add or Move the effects object
|
|||
|
if (beamModule.effectsObjectPrefabID >= 0)
|
|||
|
{
|
|||
|
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
|
|||
|
|
|||
|
if (sscManager != null)
|
|||
|
{
|
|||
|
// If the effect has not been spawned, do now
|
|||
|
if (beamModule.effectsItemKey.effectsObjectSequenceNumber == 0)
|
|||
|
{
|
|||
|
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
|
|||
|
{
|
|||
|
effectsObjectPrefabID = beamModule.effectsObjectPrefabID,
|
|||
|
position = _endPosition,
|
|||
|
rotation = beamModule.transform.rotation
|
|||
|
};
|
|||
|
|
|||
|
// Instantiate the hit effects
|
|||
|
if (sscManager.InstantiateEffectsObject(ref ieParms) != null)
|
|||
|
{
|
|||
|
if (ieParms.effectsObjectSequenceNumber > 0)
|
|||
|
{
|
|||
|
// Record the hit effect item key
|
|||
|
beamModule.effectsItemKey = new SSCEffectItemKey(ieParms.effectsObjectPrefabID, ieParms.effectsObjectPoolListIndex, ieParms.effectsObjectSequenceNumber);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// Move the existing effects object to the end of the beam
|
|||
|
else
|
|||
|
{
|
|||
|
// Currently we are not checking sequence number matching (for pooled effects) as it is faster and can
|
|||
|
// avoid doing an additional GetComponent().
|
|||
|
sscManager.MoveEffectsObject(beamModule.effectsItemKey, _endPosition, beamModule.transform.rotation, false);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// The beam isn't hitting anything AND there is an active effects object
|
|||
|
else if (beamModule.effectsObjectPrefabID >= 0 && beamModule.effectsItemKey.effectsObjectSequenceNumber > 0)
|
|||
|
{
|
|||
|
// Destroy the effects object or return it to the pool
|
|||
|
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
|
|||
|
|
|||
|
if (sscManager != null)
|
|||
|
{
|
|||
|
sscManager.DestroyEffectsObject(beamModule.effectsItemKey);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Move the beam end (assumes world space)
|
|||
|
// useWorldspace is enabled in beamModule.InitialiseBeam(..)
|
|||
|
beamModule.lineRenderer.SetPosition(0, _weaponWSFirePos);
|
|||
|
beamModule.lineRenderer.SetPosition(1, _endPosition);
|
|||
|
|
|||
|
// Consume weapon power. Consumes upt to 2x power when overheating.
|
|||
|
if (weapon.rechargeTime > 0f) { weapon.chargeAmount -= _deltaTime * (weapon.heatLevel > weapon.overHeatThreshold ? 2f - weapon.currentPerformance : 1f) / beamModule.dischargeDuration; }
|
|||
|
|
|||
|
weapon.ManageHeat(_deltaTime, 1f);
|
|||
|
|
|||
|
// If we run out of power, de-activate it before weapon starts recharging
|
|||
|
if (weapon.chargeAmount <= 0f)
|
|||
|
{
|
|||
|
weapon.chargeAmount = 0f;
|
|||
|
|
|||
|
// Unassign the beam from this weapon's fire position
|
|||
|
weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);
|
|||
|
|
|||
|
beamModule.DestroyBeam();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#if UNITY_EDITOR
|
|||
|
else { Debug.LogWarning("ERROR ship.MoveBeam has been called on the wrong beam. isInitialised: " + beamModule.isInitialised +
|
|||
|
" isBeamEnabled: " + beamModule.isBeamEnabled + " weaponIndex: " + beamModule.weaponIndex + " firePositionIndex: " + beamModule.firePositionIndex); }
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
private Vector3 GetWeaponWorldBasePosition(Weapon weapon)
|
|||
|
{
|
|||
|
return (trfmRight * weapon.relativePosition.x) +
|
|||
|
(trfmUp * weapon.relativePosition.y) +
|
|||
|
(trfmFwd * weapon.relativePosition.z) +
|
|||
|
trfmPos;
|
|||
|
}
|
|||
|
|
|||
|
private Vector3 GetWeaponWorldFireDirection(Weapon weapon)
|
|||
|
{
|
|||
|
// Get relative fire direction
|
|||
|
weaponRelativeFireDirection = weapon.fireDirectionNormalised;
|
|||
|
|
|||
|
// If this is a turret, adjust relative fire direction based on turret rotation
|
|||
|
if (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt)
|
|||
|
{
|
|||
|
// (turret rotation less ship rotate) - (original turret rotation less ship rotation) + relative fire direction
|
|||
|
weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;
|
|||
|
}
|
|||
|
|
|||
|
// Get weapon world fire direction
|
|||
|
return (trfmRight * weaponRelativeFireDirection.x) +
|
|||
|
(trfmUp * weaponRelativeFireDirection.y) +
|
|||
|
(trfmFwd * weaponRelativeFireDirection.z);
|
|||
|
}
|
|||
|
|
|||
|
private Vector3 GetWeaponWorldFirePosition(Weapon weapon, Vector3 weaponWorldBasePosition, int firePositionIndex)
|
|||
|
{
|
|||
|
// Check if there are multiple fire positions
|
|||
|
if (weapon.isMultipleFirePositions)
|
|||
|
{
|
|||
|
// Get relative fire position
|
|||
|
weaponRelativeFirePosition = weapon.firePositionList[firePositionIndex];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If there is only one fire position, relative position must be the zero vector
|
|||
|
weaponRelativeFirePosition.x = 0f;
|
|||
|
weaponRelativeFirePosition.y = 0f;
|
|||
|
weaponRelativeFirePosition.z = 0f;
|
|||
|
}
|
|||
|
// If this is a turret, adjust relative fire position based on turret rotation
|
|||
|
if (weapon.weaponTypeInt == Weapon.TurretProjectileInt || weapon.weaponTypeInt == Weapon.TurretBeamInt)
|
|||
|
{
|
|||
|
weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;
|
|||
|
}
|
|||
|
|
|||
|
return weaponWorldBasePosition +
|
|||
|
(trfmRight * weaponRelativeFirePosition.x) +
|
|||
|
(trfmUp * weaponRelativeFirePosition.y) +
|
|||
|
(trfmFwd * weaponRelativeFirePosition.z);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Begin to shut down the thrusters. Optionally override the shutdown duration.
|
|||
|
/// See also shipControlModule.ShutdownThrusterSystems(..)
|
|||
|
/// </summary>
|
|||
|
internal void ShutdownThrusterSystems (bool isInstantShutdown = false)
|
|||
|
{
|
|||
|
// Instant shutdown can be called even while in the process of shutting down.
|
|||
|
if (isThrusterSystemsStarted || isInstantShutdown)
|
|||
|
{
|
|||
|
// Perform an instant shutdown?
|
|||
|
if (isInstantShutdown || thrusterSystemShutdownDuration <= 0f)
|
|||
|
{
|
|||
|
isThrusterSystemsStarted = false;
|
|||
|
thrusterSystemShutdownTimer = 0f;
|
|||
|
thrusterSystemStartupTimer = 0f;
|
|||
|
|
|||
|
SetThrottleAllThrusters(0f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Activate the shutdown sequence
|
|||
|
|
|||
|
// if startup was in progress, begin at that point
|
|||
|
if (thrusterSystemStartupTimer > 0f && thrusterSystemStartupDuration > 0f)
|
|||
|
{
|
|||
|
thrusterSystemShutdownTimer = (1f - (thrusterSystemStartupTimer / thrusterSystemStartupDuration)) * thrusterSystemShutdownDuration;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
thrusterSystemShutdownTimer = 0.001f;
|
|||
|
}
|
|||
|
|
|||
|
// Reset the startup timer so that shutdown can continue
|
|||
|
thrusterSystemStartupTimer = 0f;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Begin to bring the thrusters online. Optionally, override the startup duration.
|
|||
|
/// As soon as the systems begin to start up, isThrusterSystemsStarted will be true.
|
|||
|
/// See also shipControlModule.StartupThrusterSystems(..)
|
|||
|
/// </summary>
|
|||
|
internal void StartupThrusterSystems (bool isInstantStartup = false)
|
|||
|
{
|
|||
|
// Instant startup can be called even while in the process of
|
|||
|
// starting up. This is actually how we trigger the end of the
|
|||
|
// startup process when the timer expires in CheckThrusterSystems(..).
|
|||
|
|
|||
|
// Perform an instant startup?
|
|||
|
if (isInstantStartup || thrusterSystemStartupDuration <= 0f)
|
|||
|
{
|
|||
|
thrusterSystemShutdownTimer = 0f;
|
|||
|
thrusterSystemStartupTimer = 0f;
|
|||
|
|
|||
|
SetThrottleAllThrusters(1f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Activate the startup sequence
|
|||
|
|
|||
|
// if shutdown was in progress, begin at that point
|
|||
|
if (thrusterSystemShutdownTimer > 0f && thrusterSystemShutdownDuration > 0f)
|
|||
|
{
|
|||
|
thrusterSystemStartupTimer = (1f - (thrusterSystemShutdownTimer / thrusterSystemShutdownDuration)) * thrusterSystemStartupDuration;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
thrusterSystemStartupTimer = 0.001f;
|
|||
|
}
|
|||
|
|
|||
|
// Reset the shutdown timer so that startup can continue
|
|||
|
thrusterSystemShutdownTimer = 0f;
|
|||
|
}
|
|||
|
|
|||
|
isThrusterSystemsStarted = true;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Non-Static Methods
|
|||
|
|
|||
|
#region Set Class Defaults
|
|||
|
|
|||
|
public void SetClassDefaults()
|
|||
|
{
|
|||
|
this.initialiseOnAwake = true;
|
|||
|
|
|||
|
this.shipPhysicsModel = ShipPhysicsModel.Arcade;
|
|||
|
|
|||
|
this.mass = 5000f;
|
|||
|
this.centreOfMass = Vector3.zero;
|
|||
|
this.setCentreOfMassManually = false;
|
|||
|
this.showCOMGizmosInSceneView = true;
|
|||
|
this.showCOLGizmosInSceneView = false;
|
|||
|
this.showCOTGizmosInSceneView = false;
|
|||
|
|
|||
|
this.thrusterList = new List<Thruster>(25);
|
|||
|
|
|||
|
// Create a default forwards-facing thruster
|
|||
|
Thruster thruster = new Thruster();
|
|||
|
if (thruster != null)
|
|||
|
{
|
|||
|
thruster.name = "Forward Thruster";
|
|||
|
thruster.forceUse = 1;
|
|||
|
thruster.primaryMomentUse = 0;
|
|||
|
thruster.secondaryMomentUse = 0;
|
|||
|
thruster.maxThrust = 100000;
|
|||
|
thruster.thrustDirection = Vector3.forward;
|
|||
|
thruster.relativePosition = Vector3.zero;
|
|||
|
this.thrusterList.Add(thruster);
|
|||
|
}
|
|||
|
isThrusterFXStationary = false;
|
|||
|
isThrusterSystemsStarted = true;
|
|||
|
thrusterSystemStartupDuration = 5f;
|
|||
|
thrusterSystemShutdownDuration = 5f;
|
|||
|
useCentralFuel = true;
|
|||
|
centralFuelLevel = 100f;
|
|||
|
|
|||
|
this.wingList = new List<Wing>(5);
|
|||
|
this.controlSurfaceList = new List<ControlSurface>(10);
|
|||
|
this.weaponList = new List<Weapon>(10);
|
|||
|
|
|||
|
// Expand editor lists by default
|
|||
|
isThrusterSystemsExpanded = true;
|
|||
|
isThrusterListExpanded = true;
|
|||
|
isWingListExpanded = true;
|
|||
|
isControlSurfaceListExpanded = true;
|
|||
|
isWeaponListExpanded = true;
|
|||
|
isDamageListExpanded = true;
|
|||
|
isMainDamageExpanded = true;
|
|||
|
showDamageInEditor = true;
|
|||
|
|
|||
|
this.dragCoefficients = new Vector3(0.5f, 1f, 0.1f);
|
|||
|
this.dragReferenceAreas = new Vector3(10f, 20f, 5f);
|
|||
|
this.centreOfDragXMoment = Vector3.zero;
|
|||
|
this.centreOfDragYMoment = Vector3.zero;
|
|||
|
this.centreOfDragZMoment = Vector3.zero;
|
|||
|
this.angularDragFactor = 1f;
|
|||
|
this.disableDragMoments = false;
|
|||
|
this.dragXMomentMultiplier = 1f;
|
|||
|
this.dragYMomentMultiplier = 1f;
|
|||
|
this.dragZMomentMultiplier = 1f;
|
|||
|
this.wingStallEffect = 1f;
|
|||
|
|
|||
|
this.arcadeUseBrakeComponent = true;
|
|||
|
this.arcadeBrakeStrength = 100f;
|
|||
|
this.arcadeBrakeIgnoreMediumDensity = false;
|
|||
|
this.arcadeBrakeMinAcceleration = 25f;
|
|||
|
|
|||
|
this.rotationalFlightAssistStrength = 3f;
|
|||
|
this.translationalFlightAssistStrength = 3f;
|
|||
|
this.stabilityFlightAssistStrength = 0f;
|
|||
|
this.brakeFlightAssistStrength = 0f;
|
|||
|
this.brakeFlightAssistMinSpeed = -10f;
|
|||
|
this.brakeFlightAssistMaxSpeed = 10f;
|
|||
|
|
|||
|
this.limitPitchAndRoll = false;
|
|||
|
this.maxPitch = 10f;
|
|||
|
this.maxTurnRoll = 15f;
|
|||
|
this.pitchSpeed = 30f;
|
|||
|
this.turnRollSpeed = 30f;
|
|||
|
this.rollControlMode = RollControlMode.YawInput;
|
|||
|
this.pitchRollMatchResponsiveness = 4f;
|
|||
|
this.stickToGroundSurface = false;
|
|||
|
this.avoidGroundSurface = false;
|
|||
|
this.useGroundMatchSmoothing = false;
|
|||
|
this.useGroundMatchLookAhead = false;
|
|||
|
this.orientUpInAir = false;
|
|||
|
this.targetGroundDistance = 5f;
|
|||
|
this.minGroundDistance = 2f;
|
|||
|
this.maxGroundDistance = 8f;
|
|||
|
this.maxGroundCheckDistance = 25f;
|
|||
|
this.groundMatchResponsiveness = 50f;
|
|||
|
this.groundMatchDamping = 50f;
|
|||
|
this.maxGroundMatchAccelerationFactor = 3f;
|
|||
|
this.centralMaxGroundMatchAccelerationFactor = 1f;
|
|||
|
this.groundNormalCalculationMode = GroundNormalCalculationMode.SmoothedNormal;
|
|||
|
this.groundNormalHistoryLength = 5;
|
|||
|
this.groundLayerMask = ~0;
|
|||
|
|
|||
|
this.inputControlAxis = InputControlAxis.None;
|
|||
|
this.inputControlLimit = 0f;
|
|||
|
this.inputControlForwardAngle = 0f;
|
|||
|
this.inputControlMovingRigidness = 25f;
|
|||
|
this.inputControlTurningRigidness = 25f;
|
|||
|
|
|||
|
this.rollPower = 1f;
|
|||
|
this.pitchPower = 1f;
|
|||
|
this.yawPower = 1f;
|
|||
|
this.steeringThrusterPriorityLevel = 0f;
|
|||
|
|
|||
|
this.pilotMomentInput = Vector3.zero;
|
|||
|
this.pilotForceInput = Vector3.zero;
|
|||
|
this.arcadePitchAcceleration = 100f;
|
|||
|
this.arcadeYawAcceleration = 100f;
|
|||
|
this.arcadeRollAcceleration = 250f;
|
|||
|
this.arcadeMaxFlightTurningAcceleration = 500f;
|
|||
|
this.arcadeMaxGroundTurningAcceleration = 500f;
|
|||
|
|
|||
|
this.mediumDensity = 1.293f;
|
|||
|
this.gravitationalAcceleration = 9.81f;
|
|||
|
this.gravityDirection = -Vector3.up;
|
|||
|
|
|||
|
this.shipDamageModel = ShipDamageModel.Simple;
|
|||
|
this.mainDamageRegion = new DamageRegion();
|
|||
|
this.mainDamageRegion.name = "Main Damage Region";
|
|||
|
this.localisedDamageRegionList = new List<DamageRegion>(10);
|
|||
|
this.useDamageMultipliers = false;
|
|||
|
this.useLocalisedDamageMultipliers = false;
|
|||
|
this.respawnTime = 5f;
|
|||
|
this.collisionRespawnPositionDelay = 5f;
|
|||
|
this.respawningMode = RespawningMode.DontRespawn;
|
|||
|
this.customRespawnPosition = Vector3.zero;
|
|||
|
this.customRespawnRotation = Vector3.zero;
|
|||
|
this.respawnVelocity = Vector3.zero;
|
|||
|
this.respawningPathGUIDHash = 0;
|
|||
|
this.minRumbleDamage = 0f;
|
|||
|
this.maxRumbleDamage = 10f;
|
|||
|
this.stuckTime = 0f;
|
|||
|
this.stuckSpeedThreshold = 0.1f;
|
|||
|
this.stuckAction = StuckAction.DoNothing;
|
|||
|
this.stuckActionPathGUIDHash = 0;
|
|||
|
|
|||
|
// By default, all ships on the same faction/side/alliance.
|
|||
|
this.factionId = 0;
|
|||
|
|
|||
|
squadronId = -1; // NOT SET
|
|||
|
|
|||
|
this.isRadarEnabled = false;
|
|||
|
this.radarBlipSize = 1;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Initialisation
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initialise all necessary ship data.
|
|||
|
/// </summary>
|
|||
|
public void Initialise(Transform trfm)
|
|||
|
{
|
|||
|
// Store physics model as a boolean value to save looking up enumeration at runtime
|
|||
|
shipPhysicsModelPhysicsBased = shipPhysicsModel == ShipPhysicsModel.PhysicsBased;
|
|||
|
|
|||
|
// New values, better for pipes
|
|||
|
pitchPIDController = new PIDController(0.5f, 0f, 0.1f);
|
|||
|
rollPIDController = new PIDController(0.5f, 0f, 0.1f);
|
|||
|
// For pitch and roll derivative on measurement is disabled so that
|
|||
|
// a) in limit pitch/roll mode we can use local-space pitch and roll measurements
|
|||
|
// b) if the target pitch/roll is quickly moving away from the measured pitch/roll the
|
|||
|
// PID controller can use a large input to prevent it
|
|||
|
pitchPIDController.derivativeOnMeasurement = false;
|
|||
|
rollPIDController.derivativeOnMeasurement = false;
|
|||
|
ReinitialisePitchRollMatchVariables();
|
|||
|
|
|||
|
// Create a new PID controller for ground match
|
|||
|
groundDistPIDController = new PIDController(0f, 0f, 0f);
|
|||
|
// For ground dist derivative on measurement is disabled so that if the target distance is quickly moving away from
|
|||
|
// the measured pitch/roll the PID controller can use a large input to prevent it
|
|||
|
groundDistPIDController.derivativeOnMeasurement = false;
|
|||
|
// Initialise ground dist PID controller variables
|
|||
|
ReinitialiseGroundMatchVariables();
|
|||
|
|
|||
|
// Create new PID controllers for input axis control
|
|||
|
inputAxisForcePIDController = new PIDController(0f, 0f, 0f);
|
|||
|
inputAxisMomentPIDController = new PIDController(0f, 0f, 0f);
|
|||
|
inputAxisForcePIDController.derivativeOnMeasurement = false;
|
|||
|
inputAxisMomentPIDController.derivativeOnMeasurement = false;
|
|||
|
ReinitialiseInputControlVariables();
|
|||
|
|
|||
|
// Create new PID controllers for stability flight assist
|
|||
|
sFlightAssistPitchPIDController = new PIDController(0f, 0f, 0f);
|
|||
|
sFlightAssistYawPIDController = new PIDController(0f, 0f, 0f);
|
|||
|
sFlightAssistRollPIDController = new PIDController(0f, 0f, 0f);
|
|||
|
ReinitialiseStabilityFlightAssistVariables();
|
|||
|
|
|||
|
// Initialise ray for raycasting against the ground
|
|||
|
raycastRay = new Ray(Vector3.zero, Vector3.up);
|
|||
|
|
|||
|
// Initialise thruster, wing, weapon and damage region variables
|
|||
|
ReinitialiseThrusterVariables();
|
|||
|
ReinitialiseWingVariables();
|
|||
|
ReinitialiseWeaponVariables();
|
|||
|
ReinitialiseDamageRegionVariables(true);
|
|||
|
|
|||
|
// Cache the Weapon FiringButton enumeration values to avoid looking up the enumeration
|
|||
|
// Used in CalculateForceAndMoment(..)
|
|||
|
weaponPrimaryFiringInt = (int)Weapon.FiringButton.Primary;
|
|||
|
weaponSecondaryFiringInt = (int)Weapon.FiringButton.Secondary;
|
|||
|
weaponAutoFiringInt = (int)Weapon.FiringButton.AutoFire;
|
|||
|
|
|||
|
// Initialise respawn data
|
|||
|
ReinitialiseRespawnVariables();
|
|||
|
|
|||
|
// Initialise input data
|
|||
|
ReinitialiseInputVariables();
|
|||
|
|
|||
|
// Get a ship ID integer from the transform ID
|
|||
|
shipId = trfm.GetInstanceID();
|
|||
|
|
|||
|
// Initially there is no radar item assigned in the radar system
|
|||
|
radarItemIndex = -1;
|
|||
|
|
|||
|
// Added in 1.3.3 for muzzle FX reparenting ONLY.
|
|||
|
// Use with caution as may need to be removed in the future if
|
|||
|
// causes issues like when destroying the ShipControlModule.
|
|||
|
shipTransform = trfm;
|
|||
|
|
|||
|
StopBoost();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises all variables needed when changing the ship physics model.
|
|||
|
/// Call after modifying shipPhysicsModel.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseShipPhysicsModel()
|
|||
|
{
|
|||
|
// Store physics model as a boolean value to save looking up enumeration at runtime
|
|||
|
shipPhysicsModelPhysicsBased = shipPhysicsModel == ShipPhysicsModel.PhysicsBased;
|
|||
|
|
|||
|
ReinitialisePitchRollMatchVariables();
|
|||
|
ReinitialiseGroundMatchVariables();
|
|||
|
ReinitialiseInputVariables();
|
|||
|
ReinitialiseInputControlVariables();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to the stability flight assist.
|
|||
|
/// Call after modifying stabilityFlightAssistStrength.
|
|||
|
/// </summary>
|
|||
|
private void ReinitialiseStabilityFlightAssistVariables()
|
|||
|
{
|
|||
|
// Configure pitch PID controller
|
|||
|
sFlightAssistPitchPIDController.pGain = 0.025f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistPitchPIDController.iGain = 0.005f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistPitchPIDController.dGain = 0.01f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistPitchPIDController.SetInputLimits(-1f, 1f);
|
|||
|
|
|||
|
// Configure yaw PID controller
|
|||
|
sFlightAssistYawPIDController.pGain = 0.025f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistYawPIDController.iGain = 0.005f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistYawPIDController.dGain = 0.01f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistYawPIDController.SetInputLimits(-1f, 1f);
|
|||
|
|
|||
|
// Configure roll PID controller
|
|||
|
sFlightAssistRollPIDController.pGain = 0.025f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistRollPIDController.iGain = 0.005f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistRollPIDController.dGain = 0.01f * stabilityFlightAssistStrength;
|
|||
|
sFlightAssistRollPIDController.SetInputLimits(-1f, 1f);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to pitch and roll match.
|
|||
|
/// </summary>
|
|||
|
private void ReinitialisePitchRollMatchVariables()
|
|||
|
{
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Pitch and roll PID controllers are always constrained to ship physics
|
|||
|
pitchPIDController.SetInputLimits(-1f, 1f);
|
|||
|
rollPIDController.SetInputLimits(-1f, 1f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Pitch and roll PID controllers are not constrained to ship physics
|
|||
|
pitchPIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
|
|||
|
rollPIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
|
|||
|
}
|
|||
|
|
|||
|
// Reset PID controllers
|
|||
|
pitchPIDController.ResetController();
|
|||
|
rollPIDController.ResetController();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to ground distance match calculations.
|
|||
|
/// Call after modifying useGroundMatchSmoothing, groundMatchResponsiveness, groundMatchDamping,
|
|||
|
/// maxGroundMatchAccelerationFactor, centralMaxGroundMatchAccelerationFactor or groundNormalHistoryLength.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseGroundMatchVariables()
|
|||
|
{
|
|||
|
// Calculate height range constant (if we are using a min-max range for target distance)
|
|||
|
float groundMatchHeightRangeConstant = 10f;
|
|||
|
if (useGroundMatchSmoothing)
|
|||
|
{
|
|||
|
// Set the constant to the minimum of: The difference between the min and target distances and the difference
|
|||
|
// between the max and target distances
|
|||
|
groundMatchHeightRangeConstant = targetGroundDistance - minGroundDistance;
|
|||
|
if (groundMatchHeightRangeConstant > maxGroundDistance - targetGroundDistance)
|
|||
|
{
|
|||
|
groundMatchHeightRangeConstant = maxGroundDistance - targetGroundDistance;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// P-gain is proportional to responsiveness and inversely proportional to height range
|
|||
|
groundDistPIDController.pGain = 100f * groundMatchResponsiveness / groundMatchHeightRangeConstant;
|
|||
|
// I-gain is half of p-gain
|
|||
|
groundDistPIDController.iGain = 50f * groundMatchResponsiveness / groundMatchHeightRangeConstant;
|
|||
|
// D-gain is proportional to damping and inversely proportional to height range
|
|||
|
groundDistPIDController.dGain = 10f * groundMatchDamping / groundMatchHeightRangeConstant;
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// In physics-based mode, the forces must stay within the limits of the ship thrusters
|
|||
|
groundDistPIDController.SetInputLimits(-1f, 1f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// In arcade mode, the forces must stay within a maximum acceleration
|
|||
|
maxGroundMatchAcceleration = Mathf.Pow(10f, maxGroundMatchAccelerationFactor);
|
|||
|
groundDistPIDController.SetInputLimits(-maxGroundMatchAcceleration, maxGroundMatchAcceleration);
|
|||
|
centralMaxGroundMatchAcceleration = Mathf.Pow(10f, centralMaxGroundMatchAccelerationFactor);
|
|||
|
}
|
|||
|
|
|||
|
// Reset the PID Controller
|
|||
|
groundDistPIDController.ResetController();
|
|||
|
|
|||
|
// Initialise ground normal history
|
|||
|
groundNormalHistory = new Vector3[groundNormalHistoryLength];
|
|||
|
for (int gnIdx = 0; gnIdx < groundNormalHistoryLength; gnIdx++) { groundNormalHistory[gnIdx] = trfmUp; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to Input Control for 2.5D flight.
|
|||
|
/// Call this after modifying inputControlAxis, inputControlMovingRigidness or inputControlTurningRigidness.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseInputControlVariables()
|
|||
|
{
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Set up the physics-based movement PID controller
|
|||
|
// 0.5, 0.1, 0.1 TODO tweak for physics-based
|
|||
|
inputAxisForcePIDController.pGain = inputControlMovingRigidness * 0.1f;
|
|||
|
inputAxisForcePIDController.iGain = inputControlMovingRigidness * 0.02f;
|
|||
|
inputAxisForcePIDController.dGain = inputControlMovingRigidness * 0.02f;
|
|||
|
|
|||
|
// Set up the physics-base rotation PID controller
|
|||
|
// 10, 2, 2 TODO tweak for physics-based
|
|||
|
inputAxisMomentPIDController.pGain = inputControlTurningRigidness * 1f;
|
|||
|
inputAxisMomentPIDController.iGain = inputControlTurningRigidness * 0.2f;
|
|||
|
inputAxisMomentPIDController.dGain = inputControlTurningRigidness * 0.2f;
|
|||
|
|
|||
|
// Set physical input limits
|
|||
|
inputAxisForcePIDController.SetInputLimits(-1f, 1f);
|
|||
|
inputAxisMomentPIDController.SetInputLimits(-1f, 1f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Set up the arcade movement PID controller
|
|||
|
inputAxisForcePIDController.pGain = inputControlMovingRigidness * 1f;
|
|||
|
inputAxisForcePIDController.iGain = inputControlMovingRigidness * 0.2f;
|
|||
|
inputAxisForcePIDController.dGain = inputControlMovingRigidness * 0.2f;
|
|||
|
|
|||
|
// Set up the arcade rotation PID controller
|
|||
|
inputAxisMomentPIDController.pGain = inputControlTurningRigidness * 1f;
|
|||
|
inputAxisMomentPIDController.iGain = inputControlTurningRigidness * 0.2f;
|
|||
|
inputAxisMomentPIDController.dGain = inputControlTurningRigidness * 0.2f;
|
|||
|
|
|||
|
// Set non-physical input limits
|
|||
|
inputAxisForcePIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
|
|||
|
inputAxisMomentPIDController.SetInputLimits(Mathf.NegativeInfinity, Mathf.Infinity);
|
|||
|
}
|
|||
|
|
|||
|
inputAxisForcePIDController.ResetController();
|
|||
|
inputAxisMomentPIDController.ResetController();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to thrusters.
|
|||
|
/// Call after modifying thrusterList.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseThrusterVariables()
|
|||
|
{
|
|||
|
// Initialise all thrusters
|
|||
|
componentListSize = thrusterList == null ? 0 : thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
thrusterList[componentIndex].Initialise();
|
|||
|
}
|
|||
|
|
|||
|
// If thruster systems are not online, immediately turn off all thrusters.
|
|||
|
if (!isThrusterSystemsStarted)
|
|||
|
{
|
|||
|
ShutdownThrusterSystems(true);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to wings.
|
|||
|
/// Call after modifying wingList.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseWingVariables()
|
|||
|
{
|
|||
|
// Initialise all wings
|
|||
|
componentListSize = wingList == null ? 0 : wingList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
wingList[componentIndex].Initialise();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to weapons.
|
|||
|
/// Call after modifying weaponList.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseWeaponVariables()
|
|||
|
{
|
|||
|
// Initialise all weapons
|
|||
|
componentListSize = weaponList == null ? 0 : weaponList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
weaponList[componentIndex].Initialise(trfmInvRot);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to damage regions.
|
|||
|
/// Call after modifying mainDamageRegion or localDamageRegionList (or modifying shipDamageModel).
|
|||
|
/// NOTE: Not required if just changing the Health or ShieldHealth
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseDamageRegionVariables(bool refreshAll = false)
|
|||
|
{
|
|||
|
// Initialise the main damage region
|
|||
|
mainDamageRegion.Initialise();
|
|||
|
|
|||
|
numLocalisedDamageRegions = 0;
|
|||
|
|
|||
|
// Only initialise localised damage regions if the ship damage model is localised
|
|||
|
if (shipDamageModel == ShipDamageModel.Localised || refreshAll)
|
|||
|
{
|
|||
|
// Initialise all localised damage regions
|
|||
|
numLocalisedDamageRegions = localisedDamageRegionList == null ? 0 : localisedDamageRegionList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < numLocalisedDamageRegions; componentIndex++)
|
|||
|
{
|
|||
|
localisedDamageRegionList[componentIndex].Initialise();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises respawn variables using the current position and rotation of the ship.
|
|||
|
/// Also needs to be called after changing respawningMode to RespawningMode.RespawnAtLastPosition.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseRespawnVariables()
|
|||
|
{
|
|||
|
// Initialise current respawn position/rotation
|
|||
|
currentRespawnPosition = trfmPos;
|
|||
|
currentRespawnRotation = trfmRot;
|
|||
|
if (respawningMode == RespawningMode.RespawnAtLastPosition)
|
|||
|
{
|
|||
|
currentCollisionRespawnPosition = currentRespawnPosition;
|
|||
|
currentCollisionRespawnRotation = currentRespawnRotation;
|
|||
|
nextCollisionRespawnPosition = currentRespawnPosition;
|
|||
|
nextCollisionRespawnRotation = currentRespawnRotation;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Re-initialises variables related to ship inputs.
|
|||
|
/// Call after modifying thrusterList or controlSurfaceList.
|
|||
|
/// Call after modifying the forceUse/primaryMomentUse/secondaryMomentUse of a thruster or the type of a control surface.
|
|||
|
/// </summary>
|
|||
|
public void ReinitialiseInputVariables()
|
|||
|
{
|
|||
|
// Initially assume that no inputs are used
|
|||
|
longitudinalThrustInputUsed = false;
|
|||
|
horizontalThrustInputUsed = false;
|
|||
|
verticalThrustInputUsed = false;
|
|||
|
pitchMomentInputUsed = false;
|
|||
|
yawMomentInputUsed = false;
|
|||
|
rollMomentInputUsed = false;
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Loop through all thrusters to check force and moment inputs
|
|||
|
componentListSize = thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the thruster we are concerned with
|
|||
|
thruster = thrusterList[componentIndex];
|
|||
|
// Check whether force inputs are used for this thruster
|
|||
|
if (thruster.forceUse == 1 || thruster.forceUse == 2) { longitudinalThrustInputUsed = true; }
|
|||
|
else if (thruster.forceUse == 3 || thruster.forceUse == 4) { verticalThrustInputUsed = true; }
|
|||
|
else if (thruster.forceUse == 5 || thruster.forceUse == 6) { horizontalThrustInputUsed = true; }
|
|||
|
// Check whether moment inputs are used for this thruster
|
|||
|
if (thruster.primaryMomentUse == 1 || thruster.primaryMomentUse == 2) { rollMomentInputUsed = true; }
|
|||
|
else if (thruster.primaryMomentUse == 3 || thruster.primaryMomentUse == 4) { pitchMomentInputUsed = true; }
|
|||
|
else if (thruster.primaryMomentUse == 5 || thruster.primaryMomentUse == 6) { yawMomentInputUsed = true; }
|
|||
|
if (thruster.secondaryMomentUse == 1 || thruster.secondaryMomentUse == 2) { rollMomentInputUsed = true; }
|
|||
|
else if (thruster.secondaryMomentUse == 3 || thruster.secondaryMomentUse == 4) { pitchMomentInputUsed = true; }
|
|||
|
else if (thruster.secondaryMomentUse == 5 || thruster.secondaryMomentUse == 6) { yawMomentInputUsed = true; }
|
|||
|
}
|
|||
|
|
|||
|
// Loop through all control surfaces to check moment inputs and brake input
|
|||
|
componentListSize = controlSurfaceList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the control surface we are concerned with
|
|||
|
controlSurface = controlSurfaceList[componentIndex];
|
|||
|
// Check whether moment inputs are used for this control surface
|
|||
|
if (controlSurface.type == ControlSurface.ControlSurfaceType.Aileron) { rollMomentInputUsed = true; }
|
|||
|
else if (controlSurface.type == ControlSurface.ControlSurfaceType.Elevator) { pitchMomentInputUsed = true; }
|
|||
|
else if (controlSurface.type == ControlSurface.ControlSurfaceType.Rudder) { yawMomentInputUsed = true; }
|
|||
|
else if (controlSurface.type == ControlSurface.ControlSurfaceType.AirBrake) { longitudinalThrustInputUsed = true; }
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Loop through all thrusters to check force inputs
|
|||
|
componentListSize = thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the thruster we are concerned with
|
|||
|
thruster = thrusterList[componentIndex];
|
|||
|
// Check whether force inputs are used for this thruster
|
|||
|
if (thruster.forceUse == 1 || thruster.forceUse == 2) { longitudinalThrustInputUsed = true; }
|
|||
|
else if (thruster.forceUse == 3 || thruster.forceUse == 4) { verticalThrustInputUsed = true; }
|
|||
|
else if (thruster.forceUse == 5 || thruster.forceUse == 6) { horizontalThrustInputUsed = true; }
|
|||
|
}
|
|||
|
|
|||
|
// In arcade mode, moment inputs only depend on the pitch/roll/yaw accelerations being non-zero
|
|||
|
pitchMomentInputUsed = arcadePitchAcceleration > 0.01f;
|
|||
|
yawMomentInputUsed = arcadeYawAcceleration > 0.01f;
|
|||
|
rollMomentInputUsed = arcadeRollAcceleration > 0.01f;
|
|||
|
|
|||
|
// In arcade mode, brakes can count for longitudinal inputs
|
|||
|
if (arcadeUseBrakeComponent) { longitudinalThrustInputUsed = true; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Update Position And Movement Data
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reset the cached velocity data. Typically called
|
|||
|
/// when the ship rigidbody is set to kinematic.
|
|||
|
/// </summary>
|
|||
|
public void ResetVelocityData()
|
|||
|
{
|
|||
|
worldVelocity = Vector3.zero;
|
|||
|
localVelocity = Vector3.zero;
|
|||
|
absLocalVelocity = Vector3.zero;
|
|||
|
worldAngularVelocity = Vector3.zero;
|
|||
|
localAngularVelocity = Vector3.zero;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update position and movement data using data obtained from a transform and a rigidbody.
|
|||
|
/// </summary>
|
|||
|
/// <param name="transform"></param>
|
|||
|
/// <param name="rigidbody"></param>
|
|||
|
public void UpdatePositionAndMovementData(Transform transform, Rigidbody rigidbody)
|
|||
|
{
|
|||
|
// Update data obtained from transform
|
|||
|
trfmPos = transform.position;
|
|||
|
trfmFwd = transform.forward;
|
|||
|
trfmRight = transform.right;
|
|||
|
trfmUp = transform.up;
|
|||
|
trfmRot = transform.rotation;
|
|||
|
trfmInvRot = Quaternion.Inverse(trfmRot);
|
|||
|
|
|||
|
// Update data obtained from rigidbody
|
|||
|
rbodyPos = rigidbody.position;
|
|||
|
rbodyRot = rigidbody.rotation;
|
|||
|
rbodyInvRot = Quaternion.Inverse(rbodyRot);
|
|||
|
rbodyFwd = rbodyRot * Vector3.forward;
|
|||
|
rbodyRight = rbodyRot * Vector3.right;
|
|||
|
rbodyUp = rbodyRot * Vector3.up;
|
|||
|
worldVelocity = rigidbody.velocity;
|
|||
|
worldAngularVelocity = rigidbody.angularVelocity;
|
|||
|
localVelocity = rbodyInvRot * worldVelocity;
|
|||
|
localAngularVelocity = rbodyInvRot * worldAngularVelocity;
|
|||
|
rBodyInertiaTensor = rigidbody.inertiaTensor;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Calculate Force and Moment
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Calculate the equivalent local resultant force and moment for the ship and store them in the supplied reference vectors.
|
|||
|
/// Should be called during FixedUpdate(), and have UpdatePositionAndMovementData() called prior to it.
|
|||
|
/// </summary>
|
|||
|
/// <param name="localResultantForce"></param>
|
|||
|
/// <param name="localResultantMoment"></param>
|
|||
|
public void CalculateForceAndMoment(ref Vector3 localResultantForce, ref Vector3 localResultantMoment)
|
|||
|
{
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float initStartTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
// TODO: OPTIMISATIONS
|
|||
|
// Reduce number of function calls
|
|||
|
// Use less math functions
|
|||
|
// Remove variable declarations
|
|||
|
// Try not to use transform functions and vector3 functions
|
|||
|
// Do as much precalculation as possible i.e. store a bool for isPhysicsBased etc.
|
|||
|
|
|||
|
#region Initialisation
|
|||
|
|
|||
|
// Read from Time property getter once
|
|||
|
float _deltaTime = Time.deltaTime;
|
|||
|
|
|||
|
// Remember the previous frame's world velocity
|
|||
|
previousFrameWorldVelocity = worldVelocity;
|
|||
|
|
|||
|
// Reset local resultant force and moment
|
|||
|
localResultantForce = Vector3.zero;
|
|||
|
localResultantMoment = Vector3.zero;
|
|||
|
|
|||
|
// Reset any inputs that aren't used (forceInput and momentInput variables used temporarily)
|
|||
|
forceInput.x = horizontalThrustInputUsed ? pilotForceInput.x : 0f;
|
|||
|
forceInput.y = verticalThrustInputUsed ? pilotForceInput.y : 0f;
|
|||
|
forceInput.z = longitudinalThrustInputUsed ? pilotForceInput.z : 0f;
|
|||
|
momentInput.x = pitchMomentInputUsed ? pilotMomentInput.x : 0f;
|
|||
|
momentInput.y = yawMomentInputUsed ? pilotMomentInput.y : 0f;
|
|||
|
momentInput.z = rollMomentInputUsed ? pilotMomentInput.z : 0f;
|
|||
|
pilotForceInput = forceInput;
|
|||
|
pilotMomentInput = momentInput;
|
|||
|
// Reset force and moment input
|
|||
|
forceInput = Vector3.zero;
|
|||
|
momentInput = Vector3.zero;
|
|||
|
|
|||
|
// Reset arcade force and moment vectors
|
|||
|
arcadeForceVector = Vector3.zero;
|
|||
|
arcadeMomentVector = Vector3.zero;
|
|||
|
|
|||
|
// Get the ship physics model
|
|||
|
shipPhysicsModelPhysicsBased = shipPhysicsModel == ShipPhysicsModel.PhysicsBased;
|
|||
|
// Get the ship damage model
|
|||
|
shipDamageModelIsSimple = shipDamageModel == ShipDamageModel.Simple;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float initEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
float groundStartTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
#region Get Ground Information
|
|||
|
|
|||
|
if (limitPitchAndRoll)
|
|||
|
{
|
|||
|
if (stickToGroundSurface || avoidGroundSurface)
|
|||
|
{
|
|||
|
// TODO: This shouldn't always be centre of the object
|
|||
|
//raycastRay.origin = trfmPos + (trfmFwd * 3.5f);
|
|||
|
raycastRay.origin = trfmPos;
|
|||
|
// If we were on the ground last frame, raycast in the "down" direction indicated by the ground normal
|
|||
|
if (stickToGroundSurfaceEnabled) { raycastRay.direction = -worldTargetPlaneNormal; }
|
|||
|
// Otherwise raycast in the ship's down direction
|
|||
|
else { raycastRay.direction = -trfmUp; }
|
|||
|
|
|||
|
// Perform the raycast
|
|||
|
if (Physics.Raycast(raycastRay, out raycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
|
|||
|
{
|
|||
|
// Single normal, derived from raycast hit normal
|
|||
|
// This is the backup for smoothed normals and average height methods
|
|||
|
worldTargetPlaneNormal = raycastHitInfo.normal;
|
|||
|
|
|||
|
if (groundNormalCalculationMode == GroundNormalCalculationMode.SmoothedNormal)
|
|||
|
{
|
|||
|
// Only use smoothed normals if the ground is using a mesh collider
|
|||
|
// Check for a valid number of triangles, as sometimes can return -1. This potentially happens when
|
|||
|
// Unity simplifies the collider (called degenerate) to improve performance.
|
|||
|
if (raycastHitInfo.collider != null && raycastHitInfo.collider.GetType() == typeof(MeshCollider) && raycastHitInfo.triangleIndex >= 0)
|
|||
|
{
|
|||
|
// Get the collider as a mesh collider
|
|||
|
groundMeshCollider = (MeshCollider)raycastHitInfo.collider;
|
|||
|
if (groundMeshCollider != null)
|
|||
|
{
|
|||
|
// Check if this mesh collider is the cached one - if it is, we can use
|
|||
|
// the previously stored data without having to look it up from the mesh
|
|||
|
if (groundMeshCollider != cachedGroundMeshCollider)
|
|||
|
{
|
|||
|
// If this mesh collider is not the cached one, cache the mesh data for next time
|
|||
|
cachedGroundMeshCollider = groundMeshCollider;
|
|||
|
// Get the shared mesh being used by the ground collider and check that it isn't null
|
|||
|
cachedGroundMesh = groundMeshCollider.sharedMesh;
|
|||
|
if (cachedGroundMesh != null)
|
|||
|
{
|
|||
|
// Get the normals and triangle indices arrays
|
|||
|
cachedGroundMeshNormals = cachedGroundMesh.normals;
|
|||
|
cachedGroundMeshTriangles = cachedGroundMesh.triangles;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Get the normals from the triangle we hit using the cached data
|
|||
|
groundNormal0 = cachedGroundMeshNormals[cachedGroundMeshTriangles[raycastHitInfo.triangleIndex * 3 + 0]];
|
|||
|
groundNormal1 = cachedGroundMeshNormals[cachedGroundMeshTriangles[raycastHitInfo.triangleIndex * 3 + 1]];
|
|||
|
groundNormal2 = cachedGroundMeshNormals[cachedGroundMeshTriangles[raycastHitInfo.triangleIndex * 3 + 2]];
|
|||
|
// Get the barycentric coordinate of the point we hit - this gives us information as to
|
|||
|
// where in the triangle this point is
|
|||
|
barycentricCoord = raycastHitInfo.barycentricCoordinate;
|
|||
|
// Interpolate between the three normals using the barycentric coordinate to
|
|||
|
// get the smoothed normal - this is how smooth shading works
|
|||
|
worldTargetPlaneNormal = groundMeshCollider.transform.TransformDirection(
|
|||
|
barycentricCoord[0] * groundNormal0 +
|
|||
|
barycentricCoord[1] * groundNormal1 +
|
|||
|
barycentricCoord[2] * groundNormal2).normalized;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
//else if (groundNormalCalculationMode == GroundNormalCalculationMode.AverageHeight)
|
|||
|
//{
|
|||
|
// float halfRectangleWidth = 3.5f;
|
|||
|
// float halfRectangleLength = 5.5f;
|
|||
|
// bool raycastFRHit, raycastFLHit, raycastRRHit, raycastRLHit;
|
|||
|
// float nUsableNormals = 0f;
|
|||
|
// Vector3 raycastFRHitPoint = Vector3.zero;
|
|||
|
// Vector3 raycastFLHitPoint = Vector3.zero;
|
|||
|
// Vector3 raycastRRHitPoint = Vector3.zero;
|
|||
|
// Vector3 raycastRLHitPoint = Vector3.zero;
|
|||
|
// RaycastHit testRaycastHitInfo;
|
|||
|
// Ray testRaycastRay = new Ray();
|
|||
|
// Vector3 averageNormal = Vector3.zero;
|
|||
|
|
|||
|
// // Front-right raycast
|
|||
|
// testRaycastRay.origin = trfmPos + (trfmRight * halfRectangleWidth) + (trfmFwd * halfRectangleLength);
|
|||
|
// testRaycastRay.direction = -trfmUp;
|
|||
|
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
|
|||
|
// {
|
|||
|
// raycastFRHit = true;
|
|||
|
// raycastFRHitPoint = testRaycastHitInfo.point;
|
|||
|
// }
|
|||
|
// else { raycastFRHit = false; }
|
|||
|
|
|||
|
// // Front-left raycast
|
|||
|
// testRaycastRay.origin = trfmPos - (trfmRight * halfRectangleWidth) + (trfmFwd * halfRectangleLength);
|
|||
|
// testRaycastRay.direction = -trfmUp;
|
|||
|
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
|
|||
|
// {
|
|||
|
// raycastFLHit = true;
|
|||
|
// raycastFLHitPoint = testRaycastHitInfo.point;
|
|||
|
// }
|
|||
|
// else { raycastFLHit = false; }
|
|||
|
|
|||
|
// // Rear-right raycast
|
|||
|
// testRaycastRay.origin = trfmPos + (trfmRight * halfRectangleWidth) - (trfmFwd * halfRectangleLength);
|
|||
|
// testRaycastRay.direction = -trfmUp;
|
|||
|
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
|
|||
|
// {
|
|||
|
// raycastRRHit = true;
|
|||
|
// raycastRRHitPoint = testRaycastHitInfo.point;
|
|||
|
// }
|
|||
|
// else { raycastRRHit = false; }
|
|||
|
|
|||
|
// // Rear-left raycast
|
|||
|
// testRaycastRay.origin = trfmPos - (trfmRight * halfRectangleWidth) - (trfmFwd * halfRectangleLength);
|
|||
|
// testRaycastRay.direction = -trfmUp;
|
|||
|
// if (Physics.Raycast(testRaycastRay, out testRaycastHitInfo, maxGroundCheckDistance, groundLayerMask, QueryTriggerInteraction.Ignore))
|
|||
|
// {
|
|||
|
// raycastRLHit = true;
|
|||
|
// raycastRLHitPoint = testRaycastHitInfo.point;
|
|||
|
// }
|
|||
|
// else { raycastRLHit = false; }
|
|||
|
|
|||
|
// if (raycastFLHit && raycastFRHit && raycastRRHit)
|
|||
|
// {
|
|||
|
// nUsableNormals += 1f;
|
|||
|
// averageNormal += Vector3.Cross(raycastRRHitPoint - raycastFRHitPoint, raycastFLHitPoint - raycastFRHitPoint).normalized;
|
|||
|
// }
|
|||
|
// if (raycastFRHit && raycastRRHit && raycastRLHit)
|
|||
|
// {
|
|||
|
// nUsableNormals += 1f;
|
|||
|
// averageNormal += Vector3.Cross(raycastRLHitPoint - raycastRRHitPoint, raycastFRHitPoint - raycastRRHitPoint).normalized;
|
|||
|
// }
|
|||
|
// if (raycastRRHit && raycastRLHit && raycastFLHit)
|
|||
|
// {
|
|||
|
// nUsableNormals += 1f;
|
|||
|
// averageNormal += Vector3.Cross(raycastFLHitPoint - raycastRLHitPoint, raycastRRHitPoint - raycastRLHitPoint).normalized;
|
|||
|
// }
|
|||
|
// if (raycastRLHit && raycastFLHit && raycastFRHit)
|
|||
|
// {
|
|||
|
// nUsableNormals += 1f;
|
|||
|
// averageNormal += Vector3.Cross(raycastFRHitPoint - raycastFLHitPoint, raycastRLHitPoint - raycastFLHitPoint).normalized;
|
|||
|
// }
|
|||
|
|
|||
|
// if (nUsableNormals > 0f) { worldTargetPlaneNormal = (averageNormal / nUsableNormals).normalized; }
|
|||
|
//}
|
|||
|
|
|||
|
// Calculate a normal averaged from the last few frames
|
|||
|
UpdateGroundNormalHistory(worldTargetPlaneNormal);
|
|||
|
worldTargetPlaneNormal = GetDampedGroundNormal();
|
|||
|
|
|||
|
// Get target plane normal in local space
|
|||
|
localTargetPlaneNormal = trfmInvRot * worldTargetPlaneNormal;
|
|||
|
|
|||
|
// Calculate distance to the ground in a direction parallel to the ground normal
|
|||
|
// (from the ground directly beneath the ship)
|
|||
|
currentPerpendicularGroundDist = Vector3.Dot(trfmPos - raycastHitInfo.point, worldTargetPlaneNormal);
|
|||
|
|
|||
|
//Debug.DrawRay(raycastRay.origin, raycastRay.direction.normalized * maxGroundCheckDistance, Color.blue);
|
|||
|
|
|||
|
// If required, use look ahead to check if there is an obstacle ahead we will need to go over
|
|||
|
if (useGroundMatchLookAhead)
|
|||
|
{
|
|||
|
float maxRaycastDistance;
|
|||
|
RaycastHit secondaryRaycastHitInfo;
|
|||
|
|
|||
|
// Loop through a series of different look ahead times, starting at half a second away
|
|||
|
for (float groundMatchLookAheadTime = 0.5f; groundMatchLookAheadTime > 0.01f; groundMatchLookAheadTime -= 0.1f)
|
|||
|
{
|
|||
|
// Calculate the corresponding look ahead distance
|
|||
|
float groundMatchLookAheadDistance = groundMatchLookAheadTime * localVelocity.z;
|
|||
|
|
|||
|
// Calculate the raycast direction
|
|||
|
raycastRay.direction = (-currentPerpendicularGroundDist * worldTargetPlaneNormal) + (groundMatchLookAheadDistance * trfmFwd);
|
|||
|
// Calculate the required raycast length to go down the same distance as the standard ground check
|
|||
|
maxRaycastDistance = Mathf.Sqrt((currentPerpendicularGroundDist * currentPerpendicularGroundDist) +
|
|||
|
(groundMatchLookAheadDistance * groundMatchLookAheadDistance)) * (maxGroundCheckDistance / currentPerpendicularGroundDist);
|
|||
|
|
|||
|
// Perform the raycast
|
|||
|
if (Physics.Raycast(raycastRay, out secondaryRaycastHitInfo, maxRaycastDistance, groundLayerMask,
|
|||
|
QueryTriggerInteraction.Ignore))
|
|||
|
{
|
|||
|
// Calculate the perpendicular ground distance (projected onto the target normal)
|
|||
|
float futurePerpendicularGroundDistance = Vector3.Dot(trfmPos - secondaryRaycastHitInfo.point, worldTargetPlaneNormal);
|
|||
|
// If the perpendicular ground distance we have calculated is LESS than the current
|
|||
|
// perpendicular ground distance (i.e. for the ground beneath the ship), use this
|
|||
|
// new ground distance instead
|
|||
|
if (futurePerpendicularGroundDistance <= currentPerpendicularGroundDist)
|
|||
|
{
|
|||
|
currentPerpendicularGroundDist = futurePerpendicularGroundDistance;
|
|||
|
//Debug.DrawRay(raycastRay.origin, raycastRay.direction.normalized * maxRaycastDistance, Color.green);
|
|||
|
// Exit the loop
|
|||
|
break;
|
|||
|
}
|
|||
|
//else
|
|||
|
//{
|
|||
|
// Debug.DrawRay(raycastRay.origin, raycastRay.direction.normalized * maxRaycastDistance, Color.red);
|
|||
|
//}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Set relevant variables, to indicate to later code that we have found a ground surface
|
|||
|
limitPitchAndRollEnabled = true;
|
|||
|
stickToGroundSurfaceEnabled = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If we were on the ground on the previous frame, and now we are not,
|
|||
|
// reset the PID controller to prevent spikes when we go back onto the ground again
|
|||
|
if (stickToGroundSurfaceEnabled) { groundDistPIDController.ResetController(); }
|
|||
|
|
|||
|
limitPitchAndRollEnabled = orientUpInAir;
|
|||
|
stickToGroundSurfaceEnabled = false;
|
|||
|
if (limitPitchAndRollEnabled)
|
|||
|
{
|
|||
|
worldTargetPlaneNormal = Vector3.up;
|
|||
|
// Update ground normal history
|
|||
|
UpdateGroundNormalHistory(worldTargetPlaneNormal);
|
|||
|
// Get target plane normal in local space
|
|||
|
localTargetPlaneNormal = trfmInvRot * worldTargetPlaneNormal;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Update ground normal history
|
|||
|
UpdateGroundNormalHistory(trfmUp);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
limitPitchAndRollEnabled = true;
|
|||
|
stickToGroundSurfaceEnabled = false;
|
|||
|
worldTargetPlaneNormal = Vector3.up;
|
|||
|
// Get target plane normal in local space
|
|||
|
localTargetPlaneNormal = trfmInvRot * worldTargetPlaneNormal;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
limitPitchAndRollEnabled = false;
|
|||
|
stickToGroundSurfaceEnabled = false;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float groundEndTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
#region Gravity
|
|||
|
|
|||
|
// Calculate acceleration due to gravity in local space (storing the value for use later)
|
|||
|
localGravityAcceleration = trfmInvRot * gravityDirection * gravitationalAcceleration;
|
|||
|
// Then add it to local resultant force vector (multiplying by mass to turn it into a force)
|
|||
|
localResultantForce += localGravityAcceleration * mass;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float inputStartTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
#region Calculate Final Inputs
|
|||
|
|
|||
|
// Calculate final input
|
|||
|
|
|||
|
// | Assists/limiters info |
|
|||
|
// Rotational flight assist:
|
|||
|
// - Always on
|
|||
|
// - If the pilot has released an axial input but we are still spinning on that axis,
|
|||
|
// modify the input to slow down the spin
|
|||
|
// Translational flight assist:
|
|||
|
// - Always on
|
|||
|
// - If the pilot has released a translational input but we are still moving on that axis,
|
|||
|
// modify the input to slow down the movement
|
|||
|
// - Is overriden on y-axis by stick to ground surface
|
|||
|
// - Currently does not apply on z-axis
|
|||
|
// - Isn't available in arcade mode (it is instead replaced by velocity match code)
|
|||
|
// Brake flight assist:
|
|||
|
// - Off by default
|
|||
|
// - Translational flight assist on z-axis only
|
|||
|
// - Only applies when no z-axis input
|
|||
|
// - Only applies when fwd/back movement is non-zero and not close to zero
|
|||
|
// - Can be overridden in forwards direction by AutoCruise on PlayerInputModule
|
|||
|
// Limit pitch and roll:
|
|||
|
// - Overrides x and z axes of rotational flight assist
|
|||
|
// - Causes y-axis rotational flight assist to apply on an axis normal to the target plane
|
|||
|
// Stick to ground surface
|
|||
|
// - Overrides y axis of pilot force input
|
|||
|
|
|||
|
// Moment inputs
|
|||
|
// - Rotational flight assist
|
|||
|
|
|||
|
if (limitPitchAndRollEnabled)
|
|||
|
{
|
|||
|
// Ground angle match assist: Calculate a target pitch and roll from the ground angle
|
|||
|
// Pitch is still affected by pilot input but is clamped between min and max pitch
|
|||
|
// Roll is controlled by yaw input or strafe input
|
|||
|
// Calculate target plane normal in local space
|
|||
|
|
|||
|
// Calculate target pitch (relative to the ground normal) in degrees
|
|||
|
// This is always dependent on the pitch input
|
|||
|
pitchAmountInput = pilotMomentInput.x * maxPitch;
|
|||
|
|
|||
|
// Calculate target roll (relative to the ground normal) in degrees
|
|||
|
// a) From the yaw input
|
|||
|
if (rollControlMode == RollControlMode.YawInput) { rollAmountInput = pilotMomentInput.y * maxTurnRoll; }
|
|||
|
// b) From the strafe input
|
|||
|
else { rollAmountInput = pilotForceInput.x * maxTurnRoll; }
|
|||
|
|
|||
|
// Calculate the maximum possible change in pitch/roll for this frame
|
|||
|
maxFramePitchDelta = _deltaTime * pitchSpeed;
|
|||
|
maxFrameRollDelta = _deltaTime * turnRollSpeed;
|
|||
|
|
|||
|
// Move the pitch amount towards the target pitch input
|
|||
|
if (pitchAmountInput >= pitchAmount - maxFramePitchDelta && pitchAmountInput <= pitchAmount + maxFramePitchDelta) { pitchAmount = pitchAmountInput; }
|
|||
|
else if (pitchAmountInput > pitchAmount) { pitchAmount += maxFramePitchDelta; }
|
|||
|
else { pitchAmount -= maxFramePitchDelta; }
|
|||
|
|
|||
|
// Move the roll amount towards the target roll input
|
|||
|
if (rollAmountInput >= rollAmount - maxFrameRollDelta && rollAmountInput <= rollAmount + maxFrameRollDelta) { rollAmount = rollAmountInput; }
|
|||
|
else if (rollAmountInput > rollAmount) { rollAmount += maxFrameRollDelta; }
|
|||
|
else { rollAmount -= maxFrameRollDelta; }
|
|||
|
|
|||
|
// Calculate target local-space roll and pitch in degrees
|
|||
|
targetLocalPitch = ((float)Math.Atan2(localTargetPlaneNormal.z, localTargetPlaneNormal.y) * Mathf.Rad2Deg) + pitchAmount;
|
|||
|
targetLocalRoll = ((float)Math.Atan2(localTargetPlaneNormal.x, localTargetPlaneNormal.y) * Mathf.Rad2Deg) + rollAmount;
|
|||
|
|
|||
|
//Debug.Log("Target pitch: " + targetLocalPitch.ToString("00.00") + ", target roll: " + targetLocalRoll.ToString("00.00"));
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Use the PID controllers to calculate the required moment inputs on the x and z axes
|
|||
|
momentInput.z = rollPIDController.RequiredInput(targetLocalRoll, 0f, _deltaTime);
|
|||
|
momentInput.x = pitchPIDController.RequiredInput(targetLocalPitch, 0f, _deltaTime);
|
|||
|
momentInput.y = 0f;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Use the PID controllers to calculate the required moments on the x and z axes
|
|||
|
arcadeMomentVector.z = rollPIDController.RequiredInput(targetLocalRoll, 0f, _deltaTime) * 50f * pitchRollMatchResponsiveness;
|
|||
|
arcadeMomentVector.x = pitchPIDController.RequiredInput(targetLocalPitch, 0f, _deltaTime) * 50f * pitchRollMatchResponsiveness;
|
|||
|
arcadeMomentVector.y = 0f;
|
|||
|
// Completely reset moment input here
|
|||
|
momentInput = Vector3.zero;
|
|||
|
}
|
|||
|
|
|||
|
// Apply typical yaw rotation with respect to ground normal instead of local y coordinate
|
|||
|
// TODO: Surely this line can be optimised...?
|
|||
|
yawVelocity = Vector3.Dot(Vector3.Project(localAngularVelocity * Mathf.Rad2Deg, localTargetPlaneNormal), localTargetPlaneNormal);
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Activate rotational flight assist if pilot releases an input
|
|||
|
// or if input is in direction opposing rotational velocity
|
|||
|
if ((pilotMomentInput.y > 0f) == (yawVelocity < 0f) || pilotMomentInput.y == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on the ground normal axis
|
|||
|
flightAssistValue = -yawVelocity / 100f * rotationalFlightAssistStrength;
|
|||
|
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
|
|||
|
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
|
|||
|
(pilotMomentInput.y > 0f ? pilotMomentInput.y : -pilotMomentInput.y))
|
|||
|
{
|
|||
|
momentInput += localTargetPlaneNormal * flightAssistValue;
|
|||
|
}
|
|||
|
else { momentInput += localTargetPlaneNormal * pilotMomentInput.y; }
|
|||
|
}
|
|||
|
else { momentInput += localTargetPlaneNormal * pilotMomentInput.y; }
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
momentInput += localTargetPlaneNormal * pilotMomentInput.y;
|
|||
|
// Activate arcade rotational flight assist if pilot releases an input
|
|||
|
// or if input is in direction opposing rotational velocity
|
|||
|
if ((pilotMomentInput.y > 0f) == (yawVelocity < 0f) || pilotMomentInput.y == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on the ground normal axis
|
|||
|
arcadeMomentVector += localTargetPlaneNormal * -yawVelocity * rotationalFlightAssistStrength;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Rotational flight assist: Input to counteract rotational velocity
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Activate rotational flight assist if pilot releases an input
|
|||
|
// or if input is in direction opposing rotational velocity
|
|||
|
if ((pilotMomentInput.x > 0f) == (localAngularVelocity.x < 0f) || pilotMomentInput.x == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on this axis
|
|||
|
flightAssistValue = -localAngularVelocity.x * Mathf.Rad2Deg / 100f * rotationalFlightAssistStrength;
|
|||
|
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
|
|||
|
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
|
|||
|
(pilotMomentInput.x > 0f ? pilotMomentInput.x : -pilotMomentInput.x)) { momentInput.x = flightAssistValue; }
|
|||
|
else { momentInput.x = pilotMomentInput.x; }
|
|||
|
}
|
|||
|
else { momentInput.x = pilotMomentInput.x; }
|
|||
|
if ((pilotMomentInput.y > 0f) == (localAngularVelocity.y < 0f) || pilotMomentInput.y == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on this axis
|
|||
|
flightAssistValue = -localAngularVelocity.y * Mathf.Rad2Deg / 100f * rotationalFlightAssistStrength;
|
|||
|
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
|
|||
|
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
|
|||
|
(pilotMomentInput.y > 0f ? pilotMomentInput.y : -pilotMomentInput.y)) { momentInput.y = flightAssistValue; }
|
|||
|
else { momentInput.y = pilotMomentInput.y; }
|
|||
|
}
|
|||
|
else { momentInput.y = pilotMomentInput.y; }
|
|||
|
if ((pilotMomentInput.z > 0f) == (localAngularVelocity.z > 0f) || pilotMomentInput.z == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on this axis
|
|||
|
flightAssistValue = localAngularVelocity.z * Mathf.Rad2Deg / 100f * rotationalFlightAssistStrength;
|
|||
|
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
|
|||
|
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
|
|||
|
(pilotMomentInput.z > 0f ? pilotMomentInput.z : -pilotMomentInput.z)) { momentInput.z = flightAssistValue; }
|
|||
|
else { momentInput.z = pilotMomentInput.z; }
|
|||
|
}
|
|||
|
else { momentInput.z = pilotMomentInput.z; }
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Activate arcade rotational flight assist if pilot releases an input
|
|||
|
// or if input is in direction opposing rotational velocity
|
|||
|
momentInput = pilotMomentInput;
|
|||
|
if ((pilotMomentInput.x > 0f) == (localAngularVelocity.x < 0f) || pilotMomentInput.x == 0f)
|
|||
|
{
|
|||
|
arcadeMomentVector.x = -localAngularVelocity.x * Mathf.Rad2Deg * rotationalFlightAssistStrength;
|
|||
|
}
|
|||
|
if ((pilotMomentInput.y > 0f) == (localAngularVelocity.y < 0f) || pilotMomentInput.y == 0f)
|
|||
|
{
|
|||
|
arcadeMomentVector.y = -localAngularVelocity.y * Mathf.Rad2Deg * rotationalFlightAssistStrength;
|
|||
|
}
|
|||
|
if ((pilotMomentInput.z > 0f) == (localAngularVelocity.z > 0f) || pilotMomentInput.z == 0f)
|
|||
|
{
|
|||
|
arcadeMomentVector.z = localAngularVelocity.z * Mathf.Rad2Deg * rotationalFlightAssistStrength;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (stabilityFlightAssistStrength > 0f)
|
|||
|
{
|
|||
|
// TODO I need to optimise the calculation section
|
|||
|
|
|||
|
#region Calculate Current Pitch, Yaw and Roll
|
|||
|
|
|||
|
// TODO I really need to optimise this properly
|
|||
|
// Calculate current pitch of the ship
|
|||
|
Vector3 pitchProjectionPlaneNormal = Vector3.Cross(Vector3.up, TransformForward);
|
|||
|
Vector3 pitchProjectedUpDirection = Vector3.ProjectOnPlane(TransformUp, pitchProjectionPlaneNormal);
|
|||
|
float stabilityCurrentPitch = Vector3.SignedAngle(Vector3.up, pitchProjectedUpDirection, pitchProjectionPlaneNormal);
|
|||
|
// Measure pitch with respect to target pitch direction
|
|||
|
stabilityCurrentPitch -= sFlightAssistTargetPitch;
|
|||
|
if (stabilityCurrentPitch > 180f) { stabilityCurrentPitch -= 360f; }
|
|||
|
else if (stabilityCurrentPitch < -180f) { stabilityCurrentPitch += 360f; }
|
|||
|
|
|||
|
// Calculate current yaw of the ship
|
|||
|
// TODO this can be massively optimised by just setting the y-value to zero
|
|||
|
Vector3 yawProjectionPlaneNormal = Vector3.up;
|
|||
|
Vector3 yawProjectedForwardDirection = Vector3.ProjectOnPlane(TransformForward, yawProjectionPlaneNormal);
|
|||
|
float stabilityCurrentYaw = Vector3.SignedAngle(Vector3.forward, yawProjectedForwardDirection, yawProjectionPlaneNormal);
|
|||
|
// Measure yaw with respect to target yaw direction
|
|||
|
stabilityCurrentYaw -= sFlightAssistTargetYaw;
|
|||
|
if (stabilityCurrentYaw > 180f) { stabilityCurrentYaw -= 360f; }
|
|||
|
else if (stabilityCurrentYaw < -180f) { stabilityCurrentYaw += 360f; }
|
|||
|
|
|||
|
// Calculate current roll of the ship
|
|||
|
Vector3 rollProjectionPlaneNormal = TransformForward;
|
|||
|
rollProjectionPlaneNormal.y = 0f;
|
|||
|
Vector3 rollProjectedUpDirection = Vector3.ProjectOnPlane(TransformUp, rollProjectionPlaneNormal);
|
|||
|
float stabilityCurrentRoll = -Vector3.SignedAngle(Vector3.up, rollProjectedUpDirection, rollProjectionPlaneNormal);
|
|||
|
// Measure roll with respect to target roll direction
|
|||
|
stabilityCurrentRoll -= sFlightAssistTargetRoll;
|
|||
|
if (stabilityCurrentRoll > 180f) { stabilityCurrentRoll -= 360f; }
|
|||
|
else if (stabilityCurrentRoll < -180f) { stabilityCurrentRoll += 360f; }
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Update Target Pitch, Yaw and Roll Values
|
|||
|
|
|||
|
// How this works: There are two (mutually exclusive) modes of operation:
|
|||
|
// 1. Setting target values for pitch/yaw/roll
|
|||
|
// 2. Applying the stability assist for pitch/yaw/roll
|
|||
|
// For example, if the target pitch is being set, this means that the stability assist will not be used
|
|||
|
// for pitch in that frame
|
|||
|
bool allowPitchStabilityAssist = true;
|
|||
|
bool allowYawStabilityAssist = true;
|
|||
|
bool allowRollStabilityAssist = true;
|
|||
|
|
|||
|
// Check if the pilot is initiating a manoeuvre on any of the three axes via pilot input
|
|||
|
bool initiatingPitchManoeuvre = pilotMomentInput.x > 0.01f || pilotMomentInput.x < -0.01f;
|
|||
|
bool initiatingYawManoeuvre = pilotMomentInput.y > 0.01f || pilotMomentInput.y < -0.01f;
|
|||
|
bool initiatingRollManoeuvre = pilotMomentInput.z > 0.01f || pilotMomentInput.z < -0.01f;
|
|||
|
|
|||
|
// If there is currently some pilot pitch/yaw/roll input...
|
|||
|
if (initiatingPitchManoeuvre || initiatingRollManoeuvre || initiatingYawManoeuvre)
|
|||
|
{
|
|||
|
// ... set the target pitch/yaw/roll
|
|||
|
sFlightAssistTargetPitch += stabilityCurrentPitch;
|
|||
|
sFlightAssistPitchPIDController.ResetController();
|
|||
|
sFlightAssistTargetYaw += stabilityCurrentYaw;
|
|||
|
sFlightAssistYawPIDController.ResetController();
|
|||
|
sFlightAssistTargetRoll += stabilityCurrentRoll;
|
|||
|
sFlightAssistRollPIDController.ResetController();
|
|||
|
allowPitchStabilityAssist = false;
|
|||
|
allowYawStabilityAssist = false;
|
|||
|
allowRollStabilityAssist = false;
|
|||
|
|
|||
|
// If a manouevre has been initiated on a given axis via pilot input, remember it
|
|||
|
// We can hence know when we are rotating on a particular axis due to pilot input
|
|||
|
// or due to drag moments etc and act accordingly
|
|||
|
// Note that pitch can affect yaw and vice versa
|
|||
|
if (initiatingPitchManoeuvre || initiatingYawManoeuvre)
|
|||
|
{
|
|||
|
sFlightAssistRotatingPitch = true;
|
|||
|
sFlightAssistRotatingYaw = true;
|
|||
|
}
|
|||
|
if (initiatingRollManoeuvre) { sFlightAssistRotatingRoll = true; }
|
|||
|
}
|
|||
|
// Otherwise...
|
|||
|
else
|
|||
|
{
|
|||
|
// If there is a significant pitch assist input to correct a pitch manoeuvre...
|
|||
|
if (sFlightAssistRotatingPitch && (momentInput.x > 0.3f || momentInput.x < -0.3f))
|
|||
|
{
|
|||
|
// ... set the target pitch
|
|||
|
sFlightAssistTargetPitch += stabilityCurrentPitch;
|
|||
|
sFlightAssistPitchPIDController.ResetController();
|
|||
|
allowPitchStabilityAssist = false;
|
|||
|
}
|
|||
|
// When the pitch assist moment input has reduced enough, we can assume we have finished
|
|||
|
// any pitch manoeuvres initiated by the pilot
|
|||
|
else { sFlightAssistRotatingPitch = false; }
|
|||
|
// If there is a significant yaw assist input to correct a yaw manoeuvre...
|
|||
|
if (sFlightAssistRotatingYaw && (momentInput.y > 0.3f || momentInput.y < -0.3f))
|
|||
|
{
|
|||
|
// ... set the target yaw
|
|||
|
sFlightAssistTargetYaw += stabilityCurrentYaw;
|
|||
|
sFlightAssistYawPIDController.ResetController();
|
|||
|
allowYawStabilityAssist = false;
|
|||
|
}
|
|||
|
// When the yaw assist moment input has reduced enough, we can assume we have finished
|
|||
|
// any yaw manoeuvres initiated by the pilot
|
|||
|
else { sFlightAssistRotatingYaw = false; }
|
|||
|
// If there is a significant roll assist input to correct a roll manoeuvre...
|
|||
|
if (sFlightAssistRotatingRoll && (momentInput.z > 0.3f || momentInput.z < -0.3f))
|
|||
|
{
|
|||
|
// ... set the target roll
|
|||
|
sFlightAssistTargetRoll += stabilityCurrentRoll;
|
|||
|
sFlightAssistRollPIDController.ResetController();
|
|||
|
allowRollStabilityAssist = false;
|
|||
|
}
|
|||
|
// When the roll assist moment input has reduced enough, we can assume we have finished
|
|||
|
// any roll manoeuvres initiated by the pilot
|
|||
|
else { sFlightAssistRotatingRoll = false; }
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Apply Stability Assist
|
|||
|
|
|||
|
if (allowPitchStabilityAssist)
|
|||
|
{
|
|||
|
// Get input from pitch PID controller and apply it
|
|||
|
momentInput.x += sFlightAssistPitchPIDController.RequiredInput(0f, stabilityCurrentPitch, _deltaTime);
|
|||
|
}
|
|||
|
|
|||
|
if (allowYawStabilityAssist)
|
|||
|
{
|
|||
|
// Get input from yaw PID controller and apply it
|
|||
|
momentInput.y += sFlightAssistYawPIDController.RequiredInput(0f, stabilityCurrentYaw, _deltaTime);
|
|||
|
}
|
|||
|
|
|||
|
if (allowRollStabilityAssist)
|
|||
|
{
|
|||
|
// Get input from roll PID controller and apply it
|
|||
|
momentInput.z += sFlightAssistRollPIDController.RequiredInput(0f, stabilityCurrentRoll, _deltaTime);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Scale moment inputs in physics-based mode - they generally need to be a lot less than force inputs
|
|||
|
momentInput.x *= pitchPower;
|
|||
|
momentInput.y *= yawPower;
|
|||
|
momentInput.z *= rollPower;
|
|||
|
|
|||
|
// Clamp moment inputs to be between -1 and 1. We don't do this in arcade mode so that moment assists/limits
|
|||
|
// can use more than the specified maximum moments if necessary
|
|||
|
if (momentInput.x > 1f) { momentInput.x = 1f; }
|
|||
|
else if (momentInput.x < -1f) { momentInput.x = -1f; }
|
|||
|
if (momentInput.y > 1f) { momentInput.y = 1f; }
|
|||
|
else if (momentInput.y < -1f) { momentInput.y = -1f; }
|
|||
|
if (momentInput.z > 1f) { momentInput.z = 1f; }
|
|||
|
else if (momentInput.z < -1f) { momentInput.z = -1f; }
|
|||
|
}
|
|||
|
|
|||
|
// Force inputs
|
|||
|
// - Translational flight assist (physics-based only)
|
|||
|
// - Stick to ground surface (physics-based only: arcade equivalent is applied separately)
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Translational flight assist: Input to counteract translational velocity
|
|||
|
|
|||
|
// Activate translational flight assist if pilot releases an input
|
|||
|
// or if input is in direction opposing velocity
|
|||
|
if ((pilotForceInput.x > 0f) == (localVelocity.x < 0f) || pilotForceInput.x == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on this axis
|
|||
|
flightAssistValue = -localVelocity.x / 100f * translationalFlightAssistStrength;
|
|||
|
if (flightAssistValue > 1f) { flightAssistValue = 1f; }
|
|||
|
else if (flightAssistValue < -1f) { flightAssistValue = -1f; }
|
|||
|
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
|
|||
|
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
|
|||
|
(pilotForceInput.x > 0f ? pilotForceInput.x : -pilotForceInput.x)) { forceInput.x = flightAssistValue; }
|
|||
|
else { forceInput.x = pilotForceInput.x; }
|
|||
|
}
|
|||
|
else { forceInput.x = pilotForceInput.x; }
|
|||
|
|
|||
|
if (stickToGroundSurfaceEnabled)
|
|||
|
{
|
|||
|
// Get input from ground distance PID controller
|
|||
|
forceInput.y = groundDistPIDController.RequiredInput(targetGroundDistance, currentPerpendicularGroundDist, _deltaTime);
|
|||
|
|
|||
|
// NOTE: This hasn't been tested yet in physics-model mode
|
|||
|
if (avoidGroundSurface)
|
|||
|
{
|
|||
|
// Only override user input if the ship is going below the targetGroundDistance
|
|||
|
forceInput.y = forceInput.y > 0f ? forceInput.y : pilotForceInput.y;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Activate translational flight assist if pilot releases an input
|
|||
|
// or if input is in direction opposing velocity
|
|||
|
if ((pilotForceInput.y > 0f) == (localVelocity.y < 0f) || pilotForceInput.y == 0f)
|
|||
|
{
|
|||
|
// Calculate the flight assist value on this axis
|
|||
|
flightAssistValue = -localVelocity.y / 100f * translationalFlightAssistStrength;
|
|||
|
if (flightAssistValue > 1f) { flightAssistValue = 1f; }
|
|||
|
else if (flightAssistValue < -1f) { flightAssistValue = -1f; }
|
|||
|
// Choose whichever value has the highest absolute value: The flight assist value or the pilot input value
|
|||
|
if ((flightAssistValue > 0f ? flightAssistValue : -flightAssistValue) >
|
|||
|
(pilotForceInput.y > 0f ? pilotForceInput.y : -pilotForceInput.y)) { forceInput.y = flightAssistValue; }
|
|||
|
else { forceInput.y = pilotForceInput.y; }
|
|||
|
}
|
|||
|
else { forceInput.y = pilotForceInput.y; }
|
|||
|
}
|
|||
|
|
|||
|
// Force input on z-axis is always just pilot input
|
|||
|
forceInput.z = pilotForceInput.z;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
forceInput.x = pilotForceInput.x;
|
|||
|
// In arcade mode, stick-to-ground force isn't achieved through thrusters
|
|||
|
// Currently this implementation won't allow for thrust pushing up/down from ground
|
|||
|
// to activate particle trails in non-physics-based mode...
|
|||
|
forceInput.y = stickToGroundSurfaceEnabled ? 0f : pilotForceInput.y;
|
|||
|
forceInput.z = pilotForceInput.z;
|
|||
|
}
|
|||
|
|
|||
|
#region Brake flight assist
|
|||
|
// Pilot or AI releases input and ship is moving forwards or backwards
|
|||
|
// Works within the speed range configured.
|
|||
|
// Does not make adjustments if velocity is very near zero.
|
|||
|
if (brakeFlightAssistStrength > 0f && forceInput.z == 0f && localVelocity.z > brakeFlightAssistMinSpeed && localVelocity.z < brakeFlightAssistMaxSpeed && (localVelocity.z < -0.01f || localVelocity.z > 0.01f))
|
|||
|
{
|
|||
|
// Max brakeFlightAssistStrength is 10, so div by 10 (x 0.1)
|
|||
|
// Apply thrust in the opposite direction to current z-axis velocity
|
|||
|
flightAssistValue = (localVelocity.z < 0f ? brakeFlightAssistStrength : -brakeFlightAssistStrength) * 0.1f;
|
|||
|
|
|||
|
// Dampen based on how close velo is to 0 using a x^2 curve when velo between -10 and +10 m/s.
|
|||
|
if (localVelocity.z > 0f && localVelocity.z < 10f) { flightAssistValue *= localVelocity.z / 10f; }
|
|||
|
else if (localVelocity.z < 0f && localVelocity.z > -10f) { flightAssistValue *= localVelocity.z / -10f; }
|
|||
|
|
|||
|
// Clamp -1.0 to 1.0
|
|||
|
if (flightAssistValue > 1f) { flightAssistValue = 1f; }
|
|||
|
else if (flightAssistValue < -1f) { flightAssistValue = -1f; }
|
|||
|
|
|||
|
forceInput.z = flightAssistValue;
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Input Control - 2.5D
|
|||
|
// TODO probably can significantly optimise the input control axis code
|
|||
|
|
|||
|
if ((int)inputControlAxis > 0)
|
|||
|
{
|
|||
|
// Y axis for side-view OR top-down flight
|
|||
|
if ((int)inputControlAxis == 2)
|
|||
|
{
|
|||
|
// Project the transform right direction onto the XZ plane
|
|||
|
Vector3 flatTrfmRight = TransformRight;
|
|||
|
flatTrfmRight.y = 0f;
|
|||
|
// Project the transform forward direction onto the XZ plane...
|
|||
|
Vector3 targetForward = TransformForward;
|
|||
|
targetForward.y = 0f;
|
|||
|
// ... then onto the plane of the ship's up and forwards directions while keeping it on the XZ plane
|
|||
|
// To do this step we subtract the projection of this vector onto flatTrfmRight
|
|||
|
targetForward -= (Vector3.Dot(targetForward, flatTrfmRight) / Vector3.Dot(flatTrfmRight, flatTrfmRight)) * flatTrfmRight;
|
|||
|
// Measure the angle from this projected forwards direction (which will be the target) to the current forwards direction
|
|||
|
float inputControlLimitAngleDelta = Vector3.SignedAngle(targetForward, TransformForward, TransformRight);
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Physics-based: Set the inputs to counteract any displacement from the target position/rotation
|
|||
|
forceInput.y = inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.y, _deltaTime);
|
|||
|
momentInput.x = inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Arcade: Use arcade force/moment to counteract any displacement from the target position/rotation
|
|||
|
arcadeForceVector.y += inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.y, _deltaTime);
|
|||
|
arcadeMomentVector.x += inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
|
|||
|
// Set inputs to zero
|
|||
|
forceInput.y = 0f;
|
|||
|
momentInput.x = 0f;
|
|||
|
}
|
|||
|
}
|
|||
|
// X axis for side-scroller-like flight
|
|||
|
else if ((int)inputControlAxis == 1)
|
|||
|
{
|
|||
|
// Calculate the normal to the plane we want to remain in
|
|||
|
Vector3 targetInputControlPlaneNormal = Quaternion.Euler(0f, inputControlForwardAngle, 0f) * Vector3.right;
|
|||
|
|
|||
|
// Project the transform up direction onto the YZ plane
|
|||
|
Vector3 flatTrfmUp = TransformUp;
|
|||
|
flatTrfmUp -= Vector3.Project(flatTrfmUp, targetInputControlPlaneNormal);
|
|||
|
// Project the transform forward direction onto the YZ plane...
|
|||
|
Vector3 targetForward = TransformForward;
|
|||
|
targetForward -= Vector3.Project(targetForward, targetInputControlPlaneNormal);
|
|||
|
// ... then onto the plane of the ship's right and forwards directions while keeping it on the YZ plane
|
|||
|
// To do this step we subtract the projection of this vector onto flatTrfmUp
|
|||
|
targetForward -= (Vector3.Dot(targetForward, flatTrfmUp) / Vector3.Dot(flatTrfmUp, flatTrfmUp)) * flatTrfmUp;
|
|||
|
// Measure the angle from this projected forwards direction (which will be the target) to the current forwards direction
|
|||
|
float inputControlLimitAngleDelta = Vector3.SignedAngle(targetForward, TransformForward, TransformUp);
|
|||
|
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Physics-based: Set the inputs to counteract any displacement from the target position/rotation
|
|||
|
forceInput.x = inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.x, _deltaTime);
|
|||
|
momentInput.y = inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Arcade: Use arcade force/moment to counteract any displacement from the target position/rotation
|
|||
|
arcadeForceVector.x += inputAxisForcePIDController.RequiredInput(inputControlLimit, TransformPosition.x, _deltaTime);
|
|||
|
arcadeMomentVector.y += inputAxisMomentPIDController.RequiredInput(0f, inputControlLimitAngleDelta, _deltaTime);
|
|||
|
// Set inputs to zero
|
|||
|
forceInput.x = 0f;
|
|||
|
momentInput.y = 0f;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float inputEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
float thrusterStartTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
#region Thrusters
|
|||
|
|
|||
|
if (thrusterList != null)
|
|||
|
{
|
|||
|
componentListSize = thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the thruster we are concerned with
|
|||
|
thruster = thrusterList[componentIndex];
|
|||
|
// Calculate thruster input - for this we need to know what type of thruster
|
|||
|
// this is so that we can read the right input/s
|
|||
|
// First set thruster input to zero
|
|||
|
thruster.currentInput = 0f;
|
|||
|
|
|||
|
// Thrusters with max heat (temperature) or with no fuel produce no thrust etc.
|
|||
|
// Check heat before fuels, so that if we run out of fuel, thruster
|
|||
|
// can still cool down.
|
|||
|
|
|||
|
if (thruster.heatLevel >= 100f || (useCentralFuel ? centralFuelLevel <= 0f : thruster.fuelLevel <= 0f))
|
|||
|
{
|
|||
|
// Check if we need to cool the thruster down
|
|||
|
thruster.ManageHeat(_deltaTime);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
// Then add inputs for force/moments to give a final input...
|
|||
|
switch (thruster.forceUse)
|
|||
|
{
|
|||
|
// Not a force thruster
|
|||
|
case 0: break;
|
|||
|
// Forward thruster
|
|||
|
case 1: thruster.currentInput += (forceInput.z > 0f ? forceInput.z : 0f); break;
|
|||
|
// Backward thruster
|
|||
|
case 2: thruster.currentInput += (forceInput.z < 0f ? -forceInput.z : 0f); break;
|
|||
|
// Upward thruster
|
|||
|
case 3: thruster.currentInput += (forceInput.y > 0f ? forceInput.y : 0f); break;
|
|||
|
// Downward thruster
|
|||
|
case 4: thruster.currentInput += (forceInput.y < 0f ? -forceInput.y : 0f); break;
|
|||
|
// Rightward thruster
|
|||
|
case 5: thruster.currentInput += (forceInput.x > 0f ? forceInput.x : 0f); break;
|
|||
|
// Leftward thruster
|
|||
|
case 6: thruster.currentInput += (forceInput.x < 0f ? -forceInput.x : 0f); break;
|
|||
|
// Not a valid thruster type
|
|||
|
#if UNITY_EDITOR
|
|||
|
default: Debug.Log("Ship - Invalid thruster force use: " + thruster.forceUse.ToString()); break;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
// Only allow thrusters to affect rotation when using the physics-based model
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Addition of moment inputs can be negative i.e. if we need to rotate in the opposite direction, add weighting
|
|||
|
// to tell this thruster not to activate
|
|||
|
|
|||
|
switch (thruster.primaryMomentUse)
|
|||
|
{
|
|||
|
// Not a moment thruster
|
|||
|
case 0: break;
|
|||
|
// Positive roll thruster
|
|||
|
case 1: if (momentInput.z / rollPower >= -(1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput += momentInput.z; } else { thruster.currentInput = 0f; } break;
|
|||
|
// Negative roll thruster
|
|||
|
case 2: if (momentInput.z / rollPower <= (1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput -= momentInput.z; } else { thruster.currentInput = 0f; } break;
|
|||
|
// Positive pitch thruster
|
|||
|
case 3: if (momentInput.x / pitchPower >= -(1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput += momentInput.x; } else { thruster.currentInput = 0f; } break;
|
|||
|
// Negative pitch thruster
|
|||
|
case 4: if (momentInput.x / pitchPower <= (1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput -= momentInput.x; } else { thruster.currentInput = 0f; } break;
|
|||
|
// Positive yaw thruster
|
|||
|
case 5: if (momentInput.y / yawPower >= -(1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput += momentInput.y; } else { thruster.currentInput = 0f; } break;
|
|||
|
// Negative yaw thruster
|
|||
|
case 6: if (momentInput.y / yawPower <= (1f - (steeringThrusterPriorityLevel * 0.9f))) { thruster.currentInput -= momentInput.y; } else { thruster.currentInput = 0f; } break;
|
|||
|
// Not a valid thruster type
|
|||
|
#if UNITY_EDITOR
|
|||
|
default: Debug.Log("Ship - Invalid thruster moment use: " + thruster.primaryMomentUse.ToString()); break;
|
|||
|
#endif
|
|||
|
}
|
|||
|
switch (thruster.secondaryMomentUse)
|
|||
|
{
|
|||
|
// Not a moment thruster
|
|||
|
case 0: break;
|
|||
|
// Positive roll thruster
|
|||
|
case 1: thruster.currentInput += momentInput.z; break;
|
|||
|
// Negative roll thruster
|
|||
|
case 2: thruster.currentInput -= momentInput.z; break;
|
|||
|
// Positive pitch thruster
|
|||
|
case 3: thruster.currentInput += momentInput.x; break;
|
|||
|
// Negative pitch thruster
|
|||
|
case 4: thruster.currentInput -= momentInput.x; break;
|
|||
|
// Positive yaw thruster
|
|||
|
case 5: thruster.currentInput += momentInput.y; break;
|
|||
|
// Negative yaw thruster
|
|||
|
case 6: thruster.currentInput -= momentInput.y; break;
|
|||
|
// Not a valid thruster type
|
|||
|
#if UNITY_EDITOR
|
|||
|
default: Debug.Log("Ship - Invalid thruster moment use: " + thruster.secondaryMomentUse.ToString()); break;
|
|||
|
#endif
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Calculate thrust force
|
|||
|
|
|||
|
// Allow for the thruster to be throttled up and down over time
|
|||
|
// Input is clamped between 0 and 1 (can only act within physical constraints of thruster)
|
|||
|
thruster.SmoothThrusterInput(_deltaTime);
|
|||
|
thrustForce = thruster.thrustDirectionNormalised * thruster.maxThrust * thruster.currentInput;
|
|||
|
|
|||
|
if (useCentralFuel)
|
|||
|
{
|
|||
|
if (thruster.fuelBurnRate > 0f && thruster.currentInput > 0f)
|
|||
|
{
|
|||
|
// Burn fuel independently to the health level. A damaged thruster will burn the same
|
|||
|
// amount of fuel but produce less thrust
|
|||
|
SetFuelLevel(centralFuelLevel - (thruster.currentInput * thruster.fuelBurnRate * _deltaTime));
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
thruster.BurnFuel(_deltaTime);
|
|||
|
}
|
|||
|
|
|||
|
thruster.ManageHeat(_deltaTime);
|
|||
|
|
|||
|
// Adjust for thruster damage
|
|||
|
if (!shipDamageModelIsSimple && thruster.damageRegionIndex > -1)
|
|||
|
{
|
|||
|
thrustForce *= thruster.CurrentPerformance;
|
|||
|
}
|
|||
|
|
|||
|
// Add calculated thrust force to local resultant force and moment
|
|||
|
localResultantForce += thrustForce;
|
|||
|
// Only allow thrusters to affect rotation when using the physics-based model
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
localResultantMoment += Vector3.Cross(thruster.relativePosition - centreOfMass, thrustForce);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float thrusterEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
float aeroStartTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
#region Boost
|
|||
|
// Is boost currently in operation?
|
|||
|
if (boostTimer > 0f)
|
|||
|
{
|
|||
|
boostTimer -= _deltaTime;
|
|||
|
localResultantForce += boostDirection * boostForce;
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Aerodynamics
|
|||
|
|
|||
|
// Dynamic viscosity of air at 25 deg C and 1 atm: 18.37 x 10^-6 Pa s
|
|||
|
// Density of air on earth at low altitudes: 1.293 kg/m^3
|
|||
|
// Aircraft head-on drag coefficient: ~0.05
|
|||
|
|
|||
|
if (mediumDensity > 0f)
|
|||
|
{
|
|||
|
//float pDragStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
#region Profile Drag
|
|||
|
|
|||
|
// Profile drag calculation
|
|||
|
|
|||
|
// Calculate local drag force vector
|
|||
|
// Drag force magnitude = 0.5 * fluid density * velocity squared * drag coefficient * cross-sectional area
|
|||
|
localDragForce = 0.5f * mediumDensity * -localVelocity;
|
|||
|
localDragForce.x *= dragCoefficients.x * dragReferenceAreas.x * (localVelocity.x > 0 ? localVelocity.x : -localVelocity.x);
|
|||
|
localDragForce.y *= dragCoefficients.y * dragReferenceAreas.y * (localVelocity.y > 0 ? localVelocity.y : -localVelocity.y);
|
|||
|
localDragForce.z *= dragCoefficients.z * dragReferenceAreas.z * (localVelocity.z > 0 ? localVelocity.z : -localVelocity.z);
|
|||
|
// Add calculated drag force to local resultant force
|
|||
|
localResultantForce += localDragForce;
|
|||
|
|
|||
|
if (!disableDragMoments)
|
|||
|
{
|
|||
|
// Add calculated drag force to local resultant moment
|
|||
|
// Centre of drag is precalculated separately for each axis
|
|||
|
|
|||
|
localResultantMoment.x += ((centreOfDragXMoment.y - centreOfMass.y) * localDragForce.z) * dragXMomentMultiplier;
|
|||
|
localResultantMoment.x += ((centreOfDragXMoment.z - centreOfMass.z) * -localDragForce.y) * dragXMomentMultiplier;
|
|||
|
|
|||
|
localResultantMoment.y += ((centreOfDragYMoment.x - centreOfMass.x) * -localDragForce.z) * dragYMomentMultiplier;
|
|||
|
localResultantMoment.y += ((centreOfDragYMoment.z - centreOfMass.z) * localDragForce.x) * dragYMomentMultiplier;
|
|||
|
|
|||
|
localResultantMoment.z += ((centreOfDragZMoment.x - centreOfMass.x) * localDragForce.y) * dragZMomentMultiplier;
|
|||
|
localResultantMoment.z += ((centreOfDragZMoment.y - centreOfMass.y) * -localDragForce.x) * dragZMomentMultiplier;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
//float pDragEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//float aDragStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
#region Angular Profile Drag
|
|||
|
|
|||
|
// Angular drag calculation
|
|||
|
|
|||
|
if (angularDragFactor > 0f || shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// TODO: Could the length, width and height be precalculated?
|
|||
|
// Step 1: We are modelling the drag as if it is occuring on a rectangular prism,
|
|||
|
// so calculate the hypothetical length, width and height of that prism from the given areas of each side
|
|||
|
dragLength = (float)Math.Sqrt(dragReferenceAreas.x * dragReferenceAreas.y / dragReferenceAreas.z);
|
|||
|
dragWidth = dragReferenceAreas.y / dragLength;
|
|||
|
dragHeight = dragReferenceAreas.z / dragWidth;
|
|||
|
|
|||
|
// Step 2: Calculate the angular drag moment on each axis
|
|||
|
|
|||
|
// TODO: Re-comment this section
|
|||
|
// TODO: Could these values be precalculated?
|
|||
|
quarticLengthXProj = IntegerPower((dragLength / 2) + centreOfMass.z - centreOfDragYMoment.z, 4) + IntegerPower((dragLength / 2) - centreOfMass.z + centreOfDragYMoment.z, 4);
|
|||
|
quarticLengthYProj = IntegerPower((dragLength / 2) + centreOfMass.z - centreOfDragXMoment.z, 4) + IntegerPower((dragLength / 2) - centreOfMass.z + centreOfDragXMoment.z, 4);
|
|||
|
quarticWidthYProj = IntegerPower((dragWidth / 2) + centreOfMass.x - centreOfDragZMoment.x, 4) + IntegerPower((dragWidth / 2) - centreOfMass.x + centreOfDragZMoment.x, 4);
|
|||
|
quarticWidthZProj = IntegerPower((dragWidth / 2) + centreOfMass.x - centreOfDragYMoment.x, 4) + IntegerPower((dragWidth / 2) - centreOfMass.x + centreOfDragYMoment.x, 4);
|
|||
|
quarticHeightXProj = IntegerPower((dragHeight / 2) + centreOfMass.y - centreOfDragZMoment.y, 4) + IntegerPower((dragHeight / 2) - centreOfMass.y + centreOfDragZMoment.y, 4);
|
|||
|
quarticHeightZProj = IntegerPower((dragHeight / 2) + centreOfMass.y - centreOfDragXMoment.y, 4) + IntegerPower((dragHeight / 2) - centreOfMass.y + centreOfDragXMoment.y, 4);
|
|||
|
if (shipPhysicsModelPhysicsBased) { angularDragMultiplier = 1f; }
|
|||
|
else { angularDragMultiplier = (float)Math.Pow(10f, angularDragFactor - 1f); }
|
|||
|
// Formula: For a rotating rectangle with the pivot at one end:
|
|||
|
// Drag moment = 1/8 * fluid density * angular velocity squared * height * drag coefficient * length to the power of four
|
|||
|
// X-axis angular drag
|
|||
|
localResultantMoment.x += mediumDensity / 8f * localAngularVelocity.x * localAngularVelocity.x * dragWidth *
|
|||
|
dragCoefficients.y * quarticLengthYProj * (localAngularVelocity.x > 0f ? -1f : 1f) * angularDragMultiplier;
|
|||
|
localResultantMoment.x += mediumDensity / 8f * localAngularVelocity.x * localAngularVelocity.x * dragWidth *
|
|||
|
dragCoefficients.z * quarticHeightZProj * (localAngularVelocity.x > 0f ? -1f : 1f) * angularDragMultiplier;
|
|||
|
// Y-axis angular drag
|
|||
|
localResultantMoment.y += mediumDensity / 8f * localAngularVelocity.y * localAngularVelocity.y * dragHeight *
|
|||
|
dragCoefficients.x * quarticLengthXProj * (localAngularVelocity.y > 0f ? -1f : 1f) * angularDragMultiplier;
|
|||
|
localResultantMoment.y += mediumDensity / 8f * localAngularVelocity.y * localAngularVelocity.y * dragHeight *
|
|||
|
dragCoefficients.z * quarticWidthZProj * (localAngularVelocity.y > 0f ? -1f : 1f) * angularDragMultiplier;
|
|||
|
// Z-axis angular drag
|
|||
|
localResultantMoment.z += mediumDensity / 8f * localAngularVelocity.z * localAngularVelocity.z * dragLength *
|
|||
|
dragCoefficients.y * quarticWidthYProj * (localAngularVelocity.z > 0f ? -1f : 1f) * angularDragMultiplier;
|
|||
|
localResultantMoment.z += mediumDensity / 8f * localAngularVelocity.z * localAngularVelocity.z * dragLength *
|
|||
|
dragCoefficients.x * quarticHeightXProj * (localAngularVelocity.z > 0f ? -1f : 1f) * angularDragMultiplier;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
//float aDragEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//float wingsStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
// Calculate the "true airspeed" - this is just velocity in forwards direction
|
|||
|
aerodynamicTrueAirspeed = localVelocity.z;
|
|||
|
|
|||
|
#region Wings
|
|||
|
|
|||
|
// Wing simulation
|
|||
|
|
|||
|
componentListSize = wingList == null ? 0 : wingList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the wing we are concerned with
|
|||
|
wing = wingList[componentIndex];
|
|||
|
// Only calculate lift if some component of the velocity is in the negative airflow direction
|
|||
|
if (aerodynamicTrueAirspeed > 0f)
|
|||
|
{
|
|||
|
//float aoaStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
#region Angle of Attack
|
|||
|
|
|||
|
// OLD UNOPTIMISED CODE. TODO: Delete
|
|||
|
////Vector3 wingPlaneNormal = Vector3.Cross(wing.liftDirection, wing.airflowDirection);
|
|||
|
//wingPlaneNormal = Vector3.Cross(wing.liftDirection, Vector3.back);
|
|||
|
//localVeloInWingPlane = Vector3.ProjectOnPlane(localVelocity, wingPlaneNormal);
|
|||
|
//// Is this the right direction?
|
|||
|
////float angleOfAttack = Vector3.SignedAngle(-wing.airflowDirection, localVeloInWingPlane, wingPlaneNormal) + wing.angleOfAttack;
|
|||
|
//wingAngleOfAttack = Vector3.SignedAngle(Vector3.forward, localVeloInWingPlane, wingPlaneNormal) + wing.angleOfAttack;
|
|||
|
|
|||
|
// Project the local velocity into the plane containing the lift direction and the forwards (negative airflow) direction
|
|||
|
// Take the cross product of lift direction and forwards direction to get the normal of the plane
|
|||
|
// Normalise it for the dot product in the next step
|
|||
|
//wingPlaneNormal = Vector3.Cross(wing.liftDirectionNormalised, Vector3.forward).normalized;
|
|||
|
wingPlaneNormal.x = wing.liftDirectionNormalised.y;
|
|||
|
wingPlaneNormal.y = -wing.liftDirectionNormalised.x;
|
|||
|
wingPlaneNormal.z = 0f;
|
|||
|
wingPlaneNormal.Normalize();
|
|||
|
//localVeloInWingPlane = (localVelocity - (Vector3.Dot(localVelocity, wingPlaneNormal) * wingPlaneNormal)).normalized;
|
|||
|
// Multiplty the dot product of the local velocity and the wing plane normal with the wing plane normal to
|
|||
|
// get the component of the local velocity in the direction of the wing plane normal, then subtract
|
|||
|
// this vector from the local velocity to project it into the desired plane
|
|||
|
// Then normalise the vector for use in the dot product of the next step
|
|||
|
// NOTE: The dot product does not require a z component as the z component of wing plane normal will always be zero,
|
|||
|
// as it is perpendicular to the forwards direction
|
|||
|
localVeloInWingPlane = (localVelocity - (((localVelocity.x * wingPlaneNormal.x) + (localVelocity.y * wingPlaneNormal.y)) * wingPlaneNormal)).normalized;
|
|||
|
|
|||
|
// The angle of attack of the wing is equal to:
|
|||
|
// The angle between the lift direction and the velocity in the wing plane (computed via arccos of dot product) MINUS
|
|||
|
// The angle between the lift direction and the forwards direction PLUS
|
|||
|
// The angle of attack of the wing due to camber
|
|||
|
// Resultant of the first thing minus the second gives the angle between forwards direction and velocity, with
|
|||
|
// forwards above velocity resulting in positive numbers
|
|||
|
|
|||
|
wingAngleOfAttack = ((float)(Math.Acos(Mathf.Clamp((wing.liftDirectionNormalised.x * localVeloInWingPlane.x) +
|
|||
|
(wing.liftDirectionNormalised.y * localVeloInWingPlane.y) +
|
|||
|
(wing.liftDirectionNormalised.z * localVeloInWingPlane.z), -1f, 1f)) -
|
|||
|
Math.Acos(Mathf.Clamp(wing.liftDirectionNormalised.z, -1f, 1f))) * Mathf.Rad2Deg) +
|
|||
|
wing.angleOfAttack;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
//float aoaEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//float lcStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
#region Lift Coefficient
|
|||
|
|
|||
|
phi0 = (20f * wingStallEffect) - 25f;
|
|||
|
phi1 = 15f;
|
|||
|
phi2 = 45f - (25f * wingStallEffect);
|
|||
|
phi3 = 75f - (50f * wingStallEffect);
|
|||
|
|
|||
|
//// If angle of attack is not between the min and max wing angles, the wing will stall and not produce any lift
|
|||
|
//if (angleOfAttack > -5f && angleOfAttack < 15f)
|
|||
|
//{
|
|||
|
// // Approximation of lift coefficient, adapted from thin airfoil theory:
|
|||
|
// // Lift coefficient = (m * angle of attack) + d
|
|||
|
// // Where m = max lift coefficient / (max wing angle - min wing angle) and d = -m * min wing angle
|
|||
|
// // Max lift coefficient = 1.5, min wing angle = -5 degrees, max wing angle = 15 degrees
|
|||
|
// float liftCoefficient = (0.075f * angleOfAttack) + 0.375f;
|
|||
|
|
|||
|
//}
|
|||
|
|
|||
|
// TODO: NEED A COMMENT FOR BELOW THEORY
|
|||
|
// Also should look at whether I should just enable/disable stalling
|
|||
|
// and just use predetermined values
|
|||
|
// Also, possibly stall angle actually comes from chord (wing width)?
|
|||
|
|
|||
|
// Stall region
|
|||
|
if (wingAngleOfAttack < phi0) { liftCoefficient = 0f; }
|
|||
|
// Increasing lift region
|
|||
|
else if (wingAngleOfAttack < phi1) { liftCoefficient = 1.6f * (wingAngleOfAttack - phi0) / (phi1 - phi0); }
|
|||
|
// Maximum lift region
|
|||
|
else if (wingAngleOfAttack < phi2) { liftCoefficient = 1.6f; }
|
|||
|
// Falloff region
|
|||
|
else if (wingAngleOfAttack < phi3) { liftCoefficient = 1.6f * (wingAngleOfAttack - phi3) / (phi2 - phi3); }
|
|||
|
// Stall region
|
|||
|
else { liftCoefficient = 0f; }
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
//float lcEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//float lidStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
#region Lift and Induced Drag
|
|||
|
|
|||
|
// Lift force magnitude = 0.5 * fluid density * velocity squared * planform wing area * coefficient of lift
|
|||
|
liftForceMagnitude = 0.5f * mediumDensity * aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * (wing.span * wing.chord * (float)Math.Cos(wingAngleOfAttack * Mathf.Deg2Rad)) * liftCoefficient;
|
|||
|
|
|||
|
// Adjust for wing damage
|
|||
|
if (!shipDamageModelIsSimple && wing.damageRegionIndex > -1)
|
|||
|
{
|
|||
|
liftForceMagnitude *= wing.CurrentPerformance;
|
|||
|
}
|
|||
|
|
|||
|
localLiftForce = liftForceMagnitude * wing.liftDirectionNormalised;
|
|||
|
// Induced drag force magnitude = 2 * lift squared / (fluid density * velocity squared * pi * wingspan squared)
|
|||
|
localInducedDragForce = 2f * liftForceMagnitude * liftForceMagnitude /
|
|||
|
(mediumDensity * aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * Mathf.PI * wing.span * wing.span) * Vector3.back;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
//float lidEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//float forceStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
// Add calculated lift and induced drag forces to local resultant force and moment
|
|||
|
localResultantForce += localLiftForce + localInducedDragForce;
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
localResultantMoment += Vector3.Cross(wing.relativePosition - centreOfMass, localLiftForce + localInducedDragForce);
|
|||
|
}
|
|||
|
|
|||
|
//float forceEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//if (componentIndex == 0)
|
|||
|
//{
|
|||
|
// Debug.Log("| Angle of attack: " + ((aoaEndTime - aoaStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Lift coefficient: " + ((lcEndTime - lcStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Lift and induced drag: " + ((lidEndTime - lidStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Force and moment: " + ((forceEndTime - forceStartTime) * 1000f).ToString("0.0000") + " ms | ");
|
|||
|
//}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
//float wingsEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//float controlStartTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
// Control surfaces only available in physics-based mode
|
|||
|
if (shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
#region Control Surfaces
|
|||
|
|
|||
|
// Control surface simulation
|
|||
|
|
|||
|
componentListSize = controlSurfaceList == null ? 0 : controlSurfaceList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the control surface we are concerned with
|
|||
|
controlSurface = controlSurfaceList[componentIndex];
|
|||
|
|
|||
|
#region Control Surface Type
|
|||
|
|
|||
|
switch (controlSurface.type)
|
|||
|
{
|
|||
|
case ControlSurface.ControlSurfaceType.Aileron:
|
|||
|
// Aileron
|
|||
|
controlSurfaceMovementAxis = Vector3.up;
|
|||
|
controlSurfaceInput = momentInput.z * (controlSurface.relativePosition.x > 0f ? 1f : -1f);
|
|||
|
break;
|
|||
|
case ControlSurface.ControlSurfaceType.Elevator:
|
|||
|
// Elevator
|
|||
|
controlSurfaceMovementAxis = Vector3.up;
|
|||
|
controlSurfaceInput = -momentInput.x;
|
|||
|
break;
|
|||
|
case ControlSurface.ControlSurfaceType.Rudder:
|
|||
|
// Rudder
|
|||
|
controlSurfaceMovementAxis = Vector3.right;
|
|||
|
controlSurfaceInput = momentInput.y;
|
|||
|
break;
|
|||
|
case ControlSurface.ControlSurfaceType.AirBrake:
|
|||
|
// Air brake
|
|||
|
controlSurfaceMovementAxis = Vector3.zero;
|
|||
|
controlSurfaceInput = forceInput.z < 0f ? -forceInput.z : 0f;
|
|||
|
break;
|
|||
|
#if UNITY_EDITOR
|
|||
|
default:
|
|||
|
// Custom (TODO)
|
|||
|
Debug.Log("Ship - Custom control surfaces not supported yet.");
|
|||
|
break;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
// When there is no input the control surface does not apply any force
|
|||
|
if (controlSurfaceInput != 0f)
|
|||
|
{
|
|||
|
#region Calculate Lift and Drag Forces
|
|||
|
|
|||
|
// Calculate the angle the control surface is inclined at
|
|||
|
// Absolute value of controlSurfaceInput is used as we only want magnitude - direction is added in a later step
|
|||
|
controlSurfaceAngle = Mathf.PI / 2f * (controlSurfaceInput > 0f ? controlSurfaceInput : -controlSurfaceInput);
|
|||
|
// Calculate the magnitude of the change (delta) in lift and drag caused by the inclination of the control surface
|
|||
|
// This is essentially the change in planform area (for lift) and the change in profile area (for drag)
|
|||
|
// multiplied by the usual aerodynamic formulae for each
|
|||
|
// TODO: Should probably work out something mildly more accurate for lift coefficient (currently is just 1.6)
|
|||
|
// TODO: Should probably have something for a drag coefficient as well (currently is just 2)
|
|||
|
// Then convert the forces into vectors by multiplying by their directions
|
|||
|
// TODO: Include relative position based on inclination
|
|||
|
// - I think this means the position of the centre of the CS adjusted for where it has been rotated to?
|
|||
|
if (controlSurfaceMovementAxis != Vector3.zero)
|
|||
|
{
|
|||
|
controlSurfaceLiftDelta = (1f - (float)Math.Cos(controlSurfaceAngle)) * 0.5f * mediumDensity *
|
|||
|
aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * controlSurface.chord * controlSurface.span * 1.6f;
|
|||
|
}
|
|||
|
else { controlSurfaceLiftDelta = 0f; }
|
|||
|
|
|||
|
controlSurfaceDragDelta = (float)Math.Sin(controlSurfaceAngle) * 0.5f * mediumDensity *
|
|||
|
aerodynamicTrueAirspeed * aerodynamicTrueAirspeed * controlSurface.chord * controlSurface.span * 2f;
|
|||
|
|
|||
|
// Adjust for control surface damage
|
|||
|
if (!shipDamageModelIsSimple && controlSurface.damageRegionIndex > -1)
|
|||
|
{
|
|||
|
controlSurfaceLiftDelta *= controlSurface.CurrentPerformance;
|
|||
|
controlSurfaceDragDelta *= controlSurface.CurrentPerformance;
|
|||
|
}
|
|||
|
|
|||
|
localLiftForce = controlSurfaceLiftDelta * (controlSurfaceInput > 0f ? -1f : 1f) * controlSurfaceMovementAxis;
|
|||
|
localDragForce = controlSurfaceDragDelta * (aerodynamicTrueAirspeed > 0f ? 1f : -1f) * Vector3.back;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
// Adjust relative position to take into account current inclination of control surface
|
|||
|
controlSurfaceRelativePosition = controlSurface.relativePosition + (controlSurfaceMovementAxis *
|
|||
|
controlSurface.chord * 0.5f * (float)Math.Sin(controlSurfaceAngle));
|
|||
|
|
|||
|
// Add calculated lift and drag forces to resultant force and moment
|
|||
|
localResultantForce += localLiftForce + localDragForce;
|
|||
|
localResultantMoment += Vector3.Cross(controlSurfaceRelativePosition - centreOfMass, localLiftForce + localDragForce);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
//float controlEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
//Debug.Log("| Profile Drag: " + ((pDragEndTime - pDragStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Angular Drag: " + ((aDragEndTime - aDragStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Wings: " + ((wingsEndTime - wingsStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Control Surfaces: " + ((controlEndTime - controlStartTime) * 1000f).ToString("0.0000") + " ms | ");
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float aeroEndTime = Time.realtimeSinceStartup;
|
|||
|
|
|||
|
float arcadeStartTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
#region Arcade Physics Adjustments
|
|||
|
|
|||
|
if (!shipPhysicsModelPhysicsBased)
|
|||
|
{
|
|||
|
// Apply moments to match pitch, yaw and roll inputs, as well as for various arcade adjustments
|
|||
|
// These include rotational flight assist and target pitch/roll moment
|
|||
|
// Multiplication by inertia tensor is used so that we can use acceleration parameters instead of force parameters
|
|||
|
localResultantMoment.x += ((momentInput.x * arcadePitchAcceleration) + arcadeMomentVector.x) * Mathf.Deg2Rad * rBodyInertiaTensor.x;
|
|||
|
localResultantMoment.y += ((momentInput.y * arcadeYawAcceleration) + arcadeMomentVector.y) * Mathf.Deg2Rad * rBodyInertiaTensor.y;
|
|||
|
localResultantMoment.z += ((momentInput.z * arcadeRollAcceleration) + arcadeMomentVector.z) * Mathf.Deg2Rad * rBodyInertiaTensor.z * -1f;
|
|||
|
|
|||
|
// TODO: Currently this doesn't take into account the force already being applied by the ship,
|
|||
|
// calculated prior to this point
|
|||
|
// Should it do that?
|
|||
|
|
|||
|
if (stickToGroundSurfaceEnabled)
|
|||
|
{
|
|||
|
// Calculate the limits for input for ground matching
|
|||
|
float maxGroundDistInput = maxGroundMatchAcceleration;
|
|||
|
if (useGroundMatchSmoothing)
|
|||
|
{
|
|||
|
// Previous values (for last two arguments): 50f, 2f
|
|||
|
maxGroundDistInput = DampedMaxAccelerationInput(currentPerpendicularGroundDist, targetGroundDistance,
|
|||
|
minGroundDistance, maxGroundDistance, centralMaxGroundMatchAcceleration, maxGroundMatchAcceleration, 200f, 0.5f);
|
|||
|
}
|
|||
|
|
|||
|
// Set input limits
|
|||
|
groundDistPIDController.SetInputLimits(-maxGroundDistInput, maxGroundDistInput);
|
|||
|
|
|||
|
// Get input from ground distance PID controller
|
|||
|
groundDistInput = groundDistPIDController.RequiredInput(targetGroundDistance, currentPerpendicularGroundDist, _deltaTime);
|
|||
|
|
|||
|
//Debug.Log("Ground dist input: " + groundDistInput.ToString("000000.0"));
|
|||
|
//Debug.Log("Current ground dist: " + currentPerpendicularGroundDist.ToString("00.00"));
|
|||
|
|
|||
|
// Are we preventing an arcade model ship from going below the target (minimum) distnace?
|
|||
|
if (avoidGroundSurface)
|
|||
|
{
|
|||
|
// Only override user input if the ship is going below the targetGroundDistance
|
|||
|
stickToGroundSurfaceEnabled = groundDistInput > 0f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (stickToGroundSurfaceEnabled)
|
|||
|
{
|
|||
|
// Apply turning force counteracting velocity on x axis if pilot releases the input
|
|||
|
// or if input is in direction opposing velocity
|
|||
|
if ((forceInput.x > 0f) == (localVelocity.x < 0f) || forceInput.x == 0f)
|
|||
|
{
|
|||
|
// The force also includes a component to counteract gravity on this axis
|
|||
|
arcadeForceVector.x = -((localVelocity.x * 0.5f / _deltaTime) + localGravityAcceleration.x);
|
|||
|
}
|
|||
|
else { arcadeForceVector.x = 0f; }
|
|||
|
// When we are sticking to the ground surface don't apply turning force on y axis,
|
|||
|
// as this force should be primarily handled by the force keeping the ship at the
|
|||
|
// target distance from the ground
|
|||
|
arcadeForceVector.y = 0f;
|
|||
|
// TODO: Maybe want to have an option to allow force to act on z as well (resulting in very arcade-like handling)
|
|||
|
arcadeForceVector.z = 0f;
|
|||
|
|
|||
|
// Clamp the magnitude of the turning force vector so that the force applied does not exceed
|
|||
|
// the maximum on-ground turning acceleration. Multiply by mass to convert the acceleration into a force
|
|||
|
arcadeForceVector = Vector3.ClampMagnitude(arcadeForceVector, arcadeMaxGroundTurningAcceleration) * mass;
|
|||
|
|
|||
|
// Moved above in 1.2.6
|
|||
|
//// Calculate the limits for input for ground matching
|
|||
|
//float maxGroundDistInput = maxGroundMatchAcceleration;
|
|||
|
//if (useGroundMatchSmoothing)
|
|||
|
//{
|
|||
|
// // Previous values (for last two arguments): 50f, 2f
|
|||
|
// maxGroundDistInput = DampedMaxAccelerationInput(currentPerpendicularGroundDist, targetGroundDistance,
|
|||
|
// minGroundDistance, maxGroundDistance, centralMaxGroundMatchAcceleration, maxGroundMatchAcceleration, 200f, 0.5f);
|
|||
|
//}
|
|||
|
|
|||
|
//// Set input limits
|
|||
|
//groundDistPIDController.SetInputLimits(-maxGroundDistInput, maxGroundDistInput);
|
|||
|
|
|||
|
// Add a force acting in the direction of the ground normal to keep the ship at a target distance
|
|||
|
// from the ground (the magnitude of the force is calculated by the associated PID controller)
|
|||
|
arcadeForceVector += trfmInvRot * worldTargetPlaneNormal * groundDistInput * mass;
|
|||
|
|
|||
|
// pre-v1.2.6 (calculated above in 1.2.6+ as groundDistInput variable)
|
|||
|
//arcadeForceVector += trfmInvRot * worldTargetPlaneNormal *
|
|||
|
// groundDistPIDController.RequiredInput(targetGroundDistance, currentPerpendicularGroundDist, _deltaTime) * mass;
|
|||
|
|
|||
|
//localResultantForce.y += stickToGroundAcc * mass;
|
|||
|
|
|||
|
//Debug.Log("Dist: " + currentPerpendicularGroundDist.ToString("00.00") + " m, acc: " + stickToGroundAcc.ToString("000.00") + "m/s^2");
|
|||
|
//Debug.Log("Speed: " + speed.ToString("0000") + " m/s");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Apply turning force counteracting velocity on x and y axes if pilot releases the input
|
|||
|
// or if input is in direction opposing velocity
|
|||
|
if ((forceInput.x > 0f) == (localVelocity.x < 0f) || forceInput.x == 0f)
|
|||
|
{
|
|||
|
// The force also includes a component to counteract gravity on this axis
|
|||
|
arcadeForceVector.x = -((localVelocity.x * 0.5f / _deltaTime) + localGravityAcceleration.x);
|
|||
|
}
|
|||
|
else { arcadeForceVector.x = 0f; }
|
|||
|
if ((forceInput.y > 0f) == (localVelocity.y < 0f) || forceInput.y == 0f)
|
|||
|
{
|
|||
|
// The force also includes a component to counteract gravity on this axis
|
|||
|
arcadeForceVector.y = -((localVelocity.y * 0.5f / _deltaTime) + localGravityAcceleration.y);
|
|||
|
}
|
|||
|
else { arcadeForceVector.y = 0f; }
|
|||
|
// Maybe want to have an option to allow force to act on z as well (resulting in very arcade-like handling)
|
|||
|
arcadeForceVector.z = 0f;
|
|||
|
|
|||
|
// Clamp the magnitude of the turning force vector so that the force applied does not exceed
|
|||
|
// the maximum in-flight turning acceleration. Multiply by mass to convert the acceleration into a force (f = ma)
|
|||
|
arcadeForceVector = Vector3.ClampMagnitude(arcadeForceVector, arcadeMaxFlightTurningAcceleration) * mass;
|
|||
|
}
|
|||
|
|
|||
|
// Braking component for arcade mode - active when moving forward and braking
|
|||
|
if (arcadeUseBrakeComponent && localVelocity.z > 0f && forceInput.z < 0f)
|
|||
|
{
|
|||
|
// Calculate magnitude of braking force using drag formula
|
|||
|
arcadeBrakeForceMagnitude = 0.5f * (arcadeBrakeIgnoreMediumDensity ? 1f : mediumDensity) * localVelocity.z * localVelocity.z * arcadeBrakeStrength * -forceInput.z;
|
|||
|
// Calculate min braking force (f = ma)
|
|||
|
arcadeBrakeForceMinMagnitude = arcadeBrakeMinAcceleration * mass;
|
|||
|
// Make sure min braking force doesn't overcompensate and cause the ship to start moving backwards
|
|||
|
if (arcadeBrakeForceMinMagnitude > localVelocity.z / _deltaTime * mass)
|
|||
|
{
|
|||
|
arcadeBrakeForceMinMagnitude = localVelocity.z / _deltaTime * mass;
|
|||
|
}
|
|||
|
// Add braking force or min braking force - whichever is larger
|
|||
|
arcadeForceVector.z -= arcadeBrakeForceMagnitude > arcadeBrakeForceMinMagnitude ? arcadeBrakeForceMagnitude : arcadeBrakeForceMinMagnitude;
|
|||
|
}
|
|||
|
|
|||
|
// Add the calculated arcade force to the local resultant force
|
|||
|
localResultantForce += arcadeForceVector;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
float arcadeEndTime = Time.realtimeSinceStartup;
|
|||
|
#endif
|
|||
|
|
|||
|
// Remember time of this fixed update
|
|||
|
lastFixedUpdateTime = Time.time;
|
|||
|
|
|||
|
#if SSC_SHIP_DEBUG
|
|||
|
//Debug.Log("| Init: " + ((initEndTime - initStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Ground: " + ((groundEndTime - groundStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Input: " + ((inputEndTime - inputStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Thrusters: " + ((thrusterEndTime - thrusterStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Aero: " + ((aeroEndTime - aeroStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Arcade: " + ((arcadeEndTime - arcadeStartTime) * 1000f).ToString("0.0000") + " ms | " +
|
|||
|
// "Total: " + ((arcadeEndTime - initStartTime) * 1000f).ToString("0.0000") + " ms | ");
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Update Weapons And Damage
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update weapons and damage information for this ship. Should be called during Update(), and have
|
|||
|
/// UpdatePositionAndMovementData() called prior to it.
|
|||
|
/// NOTE: We often hardcode the weaponType int rather than looking up the enum for the sake of performance.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipControllerManager"></param>
|
|||
|
public void UpdateWeaponsAndDamage(SSCManager shipControllerManager)
|
|||
|
{
|
|||
|
#region Initialisation
|
|||
|
|
|||
|
float _deltaTime = Time.deltaTime;
|
|||
|
|
|||
|
// Get the ship damage model
|
|||
|
shipDamageModelIsSimple = shipDamageModel == ShipDamageModel.Simple;
|
|||
|
|
|||
|
// Update the local reference, which is used also in MoveBeam()
|
|||
|
if (sscManager == null) { sscManager = shipControllerManager; }
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Weapons
|
|||
|
|
|||
|
componentListSize = weaponList == null ? 0 : weaponList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// Get the weapon we are concerned with
|
|||
|
weapon = weaponList[componentIndex];
|
|||
|
if (weapon == null) { continue; }
|
|||
|
|
|||
|
// Check that the weapon has had a projectile ID or beam ID assigned
|
|||
|
// Assumes that the IDs have been correctly assigned based on the weaponType.
|
|||
|
if (weapon.projectilePrefabID >= 0 || weapon.beamPrefabID >= 0)
|
|||
|
{
|
|||
|
// Lookup the enumeration once
|
|||
|
weaponTypeInt = weapon.weaponTypeInt;
|
|||
|
weaponFiringButtonInt = (int)weapon.firingButton;
|
|||
|
|
|||
|
// Does the weapon need to cool down??
|
|||
|
weapon.ManageHeat(_deltaTime, 0f);
|
|||
|
|
|||
|
// If the weapon is not connected to a firing mechanism we can probably ignore the reload timer
|
|||
|
// Weapons with no health or performance cannot aim, reload, move turrets, or fire.
|
|||
|
if (weaponFiringButtonInt > 0 && weapon.health > 0f && weapon.currentPerformance > 0f)
|
|||
|
{
|
|||
|
if (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt) { weapon.MoveTurret(worldVelocity, false); }
|
|||
|
|
|||
|
// Does the beam weapon need charging?
|
|||
|
if ((weaponTypeInt == Weapon.FixedBeamInt || weaponTypeInt == Weapon.TurretBeamInt) && weapon.rechargeTime > 0f && weapon.chargeAmount < 1f)
|
|||
|
{
|
|||
|
weapon.chargeAmount += _deltaTime * 1f / weapon.rechargeTime;
|
|||
|
if (weapon.chargeAmount > 1f) { weapon.chargeAmount = 1f; }
|
|||
|
}
|
|||
|
|
|||
|
// Check if this projectile weapon is reloading or not
|
|||
|
// Check if this beam weapon is powering-up or not
|
|||
|
if (weapon.reloadTimer > 0f)
|
|||
|
{
|
|||
|
// If reloading (or powering-up), update reload timer
|
|||
|
weapon.reloadTimer -= _deltaTime;
|
|||
|
}
|
|||
|
// Check if this weapon is damaged or not
|
|||
|
else if (shipDamageModelIsSimple || weapon.damageRegionIndex < 0 || weapon.Health > 0f)
|
|||
|
{
|
|||
|
// If not reloading and not damaged, check if we should fire the weapon or not
|
|||
|
// For autofiring weapons, this will return false.
|
|||
|
bool isReadyToFire = (pilotPrimaryFireInput && weaponFiringButtonInt == weaponPrimaryFiringInt) || (pilotSecondaryFireInput && weaponFiringButtonInt == weaponSecondaryFiringInt);
|
|||
|
|
|||
|
// If this is auto-firing turret check if it is ready to fire
|
|||
|
if (weaponFiringButtonInt == weaponAutoFiringInt && (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt))
|
|||
|
{
|
|||
|
// Is turret locked on target and in direct line of sight?
|
|||
|
// NOTE: If check LoS is enabled, it will still fire if another enemy is between the turret and the weapon.target.
|
|||
|
isReadyToFire = weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon));
|
|||
|
|
|||
|
// TODO - Is target in range?
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// If not reloading and not damaged, check if we should fire the weapon or not
|
|||
|
if (isReadyToFire && weapon.ammunition != 0)
|
|||
|
{
|
|||
|
// ==============================================================================
|
|||
|
// NOTE: We need to do most of these steps also in MoveBeam(..). So the commented
|
|||
|
// out methods below called in MoveBeam(..)
|
|||
|
// ==============================================================================
|
|||
|
|
|||
|
// Get base weapon world position (not accounting for relative fire position)
|
|||
|
//weaponWorldBasePosition = GetWeaponWorldBasePosition(weapon);
|
|||
|
weaponWorldBasePosition = (trfmRight * weapon.relativePosition.x) +
|
|||
|
(trfmUp * weapon.relativePosition.y) +
|
|||
|
(trfmFwd * weapon.relativePosition.z) +
|
|||
|
trfmPos;
|
|||
|
|
|||
|
// Get relative fire direction
|
|||
|
weaponRelativeFireDirection = weapon.fireDirectionNormalised;
|
|||
|
// If this is a turret, adjust relative fire direction based on turret rotation
|
|||
|
if (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt)
|
|||
|
{
|
|||
|
// (turret rotation less ship rotate) - (original turret rotation less ship rotation) + relative fire direction
|
|||
|
weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;
|
|||
|
}
|
|||
|
|
|||
|
// Get weapon world fire direction
|
|||
|
weaponWorldFireDirection = (trfmRight * weaponRelativeFireDirection.x) +
|
|||
|
(trfmUp * weaponRelativeFireDirection.y) +
|
|||
|
(trfmFwd * weaponRelativeFireDirection.z);
|
|||
|
|
|||
|
//weaponWorldFireDirection = GetWeaponWorldFireDirection(weapon);
|
|||
|
|
|||
|
// Start the firing cycle with no heat generated
|
|||
|
float heatValue = 0f;
|
|||
|
bool hasFired = false;
|
|||
|
|
|||
|
// Loop through all fire positions
|
|||
|
int firePositionListCount = weapon.isMultipleFirePositions ? weapon.firePositionList.Count : 1;
|
|||
|
for (int fp = 0; fp < firePositionListCount; fp++)
|
|||
|
{
|
|||
|
// Only fire if there is unlimited ammunition (-1) or greater than 0 projectiles available
|
|||
|
if (weapon.ammunition != 0)
|
|||
|
{
|
|||
|
// If not unlimited ammo, decrement the quantity available
|
|||
|
if (weapon.ammunition > 0) { weapon.ammunition--; }
|
|||
|
|
|||
|
// Check if there are multiple fire positions
|
|||
|
if (weapon.isMultipleFirePositions)
|
|||
|
{
|
|||
|
// Get relative fire position
|
|||
|
weaponRelativeFirePosition = weapon.firePositionList[fp];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If there is only one fire position, relative position must be the zero vector
|
|||
|
weaponRelativeFirePosition.x = 0f;
|
|||
|
weaponRelativeFirePosition.y = 0f;
|
|||
|
weaponRelativeFirePosition.z = 0f;
|
|||
|
}
|
|||
|
// If this is a turret, adjust relative fire position based on turret rotation
|
|||
|
if (weaponTypeInt == Weapon.TurretProjectileInt || weaponTypeInt == Weapon.TurretBeamInt)
|
|||
|
{
|
|||
|
weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;
|
|||
|
}
|
|||
|
|
|||
|
// Get weapon world fire position
|
|||
|
weaponWorldFirePosition = weaponWorldBasePosition +
|
|||
|
(trfmRight * weaponRelativeFirePosition.x) +
|
|||
|
(trfmUp * weaponRelativeFirePosition.y) +
|
|||
|
(trfmFwd * weaponRelativeFirePosition.z);
|
|||
|
|
|||
|
//weaponWorldFirePosition = GetWeaponWorldFirePosition(weapon, weaponWorldBasePosition, weaponRelativeFirePosition);
|
|||
|
|
|||
|
if (weaponTypeInt == Weapon.FixedBeamInt || weaponTypeInt == Weapon.TurretBeamInt)
|
|||
|
{
|
|||
|
// if the sequence number is 0, it means it is not active
|
|||
|
if (weapon.beamItemKeyList[fp].beamSequenceNumber == 0)
|
|||
|
{
|
|||
|
InstantiateBeamParameters ibParms = new InstantiateBeamParameters()
|
|||
|
{
|
|||
|
beamPrefabID = weapon.beamPrefabID,
|
|||
|
position = weaponWorldFirePosition,
|
|||
|
fwdDirection = weaponWorldFireDirection,
|
|||
|
upDirection = trfmUp,
|
|||
|
shipId = shipId,
|
|||
|
squadronId = squadronId,
|
|||
|
weaponIndex = componentIndex,
|
|||
|
firePositionIndex = fp,
|
|||
|
beamSequenceNumber = 0,
|
|||
|
beamPoolListIndex = -1
|
|||
|
};
|
|||
|
|
|||
|
// Create a beam using the ship control Manager (SSCManager)
|
|||
|
// Pass InstantiateBeamParameters by reference so we can get the beam index and sequence number back
|
|||
|
BeamModule beamModule = shipControllerManager.InstantiateBeam(ref ibParms);
|
|||
|
if (beamModule != null)
|
|||
|
{
|
|||
|
beamModule.callbackOnMove = MoveBeam;
|
|||
|
// Retrieve the unique identifiers for the beam instance
|
|||
|
weapon.beamItemKeyList[fp] = new SSCBeamItemKey(weapon.beamPrefabID, ibParms.beamPoolListIndex, ibParms.beamSequenceNumber);
|
|||
|
// Immediately update the beam position (required for pooled beams that have previously been used)
|
|||
|
MoveBeam(beamModule);
|
|||
|
|
|||
|
hasFired = true;
|
|||
|
|
|||
|
// Was a poolable muzzle fx spawned?
|
|||
|
if (beamModule.muzzleEffectsItemKey.effectsObjectSequenceNumber > 0)
|
|||
|
{
|
|||
|
// Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
|
|||
|
Transform muzzleFXTrfm = shipControllerManager.GetEffectsObjectTransform(beamModule.muzzleEffectsItemKey.effectsObjectTemplateListIndex, beamModule.muzzleEffectsItemKey.effectsObjectPoolListIndex, true);
|
|||
|
|
|||
|
if (muzzleFXTrfm != null)
|
|||
|
{
|
|||
|
muzzleFXTrfm.SetParent(beamModule.transform);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Create a new projectile using the ship controller Manager (SSCManager)
|
|||
|
// Velocity is world velocity of ship plus relative velocity of weapon due to angular velocity
|
|||
|
InstantiateProjectileParameters ipParms = new InstantiateProjectileParameters()
|
|||
|
{
|
|||
|
projectilePrefabID = weapon.projectilePrefabID,
|
|||
|
position = weaponWorldFirePosition,
|
|||
|
fwdDirection = weaponWorldFireDirection,
|
|||
|
upDirection = trfmUp,
|
|||
|
weaponVelocity = worldVelocity + Vector3.Cross(worldAngularVelocity, weaponWorldFirePosition - trfmPos),
|
|||
|
gravity = gravitationalAcceleration,
|
|||
|
gravityDirection = gravityDirection,
|
|||
|
shipId = shipId,
|
|||
|
squadronId = squadronId,
|
|||
|
targetShip = weapon.isProjectileKGuideToTarget ? weapon.targetShip : null,
|
|||
|
targetGameObject = weapon.isProjectileKGuideToTarget ? weapon.target : null,
|
|||
|
targetguidHash = weapon.isProjectileKGuideToTarget ? weapon.targetguidHash : 0,
|
|||
|
};
|
|||
|
|
|||
|
shipControllerManager.InstantiateProjectile(ref ipParms);
|
|||
|
|
|||
|
// For now, assume projectile was instantiated
|
|||
|
// Heat value is inversely proportional to the firing interval (reload time)
|
|||
|
if (weapon.reloadTime > 0f) { heatValue += 1f / weapon.reloadTime; };
|
|||
|
|
|||
|
hasFired = true;
|
|||
|
|
|||
|
// Was a poolable muzzle fx spawned?
|
|||
|
if (ipParms.muzzleEffectsObjectPoolListIndex >= 0)
|
|||
|
{
|
|||
|
// Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
|
|||
|
Transform muzzleFXTrfm = shipControllerManager.GetEffectsObjectTransform(ipParms.muzzleEffectsObjectPrefabID, ipParms.muzzleEffectsObjectPoolListIndex, true);
|
|||
|
|
|||
|
if (muzzleFXTrfm != null)
|
|||
|
{
|
|||
|
if (weaponTypeInt == Weapon.TurretProjectileInt)
|
|||
|
{
|
|||
|
muzzleFXTrfm.SetParent(weapon.turretPivotX);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// This may be risky as it could create a dependency that
|
|||
|
// causes GC and/or circular reference issues when the ShipControlModule is destroyed.
|
|||
|
// Might need a better method of doing this in the future.
|
|||
|
muzzleFXTrfm.SetParent(shipTransform);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Set reload timer to reload time
|
|||
|
weapon.reloadTimer = weapon.reloadTime;
|
|||
|
|
|||
|
// Did we just run out of ammo?
|
|||
|
if (weapon.ammunition == 0 && callbackOnWeaponNoAmmo != null)
|
|||
|
{
|
|||
|
callbackOnWeaponNoAmmo.Invoke(this, weapon);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (heatValue > 0f) { weapon.ManageHeat(_deltaTime, heatValue); }
|
|||
|
|
|||
|
// If the ship has a callback configured for the weapon, and it
|
|||
|
// has fired at least one fire point, invoke the callback.
|
|||
|
if (hasFired && callbackOnWeaponFired != null)
|
|||
|
{
|
|||
|
callbackOnWeaponFired.Invoke(this, weapon);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Damage
|
|||
|
|
|||
|
if (!Destroyed())
|
|||
|
{
|
|||
|
if (respawningMode == RespawningMode.RespawnAtLastPosition)
|
|||
|
{
|
|||
|
// Update current respawn position and rotation
|
|||
|
currentRespawnPosition = trfmPos;
|
|||
|
currentRespawnRotation = trfmRot;
|
|||
|
|
|||
|
// Increment collision respawn position timer
|
|||
|
collisionRespawnPositionTimer += _deltaTime;
|
|||
|
// When timer reaches the interval amount...
|
|||
|
if (collisionRespawnPositionTimer > collisionRespawnPositionDelay)
|
|||
|
{
|
|||
|
// Update current respawn position/rotation (from what was next)
|
|||
|
currentCollisionRespawnPosition = nextCollisionRespawnPosition;
|
|||
|
currentCollisionRespawnRotation = nextCollisionRespawnRotation;
|
|||
|
// Set next respawn position/rotation from current position/rotation
|
|||
|
nextCollisionRespawnPosition = trfmPos;
|
|||
|
nextCollisionRespawnRotation = trfmRot;
|
|||
|
// Reset the timer
|
|||
|
collisionRespawnPositionTimer = 0f;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public API Methods
|
|||
|
|
|||
|
#region Damage API Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When attaching an object to the ship, call this method for each non-trigger collider.
|
|||
|
/// It is automatically called when using VR hands to avoid them colliding with the ship
|
|||
|
/// and causing damage.
|
|||
|
/// </summary>
|
|||
|
/// <param name="colliderToAttach"></param>
|
|||
|
public void AttachCollider (Collider colliderToAttach)
|
|||
|
{
|
|||
|
if (colliderToAttach != null && !colliderToAttach.isTrigger)
|
|||
|
{
|
|||
|
attachedCollisionColliders.Add(colliderToAttach.GetInstanceID());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When attaching an object to the ship, call this method with an array of non-trigger collider.
|
|||
|
/// It is automatically called when using VR hands to avoid them colliding with the ship
|
|||
|
/// and causing damage. See also AttachCollider(..) and DetachCollider(..).
|
|||
|
/// </summary>
|
|||
|
/// <param name="collidersToAttach"></param>
|
|||
|
public void AttachColliders (Collider[] collidersToAttach)
|
|||
|
{
|
|||
|
int numAttachedColliders = collidersToAttach == null ? 0 : collidersToAttach.Length;
|
|||
|
|
|||
|
for (int colIdx = 0; colIdx < numAttachedColliders; colIdx++)
|
|||
|
{
|
|||
|
Collider attachCollider = collidersToAttach[colIdx];
|
|||
|
if (attachCollider.enabled && !attachCollider.isTrigger) { AttachCollider(attachCollider); }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When detaching or removing an object from the ship, call this method for each non-trigger collider.
|
|||
|
/// This is only required if it was first registered with AttachCollider(..).
|
|||
|
/// USAGE: DetachCollider (collider.GetInstanceID())
|
|||
|
/// </summary>
|
|||
|
/// <param name="colliderID"></param>
|
|||
|
public void DetachCollider (int colliderID)
|
|||
|
{
|
|||
|
attachedCollisionColliders.Remove(colliderID);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When detaching or removing an object from the ship, call this method with an array
|
|||
|
/// of non-trigger colliders. This is only required if they were first attached.
|
|||
|
/// </summary>
|
|||
|
/// <param name="collidersToDetach"></param>
|
|||
|
public void DetachColliders (Collider[] collidersToDetach)
|
|||
|
{
|
|||
|
int numAttachedColliders = collidersToDetach == null ? 0 : collidersToDetach.Length;
|
|||
|
|
|||
|
for (int colIdx = 0; colIdx < numAttachedColliders; colIdx++)
|
|||
|
{
|
|||
|
Collider attachCollider = collidersToDetach[colIdx];
|
|||
|
if (attachCollider != null && attachCollider.enabled) { DetachCollider(attachCollider.GetInstanceID()); }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the localised damage region with guidHash in the list of regions.
|
|||
|
/// The ship must be initialised.
|
|||
|
/// </summary>
|
|||
|
/// <param name="guidHash"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public DamageRegion GetDamageRegion(int guidHash)
|
|||
|
{
|
|||
|
DamageRegion damageRegion = null;
|
|||
|
|
|||
|
if (guidHash != 0 && shipDamageModel == ShipDamageModel.Localised)
|
|||
|
{
|
|||
|
// Attempt to find a matching guidHash
|
|||
|
for (int dmIdx = 0; dmIdx < numLocalisedDamageRegions; dmIdx++)
|
|||
|
{
|
|||
|
if (localisedDamageRegionList[dmIdx].guidHash == guidHash)
|
|||
|
{
|
|||
|
damageRegion = localisedDamageRegionList[dmIdx]; break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return damageRegion;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the localised damage region with the zero-based index in the list of regions.
|
|||
|
/// The ship must be initialised.
|
|||
|
/// </summary>
|
|||
|
/// <param name="index"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public DamageRegion GetDamageRegionByIndex(int index)
|
|||
|
{
|
|||
|
if (shipDamageModel == ShipDamageModel.Localised && index >= 0 && index < numLocalisedDamageRegions)
|
|||
|
{
|
|||
|
return localisedDamageRegionList[index];
|
|||
|
}
|
|||
|
else { return null; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the zero-based index of a local damage region in the ship given the damage region name.
|
|||
|
/// Returns -1 if not found or the Damage Model is not Localised.
|
|||
|
/// Use this sparingly as it will incur garabage. Always declare the parameter as a static readonly variable.
|
|||
|
/// Usage:
|
|||
|
/// private static readonly string EngineDamageRegionName = "Engines";
|
|||
|
/// ..
|
|||
|
/// int drIdx = GetDamageRegionIndexByName(EngineDamageRegionName);
|
|||
|
/// DamageRegion damageRegion = GetDamageRegionByIndex(drIdx);
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageRegionName"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public int GetDamageRegionIndexByName(string damageRegionName)
|
|||
|
{
|
|||
|
if (shipDamageModel == ShipDamageModel.Localised && localisedDamageRegionList != null)
|
|||
|
{
|
|||
|
return localisedDamageRegionList.FindIndex(dr => dr.name == damageRegionName);
|
|||
|
}
|
|||
|
else { return -1; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the world space central position of the localised damage region
|
|||
|
/// with the given guidHash. If the ship is not using the Localised damage model
|
|||
|
/// or a damage region with guidHash cannot be found, this returns the world space
|
|||
|
/// position of the ship.
|
|||
|
/// The ship must be initialised.
|
|||
|
/// </summary>
|
|||
|
/// <param name="guidHash"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public Vector3 GetDamageRegionWSPosition(int guidHash)
|
|||
|
{
|
|||
|
DamageRegion damageRegion = GetDamageRegion(guidHash);
|
|||
|
if (damageRegion != null)
|
|||
|
{
|
|||
|
return (trfmRight * damageRegion.relativePosition.x) +
|
|||
|
(trfmUp * damageRegion.relativePosition.y) +
|
|||
|
(trfmFwd * damageRegion.relativePosition.z) +
|
|||
|
trfmPos;
|
|||
|
}
|
|||
|
else { return TransformPosition; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the world space central position of the damage region.
|
|||
|
/// The ship must be initialised.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageRegion"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public Vector3 GetDamageRegionWSPosition(DamageRegion damageRegion)
|
|||
|
{
|
|||
|
return (trfmRight * damageRegion.relativePosition.x) +
|
|||
|
(trfmUp * damageRegion.relativePosition.y) +
|
|||
|
(trfmFwd * damageRegion.relativePosition.z) +
|
|||
|
trfmPos;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Is this world space point on the ship, currently shielded?
|
|||
|
/// If the main damage region is invincible, it will always return true.
|
|||
|
/// If the ShipDamageModel is NOT Localised, the worldSpacePoint is ignored.
|
|||
|
/// </summary>
|
|||
|
/// <param name="worldSpacePoint"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool HasActiveShield (Vector3 worldSpacePoint)
|
|||
|
{
|
|||
|
bool isShielded = false;
|
|||
|
|
|||
|
if (mainDamageRegion.isInvincible) { isShielded = true; }
|
|||
|
else if (shipDamageModel != ShipDamageModel.Localised)
|
|||
|
{
|
|||
|
isShielded = mainDamageRegion.useShielding && mainDamageRegion.ShieldHealth > 0f;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Determine which damage region was hit
|
|||
|
int numDamageRegions = localisedDamageRegionList.Count + 1;
|
|||
|
|
|||
|
// Loop through all damage regions
|
|||
|
DamageRegion thisDamageRegion;
|
|||
|
|
|||
|
// Get damage position in local space (calc once outside the loop)
|
|||
|
Vector3 localDamagePosition = trfmInvRot * (worldSpacePoint - trfmPos);
|
|||
|
|
|||
|
for (int i = 0; i < numDamageRegions; i++)
|
|||
|
{
|
|||
|
thisDamageRegion = i == 0 ? mainDamageRegion : localisedDamageRegionList[i - 1];
|
|||
|
|
|||
|
if (thisDamageRegion.useShielding && thisDamageRegion.ShieldHealth > 0f && thisDamageRegion.IsHit(localDamagePosition))
|
|||
|
{
|
|||
|
isShielded = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return isShielded;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Check if a world-space point is within the volume area of a damage region.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageRegion"></param>
|
|||
|
/// <param name="worldSpacePoint"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool IsPointInDamageRegion (DamageRegion damageRegion, Vector3 worldSpacePoint)
|
|||
|
{
|
|||
|
return damageRegion == null ? false : damageRegion.IsHit(trfmInvRot * (worldSpacePoint - trfmPos));
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Make the whole ship invincible to damage.
|
|||
|
/// For individual damageRegions change the isInvisible value on the localised region.
|
|||
|
/// </summary>
|
|||
|
public void MakeShipInvincible()
|
|||
|
{
|
|||
|
mainDamageRegion.isInvincible = true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Make the whole ship vincible to damage. When hit, the ship or shields will take damage.
|
|||
|
/// For individual damageRegions change the isInvisible value on the localised region.
|
|||
|
/// </summary>
|
|||
|
public void MakeShipVincible()
|
|||
|
{
|
|||
|
mainDamageRegion.isInvincible = false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Resets health data for the ship. Used when initialising and respawning the ship.
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public void ResetHealth()
|
|||
|
{
|
|||
|
// Reset health value for the damage region
|
|||
|
mainDamageRegion.Health = mainDamageRegion.startingHealth;
|
|||
|
|
|||
|
// Reset shield health value for the damage region
|
|||
|
mainDamageRegion.ShieldHealth = mainDamageRegion.shieldingAmount;
|
|||
|
|
|||
|
mainDamageRegion.shieldRechargeDelayTimer = 0f;
|
|||
|
|
|||
|
mainDamageRegion.isDestructObjectActivated = false;
|
|||
|
mainDamageRegion.isDestroyed = false;
|
|||
|
|
|||
|
damageCameraShakeAmountRequired = 0f;
|
|||
|
|
|||
|
if (!shipDamageModelIsSimple)
|
|||
|
{
|
|||
|
// Reset health value for each component
|
|||
|
|
|||
|
componentListSize = thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
thrusterList[componentIndex].Repair();
|
|||
|
}
|
|||
|
|
|||
|
componentListSize = wingList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
wingList[componentIndex].Health = wingList[componentIndex].startingHealth;
|
|||
|
}
|
|||
|
|
|||
|
componentListSize = controlSurfaceList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
controlSurfaceList[componentIndex].Health = controlSurfaceList[componentIndex].startingHealth;
|
|||
|
}
|
|||
|
|
|||
|
componentListSize = weaponList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
weaponList[componentIndex].Repair();
|
|||
|
}
|
|||
|
|
|||
|
if (shipDamageModel == ShipDamageModel.Localised)
|
|||
|
{
|
|||
|
// For each damage region:
|
|||
|
// reset health value, shield health value, effects object or destruct instantiated flags, and child tranform
|
|||
|
|
|||
|
for (componentIndex = 0; componentIndex < numLocalisedDamageRegions; componentIndex++)
|
|||
|
{
|
|||
|
DamageRegion _damageRegion = localisedDamageRegionList[componentIndex];
|
|||
|
|
|||
|
_damageRegion.Health = localisedDamageRegionList[componentIndex].startingHealth;
|
|||
|
_damageRegion.ShieldHealth = localisedDamageRegionList[componentIndex].shieldingAmount;
|
|||
|
_damageRegion.shieldRechargeDelayTimer = 0f;
|
|||
|
_damageRegion.isDestructionEffectsObjectInstantiated = false;
|
|||
|
_damageRegion.isDestructObjectActivated = false;
|
|||
|
_damageRegion.isDestroyed = false;
|
|||
|
// if there is one, reactivate the transform for this region.
|
|||
|
if (_damageRegion.regionChildTransform != null && !_damageRegion.regionChildTransform.gameObject.activeSelf)
|
|||
|
{
|
|||
|
_damageRegion.regionChildTransform.gameObject.SetActive(true);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (callbackOnDamage != null) { callbackOnDamage(mainDamageRegion.Health); }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Typically used when ShipDamageModel is Simple or Progressive, add health to the ship.
|
|||
|
/// If isAffectShield is true, and the health reaches the maximum configured, excess health will
|
|||
|
/// be applied to the shield for the specified DamageRegion. For Progressive Damage, health is
|
|||
|
/// also added to components that have Use Progressive Damage enabled.
|
|||
|
/// </summary>
|
|||
|
/// <param name="healthAmount"></param>
|
|||
|
/// <param name="isAffectShield"></param>
|
|||
|
public void AddHealth(float healthAmount, bool isAffectShield)
|
|||
|
{
|
|||
|
AddHealth(mainDamageRegion, healthAmount, isAffectShield);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Add health to a specific DamageRegion.
|
|||
|
/// If isAffectShield is true, and the health reaches the maximum configured, excess health will
|
|||
|
/// be applied to the shield for the specified DamageRegion. For Progressive Damage, health is
|
|||
|
/// also added to components that have Use Progressive Damage enabled.
|
|||
|
/// NOTE: -ve values are ignored. To incur damage use the ApplyNormalDamage or ApplyCollisionDamage
|
|||
|
/// API methods.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageRegion"></param>
|
|||
|
/// <param name="healthAmount"></param>
|
|||
|
/// <param name="isAffectShield"></param>
|
|||
|
public void AddHealth(DamageRegion damageRegion, float healthAmount, bool isAffectShield)
|
|||
|
{
|
|||
|
if (damageRegion != null && healthAmount > 0f)
|
|||
|
{
|
|||
|
float health = damageRegion.Health;
|
|||
|
if (health < 0f) { health = healthAmount; }
|
|||
|
else { health += healthAmount; }
|
|||
|
|
|||
|
if (health > damageRegion.startingHealth)
|
|||
|
{
|
|||
|
if (isAffectShield && damageRegion.useShielding)
|
|||
|
{
|
|||
|
float newShieldHealth = 0f;
|
|||
|
// When shielding is -ve (e.g. -0.01 when it has been used up) set the shielding amount rather than adding it
|
|||
|
if (damageRegion.ShieldHealth < 0f) { newShieldHealth = health - damageRegion.startingHealth; }
|
|||
|
else { newShieldHealth = damageRegion.ShieldHealth + health - damageRegion.startingHealth; }
|
|||
|
|
|||
|
// Cap shielding to maximum permitted.
|
|||
|
if (newShieldHealth > damageRegion.shieldingAmount) { newShieldHealth = damageRegion.shieldingAmount; }
|
|||
|
|
|||
|
damageRegion.ShieldHealth = newShieldHealth;
|
|||
|
}
|
|||
|
|
|||
|
// Cap health to maximum permitted
|
|||
|
health = damageRegion.startingHealth;
|
|||
|
}
|
|||
|
|
|||
|
damageRegion.Health = health;
|
|||
|
|
|||
|
// Not sure if this is correct... why do we do this??
|
|||
|
damageRegion.isDestructionEffectsObjectInstantiated = false;
|
|||
|
|
|||
|
// Use !shipDamageModelIsSimple first to avoid always having to lookup the enumeration
|
|||
|
if (!shipDamageModelIsSimple)
|
|||
|
{
|
|||
|
int damageRegionIndex = GetDamageRegionIndex(damageRegion);
|
|||
|
|
|||
|
// Apply health to each of the components
|
|||
|
if (damageRegionIndex >= 0)
|
|||
|
{
|
|||
|
// For Progressive, a damageRegionIndex of 0 on the component means Use Progressive Damage.
|
|||
|
// If -1, the component doesn't use Progressive damage.
|
|||
|
// For localised damage, the damageRegionIndex needs to match the damage region that health is being added to.
|
|||
|
|
|||
|
// Add health to Thrusters
|
|||
|
componentListSize = thrusterList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// For Progressive, a region index of 0 means Use Progressive Damage.
|
|||
|
if (thrusterList[componentIndex].damageRegionIndex == damageRegionIndex)
|
|||
|
{
|
|||
|
health = thrusterList[componentIndex].Health;
|
|||
|
if (health < 0f) { health = healthAmount; }
|
|||
|
else { health += healthAmount; }
|
|||
|
if (health > thrusterList[componentIndex].startingHealth) { health = thrusterList[componentIndex].startingHealth; }
|
|||
|
|
|||
|
// Only set the health value once for a given thruster
|
|||
|
thrusterList[componentIndex].Health = health;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//Add health to Wings
|
|||
|
componentListSize = wingList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// For Progressive, a region index of 0 means Use Progressive Damage
|
|||
|
if (wingList[componentIndex].damageRegionIndex == damageRegionIndex)
|
|||
|
{
|
|||
|
health = wingList[componentIndex].Health;
|
|||
|
if (health < 0f) { health = healthAmount; }
|
|||
|
else { health += healthAmount; }
|
|||
|
if (health > wingList[componentIndex].startingHealth) { health = wingList[componentIndex].startingHealth; }
|
|||
|
|
|||
|
// Only set the health value once for a given wing
|
|||
|
wingList[componentIndex].Health = health;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Add health to Control Surfaces
|
|||
|
componentListSize = controlSurfaceList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// For Progressive, a region index of 0 means Use Progressive Damage
|
|||
|
if (controlSurfaceList[componentIndex].damageRegionIndex == damageRegionIndex)
|
|||
|
{
|
|||
|
health = controlSurfaceList[componentIndex].Health;
|
|||
|
if (health < 0f) { health = healthAmount; }
|
|||
|
else { health += healthAmount; }
|
|||
|
if (health > controlSurfaceList[componentIndex].startingHealth) { health = controlSurfaceList[componentIndex].startingHealth; }
|
|||
|
|
|||
|
// Only set the health value once for a given Control Surface
|
|||
|
controlSurfaceList[componentIndex].Health = health;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Add health to Weapons
|
|||
|
componentListSize = weaponList.Count;
|
|||
|
for (componentIndex = 0; componentIndex < componentListSize; componentIndex++)
|
|||
|
{
|
|||
|
// For Progressive, a region index of 0 means Use Progressive Damage
|
|||
|
if (weaponList[componentIndex].damageRegionIndex == damageRegionIndex)
|
|||
|
{
|
|||
|
health = weaponList[componentIndex].Health;
|
|||
|
if (health < 0f) { health = healthAmount; }
|
|||
|
else { health += healthAmount; }
|
|||
|
if (health > weaponList[componentIndex].startingHealth) { health = weaponList[componentIndex].startingHealth; }
|
|||
|
|
|||
|
// Only set the health value once for a given Weapon
|
|||
|
weaponList[componentIndex].Health = health;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Applies damage to the ship due to a collision. The damage is registered as "collision" damage,
|
|||
|
/// meaning that if it destroys the ship the ship will be respawned at a previously recorded respawn position.
|
|||
|
/// </summary>
|
|||
|
/// <param name="collisionInfo"></param>
|
|||
|
public void ApplyCollisionDamage (Collision collisionInfo)
|
|||
|
{
|
|||
|
int hitColliderID = collisionInfo.collider.GetInstanceID();
|
|||
|
|
|||
|
// Avoid collision with colliders that have been registered with (attached to) this ship
|
|||
|
if (!attachedCollisionColliders.Contains(hitColliderID))
|
|||
|
{
|
|||
|
// Loop through contact points
|
|||
|
int numberOfContactPoints = collisionInfo.contactCount;
|
|||
|
float damageAtEachPoint = collisionInfo.impulse.magnitude / numberOfContactPoints;
|
|||
|
for (int c = 0; c < numberOfContactPoints; c++)
|
|||
|
{
|
|||
|
//Debug.Log("[DEBUG] " + collisionInfo.collider.name + " hit " + collisionInfo.GetContact(c).thisCollider.name + " T:" + Time.time);
|
|||
|
|
|||
|
// Apply damage at each contact point
|
|||
|
ApplyDamage(damageAtEachPoint, ProjectileModule.DamageType.Default, collisionInfo.GetContact(c).point, true);
|
|||
|
}
|
|||
|
|
|||
|
// Remember that this was collision damage
|
|||
|
lastDamageWasCollisionDamage = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Applies a specified amount of damage to the ship at a specified position. The damage is registered as "collision" damage,
|
|||
|
/// meaning that if it destroys the ship the ship will be respawned at a previously recorded respawn position.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageAmount"></param>
|
|||
|
/// <param name="damagePosition"></param>
|
|||
|
public void ApplyCollisionDamage(float damageAmount, Vector3 damagePosition)
|
|||
|
{
|
|||
|
ApplyDamage(damageAmount, ProjectileModule.DamageType.Default, damagePosition, false);
|
|||
|
// Remember that this was collision damage
|
|||
|
lastDamageWasCollisionDamage = true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Applies a specified amount of damage to the ship at a specified position.
|
|||
|
/// </summary>
|
|||
|
/// <param name="damageAmount"></param>
|
|||
|
/// <param name="damagePosition"></param>
|
|||
|
public void ApplyNormalDamage(float damageAmount, ProjectileModule.DamageType damageType, Vector3 damagePosition)
|
|||
|
{
|
|||
|
ApplyDamage(damageAmount, damageType, damagePosition, false);
|
|||
|
// Remember that this was not collision damage
|
|||
|
lastDamageWasCollisionDamage = false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the index of the last damage event. When this value changes, a damage event has occurred.
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public int LastDamageEventIndex() { return lastDamageEventIndex; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the amount of rumble required due to the last damage event.
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public float RequiredDamageRumbleAmount() { return damageRumbleAmountRequired; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the amount of camera shake required due to the last damage event
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public float RequiredCameraShakeAmount() { return damageCameraShakeAmountRequired; }
|
|||
|
#endregion
|
|||
|
|
|||
|
#region General API Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns an interpolated velocity in world space (akin to to the interpolated position/rotation on rigidbodies).
|
|||
|
/// </summary>
|
|||
|
public Vector3 GetInterpolatedWorldVelocity()
|
|||
|
{
|
|||
|
// Get the time in seconds since the last fixed update
|
|||
|
float timeSinceLastFixedUpdate = Time.time - lastFixedUpdateTime;
|
|||
|
// Interpolate between the world velocities of the last frame and the previous frame to get an interpolated world velocity
|
|||
|
return previousFrameWorldVelocity + (worldVelocity - previousFrameWorldVelocity) * (timeSinceLastFixedUpdate / Time.fixedDeltaTime);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Add temporary boost to the ship in a normalised local-space forceDirection
|
|||
|
/// which a force of forceAmount Newtons for a period of duration seconds.
|
|||
|
/// IMPORTANT: forceDirection must be normalised, otherwise you will get odd results.
|
|||
|
/// </summary>
|
|||
|
/// <param name="forceDirection"></param>
|
|||
|
/// <param name="forceAmount"></param>
|
|||
|
/// <param name="duration"></param>
|
|||
|
public void AddBoost(Vector3 forceDirection, float forceAmount, float duration)
|
|||
|
{
|
|||
|
boostDirection = forceDirection;
|
|||
|
boostForce = forceAmount;
|
|||
|
boostTimer = duration;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Immediately stop any boost that has been applied with AddBoost(..).
|
|||
|
/// </summary>
|
|||
|
public void StopBoost()
|
|||
|
{
|
|||
|
boostTimer = 0f;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Respawning API Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns true if the ship has been destroyed.
|
|||
|
/// </summary>
|
|||
|
public bool Destroyed()
|
|||
|
{
|
|||
|
if (shipDamageModel == ShipDamageModel.Simple || shipDamageModel == ShipDamageModel.Progressive)
|
|||
|
{
|
|||
|
return mainDamageRegion.Health <= 0f;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return mainDamageRegion.Health <= 0f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the position for the ship to respawn at.
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public Vector3 GetRespawnPosition()
|
|||
|
{
|
|||
|
if (respawningMode == RespawningMode.RespawnAtLastPosition)
|
|||
|
{
|
|||
|
if (lastDamageWasCollisionDamage) { return currentCollisionRespawnPosition; }
|
|||
|
else { return currentRespawnPosition; }
|
|||
|
}
|
|||
|
else if (respawningMode == RespawningMode.RespawnAtOriginalPosition)
|
|||
|
{
|
|||
|
return currentRespawnPosition;
|
|||
|
}
|
|||
|
else if (respawningMode == RespawningMode.RespawnAtSpecifiedPosition)
|
|||
|
{
|
|||
|
return customRespawnPosition;
|
|||
|
}
|
|||
|
else { return Vector3.zero; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the rotation for the ship to respawn with.
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public Quaternion GetRespawnRotation()
|
|||
|
{
|
|||
|
if (respawningMode == RespawningMode.RespawnAtLastPosition)
|
|||
|
{
|
|||
|
if (lastDamageWasCollisionDamage) { return currentCollisionRespawnRotation; }
|
|||
|
else { return currentRespawnRotation; }
|
|||
|
}
|
|||
|
else if (respawningMode == RespawningMode.RespawnAtOriginalPosition)
|
|||
|
{
|
|||
|
return currentRespawnRotation;
|
|||
|
}
|
|||
|
else if (respawningMode == RespawningMode.RespawnAtSpecifiedPosition)
|
|||
|
{
|
|||
|
return Quaternion.Euler(customRespawnRotation);
|
|||
|
}
|
|||
|
else { return Quaternion.identity; }
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Thruster API Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the (central) fuel level of the ship. Fuel Level Range 0.0 (empty) to 100.0 (full).
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetFuelLevel()
|
|||
|
{
|
|||
|
return centralFuelLevel;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the fuel level of the Thruster based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// Fuel Level Range 0.0 (empty) to 100.0 (full).
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetFuelLevel (int thrusterNumber)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
return thrusterList[thrusterNumber - 1].fuelLevel;
|
|||
|
}
|
|||
|
else { return 0f; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the heat level of the Thruster based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// Heat Level Range 0.0 (min) to 100.0 (max).
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetHeatLevel (int thrusterNumber)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
return thrusterList[thrusterNumber - 1].heatLevel;
|
|||
|
}
|
|||
|
else { return 0f; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the overheating threshold of the Thruster based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetOverheatingThreshold (int thrusterNumber)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
return thrusterList[thrusterNumber - 1].overHeatThreshold;
|
|||
|
}
|
|||
|
else { return 0f; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the Maximum Thrust of the Thruster based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1. Values are returned in kilo Newtons.
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetMaxThrust (int thrusterNumber)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
return thrusterList[thrusterNumber-1].maxThrust;
|
|||
|
}
|
|||
|
else { return 0f; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Is the thruster heat level at or above the overheating threshold?
|
|||
|
/// Thruster Number is based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool IsThrusterOverheating (int thrusterNumber)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
return thrusterList[thrusterNumber - 1].IsThrusterOverheating();
|
|||
|
}
|
|||
|
else { return false; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Repair the health of a thruster. Will also set the heat level to 0.
|
|||
|
/// Can be useful if a thruster has burnt out after being over heated.
|
|||
|
/// Thruster Number is based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
public void RepairThruster (int thrusterNumber)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
thrusterList[thrusterNumber - 1].Repair();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the central fuel level for the whole ship. If useCentralFuel is false,
|
|||
|
/// use SetFuelLevel (thrusterNumber, new FuelLevel).
|
|||
|
/// </summary>
|
|||
|
/// <param name="newFuelLevel"></param>
|
|||
|
public void SetFuelLevel (float newFuelLevel)
|
|||
|
{
|
|||
|
// Clamp the fuel level
|
|||
|
if (newFuelLevel < 0f) { newFuelLevel = 0f; }
|
|||
|
else if (newFuelLevel > 100f) { newFuelLevel = 100f; }
|
|||
|
|
|||
|
centralFuelLevel = newFuelLevel;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the fuel level of the Thruster based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// Fuel Level Range 0.0 (empty) to 100.0 (full).
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public void SetFuelLevel (int thrusterNumber, float newFuelLevel)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
thrusterList[thrusterNumber - 1].SetFuelLevel(newFuelLevel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the heat level of the Thruster based on the order it appears in the Thrusters tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1.
|
|||
|
/// Heat Level Range 0.0 (min) to 100.0 (max).
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public void SetHeatLevel (int thrusterNumber, float newHeatLevel)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
thrusterList[thrusterNumber - 1].SetHeatLevel(newHeatLevel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the Maximum Thrust of the Thruster based on the order it appears in the Thruster tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1. Values should be in kilo Newtons
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <param name="newMaxThrustkN"></param>
|
|||
|
public void SetMaxThrust (int thrusterNumber, int newMaxThrustkN)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
thrusterList[thrusterNumber - 1].maxThrust = newMaxThrustkN * 1000f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Use this if you want fine-grained control over max thruster force, otherwise use SetMaxThrust.
|
|||
|
/// Set the Maximum Thrust of the Thruster based on the order it appears in the Thruster tab
|
|||
|
/// in the ShipControlModule. Numbers begin at 1. Values are in Newtons.
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <param name="newMaxThrustNewtons"></param>
|
|||
|
public void SetMaxThrustNewtons (int thrusterNumber, float newMaxThrustNewtons)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
thrusterList[thrusterNumber - 1].maxThrust = newMaxThrustNewtons;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set all thrusters to the same throttle value.
|
|||
|
/// Values are clamped to between 0.0 and 1.0.
|
|||
|
/// </summary>
|
|||
|
/// <param name="newThrottleValue"></param>
|
|||
|
public void SetThrottleAllThrusters (float newThrottleValue)
|
|||
|
{
|
|||
|
// Clamp values
|
|||
|
if (newThrottleValue < 0f) { newThrottleValue = 0f; }
|
|||
|
else if (newThrottleValue > 1f) { newThrottleValue = 1f; }
|
|||
|
|
|||
|
int numThrusters = thrusterList == null ? 0 : thrusterList.Count;
|
|||
|
|
|||
|
for (int thIdx = 0; thIdx < numThrusters; thIdx++)
|
|||
|
{
|
|||
|
thrusterList[thIdx].throttle = newThrottleValue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the throttle for a given thruster. Numbers begin at 1. Values should be between 0.0 and 1.0.
|
|||
|
/// </summary>
|
|||
|
/// <param name="thrusterNumber"></param>
|
|||
|
/// <param name="newThrottleValue"></param>
|
|||
|
public void SetThrusterThrottle (int thrusterNumber, float newThrottleValue)
|
|||
|
{
|
|||
|
if (thrusterNumber > 0 && thrusterNumber <= thrusterList.Count)
|
|||
|
{
|
|||
|
thrusterList[thrusterNumber - 1].throttle = newThrottleValue < 0f ? 0f : newThrottleValue > 1f ? 1f : newThrottleValue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Weapon API Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Deactivate all beam weapons that are currently firing
|
|||
|
/// </summary>
|
|||
|
public void DeactivateBeams(SSCManager sscManager)
|
|||
|
{
|
|||
|
int numWeapons = NumberOfWeapons;
|
|||
|
|
|||
|
for (int weaponIdx = 0; weaponIdx < numWeapons; weaponIdx++)
|
|||
|
{
|
|||
|
Weapon weapon = weaponList[weaponIdx];
|
|||
|
if (weapon != null && (weapon.weaponTypeInt == Weapon.FixedBeamInt || weaponTypeInt == Weapon.TurretBeamInt))
|
|||
|
{
|
|||
|
int numBeams = weapon.beamItemKeyList == null ? 0 : weapon.beamItemKeyList.Count;
|
|||
|
|
|||
|
for (int bItemIdx = 0; bItemIdx < numBeams; bItemIdx++)
|
|||
|
{
|
|||
|
if (weapon.beamItemKeyList[bItemIdx].beamSequenceNumber > 0)
|
|||
|
{
|
|||
|
sscManager.DeactivateBeam(weapon.beamItemKeyList[bItemIdx]);
|
|||
|
}
|
|||
|
|
|||
|
weapon.beamItemKeyList[bItemIdx] = new SSCBeamItemKey(-1, -1, 0);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the zero-based index of a weapon on the ship given the weapon name. Returns -1 if not found.
|
|||
|
/// Use this sparingly as it will incur garabage. Always declare the parameter as a static readonly variable.
|
|||
|
/// Usage:
|
|||
|
/// private static readonly string WPNturret1Name = "Turret 1";
|
|||
|
/// ..
|
|||
|
/// int wpIdx = GetWeaponIndexByName(WPNturret1Name);
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponName"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public int GetWeaponIndexByName(string weaponName)
|
|||
|
{
|
|||
|
if (weaponList != null) { return weaponList.FindIndex(wp => wp.name == weaponName); }
|
|||
|
else { return -1; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the weapon on the ship from a zero-based index in the list of weapons.
|
|||
|
/// This will be 1 less than the number shown next to the weapon on the Combat tab.
|
|||
|
/// It validates the weaponIdx so if possible, don't call this every frame.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public Weapon GetWeaponByIndex(int weaponIdx)
|
|||
|
{
|
|||
|
if (weaponIdx >= 0 && weaponIdx < (weaponList == null ? 0 : weaponList.Count))
|
|||
|
{
|
|||
|
return weaponList[weaponIdx];
|
|||
|
}
|
|||
|
else { return null; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the heat level of the Weapon with the zero-based index in list of weapons.
|
|||
|
/// Heat Level Range 0.0 (min) to 100.0 (max).
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public float GetWeaponHeatLevel (int weaponIdx, float newHeatLevel)
|
|||
|
{
|
|||
|
if (weaponIdx >= 0 && weaponIdx < (weaponList == null ? 0 : weaponList.Count))
|
|||
|
{
|
|||
|
return weaponList[weaponIdx].heatLevel;
|
|||
|
}
|
|||
|
else { return 0f; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get the index in the weaponList of next weapon that has AutoTargetingEnabled.
|
|||
|
/// If no match is found, return -1. startIdx is the zero-based index in the
|
|||
|
/// weaponList to begin the search.
|
|||
|
/// </summary>
|
|||
|
/// <param name="startIdx"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public int GetNextAutoTargetingWeaponIndex(int startIdx)
|
|||
|
{
|
|||
|
int foundWeaponIdx = -1;
|
|||
|
|
|||
|
Weapon _tempWeapon = null;
|
|||
|
int numWeapons = weaponList == null ? 0 : weaponList.Count;
|
|||
|
if (startIdx >= 0 && startIdx < numWeapons)
|
|||
|
{
|
|||
|
// Use a for-loop rather than list.Find to avoid GC
|
|||
|
for (int weaponIdx = startIdx; weaponIdx < numWeapons; weaponIdx++)
|
|||
|
{
|
|||
|
_tempWeapon = weaponList[weaponIdx];
|
|||
|
if (_tempWeapon != null && _tempWeapon.isAutoTargetingEnabled)
|
|||
|
{
|
|||
|
foundWeaponIdx = weaponIdx;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return foundWeaponIdx;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Clears all targeting information for the weapon. This should be called if you do not
|
|||
|
/// know if the target is a ship or a gameobject.
|
|||
|
/// WARNING: For the sake of performance, does not validate weaponIdx.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx">zero-based index in list of weapons</param>
|
|||
|
public void ClearWeaponTarget(int weaponIdx)
|
|||
|
{
|
|||
|
if (weaponIdx >= 0) { weaponList[weaponIdx].ClearTarget(); }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Has the weapon with the zero-based index in list of weapons got a target
|
|||
|
/// assigned to it? The target could be a GameObject or a Ship.
|
|||
|
/// WARNING: For the sake of performance, does not validate weaponIdx.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool HasWeaponTarget(int weaponIdx)
|
|||
|
{
|
|||
|
if (weaponIdx >= 0) { return weaponList[weaponIdx].target != null; }
|
|||
|
else { return false; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the heat level of the Weapon with the zero-based index in list of weapons.
|
|||
|
/// Heat Level Range 0.0 (min) to 100.0 (max).
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public void SetWeaponHeatLevel (int weaponIdx, float newHeatLevel)
|
|||
|
{
|
|||
|
if (weaponIdx >= 0 && weaponIdx < (weaponList == null ? 0 : weaponList.Count))
|
|||
|
{
|
|||
|
weaponList[weaponIdx].SetHeatLevel(newHeatLevel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the gameobject that the weapon will attempt to aim at. Currently only applies
|
|||
|
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
|
|||
|
/// However, for the sake of performance, this method does not do any WeaponType validation.
|
|||
|
/// WARNING: This method will generate garbage. Where possible call
|
|||
|
/// SetWeaponTarget(int weaponIdx, GameObject target). If only the name is known,
|
|||
|
/// first call GetWeaponIndexByName(string weaponName) once in your Awake() routine.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponName"></param>
|
|||
|
/// <param name="target"></param>
|
|||
|
public void SetWeaponTarget(string weaponName, GameObject target)
|
|||
|
{
|
|||
|
int wpIdx = GetWeaponIndexByName(weaponName);
|
|||
|
if (wpIdx >= 0)
|
|||
|
{
|
|||
|
// It is quicker not to check the weapon type and just set the target
|
|||
|
// Call SetTarget so it is configured correctly rather than setting the public variable
|
|||
|
weaponList[wpIdx].SetTarget(target);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the gameobject that the weapon will attempt to aim at. Currently only applies
|
|||
|
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
|
|||
|
/// However, for the sake of performance, this method does not do any WeaponType validation.
|
|||
|
/// WARNING: For the sake of performance, does not validate weaponIdx.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx">zero-based index in list of weapons</param>
|
|||
|
/// <param name="target"></param>
|
|||
|
public void SetWeaponTarget(int weaponIdx, GameObject target)
|
|||
|
{
|
|||
|
// Currently don't check weapon type or number of weapons as this would incur overhead.
|
|||
|
if (weaponIdx >= 0)
|
|||
|
{
|
|||
|
// Call SetTarget so it is configured correctly rather than setting the public variable
|
|||
|
weaponList[weaponIdx].SetTarget(target);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the ship that the weapon will attempt to aim at. Currently only applies
|
|||
|
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
|
|||
|
/// However, for the sake of performance, this method does not do any WeaponType validation.
|
|||
|
/// WARNING: For the sake of performance, does not validate weaponIdx.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx">zero-based index in list of weapons</param>
|
|||
|
/// <param name="targetShipControlModule"></param>
|
|||
|
public void SetWeaponTargetShip(int weaponIdx, ShipControlModule targetShipControlModule)
|
|||
|
{
|
|||
|
// Currently don't check weapon type or number of weapons as this would incur overhead.
|
|||
|
if (weaponIdx >= 0)
|
|||
|
{
|
|||
|
// Call SetTargetShip so it is configured correctly rather than setting the public variable
|
|||
|
weaponList[weaponIdx].SetTargetShip(targetShipControlModule);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the ship's damage region that the weapon will attempt to aim at. Currently only applies
|
|||
|
/// to Weapon.WeaponType.TurretProjectile and FixedProjectile weapons with guided projectiles.
|
|||
|
/// However, for the sake of performance, this method does not do any WeaponType validation.
|
|||
|
/// WARNING: For the sake of performance, does not validate weaponIdx.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx"></param>
|
|||
|
/// <param name="targetShipControlModule"></param>
|
|||
|
/// <param name="damageRegion"></param>
|
|||
|
public void SetWeaponTargetShipDamageRegion (int weaponIdx, ShipControlModule targetShipControlModule, DamageRegion damageRegion)
|
|||
|
{
|
|||
|
// Currently don't check weapon type or number of weapons as this would incur overhead.
|
|||
|
if (weaponIdx >= 0)
|
|||
|
{
|
|||
|
// Call SetTargetShipDamageRegion so it is configured correctly rather than setting the public variable
|
|||
|
weaponList[weaponIdx].SetTargetShipDamageRegion(targetShipControlModule, damageRegion);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Performs a once-off change to the direction the weapon will fire based on a
|
|||
|
/// "target" position in world-space. The weapon will NOT stay locked onto this
|
|||
|
/// position.
|
|||
|
/// NOTE: Should NOT be used for Turrets. Use SetWeaponTarget(..) instead.
|
|||
|
/// WARNING: For the sake of performance, does not validate weaponIdx.
|
|||
|
/// </summary>
|
|||
|
/// <param name="weaponIdx">zero-based position in the list of weapons</param>
|
|||
|
/// <param name="aimAtWorldSpacePosition"></param>
|
|||
|
public void SetWeaponFireDirection(int weaponIdx, Vector3 aimAtWorldSpacePosition)
|
|||
|
{
|
|||
|
// Currently don't check weapon type or number of weapons as this would incur overhead.
|
|||
|
if (weaponIdx >= 0)
|
|||
|
{
|
|||
|
Weapon weapon = weaponList[weaponIdx];
|
|||
|
if (weapon != null)
|
|||
|
{
|
|||
|
// Get base weapon world position (not accounting for relative fire position)
|
|||
|
weaponWorldBasePosition = (trfmRight * weapon.relativePosition.x) +
|
|||
|
(trfmUp * weapon.relativePosition.y) +
|
|||
|
(trfmFwd * weapon.relativePosition.z) +
|
|||
|
trfmPos;
|
|||
|
|
|||
|
// Convert ws fire direction into local ship space
|
|||
|
weapon.fireDirection = trfmInvRot * (aimAtWorldSpacePosition - weaponWorldBasePosition);
|
|||
|
weapon.Initialise(trfmInvRot);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns whether a weapon has line of sight to the weapon's specified target (i.e. weapon.target).
|
|||
|
/// If directToTarget is set to true, will raycast directly from the weapon to the target.
|
|||
|
/// If directToTarget is set to false, will raycast in the direction the weapon is facing.
|
|||
|
/// This method will return true if the raycast hits:
|
|||
|
/// a) The weapon's specified target,
|
|||
|
/// b) An enemy ship (distinguished by faction ID) - even if it is not the target and anyEnemy is true,
|
|||
|
/// c) An object that isn't the target (if obstaclesBlockLineOfSight is set to false),
|
|||
|
/// d) Nothing.
|
|||
|
/// This method will return false if the raycast hits:
|
|||
|
/// a) A friendly ship (distinguished by faction ID),
|
|||
|
/// b) An object that isn't the target (if obstaclesBlockLineOfSight is set to true).
|
|||
|
/// c) An enemy ship that is not the target when anyEnemy is false
|
|||
|
/// </summary>
|
|||
|
/// <param name="weapon"></param>
|
|||
|
/// <param name="directToTarget"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool WeaponHasLineOfSight (Weapon weapon, bool directToTarget = false, bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
|
|||
|
{
|
|||
|
return WeaponHasLineOfSight(weapon, weapon.target, directToTarget, obstaclesBlockLineOfSight, anyEnemy);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns whether a weapon has line of sight to a target.
|
|||
|
/// If directToTarget is set to true, will raycast directly from the weapon to the target.
|
|||
|
/// If directToTarget is set to false, will raycast in the direction the weapon is facing.
|
|||
|
/// This method will return true if the raycast hits:
|
|||
|
/// a) The target,
|
|||
|
/// b) An enemy ship (distingushed by faction ID) - even if it is not the target and anyEnemy is true,
|
|||
|
/// c) An object that isn't the target (if obstaclesBlockLineOfSight is set to false),
|
|||
|
/// d) Nothing.
|
|||
|
/// This method will return false if the raycast hits:
|
|||
|
/// a) A friendly ship (distinguished by faction ID),
|
|||
|
/// b) An object that isn't the target (if obstaclesBlockLineOfSight is set to true).
|
|||
|
/// c) An enemy ship that is not the target when anyEnemy is false
|
|||
|
/// </summary>
|
|||
|
/// <param name="weapon"></param>
|
|||
|
/// <param name="target"></param>
|
|||
|
/// <param name="directToTarget"></param>
|
|||
|
/// <returns></returns>
|
|||
|
public bool WeaponHasLineOfSight(Weapon weapon, GameObject target, bool directToTarget = false, bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
|
|||
|
{
|
|||
|
// Update the line-of-sight property
|
|||
|
weapon.UpdateLineOfSight(target, trfmPos, trfmRight, trfmUp, trfmFwd, trfmInvRot, factionId,
|
|||
|
false, directToTarget, obstaclesBlockLineOfSight, anyEnemy);
|
|||
|
|
|||
|
// Return the updated property
|
|||
|
return weapon.HasLineOfSight;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|