858 lines
36 KiB
C#
858 lines
36 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
|
|
namespace SciFiShipController
|
|
{
|
|
[HelpURL("http://scsmmedia.com/ssc-documentation")]
|
|
[AddComponentMenu("Sci-Fi Ship Controller/Weapon Components/Projectile Module")]
|
|
public class ProjectileModule : MonoBehaviour
|
|
{
|
|
#region Public Enumerations
|
|
|
|
public enum DamageType
|
|
{
|
|
/// <summary>
|
|
/// Damage from projectiles of this type will be unaffected by ship damage multipliers
|
|
/// i.e. the amount of damage done to the ship will be identical to damageAmount.
|
|
/// </summary>
|
|
Default = 0,
|
|
TypeA = 100,
|
|
TypeB = 105,
|
|
TypeC = 110,
|
|
TypeD = 115,
|
|
TypeE = 120,
|
|
TypeF = 125,
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Variables
|
|
|
|
/// <summary>
|
|
/// The starting speed of the projectile in metres per second.
|
|
/// </summary>
|
|
public float startSpeed = 100f;
|
|
/// <summary>
|
|
/// Whether the projectile is affected by gravity.
|
|
/// </summary>
|
|
public bool useGravity = false;
|
|
/// <summary>
|
|
/// The type of damage the projectile does. The amount of damage dealt to a ship upon collision is dependent
|
|
/// on the ship's multiplier for this damage type (i.e. if a Type A projectile with a damage amount of 10 hits a ship
|
|
/// with a Type A damage multiplier of 2, a total damage of 20 will be done to the ship). If the damage type is set to Default,
|
|
/// the damage multipliers are ignored i.e. the damage amount is unchanged.
|
|
/// </summary>
|
|
public DamageType damageType = DamageType.Default;
|
|
/// <summary>
|
|
/// The amount of damage the projectile does on collision with a ship or object. NOTE: Non-ship objects need
|
|
/// a DamageReceiver component.
|
|
/// </summary>
|
|
public float damageAmount = 10f;
|
|
|
|
/// <summary>
|
|
/// Whether Entity Component System and Job System is used when spawning projectiles of this type
|
|
/// </summary>
|
|
public bool useECS = false;
|
|
|
|
/// <summary>
|
|
/// Whether pooling is used when spawning projectiles of this type.
|
|
/// Currently we don't support changing this at runtime.
|
|
/// </summary>
|
|
public bool usePooling = true;
|
|
/// <summary>
|
|
/// The starting size of the pool. Only relevant when usePooling is enabled.
|
|
/// </summary>
|
|
public int minPoolSize = 100;
|
|
/// <summary>
|
|
/// The maximum allowed size of the pool. Only relevant when usePooling is enabled.
|
|
/// </summary>
|
|
public int maxPoolSize = 1000;
|
|
|
|
/// <summary>
|
|
/// The projectile will be automatically despawned after this amount of time (in seconds) has elapsed.
|
|
/// </summary>
|
|
public float despawnTime = 1f;
|
|
|
|
/// <summary>
|
|
/// The ID number for this projectile prefab (as assigned by the Ship Controller Manager in the scene).
|
|
/// This is the index in the SSCManager projectileTemplatesList.
|
|
/// [INTERNAL USE ONLY]
|
|
/// </summary>
|
|
public int projectilePrefabID;
|
|
|
|
/// <summary>
|
|
/// The sound or particle FX used when a collision occurs.
|
|
/// If you modify this, call shipControlModule.ReinitialiseShipProjectilesAndEffects() and/or
|
|
/// surfaceTurretModule.ReinitialiseTurretProjectilesAndEffects() for each ship/surface turret
|
|
/// this projectile is used on.
|
|
/// </summary>
|
|
public EffectsModule effectsObject = null;
|
|
|
|
/// <summary>
|
|
/// The ID number for this projectile's destruction effects object prefab (as assigned by the Ship Controller Manager in the scene).
|
|
/// This is the index in the SSCManager effectsObjectTemplatesList. Not defined = -1.
|
|
/// [INTERNAL USE ONLY]
|
|
/// </summary>
|
|
public int effectsObjectPrefabID = -1;
|
|
|
|
/// <summary>
|
|
/// The sound or particle FX used when a collision occurs with a shield (instead of the effectsObject).
|
|
/// If you modify this, call shipControlModule.ReinitialiseShipProjectilesAndEffects() and/or
|
|
/// surfaceTurretModule.ReinitialiseTurretProjectilesAndEffects() for each ship/surface turret
|
|
/// this projectile is used on.
|
|
/// </summary>
|
|
public EffectsModule shieldEffectsObject = null;
|
|
|
|
/// <summary>
|
|
/// The ID number for this projectile's effects object prefab (as assigned by the Ship Controller Manager in the scene)
|
|
/// for when a shielded ship is hit.
|
|
/// This is the index in the SSCManager effectsObjectTemplatesList. Not defined = -1.
|
|
/// [INTERNAL USE ONLY]
|
|
/// </summary>
|
|
public int shieldEffectsObjectPrefabID = -1;
|
|
|
|
/// <summary>
|
|
/// The sound and/or particle FX used when a projectile is fired from a weapon.
|
|
/// If you modify this, call shipControlModule.ReinitialiseShipProjectilesAndEffects() and/or
|
|
/// surfaceTurretModule.ReinitialiseTurretProjectilesAndEffects() for each ship/surface turret
|
|
/// this projectile is used on.
|
|
/// </summary>
|
|
public EffectsModule muzzleEffectsObject = null;
|
|
|
|
/// <summary>
|
|
/// The distance in local space that the muzzle Effects Object should be instantiated
|
|
/// from the weapon firing point. Typically only the z-axis would be used when the projectile
|
|
/// is instantiated in front or forwards from the actual weapon.
|
|
/// </summary>
|
|
public Vector3 muzzleEffectsOffset = Vector3.zero;
|
|
|
|
/// <summary>
|
|
/// The ID number for this projectile's muzzle object prefab (as assigned by the Ship Controller Manager in the scene).
|
|
/// This is the index in the SSCManager effectsObjectTemplatesList. Not defined = -1.
|
|
/// [INTERNAL USE ONLY]
|
|
/// </summary>
|
|
public int muzzleEffectsObjectPrefabID = -1;
|
|
|
|
/// <summary>
|
|
/// The Id of the ship that fired the projectile
|
|
/// </summary>
|
|
public int sourceShipId = -1;
|
|
|
|
/// <summary>
|
|
/// The Squadron which the ship belonged to when it fired the projectile
|
|
/// </summary>
|
|
public int sourceSquadronId = -1;
|
|
|
|
/// <summary>
|
|
/// Is this projectile guided to a target with kinematics? Position and velocity will be determined by aiming at a target. It will
|
|
/// not consider the forces required to move the projectile.
|
|
/// NOTE: All projectiles in SSC use kinematics but by default they are fire and forget rather than guided towards a target.
|
|
/// If you modify this, call shipControlModule.ReinitialiseShipProjectilesAndEffects() and/or
|
|
/// surfaceTurretModule.ReinitialiseTurretProjectilesAndEffects() for each ship/surface turret
|
|
/// this projectile is used on.
|
|
/// Currently does not support DOTS/ECS.
|
|
/// </summary>
|
|
public bool isKinematicGuideToTarget = false;
|
|
|
|
/// <summary>
|
|
/// The max turning speed in degrees per second for a guided projectile.
|
|
/// Only relevant when isKinematicGuideToTarget is enabled.
|
|
/// </summary>
|
|
[Range(10f, 360f)] public float guidedMaxTurnSpeed = 90f;
|
|
|
|
/// <summary>
|
|
/// The layer mask used for collision testing for this projectile.
|
|
/// Default is everything.
|
|
/// </summary>
|
|
public LayerMask collisionLayerMask = ~0;
|
|
|
|
/// <summary>
|
|
/// The estimated range (in metres) of this projectile assuming it travels at a constant velocity.
|
|
/// </summary>
|
|
public float estimatedRange { get { return startSpeed * despawnTime; } }
|
|
|
|
/// <summary>
|
|
/// Current velocity of the projectile.
|
|
/// Should only be updated when using the sscManager.callbackProjectileMoveTo delegate.
|
|
/// </summary>
|
|
public Vector3 Velocity { get { return velocity; } set { if (isKinematicGuideToTarget) { velocity = value; } } }
|
|
|
|
/// <summary>
|
|
/// The world space position of the projectile in the current frame
|
|
/// </summary>
|
|
public Vector3 ThisFramePosition { get { return thisFramePosition; } internal set { thisFramePosition = value; } }
|
|
|
|
/// <summary>
|
|
/// The world space position of the projectile in the last frame
|
|
/// </summary>
|
|
public Vector3 LastFramePosition { get { return lastFramePosition; } internal set { lastFramePosition = value; } }
|
|
|
|
/// <summary>
|
|
/// If a ship is being targeted, will return its name. If it is being targeted by is NULL, will assume destroyed.
|
|
/// </summary>
|
|
public string TargetShipName { get { return isTargetShip ? (targetShip == null ? "Destroyed" : targetShip.name) : "-"; } }
|
|
|
|
/// <summary>
|
|
/// If a ship damage region is being targeted, will return its name. If it is being targeted but is NULL, will assume destroyed.
|
|
/// </summary>
|
|
public string TargetShipDamageRegionName { get { return isTargetShipDamageRegion ? (targetShip == null ? "Destroyed" : targetShip.IsInitialised && targetguidHash != 0 ? targetShip.shipInstance.GetDamageRegion(targetguidHash).name : "-") : "-"; } }
|
|
|
|
/// <summary>
|
|
/// If a gameobject is being targeted, will return its name
|
|
/// </summary>
|
|
public string TargetGameObjectName { get { return targetGameObject != null ? (string.IsNullOrEmpty(targetGameObject.name) ? "no name" : targetGameObject.name) : "-"; } }
|
|
|
|
#endregion
|
|
|
|
#region Protected variables
|
|
// These variables can be modified by classes that inherit from ProjectileModule
|
|
|
|
protected Vector3 velocity = Vector3.zero;
|
|
protected float speed = 0f;
|
|
protected Vector3 lastFramePosition = Vector3.zero;
|
|
protected Vector3 thisFramePosition = Vector3.zero;
|
|
|
|
protected RaycastHit hitInfo;
|
|
|
|
protected bool isInitialised = false;
|
|
|
|
protected bool isProjectileEnabled = true;
|
|
|
|
protected float despawnTimer = 0f;
|
|
#endregion
|
|
|
|
#region Private variables
|
|
|
|
/// <summary>
|
|
/// From Ship, the magnitude of the acceleration (in metres per second squared) due to gravity.
|
|
/// </summary>
|
|
private float gravitationalAcceleration;
|
|
/// <summary>
|
|
/// From Ship, the direction in world space in which gravity acts upon the ship.
|
|
/// </summary>
|
|
private Vector3 gravityDirection;
|
|
|
|
/// <summary>
|
|
/// The current ship (if any) being targeted when isKinematicGuideToTarget is true.
|
|
/// </summary>
|
|
private ShipControlModule targetShip = null;
|
|
|
|
/// <summary>
|
|
/// The guidHash of the target. Currently only set for ship damage regions.
|
|
/// </summary>
|
|
private int targetguidHash = 0;
|
|
|
|
/// <summary>
|
|
/// The current ship damage region (if any) being targeted when isKinematicGuideToTarget is true.
|
|
/// </summary>
|
|
private DamageRegion targetShipDamageRegion = null;
|
|
|
|
/// <summary>
|
|
/// The current GameObject (if any) being targeted when isKinematicGuideToTarget is true.
|
|
/// </summary>
|
|
private GameObject targetGameObject = null;
|
|
|
|
// Is a ship being targeted?
|
|
private bool isTargetShip = false;
|
|
|
|
/// <summary>
|
|
/// Is a ship's damage region being targeted?
|
|
/// </summary>
|
|
private bool isTargetShipDamageRegion = false;
|
|
|
|
/// <summary>
|
|
/// Private reference to the SSCManager in the scene. Currently only populated
|
|
/// when isKinematicGuideToTarget is true. Used to get the CallbackProjectileMoveTo
|
|
/// method from SSCManager.
|
|
/// </summary>
|
|
private SSCManager sscManager = null;
|
|
|
|
// Is there a user-defined CallbackProjectileMoveTo configured for this projectile?
|
|
private bool isCallbackOnMoveTo = false;
|
|
|
|
// Augmented Proportional Navigation (APN) variables
|
|
// current and previous frame's Line of Sight (normalised)
|
|
private Vector3 lastFrameLOSN = Vector3.zero;
|
|
private Vector3 thisFrameLOSN = Vector3.zero;
|
|
private static readonly float NavConst = 3f;
|
|
|
|
#endregion
|
|
|
|
#region Enable/Disable Event Methods
|
|
|
|
void OnDisable()
|
|
{
|
|
isInitialised = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Update Methods
|
|
|
|
// FixedUpdate is called once per physics update (typically about 50 times per second)
|
|
private void FixedUpdate ()
|
|
{
|
|
if (isInitialised && isProjectileEnabled)
|
|
{
|
|
// Remember last frame position as last frame position - we are about to update this frame position
|
|
lastFramePosition = thisFramePosition;
|
|
|
|
CalcPositionAndVelocity();
|
|
|
|
// Check to see if the projectile has collided with anything during this frame
|
|
// If nothing hit but using pooling, decrement the timer.
|
|
if (!CheckCollision() && usePooling)
|
|
{
|
|
// To avoid a pooled projectile being despawned at the incorrect time,
|
|
// do it here rather than calling Invoke("DestroyProjectile", despawnTime)
|
|
// when it is initialised.
|
|
despawnTimer += Time.deltaTime;
|
|
if (despawnTimer >= despawnTime)
|
|
{
|
|
DestroyProjectile();
|
|
}
|
|
}
|
|
|
|
// Update the position of the object
|
|
if (!isCallbackOnMoveTo) { transform.position = thisFramePosition; }
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Obsolete Methods
|
|
|
|
[System.Obsolete("This method will be removed in a future version. Please use InitialiseProjectile (InstantiateProjectileParameters ipParms).")]
|
|
public void InitialiseProjectile(Vector3 weaponVelocity, int projectilePrefabID, float gravity, Vector3 gravityDirection, int shipId, int squadronId)
|
|
{
|
|
InitialiseProjectile(new InstantiateProjectileParameters
|
|
{
|
|
weaponVelocity = weaponVelocity,
|
|
projectilePrefabID = projectilePrefabID,
|
|
gravity = gravity,
|
|
gravityDirection = gravityDirection,
|
|
shipId = shipId,
|
|
squadronId = squadronId,
|
|
// targets are not supported with this older method
|
|
targetShip = null,
|
|
targetGameObject = null,
|
|
targetguidHash = 0
|
|
}
|
|
);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Virtual and Protected Methods
|
|
|
|
/// <summary>
|
|
/// Initialises the projectile. If you wish to override this in a child (inherited) class you
|
|
/// almost always will want to call the base method first.
|
|
/// public override void InitialiseProjectile(InstantiateProjectileParameters ipParms)
|
|
/// {
|
|
/// base.InitialiseProjectile(ipParms);
|
|
/// // Do stuff here
|
|
/// }
|
|
/// </summary>
|
|
public virtual void InitialiseProjectile(InstantiateProjectileParameters ipParms)
|
|
{
|
|
// Initialise the velocity based on the forwards direction
|
|
// The forwards direction should have been set correctly prior to enabling the object
|
|
speed = startSpeed;
|
|
velocity = transform.forward * speed;
|
|
// Initialise last/this frame positions
|
|
// Shift the position forward by the weapon velocity, so that projectiles don't ever end up behind the ship
|
|
lastFramePosition = transform.position + (velocity * Time.fixedDeltaTime);
|
|
thisFramePosition = lastFramePosition;
|
|
|
|
transform.position = thisFramePosition;
|
|
|
|
// Add the weapon velocity to the projectile velocity
|
|
// This needs to be done after the above so that the projectiles aren't spawned to the sides of the ship
|
|
velocity += ipParms.weaponVelocity;
|
|
|
|
// Store the index to the ProjectileTemplate in the SSCManager projectileTemplatesList
|
|
// This is used with Projectile FX when we know the ProjectileModule but not the parent ProjectileTemplate.
|
|
this.projectilePrefabID = ipParms.projectilePrefabID;
|
|
|
|
// Store the index to the EffectsObjectTemplate in sscManager.effectsObjectTemplatesList
|
|
this.effectsObjectPrefabID = ipParms.effectsObjectPrefabID;
|
|
this.shieldEffectsObjectPrefabID = ipParms.shieldEffectsObjectPrefabID;
|
|
|
|
this.gravitationalAcceleration = ipParms.gravity;
|
|
this.gravityDirection = ipParms.gravityDirection;
|
|
this.sourceShipId = ipParms.shipId;
|
|
this.sourceSquadronId = ipParms.squadronId;
|
|
|
|
if (!useECS && isKinematicGuideToTarget)
|
|
{
|
|
targetShip = ipParms.targetShip;
|
|
targetGameObject = ipParms.targetGameObject;
|
|
targetguidHash = ipParms.targetguidHash;
|
|
|
|
if (ipParms.targetShip != null)
|
|
{
|
|
targetShipDamageRegion = null;
|
|
|
|
if (targetguidHash != 0 && targetShip.IsInitialised && targetShip.shipInstance != null)
|
|
{
|
|
targetShipDamageRegion = targetShip.shipInstance.GetDamageRegion(targetguidHash);
|
|
}
|
|
|
|
isTargetShipDamageRegion = targetShipDamageRegion != null;
|
|
isTargetShip = !isTargetShipDamageRegion;
|
|
|
|
// If not a damage region or one was not found, reset the targetguidHash.
|
|
if (isTargetShip) { targetguidHash = 0; }
|
|
}
|
|
else
|
|
{
|
|
isTargetShip = false;
|
|
isTargetShipDamageRegion = false;
|
|
targetShipDamageRegion = null;
|
|
}
|
|
|
|
// reset this frame Line of Sight (normalised)
|
|
thisFrameLOSN.x = 0f;
|
|
thisFrameLOSN.y = 0f;
|
|
thisFrameLOSN.z = 0f;
|
|
|
|
// If in a pool, this may have already been called
|
|
if (!usePooling || sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
|
|
|
|
isCallbackOnMoveTo = sscManager != null && sscManager.callbackProjectileMoveTo != null;
|
|
}
|
|
else
|
|
{
|
|
ClearTarget();
|
|
}
|
|
|
|
// After a given amount of time, automatically destroy this projectile
|
|
if (usePooling) { despawnTimer = 0f; }
|
|
else { Invoke("DestroyProjectile", despawnTime); }
|
|
|
|
isInitialised = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate new velocity and thisFramePosition
|
|
/// </summary>
|
|
protected virtual void CalcPositionAndVelocity()
|
|
{
|
|
if (useGravity)
|
|
{
|
|
// F = ma
|
|
// a = dv/dt
|
|
// F = mdv/dt
|
|
// dv = Fdt/m
|
|
|
|
velocity += (gravitationalAcceleration * Time.deltaTime * gravityDirection);
|
|
}
|
|
|
|
if (isKinematicGuideToTarget)
|
|
{
|
|
if (isCallbackOnMoveTo) { sscManager.callbackProjectileMoveTo(this); }
|
|
else { GuideToTarget(); }
|
|
}
|
|
|
|
// Move the projectile according to its current velocity
|
|
thisFramePosition = lastFramePosition + (velocity * Time.deltaTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check to see if the projectile has collided with anything during this frame
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected virtual bool CheckCollision()
|
|
{
|
|
// TODO: Look into whether this can be done using RaycastCommand with JobSystem
|
|
// LayerMask defaults to everything (inverse of nothing ~0). Don't detect trigger colliders
|
|
if (Physics.Linecast(lastFramePosition, thisFramePosition, out hitInfo, collisionLayerMask, QueryTriggerInteraction.Ignore))
|
|
{
|
|
bool isShieldHit = false;
|
|
ShipControlModule shipControlModule;
|
|
|
|
// Do we need to check for ship shield hits?
|
|
if (shieldEffectsObjectPrefabID >= 0 && CheckShipHit(hitInfo, damageAmount, damageType, sourceShipId, sourceSquadronId, projectilePrefabID, out shipControlModule))
|
|
{
|
|
isShieldHit = shipControlModule.shipInstance.HasActiveShield(hitInfo.point);
|
|
}
|
|
// No shield effects so perform a regular CheckShipHit
|
|
else if (shieldEffectsObjectPrefabID < 0 && CheckShipHit(hitInfo, damageAmount, damageType, sourceShipId, sourceSquadronId, projectilePrefabID))
|
|
{
|
|
// No need to do anything else here
|
|
}
|
|
else
|
|
{
|
|
// If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
|
|
CheckObjectHit(hitInfo, damageAmount, damageType, sourceShipId, sourceSquadronId, projectilePrefabID);
|
|
}
|
|
|
|
if (isShieldHit && shieldEffectsObjectPrefabID >= 0)
|
|
{
|
|
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
|
|
|
|
if (sscManager != null)
|
|
{
|
|
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
|
|
{
|
|
effectsObjectPrefabID = shieldEffectsObjectPrefabID,
|
|
position = hitInfo.point + (hitInfo.normal * 0.0005f),
|
|
rotation = Quaternion.LookRotation(-hitInfo.normal),
|
|
};
|
|
|
|
// For projectiles we don't need to get the effectsObject key from ieParms.
|
|
sscManager.InstantiateEffectsObject(ref ieParms);
|
|
}
|
|
}
|
|
else if (!isShieldHit && effectsObjectPrefabID >= 0 && effectsObject != null)
|
|
{
|
|
if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
|
|
|
|
//SSCManager sscManager = SSCManager.GetOrCreateManager();
|
|
if (sscManager != null)
|
|
{
|
|
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
|
|
{
|
|
effectsObjectPrefabID = effectsObjectPrefabID,
|
|
position = hitInfo.point,
|
|
rotation = transform.rotation
|
|
};
|
|
|
|
// For projectiles we don't need to get the effectsObject key from ieParms.
|
|
sscManager.InstantiateEffectsObject(ref ieParms);
|
|
}
|
|
}
|
|
|
|
DestroyProjectile();
|
|
|
|
return true;
|
|
}
|
|
else { return false; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the projectile. How this is done depends on what system is being used (i.e. pooling etc.).
|
|
/// When overriding, write your own logic then call base.DestroyProjectile().
|
|
/// </summary>
|
|
protected virtual void DestroyProjectile()
|
|
{
|
|
if (usePooling)
|
|
{
|
|
if (isKinematicGuideToTarget)
|
|
{
|
|
ClearTarget();
|
|
}
|
|
|
|
// Deactivate the projectile
|
|
gameObject.SetActive(false);
|
|
}
|
|
else
|
|
{
|
|
// Destroy the projectile
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// It MUST update the velocity and tranform.forward. Does not update the transform.position.
|
|
/// FUTURE - be able to track other rigid bodies, not just Ships.
|
|
/// FUTURE - be able to be guided towards a Location (which may not have a gameObject)
|
|
/// To write your own version set sscManager.callbackProjectileMoveTo(..).
|
|
/// </summary>
|
|
protected void GuideToTarget()
|
|
{
|
|
if (isKinematicGuideToTarget && (isTargetShip || isTargetShipDamageRegion || targetGameObject != null))
|
|
{
|
|
// Did the target become null without us knowing?
|
|
if ((isTargetShip || isTargetShipDamageRegion) && targetShip == null) { ClearTarget(); }
|
|
// If the target ship has been destroyed, stop targetting this ship
|
|
else if ((isTargetShip || isTargetShipDamageRegion) && targetShip.shipInstance.Destroyed()) { ClearTarget(); }
|
|
else
|
|
{
|
|
// Simple (rubbish) chase-style guidance
|
|
//transform.LookAt(targetShip.transform);
|
|
//velocity = transform.forward * velocity.magnitude;
|
|
|
|
// Augmented Proportional Navigation
|
|
Vector3 targetPosition = isTargetShip ? targetShip.shipInstance.TransformPosition : (isTargetShipDamageRegion ? targetShip.shipInstance.GetDamageRegionWSPosition(targetShipDamageRegion) : targetGameObject.transform.position);
|
|
|
|
lastFrameLOSN = thisFrameLOSN;
|
|
thisFrameLOSN = (targetPosition - transform.position).normalized;
|
|
|
|
if (lastFrameLOSN.x != 0f || lastFrameLOSN.y != 0f || lastFrameLOSN.z != 0f)
|
|
{
|
|
Vector3 deltaLOS = thisFrameLOSN - lastFrameLOSN;
|
|
|
|
// The rate at which the LOS angle is changing
|
|
// When angleRateLOS is zero the missile is on a collision course with the target.
|
|
float angleRateLOS = deltaLOS.magnitude;
|
|
|
|
// Closing velocity is -deltaLOS.
|
|
|
|
// Proportional Navigation v = NavConst * closing_velocity * angleRateLOS
|
|
|
|
float apnBias = gravitationalAcceleration * Time.deltaTime * (NavConst * 0.5f);
|
|
|
|
// NOTE: Final velocity should have the same magnitude (length) of the original velocity.
|
|
Vector3 apn_acceleration = (thisFrameLOSN * (angleRateLOS * NavConst) + (deltaLOS * apnBias)).normalized;
|
|
// Avoid forward = zero vector.
|
|
if (apn_acceleration.x == 0f && apn_acceleration.y == 0f && apn_acceleration.z == 0f) { }
|
|
else
|
|
{
|
|
// The initial rotation is defined by the transform.forwards direction
|
|
// The target rotation is defined by the apn_acceleration direction
|
|
// In this frame we are only allowed to turn by (guidedMaxTurnSpeed * Time.deltaTime) degrees
|
|
transform.forward = Quaternion.RotateTowards(Quaternion.LookRotation(transform.forward),
|
|
Quaternion.LookRotation(apn_acceleration), guidedMaxTurnSpeed * Time.deltaTime) * Vector3.forward;
|
|
}
|
|
velocity = transform.forward * velocity.magnitude;
|
|
|
|
// Commanded Acceleration = NavConst * Vc * angleRateLOS + ( NavConst * Nt ) / 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY]
|
|
/// Stop targeting anything.
|
|
/// </summary>
|
|
internal void ClearTarget()
|
|
{
|
|
targetShip = null;
|
|
targetGameObject = null;
|
|
targetShipDamageRegion = null;
|
|
targetguidHash = 0;
|
|
isTargetShip = false;
|
|
isTargetShipDamageRegion = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY] - subject to change without notice.
|
|
/// Currently does not support DOTS/ECS
|
|
/// </summary>
|
|
/// <param name="shipControlModule"></param>
|
|
internal void SetTargetShip(ShipControlModule shipControlModule)
|
|
{
|
|
if (!useECS && isKinematicGuideToTarget)
|
|
{
|
|
targetShip = shipControlModule;
|
|
|
|
isTargetShip = targetShip != null;
|
|
|
|
if (isTargetShip) { targetGameObject = shipControlModule.gameObject; }
|
|
else { targetGameObject = null; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY] - subject to change without notice.
|
|
/// Currently does not support DOTS/ECS.
|
|
/// If a ship is being targeted, call SetTargetShip(..) instead.
|
|
/// </summary>
|
|
/// <param name="targetGameObj"></param>
|
|
internal void SetTarget(GameObject targetGameObj)
|
|
{
|
|
if (!useECS && isKinematicGuideToTarget)
|
|
{
|
|
targetGameObject = targetGameObj;
|
|
|
|
// clear target ship
|
|
targetShip = null;
|
|
isTargetShip = false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Static Methods
|
|
|
|
/// <summary>
|
|
/// Determine if a ship has been hit. Apply damage as required.
|
|
/// Can be called by both an instance of ProjectileModule and ProjectileSystem.
|
|
/// </summary>
|
|
/// <param name="raycastHitInfo"></param>
|
|
/// <param name="projectileDamageAmount"></param>
|
|
/// <param name="projectileDamageType"></param>
|
|
/// <param name="sourceShipId"></param>
|
|
/// <param name="sourceShipSquadronId"></param>
|
|
/// <param name="projectilePrefabID"></param>
|
|
/// <returns></returns>
|
|
public static bool CheckShipHit (RaycastHit raycastHitInfo, float projectileDamageAmount, DamageType projectileDamageType,
|
|
int sourceShipId, int sourceShipSquadronId, int projectilePrefabID)
|
|
{
|
|
bool isHit = false;
|
|
Rigidbody hitRigidbody = raycastHitInfo.rigidbody;
|
|
|
|
if (hitRigidbody != null)
|
|
{
|
|
// Check if there is a ship control module attached to the hit object
|
|
ShipControlModule shipControlModule;
|
|
|
|
//if (shipControlModule != null)
|
|
if (hitRigidbody.TryGetComponent(out shipControlModule))
|
|
{
|
|
// Apply damage to the ship
|
|
shipControlModule.shipInstance.ApplyNormalDamage(projectileDamageAmount, projectileDamageType, raycastHitInfo.point);
|
|
|
|
// If required, call the custom method
|
|
if (shipControlModule.callbackOnHit != null)
|
|
{
|
|
// Get a reference to the SSCManager
|
|
SSCManager sscManager = SSCManager.GetOrCreateManager();
|
|
|
|
// Create a struct with the necessary parameters
|
|
CallbackOnShipHitParameters callbackOnShipHitParameters = new CallbackOnShipHitParameters
|
|
{
|
|
hitInfo = raycastHitInfo,
|
|
projectilePrefab = sscManager.GetProjectilePrefab(projectilePrefabID),
|
|
beamPrefab = null,
|
|
damageAmount = projectileDamageAmount,
|
|
sourceSquadronId = sourceShipSquadronId,
|
|
sourceShipId = sourceShipId
|
|
};
|
|
// Call the custom callback
|
|
shipControlModule.callbackOnHit(callbackOnShipHitParameters);
|
|
}
|
|
|
|
isHit = true;
|
|
}
|
|
}
|
|
|
|
return isHit;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine if a ship has been hit. Apply damage as required.
|
|
/// </summary>
|
|
/// <param name="raycastHitInfo"></param>
|
|
/// <param name="projectileDamageAmount"></param>
|
|
/// <param name="sourceShipId"></param>
|
|
/// <param name="sourceShipSquadronId"></param>
|
|
public static bool CheckShipHit (RaycastHit raycastHitInfo, float projectileDamageAmount, DamageType projectileDamageType,
|
|
int sourceShipId, int sourceShipSquadronId, int projectilePrefabID, out ShipControlModule shipControlModule)
|
|
{
|
|
bool isHit = false;
|
|
Rigidbody hitRigidbody = raycastHitInfo.rigidbody;
|
|
|
|
if (hitRigidbody != null)
|
|
{
|
|
// Check if there is a ship control module attached to the hit object
|
|
if (hitRigidbody.TryGetComponent(out shipControlModule))
|
|
{
|
|
// Apply damage to the ship
|
|
shipControlModule.shipInstance.ApplyNormalDamage(projectileDamageAmount, projectileDamageType, raycastHitInfo.point);
|
|
|
|
// If required, call the custom method
|
|
if (shipControlModule.callbackOnHit != null)
|
|
{
|
|
// Get a reference to the SSCManager
|
|
SSCManager sscManager = SSCManager.GetOrCreateManager();
|
|
|
|
// Create a struct with the necessary parameters
|
|
CallbackOnShipHitParameters callbackOnShipHitParameters = new CallbackOnShipHitParameters
|
|
{
|
|
hitInfo = raycastHitInfo,
|
|
projectilePrefab = sscManager.GetProjectilePrefab(projectilePrefabID),
|
|
beamPrefab = null,
|
|
damageAmount = projectileDamageAmount,
|
|
sourceSquadronId = sourceShipSquadronId,
|
|
sourceShipId = sourceShipId
|
|
};
|
|
// Call the custom callback
|
|
shipControlModule.callbackOnHit(callbackOnShipHitParameters);
|
|
}
|
|
|
|
isHit = true;
|
|
}
|
|
}
|
|
else { shipControlModule = null; }
|
|
|
|
return isHit;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine if an object with a DamageReceiver component attached has been hit by a Projectile.
|
|
/// TODO - check performance
|
|
/// </summary>
|
|
/// <param name="raycastHitInfo"></param>
|
|
/// <param name="projectileDamageAmount"></param>
|
|
/// <param name="sourceShipId"></param>
|
|
/// <param name="sourceShipSquadronId"></param>
|
|
/// <param name="projectilePrefabID"></param>
|
|
/// <returns></returns>
|
|
public static bool CheckObjectHit (RaycastHit raycastHitInfo, float projectileDamageAmount, DamageType projectileDamageType,
|
|
int sourceShipId, int sourceShipSquadronId, int projectilePrefabID)
|
|
{
|
|
bool isHit = false;
|
|
|
|
if (raycastHitInfo.collider != null)
|
|
{
|
|
DamageReceiver damageReceiver = raycastHitInfo.collider.GetComponent<DamageReceiver>();
|
|
if (damageReceiver != null && damageReceiver.callbackOnHit != null)
|
|
{
|
|
// Get a reference to the SSCManager
|
|
SSCManager sscManager = SSCManager.GetOrCreateManager();
|
|
|
|
// Create a struct with the necessary parameters
|
|
CallbackOnObjectHitParameters callbackOnObjectHitParameters = new CallbackOnObjectHitParameters
|
|
{
|
|
hitInfo = raycastHitInfo,
|
|
projectilePrefab = sscManager.GetProjectilePrefab(projectilePrefabID),
|
|
beamPrefab = null,
|
|
damageAmount = projectileDamageAmount,
|
|
sourceSquadronId = sourceShipSquadronId
|
|
};
|
|
// Call the custom callback
|
|
damageReceiver.callbackOnHit(callbackOnObjectHitParameters);
|
|
|
|
isHit = true;
|
|
}
|
|
}
|
|
|
|
return isHit;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public API Methods
|
|
|
|
/// <summary>
|
|
/// If
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public ShipControlModule GetTargetShip()
|
|
{
|
|
if (isKinematicGuideToTarget) { return targetShip; }
|
|
else { return null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Re-enable a projectile after it has been disabled with
|
|
/// DisableProjectile(). See also SSCManager.ResumeProjectiles()
|
|
/// </summary>
|
|
public void EnableProjectile()
|
|
{
|
|
isProjectileEnabled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Useful when you want to pause the action in a game.
|
|
/// Should always be called BEFORE setting Time.timeScale to 0.
|
|
/// See also SSCManager.PauseProjectiles().
|
|
/// </summary>
|
|
public void DisableProjectile()
|
|
{
|
|
isProjectileEnabled = false;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|