1353 lines
64 KiB
C#
1353 lines
64 KiB
C#
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
|
|||
|
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
|
|||
|
namespace SciFiShipController
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Component to control the docking and undocking of ships on larger ships (motherships)
|
|||
|
/// or stationary objects like hangars. Works with ShipDockingStation.
|
|||
|
/// </summary>
|
|||
|
[AddComponentMenu("Sci-Fi Ship Controller/Docking Components/Ship Docking")]
|
|||
|
[HelpURL("http://scsmmedia.com/ssc-documentation")]
|
|||
|
[RequireComponent(typeof(ShipControlModule))]
|
|||
|
public class ShipDocking : MonoBehaviour
|
|||
|
{
|
|||
|
#region Enumerations
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The possible states of a ship which supports docking.
|
|||
|
/// </summary>
|
|||
|
public enum DockingState
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Ship is not docked and can fly around unhindered
|
|||
|
/// </summary>
|
|||
|
NotDocked = 0,
|
|||
|
/// <summary>
|
|||
|
/// Ship is currently attempting to dock at a docking point on a Ship Docking Station.
|
|||
|
/// It may be assigned to a docking point Entry Path.
|
|||
|
/// </summary>
|
|||
|
Docking = 1,
|
|||
|
/// <summary>
|
|||
|
/// Ship is currently attempting to depart from a docking point on a Ship Docking station.
|
|||
|
/// It may be assigned to a docking point Exit Path.
|
|||
|
/// </summary>
|
|||
|
Undocking = 2,
|
|||
|
/// <summary>
|
|||
|
/// Ship is docked at a docking point on a Ship Docking Station. Typically it wll be set
|
|||
|
/// to kinematic.
|
|||
|
/// </summary>
|
|||
|
Docked = 3
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL USE ONLY]
|
|||
|
/// This is a subset of DockingState
|
|||
|
/// </summary>
|
|||
|
public enum InitialDockingState
|
|||
|
{
|
|||
|
NotDocked = 0,
|
|||
|
Docked = 3
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Static Properties
|
|||
|
|
|||
|
// variables to avoid enumeration lookups
|
|||
|
public static readonly int notDockedInt = (int)DockingState.NotDocked;
|
|||
|
public static readonly int dockingInt = (int)DockingState.Docking;
|
|||
|
public static readonly int undockingInt = (int)DockingState.Undocking;
|
|||
|
public static readonly int dockedInt = (int)DockingState.Docked;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Variables - General
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// If enabled, the Initialise() will be called as soon as Awake() runs. This should be disabled if you are
|
|||
|
/// instantiating the ShipDocking through code.
|
|||
|
/// </summary>
|
|||
|
public bool initialiseOnAwake = false;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Ships can start in a state of Docked or Undocked.
|
|||
|
/// </summary>
|
|||
|
public InitialDockingState initialDockingState = InitialDockingState.NotDocked;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// How close the ship has to be (in metres) to the docking position before it can become docked.
|
|||
|
/// </summary>
|
|||
|
public float landingDistancePrecision = 0.01f;
|
|||
|
|
|||
|
[Range(0.1f, 10f)]
|
|||
|
/// <summary>
|
|||
|
/// How close the ship has to be (in degrees) to the docking rotation before it can become docked.
|
|||
|
/// </summary>
|
|||
|
public float landingAnglePrecision = 2f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// How close the ship has to be (in metres) to the hovering position before it is deemed to have reached the hover position.
|
|||
|
/// </summary>
|
|||
|
public float hoverDistancePrecision = 1f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// How close the ship has to be (in degrees) to the hovering rotation before it is deemed to have reached the hover position.
|
|||
|
/// </summary>
|
|||
|
[Range(0.1f, 30f)]
|
|||
|
public float hoverAnglePrecision = 10f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Target time to lift off from the landing position and move to the hover position.
|
|||
|
/// Has no effect if the docking point hover height is 0.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 60f)]
|
|||
|
public float liftOffDuration = 2f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Target time to move from the hover position to the landing position.
|
|||
|
/// Has no effect if the docking point hover height is 0.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 60f)]
|
|||
|
public float landingDuration = 2f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Should physics collisions been detected when the state is Docked?
|
|||
|
/// </summary>
|
|||
|
public bool detectCollisionsWhenDocked = false;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When used with ShipDockingStation.UndockShip(..), the number of seconds
|
|||
|
/// that the undocking manoeuvre is delayed. This allows you to create cinematic
|
|||
|
/// effects or perform other actions, before the Undocking process begins.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 60f)] public float undockingDelay = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// When value is greater than 0, the number of seconds the ship waits while docked,
|
|||
|
/// before automatically attempting to start the undocking procedure.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 300f)] public float autoUndockTime = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This is additional velocity in an upwards direction relative to the mothership.
|
|||
|
/// </summary>
|
|||
|
public float undockVertVelocity = 2f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This is additional velocity in a forward direction relative to the mothership.
|
|||
|
/// </summary>
|
|||
|
public float undockFwdVelocity = 2f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The amount of force applied by the catapult when undocking in KiloNewtons.
|
|||
|
/// </summary>
|
|||
|
public float catapultThrust = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The number of seconds that the force is applied from the catapult to the ship
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float catapultDuration = 2f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// A list of ship docking adapters. These are points on the ship where
|
|||
|
/// it can dock with a ShipDockingPoint on a ShipDockingStation.
|
|||
|
/// Typically you should not be updating this list yourself.
|
|||
|
/// </summary>
|
|||
|
public List<ShipDockingAdapter> adapterList;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL ONLY]
|
|||
|
/// </summary>
|
|||
|
[HideInInspector] public bool allowRepaint = false;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL ONLY]
|
|||
|
/// Is the adapter list expanded in the ShipDockingEditor?
|
|||
|
/// </summary>
|
|||
|
public bool isAdapterListExpanded = false;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Variables - Events
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Methods that get called immediately after the ship has finished docking.
|
|||
|
/// </summary>
|
|||
|
public SSCDockingEvt1 onPostDocked = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, to delay the actioning of any onPostDocked methods.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float onPostDockedDelay = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Methods that get called immediately after the Hover point is reached when docking.
|
|||
|
/// Typically used to perform a non-docking API action like lowering landing gear,
|
|||
|
/// disabling radar, disarming weapons etc.
|
|||
|
/// WARNING: Be careful not to call other docking APIs that might create a circular loop.
|
|||
|
/// </summary>
|
|||
|
public SSCDockingEvt1 onPostDockingHover = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, to delay the actioning of any onPostDockingHover methods.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float onPostDockingHoverDelay = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Methods that get called immediately after docking has started.
|
|||
|
/// Typically used to perform a non-docking API action like chatter with ground staff,
|
|||
|
/// disabling radar, preparing for landing etc.
|
|||
|
/// WARNING: Be careful not to call other docking APIs that might create a circular loop.
|
|||
|
/// </summary>
|
|||
|
public SSCDockingEvt1 onPostDockingStart = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, to delay the actioning of any onPostDockingStart methods.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float onPostDockingStartDelay = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Methods that get called immediately after the ship has finished undocking.
|
|||
|
/// </summary>
|
|||
|
public SSCDockingEvt1 onPostUndocked = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, to delay the actioning of any onPostUndocked methods.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float onPostUndockedDelay = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Methods that get called immediately after the Hover point is reached when undocking.
|
|||
|
/// Typically used to perform a non-docking API action like raising landing gear, enabling
|
|||
|
/// radar, arming weapons etc.
|
|||
|
/// WARNING: Be careful not to call other docking APIs that might create a circular loop.
|
|||
|
/// </summary>
|
|||
|
public SSCDockingEvt1 onPostUndockingHover = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, to delay the actioning of any onPostUndockingHover methods.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float onPostUndockingHoverDelay = 0f;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Methods that get called immediately after undocking has started.
|
|||
|
/// Typically used to perform a non-docking API action like dust or steam particle effects
|
|||
|
/// or opening hanger doors.
|
|||
|
/// WARNING: Be careful not to call other docking APIs that might create a circular loop.
|
|||
|
/// </summary>
|
|||
|
public SSCDockingEvt1 onPostUndockingStart = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The time, in seconds, to delay the actioning of any onPostUndockingStart methods.
|
|||
|
/// </summary>
|
|||
|
[Range(0f, 30f)] public float onPostUndockingStartDelay = 0f;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Properties
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READ ONLY] The ID or the docking point on the shipDockingStation.
|
|||
|
/// To set, call shipDockingStation.AssignShipToDockingPoint(..).
|
|||
|
/// Internally uses the index in the list of docking points - however,
|
|||
|
/// this is subject to change.
|
|||
|
/// </summary>
|
|||
|
public int DockingPointId { get; internal set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Typically used for debugging, is the Hover Point the target?
|
|||
|
/// </summary>
|
|||
|
public bool IsHoverPointTarget { get { return isHoverTarget; } }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Is the docking component initialised?
|
|||
|
/// </summary>
|
|||
|
public bool IsInitialised { get; private set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [READ ONLY] The docking station the ship may be docked with.
|
|||
|
/// To set, call shipDockingStation.AssignShipToDockingPoint(..)
|
|||
|
/// </summary>
|
|||
|
public ShipDockingStation shipDockingStation { get; internal set; }
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public Delegates
|
|||
|
|
|||
|
public delegate void CallbackOnStateChange(ShipDocking shipDocking, ShipControlModule shipControlModule, ShipAIInputModule shipAIInputModule, DockingState previousDockingState);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The name of the custom method that is called immediately after the state is changed.
|
|||
|
/// Your method must take 4 parameters: shipDocking (never null), shipControlModule (never null),
|
|||
|
/// shipAIInputModule (could be null) and previousDockingState.
|
|||
|
/// This should be a lightweight method to avoid performance issues.
|
|||
|
/// Your method will NOT be called if ShipDocking.IsInitialised is false.
|
|||
|
/// </summary>
|
|||
|
public CallbackOnStateChange callbackOnStateChange = null;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private variables
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// [INTERNAL ONLY]
|
|||
|
/// Remember which tabs etc were shown in the editor
|
|||
|
/// </summary>
|
|||
|
[SerializeField] private int selectedTabInt = 0;
|
|||
|
|
|||
|
private ShipControlModule shipControlModule;
|
|||
|
private ShipAIInputModule shipAIInputModule;
|
|||
|
private bool isShipInitialised = false;
|
|||
|
internal ShipControlModule dockWithShip;
|
|||
|
private SSCManager sscManager;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The current state of the ship
|
|||
|
/// </summary>
|
|||
|
private DockingState dockingState = DockingState.NotDocked;
|
|||
|
private int dockingStateInt = 0;
|
|||
|
|
|||
|
// TODO: CHECK USAGE
|
|||
|
private Vector3 dockedRelativePosition;
|
|||
|
private Quaternion dockedRelativeRotation;
|
|||
|
|
|||
|
internal RigidbodyInterpolation originalRBInterpolation = RigidbodyInterpolation.None;
|
|||
|
internal bool isOriginalRBInterpolationSet = false;
|
|||
|
|
|||
|
private Vector3 targetDockingPosition = Vector3.zero;
|
|||
|
private Quaternion targetDockingRotation = Quaternion.identity;
|
|||
|
|
|||
|
private bool isInAIDockingState = false;
|
|||
|
private bool isHoverTarget = false;
|
|||
|
private float dockingActionCompletedDistance = 1f;
|
|||
|
private float dockingActionCompletedAngle = 1f;
|
|||
|
|
|||
|
private float autoUndockTimer = 0f;
|
|||
|
|
|||
|
private ShipAIInputModule.CallbackCompletedStateAction originalCompletedStateActionCallback = null;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Initialisation Methods
|
|||
|
|
|||
|
void Awake()
|
|||
|
{
|
|||
|
if (initialiseOnAwake) { Initialise(); }
|
|||
|
}
|
|||
|
|
|||
|
public void Initialise()
|
|||
|
{
|
|||
|
if (!IsInitialised)
|
|||
|
{
|
|||
|
shipControlModule = GetComponent<ShipControlModule>();
|
|||
|
if (shipControlModule != null)
|
|||
|
{
|
|||
|
shipAIInputModule = GetComponent<ShipAIInputModule>();
|
|||
|
|
|||
|
// cache to avoid having to check for null etc in FixedUpdate
|
|||
|
isShipInitialised = shipControlModule.IsInitialised;
|
|||
|
}
|
|||
|
|
|||
|
// Add capacity for 1 docking adapter as this is the current default.
|
|||
|
if (adapterList == null) { adapterList = new List<ShipDockingAdapter>(1); }
|
|||
|
if (adapterList != null && adapterList.Count == 0)
|
|||
|
{
|
|||
|
adapterList.Add(new ShipDockingAdapter());
|
|||
|
}
|
|||
|
|
|||
|
// Keep compiler happy
|
|||
|
if (selectedTabInt < 0) { }
|
|||
|
|
|||
|
IsInitialised = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Event Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Automatically called by Unity immediately before the object is destroyed
|
|||
|
/// </summary>
|
|||
|
private void OnDestroy()
|
|||
|
{
|
|||
|
RemoveListeners();
|
|||
|
CancelInvoke();
|
|||
|
StopAllCoroutines();
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Update Methods
|
|||
|
|
|||
|
private void Update ()
|
|||
|
{
|
|||
|
if (isShipInitialised)
|
|||
|
{
|
|||
|
// Is docked?
|
|||
|
if (dockingStateInt == dockedInt)
|
|||
|
{
|
|||
|
// IsKinematic should be enabled because ship is disabled
|
|||
|
if (shipControlModule.ShipRigidbody.isKinematic)
|
|||
|
{
|
|||
|
if (dockWithShip != null && dockWithShip.ShipIsEnabled())
|
|||
|
{
|
|||
|
// Translate the relative local-space offset of the docked ship
|
|||
|
// Update the rotation of the docked ship by adding the local rotation of the docked ship relative to the mother ship.
|
|||
|
transform.SetPositionAndRotation(
|
|||
|
dockWithShip.transform.position + dockWithShip.transform.TransformDirection(dockedRelativePosition),
|
|||
|
dockWithShip.transform.rotation * dockedRelativeRotation);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Is this a moving ship docking station?
|
|||
|
if (isInAIDockingState && shipDockingStation != null && shipDockingStation.IsMotherShip)
|
|||
|
{
|
|||
|
UpdateDockingWSPositionAndRotation(shipDockingStation.GetDockingPoint(DockingPointId),
|
|||
|
shipAIInputModule, isHoverTarget, dockingActionCompletedDistance, dockingActionCompletedAngle);
|
|||
|
}
|
|||
|
|
|||
|
// Auto undocking - do last
|
|||
|
// Check if auto undock is active (gets activated at end of SetState(..)
|
|||
|
if (autoUndockTime > 0f && dockingStateInt == dockedInt && autoUndockTimer > 0f)
|
|||
|
{
|
|||
|
autoUndockTimer += Time.deltaTime;
|
|||
|
|
|||
|
if (autoUndockTimer > autoUndockTime)
|
|||
|
{
|
|||
|
// Reset time (auto undock is not active)
|
|||
|
autoUndockTimer = 0f;
|
|||
|
|
|||
|
shipDockingStation.UnDockShip(shipControlModule);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Methods
|
|||
|
|
|||
|
private bool IsShipDocked()
|
|||
|
{
|
|||
|
if (dockingStateInt == dockedInt)
|
|||
|
{
|
|||
|
// May be other conditions...
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
else { return false; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Assign a new path to an AI Ship.
|
|||
|
/// If there is no matching path, then this will return false.
|
|||
|
/// </summary>
|
|||
|
/// <param name="pathGUIDHash"></param>
|
|||
|
private bool AssignNewPath(int pathGUIDHash)
|
|||
|
{
|
|||
|
bool isAssigned = false;
|
|||
|
|
|||
|
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
|
|||
|
if (sscManager != null)
|
|||
|
{
|
|||
|
PathData pathData = sscManager.GetPath(pathGUIDHash);
|
|||
|
|
|||
|
if (pathData != null)
|
|||
|
{
|
|||
|
// Set the ship's state to the "move to" state
|
|||
|
shipAIInputModule.SetState(AIState.moveToStateID);
|
|||
|
// Set the target path to the new path
|
|||
|
shipAIInputModule.AssignTargetPath(pathData);
|
|||
|
isAssigned = true;
|
|||
|
}
|
|||
|
}
|
|||
|
return isAssigned;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Invoke any methods configured in the editor (persistent) or with AddListener (non-persistent)
|
|||
|
/// after the delayTime in seconds.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingStationID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="dockingPointId"></param>
|
|||
|
/// <param name="delayTime"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private IEnumerator OnPostDockedDelayed (int shipDockingStationID, int shipId, int dockingPointId, float delayTime)
|
|||
|
{
|
|||
|
yield return new WaitForSeconds(delayTime);
|
|||
|
|
|||
|
// These will ALWAYS fire although events are checked before calling this method.
|
|||
|
if (onPostDocked != null)
|
|||
|
{
|
|||
|
onPostDocked.Invoke(shipDockingStationID, shipId, dockingPointId, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Invoke any methods configured in the editor (persistent) or with AddListener (non-persistent)
|
|||
|
/// after the delayTime in seconds.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingStationID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="dockingPointId"></param>
|
|||
|
/// <param name="delayTime"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private IEnumerator OnPostDockingHoverDelayed (int shipDockingStationID, int shipId, int dockingPointId, float delayTime)
|
|||
|
{
|
|||
|
yield return new WaitForSeconds(delayTime);
|
|||
|
|
|||
|
// These will ALWAYS fire although events are checked before calling this method.
|
|||
|
if (onPostDockingHover != null)
|
|||
|
{
|
|||
|
onPostDockingHover.Invoke(shipDockingStationID, shipId, dockingPointId, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Invoke any methods configured in the editor (persistent) or with AddListener (non-persistent)
|
|||
|
/// after the delayTime in seconds.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingStationID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="dockingPointId"></param>
|
|||
|
/// <param name="delayTime"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private IEnumerator OnPostDockingStartDelayed (int shipDockingStationID, int shipId, int dockingPointId, float delayTime)
|
|||
|
{
|
|||
|
yield return new WaitForSeconds(delayTime);
|
|||
|
|
|||
|
// These will ALWAYS fire although events are checked before calling this method.
|
|||
|
if (onPostDockingStart != null)
|
|||
|
{
|
|||
|
onPostDockingStart.Invoke(shipDockingStationID, shipId, dockingPointId, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Invoke any methods configured in the editor (persistent) or with AddListener (non-persistent)
|
|||
|
/// after the delayTime in seconds.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingStationID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="dockingPointId"></param>
|
|||
|
/// <param name="delayTime"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private IEnumerator OnPostUndockedDelayed (int shipDockingStationID, int shipId, int dockingPointId, float delayTime)
|
|||
|
{
|
|||
|
yield return new WaitForSeconds(delayTime);
|
|||
|
|
|||
|
// These will ALWAYS fire although events are checked before calling this method.
|
|||
|
if (onPostUndocked != null)
|
|||
|
{
|
|||
|
onPostUndocked.Invoke(shipDockingStationID, shipId, dockingPointId, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Invoke any methods configured in the editor (persistent) or with AddListener (non-persistent)
|
|||
|
/// after the delayTime in seconds.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingStationID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="dockingPointId"></param>
|
|||
|
/// <param name="delayTime"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private IEnumerator OnPostUndockingHoverDelayed (int shipDockingStationID, int shipId, int dockingPointId, float delayTime)
|
|||
|
{
|
|||
|
yield return new WaitForSeconds(delayTime);
|
|||
|
|
|||
|
// These will ALWAYS fire although events are checked before calling this method.
|
|||
|
if (onPostUndockingHover != null)
|
|||
|
{
|
|||
|
onPostUndockingHover.Invoke(shipDockingStationID, shipId, dockingPointId, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Invoke any methods configured in the editor (persistent) or with AddListener (non-persistent)
|
|||
|
/// after the delayTime in seconds.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingStationID"></param>
|
|||
|
/// <param name="shipId"></param>
|
|||
|
/// <param name="dockingPointId"></param>
|
|||
|
/// <param name="delayTime"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private IEnumerator OnPostUndockingStartDelayed (int shipDockingStationID, int shipId, int dockingPointId, float delayTime)
|
|||
|
{
|
|||
|
yield return new WaitForSeconds(delayTime);
|
|||
|
|
|||
|
// These will ALWAYS fire although events are checked before calling this method.
|
|||
|
if (onPostUndockingStart != null)
|
|||
|
{
|
|||
|
onPostUndockingStart.Invoke(shipDockingStationID, shipId, dockingPointId, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Calculates the world space docking position and rotation for an AI ship at a given docking point.
|
|||
|
/// If hoverPosition is true, it will calculate the offset hover position.
|
|||
|
/// Then it updates the ship AI Input module with this information.
|
|||
|
/// Important: It does not set the docking state.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingPoint"></param>
|
|||
|
/// <param name="shipAIInputModule"></param>
|
|||
|
/// <param name="hoverPosition"></param>
|
|||
|
/// <param name="actionCompletedDistance"></param>
|
|||
|
private void UpdateDockingWSPositionAndRotation (ShipDockingPoint shipDockingPoint, ShipAIInputModule shipAIInputModule,
|
|||
|
bool hoverPosition, float actionCompletedDistance, float actionCompletedAngularDistance)
|
|||
|
{
|
|||
|
if (shipDockingPoint != null)
|
|||
|
{
|
|||
|
Vector3 targetDockingPos = Vector3.zero;
|
|||
|
Quaternion targetDockingRot = Quaternion.identity;
|
|||
|
|
|||
|
// Calculate the world space docking position and rotation
|
|||
|
CalculateDockingWSPositionAndRotation(shipDockingPoint, hoverPosition, ref targetDockingPos, ref targetDockingRot);
|
|||
|
|
|||
|
// Assign target information to AI ship
|
|||
|
AssignDockingWSPositionAndRotation(shipAIInputModule, targetDockingPos, targetDockingRot,
|
|||
|
shipDockingPoint, actionCompletedDistance, actionCompletedAngularDistance);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Calculates the world space docking position and rotation for an AI ship at a given docking point.
|
|||
|
/// If hoverPosition is true, it will calculate the offset hover position.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipDockingPoint"></param>
|
|||
|
/// <param name="hoverPosition"></param>
|
|||
|
/// <param name="targetDockingPos"></param>
|
|||
|
/// <param name="targetDockingRot"></param>
|
|||
|
private void CalculateDockingWSPositionAndRotation (ShipDockingPoint shipDockingPoint,
|
|||
|
bool hoverPosition, ref Vector3 targetDockingPos, ref Quaternion targetDockingRot)
|
|||
|
{
|
|||
|
// Target docking position calculation:
|
|||
|
// Start with the world space position of the docking point
|
|||
|
targetDockingPos = shipDockingStation.GetDockingPointPositionWS(shipDockingPoint);
|
|||
|
// If required, offset the position by a hover distance
|
|||
|
if (hoverPosition)
|
|||
|
{
|
|||
|
targetDockingPos += shipDockingStation.GetDockingPointRotation(shipDockingPoint) * Vector3.up * shipDockingPoint.hoverHeight;
|
|||
|
}
|
|||
|
|
|||
|
// Calculate the target rotation of the ship relative to the docking point
|
|||
|
Vector3 adapterDirection = adapterList[0].relativeDirection;
|
|||
|
float XYPlaneLength = Mathf.Sqrt((adapterDirection.x * adapterDirection.x) + (adapterDirection.y * adapterDirection.y));
|
|||
|
// Idea is that we first rotate the ship around the z-axis to get the XY direction pointing downwards, then
|
|||
|
// we rotate around the x-axis to get the YZ direction pointing downwards
|
|||
|
Quaternion shipRelativeRotation = Quaternion.Euler(Mathf.Atan2(adapterDirection.z, XYPlaneLength) * Mathf.Rad2Deg, 0f,
|
|||
|
-Mathf.Atan2(adapterDirection.x, -adapterDirection.y) * Mathf.Rad2Deg);
|
|||
|
// For special case of directly forwards direction, flip upside down to give more natural direction
|
|||
|
if ((adapterDirection.x > 0f ? adapterDirection.x : -adapterDirection.x) < 0.001f &&
|
|||
|
(adapterDirection.y > 0f ? adapterDirection.y : -adapterDirection.y) < 0.001f &&
|
|||
|
adapterDirection.z > 0.001f)
|
|||
|
{
|
|||
|
shipRelativeRotation *= Quaternion.Euler(0f, 0f, 180f);
|
|||
|
}
|
|||
|
|
|||
|
// Target docking rotation calculation:
|
|||
|
// Start with the world space rotation of the docking point
|
|||
|
targetDockingRot = shipDockingStation.GetDockingPointRotation(shipDockingPoint);
|
|||
|
// Then add the rotation of the ship relative to the docking point
|
|||
|
targetDockingRot *= shipRelativeRotation;
|
|||
|
|
|||
|
// Subtract the adapter relative position (in world space), so that the adapter point will line up with the docking point
|
|||
|
targetDockingPos -= targetDockingRot * adapterList[0].relativePosition;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Assigns a target position, rotation and radius to an AI ship in the docking AI state.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipAIInputModule"></param>
|
|||
|
/// <param name="targetDockingPos"></param>
|
|||
|
/// <param name="targetDockingRot"></param>
|
|||
|
/// <param name="shipDockingPoint"></param>
|
|||
|
/// <param name="actionCompletedDistance"></param>
|
|||
|
/// <param name="actionCompletedAngularDistance"></param>
|
|||
|
private void AssignDockingWSPositionAndRotation (ShipAIInputModule shipAIInputModule, Vector3 targetDockingPos,
|
|||
|
Quaternion targetDockingRot, ShipDockingPoint shipDockingPoint, float actionCompletedDistance,
|
|||
|
float actionCompletedAngularDistance)
|
|||
|
{
|
|||
|
// Assign target position, rotation and radius data to AI ship
|
|||
|
shipAIInputModule.AssignTargetPosition(targetDockingPos);
|
|||
|
shipAIInputModule.AssignTargetRotation(targetDockingRot);
|
|||
|
// Target radius must be at least 1 metre
|
|||
|
shipAIInputModule.AssignTargetRadius(shipDockingPoint.hoverHeight > 1f ? shipDockingPoint.hoverHeight : 1f);
|
|||
|
shipAIInputModule.AssignTargetDistance(actionCompletedDistance);
|
|||
|
shipAIInputModule.AssignTargetAngularDistance(actionCompletedAngularDistance);
|
|||
|
shipAIInputModule.AssignTargetVelocity(shipDockingStation.GetStationVelocity());
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Try to join an entry or exit path at the closest point to the ship
|
|||
|
/// </summary>
|
|||
|
/// <param name="guidHashPath"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private bool TryJointPathAtClosestPoint(int guidHashPath)
|
|||
|
{
|
|||
|
bool isJoinedPath = false;
|
|||
|
|
|||
|
// Attempt ot join exit path (if there is one)
|
|||
|
if (AssignNewPath(guidHashPath))
|
|||
|
{
|
|||
|
// Find the closest point on the exit path
|
|||
|
PathData pathData = sscManager.GetPath(guidHashPath);
|
|||
|
|
|||
|
if (pathData != null)
|
|||
|
{
|
|||
|
Vector3 closestPointOnPath = Vector3.zero;
|
|||
|
float closestPointOnPathTValue = 0f;
|
|||
|
int prevPathLocationIdx = 0;
|
|||
|
|
|||
|
if (SSCMath.FindClosestPointOnPath(pathData, shipControlModule.shipInstance.TransformPosition, ref closestPointOnPath, ref closestPointOnPathTValue, ref prevPathLocationIdx))
|
|||
|
{
|
|||
|
shipAIInputModule.SetPreviousTargetPathLocationIndex(prevPathLocationIdx);
|
|||
|
int nextExitPathLocationIndex = SSCManager.GetNextPathLocationIndex(pathData, prevPathLocationIdx, pathData.isClosedCircuit);
|
|||
|
|
|||
|
if (nextExitPathLocationIndex >= 0)
|
|||
|
{
|
|||
|
shipAIInputModule.SetCurrentTargetPathLocationIndex(nextExitPathLocationIndex, closestPointOnPathTValue);
|
|||
|
shipAIInputModule.SetPreviousTargetPathLocationIndex(prevPathLocationIdx);
|
|||
|
isJoinedPath = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return isJoinedPath;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Internal Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// If it has not already been saved (set), record
|
|||
|
/// the original rigidbody interpolation setting
|
|||
|
/// of the ship this component is attached to.
|
|||
|
/// </summary>
|
|||
|
internal void SaveInterpolation()
|
|||
|
{
|
|||
|
if (!isOriginalRBInterpolationSet)
|
|||
|
{
|
|||
|
// If the ship is initialised use the cached rigidbody, else fetch it
|
|||
|
Rigidbody rbShip = isShipInitialised ? shipControlModule.ShipRigidbody : shipControlModule.GetComponent<Rigidbody>();
|
|||
|
|
|||
|
// Remember the interpolation setting on the ship
|
|||
|
if (rbShip != null)
|
|||
|
{
|
|||
|
originalRBInterpolation = rbShip.interpolation;
|
|||
|
isOriginalRBInterpolationSet = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This can be used when another feature triggers undocking. It ensures
|
|||
|
/// that if the countdown has begun it doesn't continue.
|
|||
|
/// </summary>
|
|||
|
internal void ResetAutoUndock()
|
|||
|
{
|
|||
|
autoUndockTimer = 0f;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Internal Callback Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Callback for when the ship AI has completed a state action.
|
|||
|
/// </summary>
|
|||
|
/// <param name="shipAIInputModule"></param>
|
|||
|
internal void AICompletedStateActionCallback(ShipAIInputModule shipAIInputModule)
|
|||
|
{
|
|||
|
if (shipAIInputModule != null && shipAIInputModule.IsInitialised)
|
|||
|
{
|
|||
|
// By default, not in AI docking state
|
|||
|
isInAIDockingState = false;
|
|||
|
|
|||
|
dockingStateInt = (int)dockingState;
|
|||
|
int shipAIStateID = shipAIInputModule.GetState();
|
|||
|
|
|||
|
#region If Docking
|
|||
|
if (dockingStateInt == dockingInt)
|
|||
|
{
|
|||
|
ShipDockingPoint shipDockingPoint = shipDockingStation.GetDockingPoint(DockingPointId);
|
|||
|
|
|||
|
if (shipAIStateID == AIState.dockingStateID)
|
|||
|
{
|
|||
|
// Calculate the world space docking position and rotation for the landing position
|
|||
|
Vector3 targetDockingPos = Vector3.zero;
|
|||
|
Quaternion targetDockingRot = Quaternion.identity;
|
|||
|
CalculateDockingWSPositionAndRotation(shipDockingPoint, false, ref targetDockingPos, ref targetDockingRot);
|
|||
|
|
|||
|
// Check if we have reached the landing position
|
|||
|
if (Vector3.SqrMagnitude(shipAIInputModule.GetTargetPosition() - targetDockingPos) < landingDistancePrecision * landingDistancePrecision)
|
|||
|
{
|
|||
|
// We have now finished the landing manoeuvre, set the state to docked
|
|||
|
SetState(DockingState.Docked);
|
|||
|
// Clean up - set the callback for CompletedStateAction back to its original value
|
|||
|
shipAIInputModule.callbackCompletedStateAction = originalCompletedStateActionCallback;
|
|||
|
shipAIInputModule.SetState(AIState.idleStateID);
|
|||
|
|
|||
|
// If there are Post Docked event peristent AND/OR non-persistent listeners.
|
|||
|
if (SSCUtils.HasListeners(onPostDocked))
|
|||
|
{
|
|||
|
if (onPostDockedDelay > 0f)
|
|||
|
{
|
|||
|
StartCoroutine(OnPostDockedDelayed(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, onPostDockingHoverDelay));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
onPostDocked.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// We have now finished the hover manoeuvre
|
|||
|
// Now we need to start the landing manoeuvre
|
|||
|
|
|||
|
// Reset the state action completed flag
|
|||
|
shipAIInputModule.SetHasCompletedStateAction(false);
|
|||
|
|
|||
|
// Set the ship to target the landing position (we have already calculated it)
|
|||
|
// Action will be completed when we get with x metres of the target position
|
|||
|
dockingActionCompletedDistance = landingDistancePrecision;
|
|||
|
dockingActionCompletedAngle = landingAnglePrecision;
|
|||
|
AssignDockingWSPositionAndRotation(shipAIInputModule, targetDockingPos, targetDockingRot,
|
|||
|
shipDockingPoint, dockingActionCompletedDistance, dockingActionCompletedAngle);
|
|||
|
|
|||
|
shipAIInputModule.AssignTargetTime(landingDuration);
|
|||
|
isInAIDockingState = true;
|
|||
|
isHoverTarget = false;
|
|||
|
|
|||
|
// If there are Post Docking Hover event peristent AND/OR non-persistent listeners.
|
|||
|
if (SSCUtils.HasListeners(onPostDockingHover))
|
|||
|
{
|
|||
|
if (onPostDockingHoverDelay > 0f)
|
|||
|
{
|
|||
|
StartCoroutine(OnPostDockingHoverDelayed(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, onPostDockingHoverDelay));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
onPostDockingHover.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If we were docking, we have now reached the end of the entry path
|
|||
|
// Now we need to start the landing manoeuvre
|
|||
|
// Set the ship's state to the "docking" state
|
|||
|
shipAIInputModule.SetState(AIState.dockingStateID);
|
|||
|
|
|||
|
// Set the ship to target the hover position
|
|||
|
// Action will be completed when we get with hover distance & angle of the target (hover) position
|
|||
|
dockingActionCompletedDistance = hoverDistancePrecision;
|
|||
|
dockingActionCompletedAngle = hoverAnglePrecision;
|
|||
|
UpdateDockingWSPositionAndRotation(shipDockingPoint, shipAIInputModule, true,
|
|||
|
dockingActionCompletedDistance, dockingActionCompletedAngle);
|
|||
|
|
|||
|
shipAIInputModule.AssignTargetTime(landingDuration);
|
|||
|
|
|||
|
isInAIDockingState = true;
|
|||
|
isHoverTarget = true;
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Else Undocking
|
|||
|
else if (dockingStateInt == undockingInt)
|
|||
|
{
|
|||
|
if (shipAIStateID == AIState.dockingStateID)
|
|||
|
{
|
|||
|
// If we were undocking, we have now finished the liftoff manoeuvre
|
|||
|
// We need to transition to following the exit path
|
|||
|
ShipDockingPoint shipDockingPoint = shipDockingStation.GetDockingPoint(DockingPointId);
|
|||
|
if (shipDockingPoint != null)
|
|||
|
{
|
|||
|
isHoverTarget = false;
|
|||
|
if (!AssignNewPath(shipDockingPoint.guidHashExitPath))
|
|||
|
{
|
|||
|
// If no valid exit path, immediately finish undocking manoeuvre
|
|||
|
SetState(DockingState.NotDocked);
|
|||
|
// Clean up - set the callback for CompletedStateAction back to its original value
|
|||
|
shipAIInputModule.callbackCompletedStateAction = originalCompletedStateActionCallback;
|
|||
|
shipAIInputModule.SetState(AIState.idleStateID);
|
|||
|
// Call the callback to let the script know that it needs to take action
|
|||
|
if (originalCompletedStateActionCallback != null) { originalCompletedStateActionCallback(shipAIInputModule); }
|
|||
|
}
|
|||
|
// Catapult launch for AI or AI assisted with an Exit path once hover height has been reached
|
|||
|
else if (catapultThrust > 0f)
|
|||
|
{
|
|||
|
shipControlModule.shipInstance.AddBoost(Vector3.forward, catapultThrust, catapultDuration);
|
|||
|
}
|
|||
|
|
|||
|
// If there is a Post Undocking Hover event peristent AND/OR non-persistent listeners.
|
|||
|
if (SSCUtils.HasListeners(onPostUndockingHover))
|
|||
|
{
|
|||
|
if (onPostUndockingHoverDelay > 0f)
|
|||
|
{
|
|||
|
StartCoroutine(OnPostUndockingHoverDelayed(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, onPostUndockingHoverDelay));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
onPostUndockingHover.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If we were undocking, we have now reached the end of the exit path
|
|||
|
// Set the state to not docked
|
|||
|
SetState(DockingState.NotDocked);
|
|||
|
// Clean up - set the callback for CompletedStateAction back to its original value
|
|||
|
shipAIInputModule.callbackCompletedStateAction = originalCompletedStateActionCallback;
|
|||
|
// v1.2.3+ Undocking AI ships become idle at end of exit path
|
|||
|
shipAIInputModule.SetState(AIState.idleStateID);
|
|||
|
// Call the callback to let the script know that it needs to take action
|
|||
|
if (originalCompletedStateActionCallback != null) { originalCompletedStateActionCallback(shipAIInputModule); }
|
|||
|
|
|||
|
// If there any Post Undocked event peristent AND/OR non-persistent listeners.
|
|||
|
if (SSCUtils.HasListeners(onPostUndocked))
|
|||
|
{
|
|||
|
if (onPostUndockedDelay > 0f)
|
|||
|
{
|
|||
|
StartCoroutine(OnPostUndockedDelayed(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, onPostDockingHoverDelay));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
onPostUndocked.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public API Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Return the current docking state
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public DockingState GetState()
|
|||
|
{
|
|||
|
return dockingState;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Return the current docking state as an integer.
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public int GetStateInt()
|
|||
|
{
|
|||
|
return dockingStateInt;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Call this when you wish to remove any custom (non-persistent) event listeners,
|
|||
|
/// like after creating them in code and then destroying the object.
|
|||
|
/// This is automatically called by OnDestroy.
|
|||
|
/// </summary>
|
|||
|
public void RemoveListeners()
|
|||
|
{
|
|||
|
if (IsInitialised)
|
|||
|
{
|
|||
|
if (onPostDocked != null) { onPostDocked.RemoveAllListeners(); }
|
|||
|
if (onPostDockingHover != null) { onPostDockingHover.RemoveAllListeners(); }
|
|||
|
if (onPostDockingStart != null) { onPostDockingStart.RemoveAllListeners(); }
|
|||
|
if (onPostUndocked != null) { onPostUndocked.RemoveAllListeners(); }
|
|||
|
if (onPostUndockingHover != null) { onPostUndockingHover.RemoveAllListeners(); }
|
|||
|
if (onPostUndockingStart != null) { onPostUndockingStart.RemoveAllListeners(); }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Set the docking state of the ship.
|
|||
|
/// When undocking, the velocity of ShipDockingStation (mothership) is considered.
|
|||
|
/// If configured, a custom method is called after the state has been changed.
|
|||
|
/// </summary>
|
|||
|
/// <param name="dockingState"></param>
|
|||
|
public void SetState (DockingState dockingState)
|
|||
|
{
|
|||
|
if (!IsInitialised)
|
|||
|
{
|
|||
|
#if UNITY_EDITOR
|
|||
|
Debug.LogWarning("ERROR: ShipDocking.SetState was called before it was initialised on " + gameObject.name + ". Either set initialiseOnAwake in the Editor or call Initialise() at runtime.");
|
|||
|
#endif
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int previousDockingStateInt = dockingStateInt;
|
|||
|
|
|||
|
this.dockingState = dockingState;
|
|||
|
dockingStateInt = (int)dockingState;
|
|||
|
|
|||
|
// By default, not in AI docking state
|
|||
|
isInAIDockingState = false;
|
|||
|
|
|||
|
if (shipControlModule != null)
|
|||
|
{
|
|||
|
// Used for AI docking without a path
|
|||
|
bool immediatelyCompleteState = false;
|
|||
|
|
|||
|
// Ensure autoundocking is reset if the state is changed by another feature or API call.
|
|||
|
autoUndockTimer = 0f;
|
|||
|
|
|||
|
#region Docked
|
|||
|
if (dockingStateInt == dockedInt)
|
|||
|
{
|
|||
|
if (isShipInitialised && !shipControlModule.shipInstance.isThrusterFXStationary)
|
|||
|
{
|
|||
|
shipControlModule.StopThrusterEffects();
|
|||
|
}
|
|||
|
// NOTE: This will pause (but not stop) the thruster FX.
|
|||
|
shipControlModule.DisableShipMovement();
|
|||
|
// Check if we should override default behaviour after ship physics have been disabled
|
|||
|
if (detectCollisionsWhenDocked) { shipControlModule.ShipRigidbody.detectCollisions = true; }
|
|||
|
isHoverTarget = false;
|
|||
|
|
|||
|
if (shipDockingStation != null)
|
|||
|
{
|
|||
|
ShipDockingPoint shipDockingPoint = shipDockingStation.GetDockingPoint(DockingPointId);
|
|||
|
|
|||
|
if (shipDockingPoint != null)
|
|||
|
{
|
|||
|
// NOTE: The scaled relative position hasn't been tested with moving motherships.
|
|||
|
dockedRelativePosition = shipDockingStation.GetScaledRelativePosition(shipDockingPoint);
|
|||
|
dockedRelativeRotation = Quaternion.Euler(shipDockingPoint.relativeRotation);
|
|||
|
|
|||
|
// Set the initial position and rotation of the ship - snap to the docking point
|
|||
|
CalculateDockingWSPositionAndRotation(shipDockingPoint, false, ref targetDockingPosition, ref targetDockingRotation);
|
|||
|
transform.SetPositionAndRotation(targetDockingPosition, targetDockingRotation);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// If the ship is initialised use the cached rigidbody, else fetch it
|
|||
|
Rigidbody rbShip = isShipInitialised ? shipControlModule.ShipRigidbody : shipControlModule.GetComponent<Rigidbody>();
|
|||
|
|
|||
|
// Remember the interpolation setting on the docking ship
|
|||
|
if (rbShip != null && !isOriginalRBInterpolationSet)
|
|||
|
{
|
|||
|
originalRBInterpolation = rbShip.interpolation;
|
|||
|
isOriginalRBInterpolationSet = true;
|
|||
|
}
|
|||
|
|
|||
|
// Does this Docking Station have a mothership?
|
|||
|
if (dockWithShip != null)
|
|||
|
{
|
|||
|
// Where possible use the cached rigidbodies
|
|||
|
if (isShipInitialised && dockWithShip.IsInitialised)
|
|||
|
{
|
|||
|
// Match the interpolation setting on the ship being docked to, so that we avoid jerky behaviour
|
|||
|
shipControlModule.ShipRigidbody.interpolation = dockWithShip.ShipRigidbody.interpolation;
|
|||
|
}
|
|||
|
else if (rbShip != null)
|
|||
|
{
|
|||
|
// Match the interpolation setting on the ship being docked to, so that we avoid jerky behaviour
|
|||
|
Rigidbody rbShipToDockWith = dockWithShip.GetComponent<Rigidbody>();
|
|||
|
if (rbShipToDockWith != null) { rbShip.interpolation = rbShipToDockWith.interpolation; }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region NotDocked
|
|||
|
else if (dockingStateInt == notDockedInt)
|
|||
|
{
|
|||
|
// If previous state was docked, reset the velocity, else don't.
|
|||
|
shipControlModule.EnableShipMovement(previousDockingStateInt == dockedInt);
|
|||
|
|
|||
|
isHoverTarget = false;
|
|||
|
|
|||
|
// If previous state was docked, docking or undocking restore the original
|
|||
|
// rigidbody interpolation.
|
|||
|
if (previousDockingStateInt != notDockedInt)
|
|||
|
{
|
|||
|
// If the ship is initialised use the cached rigidbody, else fetch it
|
|||
|
Rigidbody rbShip = shipControlModule.IsInitialised ? shipControlModule.ShipRigidbody : shipControlModule.GetComponent<Rigidbody>();
|
|||
|
|
|||
|
// Restore the original rigidbody interpolation
|
|||
|
if (rbShip != null && isOriginalRBInterpolationSet)
|
|||
|
{
|
|||
|
rbShip.interpolation = originalRBInterpolation;
|
|||
|
isOriginalRBInterpolationSet = false;
|
|||
|
}
|
|||
|
|
|||
|
// Check for a mothership. i.e. a Docking Station on a Ship
|
|||
|
if (shipControlModule.IsInitialised && dockWithShip != null && dockWithShip.IsInitialised)
|
|||
|
{
|
|||
|
// Undock with the same velocity as the mothership
|
|||
|
|
|||
|
shipControlModule.ShipRigidbody.velocity = dockWithShip.ShipRigidbody.velocity + (dockWithShip.shipInstance.RigidbodyUp * undockVertVelocity) + (dockWithShip.shipInstance.RigidbodyForward * undockFwdVelocity)
|
|||
|
+ Vector3.Cross(dockWithShip.ShipRigidbody.angularVelocity, shipControlModule.ShipRigidbody.position - dockWithShip.transform.TransformPoint(dockWithShip.shipInstance.centreOfMass));
|
|||
|
shipControlModule.ShipRigidbody.angularVelocity = dockWithShip.ShipRigidbody.angularVelocity;
|
|||
|
}
|
|||
|
|
|||
|
// Check for catapult with non-AI assisted undocking OR AI-assist with no Exit Path
|
|||
|
if (previousDockingStateInt == dockedInt && catapultThrust > 0f)
|
|||
|
{
|
|||
|
shipControlModule.shipInstance.AddBoost(Vector3.forward, catapultThrust, catapultDuration);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Docking or UnDocking
|
|||
|
else if (dockingStateInt == dockingInt || dockingStateInt == undockingInt)
|
|||
|
{
|
|||
|
if (previousDockingStateInt == dockedInt)
|
|||
|
{
|
|||
|
// If the ship was previously docked, we'll probably need to also reenable it.
|
|||
|
if (!shipControlModule.ShipMovementIsEnabled() || !shipControlModule.ShipIsEnabled()) { shipControlModule.EnableShip(false, true); }
|
|||
|
}
|
|||
|
|
|||
|
if (shipDockingStation != null && shipDockingStation.IsDockingPointPathsInitialised)
|
|||
|
{
|
|||
|
ShipDockingPoint shipDockingPoint = shipDockingStation.GetDockingPoint(DockingPointId);
|
|||
|
|
|||
|
if (shipDockingPoint != null)
|
|||
|
{
|
|||
|
// If this is an AI Ship (or AI-assisted player ship), assign it a path to follow (if it is setup in the docking point)
|
|||
|
if (shipAIInputModule != null && shipAIInputModule.IsInitialised)
|
|||
|
{
|
|||
|
// Is switching directly to docking while currently undocking
|
|||
|
if (previousDockingStateInt == undockingInt && dockingStateInt == dockingInt)
|
|||
|
{
|
|||
|
// Restore user callback method (if any) - this gets updated again below
|
|||
|
shipAIInputModule.callbackCompletedStateAction = originalCompletedStateActionCallback;
|
|||
|
|
|||
|
// Where are we in the current undocking maneouvre?
|
|||
|
int shipAIStateID = shipAIInputModule.GetState();
|
|||
|
|
|||
|
// While undocking, was ship heading towards the hover position?
|
|||
|
if (isHoverTarget)
|
|||
|
{
|
|||
|
// Move directly towards the docking point
|
|||
|
isHoverTarget = false;
|
|||
|
isInAIDockingState = true;
|
|||
|
}
|
|||
|
// On Exit path, attempt to join Entry path
|
|||
|
else if (shipAIStateID == AIState.moveToStateID)
|
|||
|
{
|
|||
|
//Debug.Log("[DEBUG] switching directly between undocking and docking while on Exit path. AIState: " + shipAIStateID + " T:" + Time.time);
|
|||
|
|
|||
|
// Attempt ot join entry path (if there is one)
|
|||
|
if (!TryJointPathAtClosestPoint(shipDockingPoint.guidHashEntryPath))
|
|||
|
{
|
|||
|
// No valid path, one was not specified, or close to end of path, so assume entry path has been completed.
|
|||
|
immediatelyCompleteState = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// Is switching directly to undocking while currently docking
|
|||
|
else if (previousDockingStateInt == dockingInt && dockingStateInt == undockingInt)
|
|||
|
{
|
|||
|
// Restore user callback method (if any) - this gets updated again below
|
|||
|
shipAIInputModule.callbackCompletedStateAction = originalCompletedStateActionCallback;
|
|||
|
|
|||
|
// Where are we in the current docking maneouvre?
|
|||
|
int shipAIStateID = shipAIInputModule.GetState();
|
|||
|
|
|||
|
// While docking, was ship heading towards the hover position?
|
|||
|
if (isHoverTarget)
|
|||
|
{
|
|||
|
// Stop flying towards hover position, and attempt to fly along exit path (if there is one).
|
|||
|
immediatelyCompleteState = true;
|
|||
|
}
|
|||
|
// Descending from hover positon to docking point
|
|||
|
else if (shipAIStateID == AIState.dockingStateID)
|
|||
|
{
|
|||
|
// Commence undocking
|
|||
|
shipAIInputModule.SetState(AIState.dockingStateID);
|
|||
|
|
|||
|
// Action will be completed when we get close to the target position
|
|||
|
dockingActionCompletedDistance = hoverDistancePrecision;
|
|||
|
dockingActionCompletedAngle = hoverAnglePrecision;
|
|||
|
UpdateDockingWSPositionAndRotation(shipDockingPoint, shipAIInputModule, true,
|
|||
|
dockingActionCompletedDistance, dockingActionCompletedAngle);
|
|||
|
|
|||
|
shipAIInputModule.AssignTargetTime(landingDuration);
|
|||
|
|
|||
|
isInAIDockingState = true;
|
|||
|
isHoverTarget = true;
|
|||
|
}
|
|||
|
// On entry path
|
|||
|
else if (shipAIStateID == AIState.moveToStateID)
|
|||
|
{
|
|||
|
// Try to join the exit path at the closest point to the ship
|
|||
|
if (!TryJointPathAtClosestPoint(shipDockingPoint.guidHashExitPath))
|
|||
|
{
|
|||
|
immediatelyCompleteState = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// Assign the path when docking
|
|||
|
else if (dockingStateInt == dockingInt)
|
|||
|
{
|
|||
|
if (!AssignNewPath(shipDockingPoint.guidHashEntryPath))
|
|||
|
{
|
|||
|
// No path or one was not specified, so assume entry path has been completed.
|
|||
|
immediatelyCompleteState = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// When exiting (undocking), set the ship's AI state to the "docking"
|
|||
|
// state to carry out the liftoff manoeuvre, before following the
|
|||
|
// exit path (if there is one).
|
|||
|
// NOTE: The ShipAI state of "docking" covers both docking and undocking.
|
|||
|
shipAIInputModule.SetState(AIState.dockingStateID);
|
|||
|
|
|||
|
// Action will be completed when we get close to the target position
|
|||
|
dockingActionCompletedDistance = hoverDistancePrecision;
|
|||
|
dockingActionCompletedAngle = hoverAnglePrecision;
|
|||
|
UpdateDockingWSPositionAndRotation(shipDockingPoint, shipAIInputModule, true,
|
|||
|
dockingActionCompletedDistance, dockingActionCompletedAngle);
|
|||
|
|
|||
|
shipAIInputModule.AssignTargetTime(liftOffDuration);
|
|||
|
isInAIDockingState = true;
|
|||
|
isHoverTarget = true;
|
|||
|
}
|
|||
|
// Remember any callback the user had for CompletedStateAction
|
|||
|
originalCompletedStateActionCallback = shipAIInputModule.callbackCompletedStateAction;
|
|||
|
// Replace it with our own callback
|
|||
|
shipAIInputModule.callbackCompletedStateAction = AICompletedStateActionCallback;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#if UNITY_EDITOR
|
|||
|
else
|
|||
|
{
|
|||
|
Debug.Log("ERROR: attempting Docking or Undocking without a ShipDockingStation or without correctly initialising it");
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Callbacks, Notifications and AutoUndocking
|
|||
|
|
|||
|
if (callbackOnStateChange != null) { callbackOnStateChange(this, shipControlModule, shipAIInputModule, (DockingState)previousDockingStateInt); }
|
|||
|
|
|||
|
// AI docking without an entry path (added in 1.2.1)
|
|||
|
if (immediatelyCompleteState) { AICompletedStateActionCallback(shipAIInputModule); }
|
|||
|
|
|||
|
if (dockingStateInt == dockedInt && shipDockingStation != null)
|
|||
|
{
|
|||
|
// Do we need to notify the ship docking station that the ship has finished docking?
|
|||
|
// This will call any event methods configured on the ship docking station.
|
|||
|
if (SSCUtils.HasListeners(shipDockingStation.onPostDocked))
|
|||
|
{
|
|||
|
shipDockingStation.onPostDocked.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.GetShipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
|
|||
|
// If auto undocking is enabled, restart (activate) the timer.
|
|||
|
if (autoUndockTime > 0f) { autoUndockTimer = 0.0001f; }
|
|||
|
}
|
|||
|
|
|||
|
// Has started undocking from the Docked state
|
|||
|
if (dockingStateInt == undockingInt && previousDockingStateInt == dockedInt)
|
|||
|
{
|
|||
|
// If there is a Post Undocking Start event peristent AND/OR non-persistent listeners.
|
|||
|
if (SSCUtils.HasListeners(onPostUndockingStart))
|
|||
|
{
|
|||
|
if (onPostUndockingStartDelay > 0f)
|
|||
|
{
|
|||
|
StartCoroutine(OnPostUndockingStartDelayed(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, onPostUndockingStartDelay));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
onPostUndockingStart.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// Has started docking from the NotDocked state
|
|||
|
else if (dockingStateInt == dockingInt && previousDockingStateInt == notDockedInt)
|
|||
|
{
|
|||
|
// If there is a Post Docking Start event peristent AND/OR non-persistent listeners.
|
|||
|
if (SSCUtils.HasListeners(onPostDockingStart))
|
|||
|
{
|
|||
|
if (onPostDockingStartDelay > 0f)
|
|||
|
{
|
|||
|
StartCoroutine(OnPostDockingStartDelayed(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, onPostDockingStartDelay));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
onPostDockingStart.Invoke(shipDockingStation.ShipDockingStationId, shipControlModule.shipInstance.shipId, DockingPointId + 1, Vector3.zero);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prevent a delayed Post Docked event from running once the delay has been triggered by the ship has docked.
|
|||
|
/// </summary>
|
|||
|
public void StopOnPostDocked()
|
|||
|
{
|
|||
|
StopCoroutine("OnPostDockedDelayed");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prevent a delayed Post Docking Hover event from running once the delay has been triggered
|
|||
|
/// by the ship reaching the hover point while docking.
|
|||
|
/// </summary>
|
|||
|
public void StopOnPostDockingHover()
|
|||
|
{
|
|||
|
StopCoroutine("OnPostDockingHoverDelayed");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prevent a delayed Post Docking Start event from running once the delay has been triggered
|
|||
|
/// by the ship starting to dock.
|
|||
|
/// </summary>
|
|||
|
public void StopOnPostDockingStart()
|
|||
|
{
|
|||
|
StopCoroutine("OnPostDockingStartDelayed");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prevent a delayed Post Undocked event from running once the delay has been triggered by the
|
|||
|
/// ship finishing the docking manoeuvre.
|
|||
|
/// </summary>
|
|||
|
public void StopOnPostUndocked()
|
|||
|
{
|
|||
|
StopCoroutine("OnPostUndockedDelayed");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prevent a delayed Post Undocking Hover event from running once the delay has been triggered
|
|||
|
/// by the ship reaching the hover point while undocking.
|
|||
|
/// </summary>
|
|||
|
public void StopOnPostUndockingHover()
|
|||
|
{
|
|||
|
StopCoroutine("OnPostUndockingHoverDelayed");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prevent a delayed Post Undocking Start event from running once the delay has been triggered
|
|||
|
/// by the ship starting to undock.
|
|||
|
/// </summary>
|
|||
|
public void StopOnPostUndockingStart()
|
|||
|
{
|
|||
|
StopCoroutine("OnPostUndockingStartDelayed");
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|