rabidus-test/Assets/SCSM/SciFiShipController/Scripts/SSCManager.cs

4715 lines
226 KiB
C#
Raw Permalink Normal View History

2023-07-24 16:38:13 +03:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if SSC_ENTITIES
using Unity.Transforms;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Collections;
using Unity.Rendering;
#endif
// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
namespace SciFiShipController
{
[AddComponentMenu("Sci-Fi Ship Controller/Managers/Ship Controller Manager")]
[HelpURL("http://scsmmedia.com/ssc-documentation")]
public class SSCManager : MonoBehaviour
{
#region Public Variables and Properties
/// <summary>
/// [READONLY] Are all the projectiles in the scene currently paused?
/// See also PauseProjectiles() and ResumeProjectiles()
/// </summary>
public bool IsProjectilesPaused { get { return isProjectilesPaused; } }
/// <summary>
/// [READONLY] Are all the beams in the scene currently paused?
/// See also PauseBeams() and ResumeBeams()
/// </summary>
public bool IsBeamsPaused { get { return isBeamsPaused; } }
/// <summary>
/// [READONLY] Are all the destructs in the scene currently paused?
/// See also PauseDestructs() and ResumeDestructs()
/// </summary>
public bool IsDestructsPaused { get { return isDestructsPaused; } }
/// <summary>
/// [READONLY] Are all the effects objects in the scene currently paused?
/// See also PauseEffectsObjects() and ResumeEffectsObjects()
/// </summary>
public bool IsEffectsObjectsPaused { get { return isEffectsObjectsPaused; } }
public List<ProjectileTemplate> ProjectileTemplatesList { get { return projectileTemplatesList; } }
public List<BeamTemplate> BeamTemplatesList { get { return beamTemplateList; } }
public List<DestructTemplate> DestructTemplatesList { get { return destructTemplateList; } }
public List<EffectsObjectTemplate> EffectsObjectTemplatesList { get { return effectsObjectTemplatesList; } }
// Path data variables
public List<PathData> pathDataList;
public bool isPathDataListExpanded;
public int pathDataActiveGUIDHash = -1; // The active path (there can only be one active)
public string editorSearchPathFilter;
[Range(1f, 100f)] public float pathDisplayResolution = 10f; // Default to approx. 10 metre-segments
[Range(0.05f, 1f)] public float pathPrecision = 0.5f;
// Location data variables
public List<LocationData> locationDataList;
[Range(0f,50f)] public float locationDefaultNormalOffset = 1f;
public bool isLocationDataListExpanded;
public bool allowRepaint = false;
[Range(1f,1000f)] public float findZoomDistance = 50f;
public string editorSearchLocationFilter;
// Options variables
public bool isAutosizeLocationGizmo = true;
public Color locationGizmoColour = new Color(1f, 0.92f, 0.016f, 0.6f);
public Color defaultPathControlGizmoColour = new Color(1f, 0f, 0f, 0.8f);
[Range(1f, 50f)] public float defaultPathControlOffset = 10f;
/// <summary>
/// [INTERNAL USE ONLY] Instead, call sscManager.EnableRadar() or DisableRadar().
/// </summary>
public bool isRadarEnabled;
#endregion
#region Public Static Variables
/// <summary>
/// Used to denote that a prefab is not pooled.
/// See GetorCreateEffectsPool(..).
/// </summary>
public static int NoPrefabID = -1;
#endregion
#region Public Delegates
/// <summary>
/// Optional callback for use with kinematic guided projectiles. Stored in SSCManager to
/// avoid having one delegate for every projectile in the scene.
/// </summary>
/// <param name="projectileModule"></param>
public delegate void CallbackProjectileMoveTo(ProjectileModule projectileModule);
/// <summary>
/// The name of the custom method that is called when a kinematic guided projectile is being moved.
/// Your method must take 1 parameter of type ProjectileModule. It MUST correctly update the Velocity
/// property AND transform.position on the projectile. Optionally it can update tranform.rotation.
/// This should be a lightweight method to avoid performance issues as it is typically called
/// each FixedUpdate.
/// </summary>
public CallbackProjectileMoveTo callbackProjectileMoveTo = null;
#endregion
#region Private Variables
private List<ProjectileTemplate> projectileTemplatesList;
private ProjectileTemplate projectileTemplate;
private GameObject projectileGameObjectInstance;
private List<BeamTemplate> beamTemplateList;
private BeamTemplate beamTemplate;
private GameObject beamGameObjectInstance;
private List<DestructTemplate> destructTemplateList;
private DestructTemplate destructTemplate;
private GameObject destructGameObjectInstance;
private List<EffectsObjectTemplate> effectsObjectTemplatesList;
private EffectsObjectTemplate effectsObjectTemplate;
private GameObject effectsObjectGameObjectInstance;
private static SSCManager currentManager = null;
private bool isInitialised = false;
private Transform projectilePooledTrfm = null;
private Transform projectileNonPooledTrfm = null;
private Transform beamPooledTrfm = null;
private Transform beamNonPooledTrfm = null;
private Transform destructPooledTrfm = null;
private Transform destructNonPooledTrfm = null;
private Transform effectsPooledTrfm = null;
private Transform effectsNonPooledTrfm = null;
private bool isBeamsPaused = false;
private bool isDestructsPaused = false;
private bool isProjectilesPaused = false;
private bool isEffectsObjectsPaused = false;
/// <summary>
/// This is used to get a quick estimate of a Path Section length
/// or distance. See CalcPathSegments(..).
/// </summary>
private int numPathSegmentsForEstimate = 8;
/// <summary>
/// The Path segment length used for maximum accuracy or precision.
/// </summary>
private float maxPathSegmentPrecisionLength = 0.5f;
private SSCRadar sscRadar = null;
#endregion
#if SSC_ENTITIES
public static ProjectileSystem projectileSystem;
public static EntityManager entityManager;
public static World sscWorld;
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
public static BlobAssetStore blobAssetStore;
#endif
/// <summary>
/// This is only set when there are weapons that use DOTS-enabled projectile modules
/// </summary>
public bool isProjectileSytemUpdateRequired = false;
#endif
#region Public Methods
/// <summary>
/// Initialises the Ship Controller Manager instance.
/// </summary>
public void Initialise ()
{
// Initialise the projectile templates list with an initial capacity of 10 projectile types
projectileTemplatesList = new List<ProjectileTemplate>(10);
// Initialise the beam templates list with an initial capacity of 10 beam types
beamTemplateList = new List<BeamTemplate>(10);
// Initialise the destruct templates list with an initial capacity of 10 destruct types
destructTemplateList = new List<DestructTemplate>(10);
// Initialise the effects object templates list with an initial capacity of 25 effects object types
effectsObjectTemplatesList = new List<EffectsObjectTemplate>(25);
// AI data
if (locationDataList == null) { locationDataList = new List<LocationData>(10); }
if (pathDataList == null) { pathDataList = new List<PathData>(5); }
// Beam child transforms
beamPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "BeamPooled");
beamNonPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "BeamNonPooled");
// Destruct child transforms
destructPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "DestructPooled");
destructNonPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "DestructNonPooled");
// Projectile child transforms
projectilePooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "ProjectilePooled");
projectileNonPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "ProjectileNonPooled");
// Effects object child transforms
effectsPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "EffectPooled");
effectsNonPooledTrfm = SSCUtils.GetOrCreateChildTransform(transform, "EffectNonPooled");
#if SSC_ENTITIES
// NOTE: GetDefaultWorld() may fail in the editor if the scene hasn't been run in this session.
if (Application.isPlaying)
{
// Get the world that will "hold" the entities.
if (sscWorld == null)
{
sscWorld = DOTSHelper.GetDefaultWorld();
}
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
if (blobAssetStore == null)
{
blobAssetStore = new BlobAssetStore();
}
#endif
if (sscWorld == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager.Initialise - could not get entities World. Please Play the scene once to fix. If problem persists PLEASE REPORT");
#endif
return;
}
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
else if (blobAssetStore == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager.Initialise - could not create BlobAssetStore - PLEASE REPORT");
#endif
return;
}
#endif
else
{
// Create the ProjectileSystem manually so we can update it from FixedUpdate().
if (projectileSystem == null) { projectileSystem = sscWorld.GetOrCreateSystem<ProjectileSystem>(); }
if (projectileSystem != null)
{
// Get the current entity manager. If it doesn't exist, create one.
entityManager = sscWorld.EntityManager;
}
#if UNITY_EDITOR
else { Debug.LogWarning("SSCManager.Initialise - could not create ProjectileSystem() - PLEASE REPORT"); }
#endif
}
}
#endif
isInitialised = true;
// Check to see if any locations are enabled for radar
// NOTE: This must appear AFTER isInitialised = true.
int numLocations = locationDataList == null ? 0 : locationDataList.Count;
for (int lIdx = 0; lIdx < numLocations; lIdx++)
{
if (locationDataList[lIdx].isRadarEnabled)
{
if (sscRadar == null) { sscRadar = SSCRadar.GetOrCreateRadar(); }
if (sscRadar != null) { EnableRadar(locationDataList[lIdx]); }
}
}
}
/// <summary>
/// Updates the projectiles and effects objects lists in the Ship Controller Manager given a ship instance, and updates
/// the IDs for:
/// - Each weapon's projectile
/// - Each projectile's effects object
/// - Each damage region's effects object
/// </summary>
/// <param name="weaponList"></param>
public void UpdateProjectilesAndEffects (Ship shipInstance)
{
if (isInitialised)
{
if (shipInstance != null)
{
if (shipInstance.weaponList != null)
{
// Loop through the list of weapons
int weaponListCount = shipInstance.weaponList.Count;
for (int w = 0; w < weaponListCount; w++)
{
Weapon weapon = shipInstance.weaponList[w];
// Only process projectile weapons
if (weapon.IsProjectileWeapon)
{
if (weapon.projectilePrefab != null)
{
UpdateWeaponProjectileAndEffects(weapon);
}
#if UNITY_EDITOR
else
{
// If the projectile prefab is null, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects() Warning: Projectile for a weapon ("
+ weapon.name + ") was null. " + "This weapon will not be able to fire projectiles.");
}
#endif
}
}
}
#if UNITY_EDITOR
else
{
// If the weapon list is null, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects() Warning: Weapon list was null.");
}
#endif
if (shipInstance.mainDamageRegion != null && shipInstance.localisedDamageRegionList != null)
{
int localisedDamageRegionListCount = shipInstance.localisedDamageRegionList.Count;
// Loop through all the damage regions of this ship
DamageRegion damageRegion;
for (int d = 0; d < localisedDamageRegionListCount + 1; d++)
{
if (d == 0) { damageRegion = shipInstance.mainDamageRegion; }
else { damageRegion = shipInstance.localisedDamageRegionList[d - 1]; }
// Check if the damage region has an effects object
if (damageRegion != null && damageRegion.destructionEffectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = damageRegion.destructionEffectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
damageRegion.effectsObjectPrefabID = AddEffectsObjectTemplate(damageRegion.destructionEffectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the damage region
damageRegion.effectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
}
}
#if UNITY_EDITOR
else
{
// If the weapon list is null, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects() Warning: main damage region or localised damage region list is null");
}
#endif
}
#if UNITY_EDITOR
else
{
// If any of the damage region variables are null, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects() Warning: shipInstance is null.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects() Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the beams and effects objects lists in the Ship Controller Manager given a ship instance, and updates
/// the ids for:
/// - Each weapon's beam
/// - Each beam's effects object
/// </summary>
/// <param name="shipInstance"></param>
public void UpdateBeamsAndEffects (Ship shipInstance)
{
if (isInitialised)
{
if (shipInstance != null)
{
if (shipInstance.weaponList != null)
{
// Loop through the list of weapons
int weaponListCount = shipInstance.weaponList.Count;
for (int w = 0; w < weaponListCount; w++)
{
Weapon weapon = shipInstance.weaponList[w];
// Only process beam weapons
if (weapon.IsBeamWeapon)
{
if (weapon.beamPrefab != null)
{
UpdateWeaponBeamAndEffects(weapon);
}
#if UNITY_EDITOR
else
{
// If the beam prefab is null, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: Beam for a weapon ("
+ weapon.name + ") was null. " + "This weapon will not be able to fire beams.");
}
#endif
}
}
}
#if UNITY_EDITOR
else
{
// If the weapon list is null, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: Weapon list was null.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If any of the damage region variables are null, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: ship instance is null");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the beams and effects objects lists in the Ship Controller Manager given a surface turret instance, and updates
/// the ids for:
/// - The weapon's beam
/// - Each beam's effects object
/// </summary>
/// <param name="surfaceTurretModule"></param>
public void UpdateBeamsAndEffects (SurfaceTurretModule surfaceTurretModule)
{
if (isInitialised)
{
if (surfaceTurretModule != null)
{
if (surfaceTurretModule.weapon != null)
{
// Only process beam weapons
if (surfaceTurretModule.weapon.IsBeamWeapon)
{
if (surfaceTurretModule.weapon.beamPrefab != null)
{
UpdateWeaponBeamAndEffects(surfaceTurretModule.weapon);
}
#if UNITY_EDITOR
else
{
// If the beam prefab is null, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: Beam for a weapon ("
+ surfaceTurretModule.weapon.name + ") was null. " + "This weapon will not be able to fire beams.");
}
#endif
}
}
#if UNITY_EDITOR
else
{
// If the weapon is null, log an error
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() ERROR: SurfaceTurretModule Weapon was null.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If surfaceTurretModule variables is null, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: surfaceTurretModule is null");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateBeamsAndEffects() Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the DestructModule objects lists in the Ship Controller Manager given a Ship instance
/// </summary>
/// <param name="surfaceTurretModule"></param>
public void UpdateDestructObjects(Ship shipInstance)
{
if (isInitialised)
{
if (shipInstance != null)
{
if (shipInstance.mainDamageRegion != null && shipInstance.localisedDamageRegionList != null)
{
int localisedDamageRegionListCount = shipInstance.localisedDamageRegionList.Count;
// Loop through all the damage regions of this ship
DamageRegion damageRegion;
for (int d = 0; d < localisedDamageRegionListCount + 1; d++)
{
if (d == 0) { damageRegion = shipInstance.mainDamageRegion; }
else { damageRegion = shipInstance.localisedDamageRegionList[d - 1]; }
// Check if the damage region has a destruct object
if (damageRegion != null && damageRegion.destructObject != null)
{
if (damageRegion.destructObject != null)
{
// Get the transform instance ID for this destruct prefab
int destructTransformID = damageRegion.destructObject.transform.GetInstanceID();
// Search the destruct templates list to see if we already have a destruct prefab with the same instance ID
int destructTemplateIndex = destructTemplateList.FindIndex(e => e.instanceID == destructTransformID);
if (destructTemplateIndex == -1)
{
// If no match was found, create a new destruct template for this prefab
damageRegion.destructObjectPrefabID = AddDestructTemplate(damageRegion.destructObject, destructTransformID);
}
else
{
// Save the destruct template index in the surface turret
damageRegion.destructObjectPrefabID = destructTemplateIndex;
}
}
else { damageRegion.destructObjectPrefabID = -1; }
damageRegion.isDestructObjectActivated = false;
}
}
}
#if UNITY_EDITOR
else
{
// If the weapon list is null, log a warning
Debug.LogWarning("SSCManager UpdateDestructObjects(shipInstance) Warning: main damage region or localised damage region list is null");
}
#endif
}
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateDestructObjects(shipInstance) Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the DestructModule objects lists in the Ship Controller Manager given a SurfaceTurretModule instance
/// </summary>
/// <param name="surfaceTurretModule"></param>
public void UpdateDestructObjects(SurfaceTurretModule surfaceTurretModule)
{
if (isInitialised)
{
if (surfaceTurretModule.destructObject != null)
{
// Get the transform instance ID for this destruct prefab
int destructTransformID = surfaceTurretModule.destructObject.transform.GetInstanceID();
// Search the destruct templates list to see if we already have a destruct prefab with the same instance ID
int destructTemplateIndex = destructTemplateList.FindIndex(e => e.instanceID == destructTransformID);
if (destructTemplateIndex == -1)
{
// If no match was found, create a new destruct template for this prefab
surfaceTurretModule.destructObjectPrefabID = AddDestructTemplate(surfaceTurretModule.destructObject, destructTransformID);
}
else
{
// Save the destruct template index in the surface turret
surfaceTurretModule.destructObjectPrefabID = destructTemplateIndex;
}
}
else { surfaceTurretModule.destructObjectPrefabID = -1; }
surfaceTurretModule.isDestructObjectActivated = false;
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateDestructObjects(SurfaceTurretModule surfaceTurretModule) Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the DestructModule objects lists in the Ship Controller Manager given a DestructibleObjectModule instance
/// </summary>
/// <param name="destructibleObjectModule"></param>
public void UpdateDestructObjects(DestructibleObjectModule destructibleObjectModule)
{
if (isInitialised)
{
if (destructibleObjectModule.destructObject != null)
{
// Get the transform instance ID for this destruct prefab
int destructTransformID = destructibleObjectModule.destructObject.transform.GetInstanceID();
// Search the destruct templates list to see if we already have a destruct prefab with the same instance ID
int destructTemplateIndex = destructTemplateList.FindIndex(e => e.instanceID == destructTransformID);
if (destructTemplateIndex == -1)
{
// If no match was found, create a new destruct template for this prefab
destructibleObjectModule.destructObjectPrefabID = AddDestructTemplate(destructibleObjectModule.destructObject, destructTransformID);
}
else
{
// Save the destruct template index in the surface turret
destructibleObjectModule.destructObjectPrefabID = destructTemplateIndex;
}
}
else { destructibleObjectModule.destructObjectPrefabID = -1; }
destructibleObjectModule.isDestructObjectActivated = false;
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateDestructObjects(DestructibleObjectModule destructibleObjectModule) Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the projectiles and effects objects lists in the Ship Controller Manager given a SurfaceTurretModule instance,
/// and updates the IDs for:
/// - The weapon's projectile
/// - The projectile's effects object
/// - The damage effects object
/// </summary>
/// <param name="surfaceTurretModule"></param>
public void UpdateProjectilesAndEffects (SurfaceTurretModule surfaceTurretModule)
{
if (isInitialised)
{
if (surfaceTurretModule != null && surfaceTurretModule.weapon != null)
{
// Update projectile weapon (if used)
if (surfaceTurretModule.weapon.IsProjectileWeapon)
{
if (surfaceTurretModule.weapon.projectilePrefab != null)
{
UpdateWeaponProjectileAndEffects(surfaceTurretModule.weapon);
}
#if UNITY_EDITOR
else
{
// If the projectile prefab is null, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects() Warning: Projectile for a surfaceTurretModule ("
+ surfaceTurretModule.name + ") was null. " + "This weapon will not be able to fire projectiles.");
}
#endif
}
if (surfaceTurretModule.destructionEffectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = surfaceTurretModule.destructionEffectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
surfaceTurretModule.effectsObjectPrefabID = AddEffectsObjectTemplate(surfaceTurretModule.destructionEffectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the surface turret
surfaceTurretModule.effectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
else { surfaceTurretModule.effectsObjectPrefabID = -1; }
surfaceTurretModule.isDestructionEffectsObjectInstantiated = false;
}
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateProjectilesAndEffects(SurfaceTurretModule surfaceTurretModule) Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Updates the effects objects lists in the Ship Controller Manager given a DestructibleObjectModule instance,
/// and updates the IDs for:
/// - The damage effects object
/// </summary>
/// <param name="destructibleObjectModule"></param>
public void UpdateEffects (DestructibleObjectModule destructibleObjectModule)
{
if (isInitialised)
{
if (destructibleObjectModule != null)
{
if (destructibleObjectModule.destructionEffectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = destructibleObjectModule.destructionEffectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
destructibleObjectModule.effectsObjectPrefabID = AddEffectsObjectTemplate(destructibleObjectModule.destructionEffectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the surface turret
destructibleObjectModule.effectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
else { destructibleObjectModule.effectsObjectPrefabID = -1; }
destructibleObjectModule.isDestructionEffectsObjectInstantiated = false;
}
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager UpdateEffects(DestructibleObjectModule destructibleObjectModule) Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Instantiates the beam with ID beamPrefabID at the position specified in world space and with the forwards
/// and up directions specified.
/// (ibParms.beamPrefabID is the ID sent back to each weapon after calling the UpdateBeamsAndEffects() method).
/// ibParms.effectsObjectPrefabID is ignored as it is looked up in this method.
/// </summary>
/// <param name="ibParms"></param>
public BeamModule InstantiateBeam(ref InstantiateBeamParameters ibParms)
{
BeamModule beamModule = null;
if (isInitialised)
{
if (ibParms.beamPrefabID >= 0 && ibParms.beamPrefabID < beamTemplateList.Count)
{
// Get the beam template using its ID (this is simply the index of it in the beam template list)
beamTemplate = beamTemplateList[ibParms.beamPrefabID];
// Get the effects prefab ID. This is the index in list of effect templates.
ibParms.effectsObjectPrefabID = beamTemplate.beamPrefab.effectsObjectPrefabID;
if (beamTemplate.beamPrefab.usePooling)
{
// If we are using pooling, find the first inactive beam
if (beamTemplate.beamPoolList == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager InstantiateBeam() beamPoolList is null. We do not support changing to usePooling for a beam at runtime.");
#endif
}
else
{
int firstInactiveIndex = beamTemplate.beamPoolList.FindIndex(p => !p.activeInHierarchy);
if (firstInactiveIndex == -1)
{
// All beams in the pool are currently active
// So if we want to get a new one, we'll need to add a new one to the pool
// First check if we have reached the max pool size
if (beamTemplate.currentPoolSize < beamTemplate.beamPrefab.maxPoolSize)
{
// If we are still below the max pool size, add a new beam instance to the pool
// Instantiate the beam object with the correct position and rotation
beamGameObjectInstance = Instantiate(beamTemplate.beamPrefab.gameObject,
ibParms.position, Quaternion.LookRotation(ibParms.fwdDirection, ibParms.upDirection));
// Set the object's parent to be the manager
beamGameObjectInstance.transform.SetParent(beamPooledTrfm);
// Initialise the beam
beamModule = beamGameObjectInstance.GetComponent<BeamModule>();
ibParms.beamSequenceNumber = beamModule.InitialiseBeam(ibParms);
// Add the object to the list of pooled objects
beamTemplate.beamPoolList.Add(beamGameObjectInstance);
// Set the beam id to the last index position in the list
ibParms.beamPoolListIndex = beamTemplate.currentPoolSize;
// Update the current pool size counter
beamTemplate.currentPoolSize++;
}
}
else
{
// Get the beam object
beamGameObjectInstance = beamTemplate.beamPoolList[firstInactiveIndex];
// Position the object
beamGameObjectInstance.transform.SetPositionAndRotation(ibParms.position, Quaternion.LookRotation(ibParms.fwdDirection, ibParms.upDirection));
// Set the object to active
beamGameObjectInstance.SetActive(true);
// Initialise the beam
beamModule = beamGameObjectInstance.GetComponent<BeamModule>();
ibParms.beamSequenceNumber = beamModule.InitialiseBeam(ibParms);
ibParms.beamPoolListIndex = firstInactiveIndex;
}
}
}
else
{
// If we are not using pooling, simply instantiate the beam
beamGameObjectInstance = Instantiate(beamTemplate.beamPrefab.gameObject,
ibParms.position, Quaternion.LookRotation(ibParms.fwdDirection, ibParms.upDirection));
// Set the object's parent to be the manager
beamGameObjectInstance.transform.SetParent(beamNonPooledTrfm);
// Initialise the beam
beamModule = beamGameObjectInstance.GetComponent<BeamModule>();
ibParms.beamSequenceNumber = beamModule.InitialiseBeam(ibParms);
// We are not using pooling so reset the index to -1.
ibParms.beamPoolListIndex = -1;
}
// Spawn a muzzle effects object if there is one
// NOTE: Currently it doesn't move with ships
if (beamTemplate.beamPrefab.muzzleEffectsObjectPrefabID >= 0)
{
Quaternion _objRotation = Quaternion.LookRotation(ibParms.fwdDirection, ibParms.upDirection);
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
{
effectsObjectPrefabID = beamTemplate.beamPrefab.muzzleEffectsObjectPrefabID,
position = ibParms.position + (_objRotation * beamTemplate.beamPrefab.muzzleEffectsOffset),
rotation = _objRotation
};
// Instantiate the muzzle effects
if (InstantiateEffectsObject(ref ieParms) != null)
{
if (ieParms.effectsObjectSequenceNumber > 0)
{
// Record the muzzle effect item key
beamModule.muzzleEffectsItemKey = new SSCEffectItemKey(ieParms.effectsObjectPrefabID, ieParms.effectsObjectPoolListIndex, ieParms.effectsObjectSequenceNumber);
}
}
}
}
#if UNITY_EDITOR
else
{
// If an invalid ID is provided, log a warning
Debug.LogWarning("SSCManager InstantiateBeam() Warning: Provided beamPrefabID was invalid. " +
"To get the correct ID, call UpdateBeamsAndEffects() for this ship or surface turret, and use the " +
"beamPrefabID populated for each weapon.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager InstantiateBeam() Warning: Method was called before the manager was initialised.");
}
#endif
return beamModule;
}
/// <summary>
/// Instantiates the destruct object with ID destructPrefabID at the position specified in world space
/// (destructPrefabID is the ID sent back to each SurfaceTurret or Ship after calling the UpdateProjectilesAndDestruct() method).
/// If non-pooled and isExplodeOnStart is true, the power and radius will always be the prefab defaults.
/// </summary>
/// <param name="projectilePrefabID"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
public DestructModule InstantiateDestruct (ref InstantiateDestructParameters dstParms)
{
DestructModule _destructModule = null;
if (isInitialised)
{
// Check that a valid ID has been provided
if (dstParms.destructPrefabID >= 0 && dstParms.destructPrefabID < destructTemplateList.Count)
{
// Get the destruct object template using its ID (this is just the index of it in the destruct object template list)
destructTemplate = destructTemplateList[dstParms.destructPrefabID];
// Currently destruct objects are only instantiated as normal Unity gameobjects with optional pooling
if (destructTemplate.destructPrefab.usePooling)
{
// If we are using pooling, find the first inactive destruct object
if (destructTemplate.destructPoolList == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager InstantiateProjectile() destructPoolList is null. We do not support changing to usePooling for an destruct object at runtime.");
#endif
}
else
{
int firstInactiveIndex = destructTemplate.destructPoolList.FindIndex(e => !e.activeInHierarchy);
if (firstInactiveIndex == -1)
{
// All destruct objects in the pool are currently active
// So if we want to get a new one, we'll need to add a new one to the pool
// First check if we have reached the max pool size
if (destructTemplate.currentPoolSize < destructTemplate.destructPrefab.maxPoolSize)
{
// Always disable isExplodeOnStart when pooling is enabled
destructTemplate.destructPrefab.isExplodeOnStart = false;
// If we are still below the max pool size, add a new destruct object instance to the pool
// Instantiate the destruct object with the correct position and rotation
destructGameObjectInstance = Instantiate(destructTemplate.destructPrefab.gameObject,
dstParms.position, dstParms.rotation);
// Set the object's parent to be the manager
destructGameObjectInstance.transform.SetParent(destructPooledTrfm);
// Initialise the destruct object
_destructModule = destructGameObjectInstance.GetComponent<DestructModule>();
if (_destructModule.InitialiseDestruct())
{
dstParms.destructSequenceNumber = _destructModule.ActivateModule(destructTemplate.currentPoolSize);
dstParms.destructPoolListIndex = destructTemplate.currentPoolSize;
// Add the object to the list of pooled objects
destructTemplate.destructPoolList.Add(destructGameObjectInstance);
// Update the current pool size counter
destructTemplate.currentPoolSize++;
_destructModule.Explode(dstParms);
}
}
}
else
{
// Get the destruct object
destructGameObjectInstance = destructTemplate.destructPoolList[firstInactiveIndex];
// Position the object
destructGameObjectInstance.transform.SetPositionAndRotation(dstParms.position, dstParms.rotation);
// Set the object to active
destructGameObjectInstance.SetActive(true);
// Initialise the destruct object
_destructModule = destructGameObjectInstance.GetComponent<DestructModule>();
if (_destructModule.InitialiseDestruct())
{
dstParms.destructSequenceNumber = _destructModule.ActivateModule(firstInactiveIndex);
dstParms.destructPoolListIndex = firstInactiveIndex;
_destructModule.Explode(dstParms);
}
}
}
}
else
{
// If we are not using pooling, simply instantiate the effect
destructGameObjectInstance = Instantiate(destructTemplate.destructPrefab.gameObject,
dstParms.position, dstParms.rotation);
// Set the object's parent to be the manager
destructGameObjectInstance.transform.SetParent(destructNonPooledTrfm);
_destructModule = destructGameObjectInstance.GetComponent<DestructModule>();
// Custom settings is dstParms will only be used if isExplodeOnStart not enabled.
if (!_destructModule.isExplodeOnStart)
{
// Initialise the destruct object
if (_destructModule.InitialiseDestruct())
{
dstParms.destructSequenceNumber = _destructModule.ActivateModule(-1);
dstParms.destructPoolListIndex = -1;
_destructModule.Explode(dstParms);
}
}
}
}
#if UNITY_EDITOR
else
{
// If an invalid ID is provided, log a warning
Debug.LogWarning("SSCManager InstantiateDestruct() Warning: Provided destructPrefabID was invalid (" + dstParms.destructPrefabID +
"). To get the correct ID, call UpdateProjectilesAndDestruct(..) for the ship or SurfaceTurret, and use the " +
"destructPrefabID populated for each projectile / damage region.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager InstantiateDestruct() Warning: Method was called before the manager was initialised.");
}
#endif
return _destructModule;
}
[System.Obsolete("This method will be removed in a future version. Please use InstantiateProjectile (InstantiateProjectileParameters ipParms).")]
public void InstantiateProjectile(int projectilePrefabID, Vector3 position, Vector3 fwdDirection, Vector3 upDirection, Vector3 weaponVelocity, float gravity, Vector3 gravityDirection, int shipId, int squadronId)
{
InstantiateProjectileParameters ipParms = new InstantiateProjectileParameters()
{
projectilePrefabID = projectilePrefabID,
position = position,
fwdDirection = fwdDirection,
upDirection = upDirection,
weaponVelocity = weaponVelocity,
gravity = gravity,
gravityDirection = gravityDirection,
shipId = shipId,
squadronId = squadronId
};
InstantiateProjectile(ref ipParms);
}
/// <summary>
/// Instantiates the projectile with ID projectilePrefabID at the position specified in world space and with the forwards
/// and up directions specified
/// (ipParms.projectilePrefabID is the ID sent back to each weapon after calling the UpdateProjectilesAndEffects() method).
/// ipParms.effectsObjectPrefabID is ignored as it is looked up in this method.
/// </summary>
/// <param name="ipParms"></param>
public void InstantiateProjectile (ref InstantiateProjectileParameters ipParms)
{
if (isInitialised)
{
// Muzzle FX hasn't been spawned yet.
ipParms.muzzleEffectsObjectPrefabID = -1;
ipParms.muzzleEffectsObjectPoolListIndex = -1;
// Check that a valid ID has been provided
if (ipParms.projectilePrefabID >= 0 && ipParms.projectilePrefabID < projectileTemplatesList.Count)
{
// Get the projectile template using its ID (this is simply the index of it in the projectile template list)
projectileTemplate = projectileTemplatesList[ipParms.projectilePrefabID];
// Get the regular destruct effects prefab ID. This is the index in list of effect templates.
ipParms.effectsObjectPrefabID = projectileTemplate.projectilePrefab.effectsObjectPrefabID;
// Get the shield hit destruct effects prefab ID. This is the index in list of effect templates.
ipParms.shieldEffectsObjectPrefabID = projectileTemplate.projectilePrefab.shieldEffectsObjectPrefabID;
// Next, check how we need to instantiate this projectile
// DOTS/ECS Implementation
/// TODO - DOTS include shieldEffectsObjectPrefabID
if (projectileTemplate.projectilePrefab.useECS)
{
#if SSC_ENTITIES
// Now spawn a projectile entity at the player's position, with some forward velocity
ProjectileSystem.CreateProjectile
(
ipParms.position,
new float3(ipParms.weaponVelocity),
new float3(ipParms.fwdDirection),
new float3(ipParms.upDirection),
projectileTemplate.projectilePrefab.startSpeed,
Time.fixedDeltaTime,
projectileTemplate.projectilePrefab.useGravity,
ipParms.gravity,
new float3(ipParms.gravityDirection),
projectileTemplate.projectilePrefab.damageAmount,
projectileTemplate.projectilePrefab.despawnTime,
projectileTemplate.projectilePrefab.projectilePrefabID,
ipParms.effectsObjectPrefabID,
ipParms.shieldEffectsObjectPrefabID,
ipParms.shipId,
ipParms.squadronId,
(int)projectileTemplate.projectilePrefab.damageType,
projectileTemplate.projectilePrefabEntity
);
#endif
}
else if (projectileTemplate.projectilePrefab.usePooling)
{
// If we are using pooling, find the first inactive projectile
if (projectileTemplate.projectilePool == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager InstantiateProjectile() projectilePool is null. We do not support changing to usePooling for a projectile at runtime.");
#endif
}
else
{
int firstInactiveIndex = projectileTemplate.projectilePool.FindIndex(p => !p.activeInHierarchy);
if (firstInactiveIndex == -1)
{
// All projectiles in the pool are currently active
// So if we want to get a new one, we'll need to add a new one to the pool
// First check if we have reached the max pool size
if (projectileTemplate.currentPoolSize < projectileTemplate.projectilePrefab.maxPoolSize)
{
// If we are still below the max pool size, add a new projectile instance to the pool
// Instantiate the projectile object with the correct position and rotation
projectileGameObjectInstance = Instantiate(projectileTemplate.projectilePrefab.gameObject,
ipParms.position, Quaternion.LookRotation(ipParms.fwdDirection, ipParms.upDirection));
// Set the object's parent to be the manager
projectileGameObjectInstance.transform.SetParent(projectilePooledTrfm);
// Initialise the projectile
projectileGameObjectInstance.GetComponent<ProjectileModule>().InitialiseProjectile(ipParms);
// Add the object to the list of pooled objects
projectileTemplate.projectilePool.Add(projectileGameObjectInstance);
// Update the current pool size counter
projectileTemplate.currentPoolSize++;
}
}
else
{
// Get the projectile object
projectileGameObjectInstance = projectileTemplate.projectilePool[firstInactiveIndex];
// Position the object
projectileGameObjectInstance.transform.SetPositionAndRotation(ipParms.position, Quaternion.LookRotation(ipParms.fwdDirection, ipParms.upDirection));
// Set the object to active
projectileGameObjectInstance.SetActive(true);
// Initialise the projectile
projectileGameObjectInstance.GetComponent<ProjectileModule>().InitialiseProjectile(ipParms);
}
}
}
else
{
// If we are not using DOTS or pooling, simply instantiate the projectile
projectileGameObjectInstance = Instantiate(projectileTemplate.projectilePrefab.gameObject,
ipParms.position, Quaternion.LookRotation(ipParms.fwdDirection, ipParms.upDirection));
// Set the object's parent to be the manager
projectileGameObjectInstance.transform.SetParent(projectileNonPooledTrfm);
// Initialise the projectile
projectileGameObjectInstance.GetComponent<ProjectileModule>().InitialiseProjectile(ipParms);
}
// Spawn a muzzle effects object if there is one
// NOTE: Currently it doesn't move with the ship
// TODO - add the muzzle ieParms.effectsObjectPoolListIndex to ipParms
// Set ipParms as ref so we can parent the FX to the weapon. It will only work for pooled muzzle fx.
if (projectileTemplate.projectilePrefab.muzzleEffectsObjectPrefabID >= 0)
{
Quaternion _objRotation = Quaternion.LookRotation(ipParms.fwdDirection, ipParms.upDirection);
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
{
effectsObjectPrefabID = projectileTemplate.projectilePrefab.muzzleEffectsObjectPrefabID,
position = ipParms.position + (_objRotation * projectileTemplate.projectilePrefab.muzzleEffectsOffset),
rotation = _objRotation
};
// For projectiles we don't need to get the effectsObject key from ieParms.
InstantiateEffectsObject(ref ieParms);
// Return the spawned muzzle FX back to the caller so it can be parented (if required).
// If it is not spawned or is not pooled, the value will be -1.
// Need both the (template) PrefabID and the index in the pool.
ipParms.muzzleEffectsObjectPrefabID = ieParms.effectsObjectPrefabID;
ipParms.muzzleEffectsObjectPoolListIndex = ieParms.effectsObjectPoolListIndex;
}
}
#if UNITY_EDITOR
else
{
// If an invalid ID is provided, log a warning
Debug.LogWarning("SSCManager InstantiateProjectile() Warning: Provided projectilePrefabID was invalid. " +
"To get the correct ID, call UpdateProjectilesAndEffects() for this ship, and use the " +
"projectilePrefabID populated for each weapon.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager InstantiateProjectile() Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Instantiates the effects object with ID effectsObjectPrefabID at the position specified in world space
/// (effectsObjectPrefabID is the ID sent back to each projectile / damage region after calling the UpdateProjectilesAndEffects() method).
/// </summary>
/// <param name="effectsObjectPrefabID"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
[System.Obsolete("This method will be removed in a future version. Please use InstantiateEffectsObject (InstantiateEffectsObjectParameters ieParms).")]
public void InstantiateEffectsObject(int effectsObjectPrefabID, Vector3 position, Quaternion rotation)
{
InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
{
effectsObjectPrefabID = effectsObjectPrefabID,
position = position,
rotation = rotation
};
InstantiateEffectsObject(ref ieParms);
}
/// <summary>
/// Instantiates the effects object with ID effectsObjectPrefabID at the position specified in world space
/// (effectsObjectPrefabID is the ID in ieParms sent back to each projectile / damage region after calling the UpdateProjectilesAndEffects() method).
/// </summary>
/// <param name="ieParms"></param>
/// <returns></returns>
public EffectsModule InstantiateEffectsObject (ref InstantiateEffectsObjectParameters ieParms)
{
EffectsModule _effectsModule = null;
if (isInitialised)
{
// Check that a valid ID has been provided
if (ieParms.effectsObjectPrefabID >= 0 && ieParms.effectsObjectPrefabID < effectsObjectTemplatesList.Count)
{
// Get the effects object template using its ID (this is just the index of it in the effects object template list)
effectsObjectTemplate = effectsObjectTemplatesList[ieParms.effectsObjectPrefabID];
// Currently effects objects are only instantiated as normal Unity gameobjects with optional pooling
if (effectsObjectTemplate.effectsObjectPrefab.usePooling)
{
// If we are using pooling, find the first inactive effects object
if (effectsObjectTemplate.effectsObjectPool == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager InstantiateEffectsObject() effectsObjectPool is null. We do not support changing to usePooling for an effects object at runtime.");
#endif
}
else
{
int firstInactiveIndex = effectsObjectTemplate.effectsObjectPool.FindIndex(e => !e.activeInHierarchy);
if (firstInactiveIndex == -1)
{
// All effects objects in the pool are currently active
// So if we want to get a new one, we'll need to add a new one to the pool
// First check if we have reached the max pool size
if (effectsObjectTemplate.currentPoolSize < effectsObjectTemplate.effectsObjectPrefab.maxPoolSize)
{
// If we are still below the max pool size, add a new effects object instance to the pool
// Instantiate the effects object with the correct position and rotation
effectsObjectGameObjectInstance = Instantiate(effectsObjectTemplate.effectsObjectPrefab.gameObject,
ieParms.position, ieParms.rotation);
// Set the object's parent to be the manager
effectsObjectGameObjectInstance.transform.SetParent(effectsPooledTrfm);
// Initialise the effects object
_effectsModule = effectsObjectGameObjectInstance.GetComponent<EffectsModule>();
ieParms.effectsObjectSequenceNumber = _effectsModule.InitialiseEffectsObject();
ieParms.effectsObjectPoolListIndex = effectsObjectTemplate.currentPoolSize;
// Add the object to the list of pooled objects
effectsObjectTemplate.effectsObjectPool.Add(effectsObjectGameObjectInstance);
// Update the current pool size counter
effectsObjectTemplate.currentPoolSize++;
}
}
else
{
// Get the effects object
effectsObjectGameObjectInstance = effectsObjectTemplate.effectsObjectPool[firstInactiveIndex];
// Position the object
effectsObjectGameObjectInstance.transform.SetPositionAndRotation(ieParms.position, ieParms.rotation);
// Set the object to active
effectsObjectGameObjectInstance.SetActive(true);
// Initialise the effects object
_effectsModule = effectsObjectGameObjectInstance.GetComponent<EffectsModule>();
ieParms.effectsObjectSequenceNumber = _effectsModule.InitialiseEffectsObject();
ieParms.effectsObjectPoolListIndex = firstInactiveIndex;
}
}
}
else
{
// If we are not using DOTS or pooling, simply instantiate the effect
effectsObjectGameObjectInstance = Instantiate(effectsObjectTemplate.effectsObjectPrefab.gameObject,
ieParms.position, ieParms.rotation);
// Set the object's parent to be the manager
effectsObjectGameObjectInstance.transform.SetParent(effectsNonPooledTrfm);
// Initialise the effects object
_effectsModule = effectsObjectGameObjectInstance.GetComponent<EffectsModule>();
ieParms.effectsObjectSequenceNumber = _effectsModule.InitialiseEffectsObject();
ieParms.effectsObjectPoolListIndex = -1;
}
}
#if UNITY_EDITOR
else
{
// If an invalid ID is provided, log a warning
Debug.LogWarning("SSCManager InstantiateEffectsObject() Warning: Provided effectsObjectPrefabID was invalid (" + ieParms.effectsObjectPrefabID +
"). To get the correct ID, call UpdateProjectilesAndEffects(..) for the ship or SurfaceTurret, and use the " +
"effectsObjectPrefabID populated for each projectile / damage region.");
}
#endif
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager InstantiateEffectsObject() Warning: Method was called before the manager was initialised.");
}
#endif
return _effectsModule;
}
#endregion
#region Private and Internal Methods
/// <summary>
/// Adds a new projectile template to the list. Returns the projectile template index.
/// </summary>
/// <param name="projectilePrefab"></param>
/// <param name="projectileTransformID"></param>
private int AddProjectileTemplate (ProjectileModule projectilePrefab, int projectileTransformID)
{
// Create a new projectile template for this prefab
projectileTemplate = new ProjectileTemplate(projectilePrefab, projectileTransformID);
// Add the new projectile template to the end of the list
projectileTemplatesList.Add(projectileTemplate);
// Get the index of the new projectile template
int projectileTemplateIndex = projectileTemplatesList.Count - 1;
// Don't need to do anything else for non-pooling.
// If we are using pooling for this projectile, set up the pool
if (projectileTemplate.projectilePrefab.usePooling)
{
// Initialise projectile pool with capacity of minimum pool size
projectileTemplate.currentPoolSize = projectileTemplate.projectilePrefab.minPoolSize;
projectileTemplate.projectilePool = new List<GameObject>(projectileTemplate.currentPoolSize);
// Create the objects in the pool
for (int i = 0; i < projectileTemplate.currentPoolSize; i++)
{
// Instantiate the projectile object
projectileGameObjectInstance = Instantiate(projectileTemplate.projectilePrefab.gameObject);
// Set the object's parent to be the manager
projectileGameObjectInstance.transform.SetParent(projectilePooledTrfm);
// Set the object to inactive
projectileGameObjectInstance.SetActive(false);
// Add the object to the list of pooled objects
projectileTemplate.projectilePool.Add(projectileGameObjectInstance);
}
}
#if SSC_ENTITIES
// If the projectile module uses DOTS/ECS then (currently) it requires updating
// with FixedUpdate() from within SSCManager. NOTE: There can be 0, 1 or more projectile prefabs
// with DOTS enabled. See also Initialise ().
else if (projectileTemplate.projectilePrefab.useECS)
{
isProjectileSytemUpdateRequired = true;
}
#endif
// When a new ProjectileTemplate is added to projectileTemplatesList, store the
// index of the ProjectileTemplate in the ProjectileModule attached to the ProjectileTemplate.
// This is used with Projectile FX when we know the ProjectileModule but not the parent ProjectileTemplate.
if (projectileTemplate.projectilePrefab != null)
{
projectileTemplate.projectilePrefab.projectilePrefabID = projectileTemplateIndex;
}
return projectileTemplateIndex;
}
/// <summary>
/// Adds a new beam template to the list. Returns the beam template index.
/// </summary>
/// <param name="beamPrefab"></param>
/// <param name="beamTransformID"></param>
/// <returns></returns>
private int AddBeamTemplate (BeamModule beamPrefab, int beamTransformID)
{
// Create a new beam template for this prefab
beamTemplate = new BeamTemplate(beamPrefab, beamTransformID);
// Add the new beam template to the end of the list
beamTemplateList.Add(beamTemplate);
// Get the index of the new beam template
int beamTemplateIndex = beamTemplateList.Count - 1;
// Don't need to do anything else for non-pooling.
// If we are using pooling for this beam, set up the pool
if (beamTemplate.beamPrefab.usePooling)
{
// Initialise beam pool with capacity of minimum pool size
beamTemplate.currentPoolSize = beamTemplate.beamPrefab.minPoolSize;
beamTemplate.beamPoolList = new List<GameObject>(beamTemplate.currentPoolSize);
// Create the objects in the pool
for (int i = 0; i < beamTemplate.currentPoolSize; i++)
{
// Instantiate the beam object
beamGameObjectInstance = Instantiate(beamTemplate.beamPrefab.gameObject);
// Set the object's parent to be the manager
beamGameObjectInstance.transform.SetParent(beamPooledTrfm);
// Set the object to inactive
beamGameObjectInstance.SetActive(false);
// Add the object to the list of pooled objects
beamTemplate.beamPoolList.Add(beamGameObjectInstance);
}
}
// When a new BeamTemplate is added to beamTemplateList, store the
// index of the BeamTemplate in the BeamModule attached to the BeamTemplate.
// This is used with Beam FX when we know the BeamModule but not the parent BeamTemplate.
if (beamTemplate.beamPrefab != null)
{
beamTemplate.beamPrefab.beamPrefabID = beamTemplateIndex;
}
return beamTemplateIndex;
}
/// <summary>
/// Adds a new destruct template to the list.
/// TODO: consider initialising when first adding to the pool
/// TODO: turn off isExplodeOnStart
/// </summary>
/// <param name="destructPrefab"></param>
/// <param name="destructTransformID"></param>
private int AddDestructTemplate (DestructModule destructPrefab, int destructTransformID)
{
// Create a new destruct template for this prefab
destructTemplate = new DestructTemplate(destructPrefab, destructTransformID);
// Add the new destruct object template to the end of the list
destructTemplateList.Add(destructTemplate);
// Get the index of the new destruct object template
int destructTemplateIndex = destructTemplateList.Count - 1;
// Don't need to do anything else for non-pooling.
// If we are using pooling for this Destruct module, set up the pool
if (destructTemplate.destructPrefab.usePooling)
{
// Always disable isExplodeOnStart when using pooling
destructTemplate.destructPrefab.isExplodeOnStart = false;
// Initialise destruct pool with capacity of minimum pool size
destructTemplate.currentPoolSize = destructTemplate.destructPrefab.minPoolSize;
destructTemplate.destructPoolList = new List<GameObject>(destructTemplate.currentPoolSize);
// Create the objects in the pool
for (int i = 0; i < destructTemplate.currentPoolSize; i++)
{
// Instantiate the destruct object
destructGameObjectInstance = Instantiate(destructTemplate.destructPrefab.gameObject);
// Set the object's parent to be the manager
destructGameObjectInstance.transform.SetParent(destructPooledTrfm);
// Set the object to inactive
destructGameObjectInstance.SetActive(false);
// Add the object to the list of pooled objects
destructTemplate.destructPoolList.Add(destructGameObjectInstance);
}
}
return destructTemplateIndex;
}
/// <summary>
/// Adds a new effects object template to the list.
/// </summary>
/// <param name="effectsObjectPrefab"></param>
/// <param name="effectsObjectTransformID"></param>
private int AddEffectsObjectTemplate (EffectsModule effectsObjectPrefab, int effectsObjectTransformID)
{
// Create a new effects object template for this prefab
effectsObjectTemplate = new EffectsObjectTemplate(effectsObjectPrefab, effectsObjectTransformID);
// Add the new effects object template to the end of the list
effectsObjectTemplatesList.Add(effectsObjectTemplate);
// Get the index of the new effects object template
int effectsObjectTemplateIndex = effectsObjectTemplatesList.Count - 1;
// Don't need to do anything else for non-pooling.
// If we are using pooling for this effects module, set up the pool
if (effectsObjectTemplate.effectsObjectPrefab.usePooling)
{
// Initialise effects pool with capacity of minimum pool size
effectsObjectTemplate.currentPoolSize = effectsObjectTemplate.effectsObjectPrefab.minPoolSize;
effectsObjectTemplate.effectsObjectPool = new List<GameObject>(effectsObjectTemplate.currentPoolSize);
// Create the objects in the pool
for (int i = 0; i < effectsObjectTemplate.currentPoolSize; i++)
{
// Instantiate the effects object
effectsObjectGameObjectInstance = Instantiate(effectsObjectTemplate.effectsObjectPrefab.gameObject);
// Set the object's parent to be the manager
effectsObjectGameObjectInstance.transform.SetParent(effectsPooledTrfm);
// Set the object to inactive
effectsObjectGameObjectInstance.SetActive(false);
// Add the object to the list of pooled objects
effectsObjectTemplate.effectsObjectPool.Add(effectsObjectGameObjectInstance);
}
}
return effectsObjectTemplateIndex;
}
/// <summary>
/// Find an existing projectile template for this weapon, or add a new one.
/// Find an existing effects template for this weapon, or add a new one.
/// Update the estimatedRange of the weapon.
/// </summary>
/// <param name="weapon"></param>
private void UpdateWeaponProjectileAndEffects(Weapon weapon)
{
// Get the transform instance ID for this projectile prefab
int projectileTransformID = weapon.projectilePrefab.transform.GetInstanceID();
// Search the projectile templates list to see if we already have a
// projectile prefab with the same instance ID
int projectileTemplateIndex = projectileTemplatesList.FindIndex(p => p.instanceID == projectileTransformID);
if (projectileTemplateIndex == -1)
{
// If no match was found, create a new projectile template for this prefab
weapon.projectilePrefabID = AddProjectileTemplate(weapon.projectilePrefab, projectileTransformID);
// Check if the projectile has a destruction effects object
if (weapon.projectilePrefab.effectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = weapon.projectilePrefab.effectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
weapon.projectilePrefab.effectsObjectPrefabID = AddEffectsObjectTemplate(weapon.projectilePrefab.effectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the projectile
weapon.projectilePrefab.effectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
// No destruction effects object for this projectile
else { weapon.projectilePrefab.effectsObjectPrefabID = -1; }
// Check if the projectile has a destruction shield effects object
if (weapon.projectilePrefab.shieldEffectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = weapon.projectilePrefab.shieldEffectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new shield effects object template for this prefab
weapon.projectilePrefab.shieldEffectsObjectPrefabID = AddEffectsObjectTemplate(weapon.projectilePrefab.shieldEffectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the projectile
weapon.projectilePrefab.shieldEffectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
// No destruction shield effects object for this projectile
else { weapon.projectilePrefab.shieldEffectsObjectPrefabID = -1; }
// Check if the projectile has a muzzle effects object
if (weapon.projectilePrefab.muzzleEffectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = weapon.projectilePrefab.muzzleEffectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
weapon.projectilePrefab.muzzleEffectsObjectPrefabID = AddEffectsObjectTemplate(weapon.projectilePrefab.muzzleEffectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the projectile
weapon.projectilePrefab.muzzleEffectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
// No muzzle effects object for this projectile
else { weapon.projectilePrefab.muzzleEffectsObjectPrefabID = -1; }
}
else
{
// Save the projectile template index in the weapon
weapon.projectilePrefabID = projectileTemplateIndex;
}
weapon.estimatedRange = weapon.projectilePrefab.estimatedRange;
weapon.isProjectileKGuideToTarget = weapon.projectilePrefab.isKinematicGuideToTarget;
}
/// <summary>
/// Find an existing beam template for this weapon, or add a new one.
/// Find an existing effects template for this weapon, or add a new one.
/// Update the estimated range of the weapon.
/// </summary>
/// <param name="weapon"></param>
private void UpdateWeaponBeamAndEffects(Weapon weapon)
{
// Get the transform instance ID for this beam prefab
int beamTransformID = weapon.beamPrefab.transform.GetInstanceID();
// Search the beam templates list to see if we already have a
// beam prefab with the same instance ID
int beamTemplateIndex = beamTemplateList.FindIndex(p => p.instanceID == beamTransformID);
if (beamTemplateIndex == -1)
{
// If no match was found, create a new beam template for this prefab
weapon.beamPrefabID = AddBeamTemplate(weapon.beamPrefab, beamTransformID);
// Check if the beam has a hit effects object
if (weapon.beamPrefab.effectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = weapon.beamPrefab.effectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
weapon.beamPrefab.effectsObjectPrefabID = AddEffectsObjectTemplate(weapon.beamPrefab.effectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the beam
weapon.beamPrefab.effectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
// No hit effects object for this beam
else { weapon.beamPrefab.effectsObjectPrefabID = -1; }
// Check if the beam has a muzzle effects object
if (weapon.beamPrefab.muzzleEffectsObject != null)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = weapon.beamPrefab.muzzleEffectsObject.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
int effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == -1)
{
// If no match was found, create a new effects object template for this prefab
weapon.beamPrefab.muzzleEffectsObjectPrefabID = AddEffectsObjectTemplate(weapon.beamPrefab.muzzleEffectsObject, effectsObjectTransformID);
}
else
{
// Save the effect template index in the beam
weapon.beamPrefab.muzzleEffectsObjectPrefabID = effectsObjectTemplateIndex;
}
}
// No muzzle effects object for this beam
else { weapon.beamPrefab.muzzleEffectsObjectPrefabID = -1; }
}
else
{
// Save the beam template index in the weapon
weapon.beamPrefabID = beamTemplateIndex;
}
weapon.estimatedRange = weapon.maxRange;
}
/// <summary>
/// Given an initial Path section length, calculate the number of segments to use to get an
/// Path distance. Currently it uses a global pathAccuracy value.
/// </summary>
/// <param name="pathData"></param>
/// <param name="prevPathPointIndex"></param>
/// <param name="initialSectionDistance"></param>
/// <returns></returns>
private int CalcPathSegments(PathData pathData, int prevPathPointIndex, float initialSectionDistance)
{
int numSegments = 1;
float sectionDistance = initialSectionDistance;
// If the length of the Path section is unknown, first do a estimate with only a few segments
if (initialSectionDistance <= 0f)
{
if (!SSCMath.GetDistanceBetweenPathPoints(pathData, prevPathPointIndex, numPathSegmentsForEstimate, ref sectionDistance))
{
// If this failed, there is not much we can do
return numPathSegmentsForEstimate;
}
}
// Round up. Path Accuracy is between 0.05 and 1.0
numSegments = (int)(0.5f + (pathPrecision * sectionDistance / maxPathSegmentPrecisionLength));
if (numSegments < numPathSegmentsForEstimate) { numSegments = numPathSegmentsForEstimate; }
return numSegments;
}
/// <summary>
/// Deactivate a beam
/// </summary>
/// <param name="beamItemKey"></param>
internal void DeactivateBeam(SSCBeamItemKey beamItemKey)
{
// Is this beam pooled?
if (beamItemKey.beamPoolListIndex >= 0 && beamItemKey.beamSequenceNumber != 0)
{
if (beamItemKey.beamTemplateListIndex >= 0)
{
BeamTemplate _beamTemplate = beamTemplateList[beamItemKey.beamTemplateListIndex];
if (_beamTemplate != null && _beamTemplate.beamPrefab != null && _beamTemplate.beamPrefab.usePooling)
{
GameObject pmGO = _beamTemplate.beamPoolList[beamItemKey.beamPoolListIndex];
if (pmGO.activeInHierarchy)
{
BeamModule beamModule = pmGO.GetComponent<BeamModule>();
// Verify we have the correct beam
if (beamModule != null && beamModule.itemSequenceNumber == beamItemKey.beamSequenceNumber)
{
beamModule.DestroyBeam();
}
}
}
}
}
// Is this an active beam non-pooled beam?
else if (beamItemKey.beamSequenceNumber != 0 && beamNonPooledTrfm != null)
{
BeamModule[] _beamModules = beamNonPooledTrfm.GetComponentsInChildren<BeamModule>(false);
int _numBeamModules = _beamModules == null ? 0 : _beamModules.Length;
for (int bmIdx = 0; bmIdx < _numBeamModules; bmIdx++)
{
BeamModule beamModule = _beamModules[bmIdx];
// Verify we have the correct beam
if (beamModule.itemSequenceNumber == beamItemKey.beamSequenceNumber)
{
beamModule.DestroyBeam();
}
}
}
}
/// <summary>
/// Destroy the effects Object or return it to the pool.
/// </summary>
/// <param name="effectItemKey"></param>
internal void DestroyEffectsObject(SSCEffectItemKey effectItemKey)
{
// Is this effect pooled?
if (effectItemKey.effectsObjectPoolListIndex >= 0 && effectItemKey.effectsObjectSequenceNumber != 0)
{
if (effectItemKey.effectsObjectTemplateListIndex >= 0)
{
EffectsObjectTemplate _effectsTemplate = effectsObjectTemplatesList[effectItemKey.effectsObjectTemplateListIndex];
if (_effectsTemplate != null && _effectsTemplate.effectsObjectPrefab != null && _effectsTemplate.effectsObjectPrefab.usePooling)
{
GameObject pmGO = _effectsTemplate.effectsObjectPool[effectItemKey.effectsObjectPoolListIndex];
if (pmGO.activeInHierarchy)
{
EffectsModule _effectsModule = pmGO.GetComponent<EffectsModule>();
// Verify we have the correct effects object
if (_effectsModule != null && _effectsModule.itemSequenceNumber == effectItemKey.effectsObjectSequenceNumber)
{
_effectsModule.CancelInvoke(EffectsModule.destroyMethodName);
_effectsModule.DestroyEffectsObject();
}
}
}
}
}
// Is this a non-pooled effect?
else if (effectItemKey.effectsObjectSequenceNumber != 0 && effectsNonPooledTrfm != null)
{
EffectsModule[] _effectsModules = effectsNonPooledTrfm.GetComponentsInChildren<EffectsModule>(false);
int _numEffectsModules = _effectsModules == null ? 0 : _effectsModules.Length;
for (int emIdx = 0; emIdx < _numEffectsModules; emIdx++)
{
EffectsModule _effectsModule = _effectsModules[emIdx];
// Verify we have the correct effects object
if (_effectsModule.itemSequenceNumber == effectItemKey.effectsObjectSequenceNumber)
{
_effectsModule.CancelInvoke(EffectsModule.destroyMethodName);
_effectsModule.DestroyEffectsObject();
}
}
}
}
/// <summary>
/// Return the transform for an instance of an EffectsModule in an effects pool.
/// If checkIsReparented is true, it will return null if IsReparented is false.
/// </summary>
/// <param name="effectsObjectPrefabID"></param>
/// <param name="effectsObjectPoolListIndex"></param>
/// <param name="checkIsReparented"></param>
/// <returns></returns>
internal Transform GetEffectsObjectTransform(int effectsObjectPrefabID, int effectsObjectPoolListIndex, bool checkIsReparented)
{
if (effectsObjectPrefabID >= 0 && effectsObjectPoolListIndex >= 0)
{
if (checkIsReparented)
{
effectsObjectTemplate = effectsObjectTemplatesList[effectsObjectPrefabID];
if (effectsObjectTemplate.effectsObjectPrefab.isReparented)
{
return effectsObjectTemplate.effectsObjectPool[effectsObjectPoolListIndex].transform;
}
else
{
return null;
}
}
else
{
return effectsObjectTemplatesList[effectsObjectPrefabID].effectsObjectPool[effectsObjectPoolListIndex].transform;
}
}
else
{
return null;
}
}
/// <summary>
/// Change the transform position and rotation of an EffectsObject.
/// It is faster to not validate sequence numbers for pooled effects.
/// </summary>
/// <param name="effectItemKey"></param>
/// <param name="newPosition"></param>
/// <param name="newRotation"></param>
/// <param name="isValidateSequenceNumber"></param>
internal bool MoveEffectsObject(SSCEffectItemKey effectItemKey, Vector3 newPosition, Quaternion newRotation, bool isValidateSequenceNumber)
{
bool isEffectActiveAndValid = false;
// Is this effect pooled?
if (effectItemKey.effectsObjectPoolListIndex >= 0 && effectItemKey.effectsObjectSequenceNumber != 0)
{
if (effectItemKey.effectsObjectTemplateListIndex >= 0)
{
EffectsObjectTemplate _effectsTemplate = effectsObjectTemplatesList[effectItemKey.effectsObjectTemplateListIndex];
if (_effectsTemplate != null && _effectsTemplate.effectsObjectPrefab != null && _effectsTemplate.effectsObjectPrefab.usePooling)
{
GameObject pmGO = _effectsTemplate.effectsObjectPool[effectItemKey.effectsObjectPoolListIndex];
if (pmGO.activeInHierarchy)
{
if (!isValidateSequenceNumber || pmGO.GetComponent<EffectsModule>().itemSequenceNumber == effectItemKey.effectsObjectSequenceNumber)
{
pmGO.transform.SetPositionAndRotation(newPosition, newRotation);
isEffectActiveAndValid = true;
}
}
}
}
}
// Is this a non-pooled effect?
else if (effectItemKey.effectsObjectSequenceNumber != 0 && effectsNonPooledTrfm != null)
{
EffectsModule[] _effectsModules = effectsNonPooledTrfm.GetComponentsInChildren<EffectsModule>(false);
int _numEffectsModules = _effectsModules == null ? 0 : _effectsModules.Length;
for (int emIdx = 0; emIdx < _numEffectsModules; emIdx++)
{
EffectsModule _effectsModule = _effectsModules[emIdx];
// Verify we have the correct beam
if (_effectsModule.itemSequenceNumber == effectItemKey.effectsObjectSequenceNumber)
{
_effectsModule.transform.SetPositionAndRotation(newPosition, newRotation);
isEffectActiveAndValid = true;
break;
}
}
}
return isEffectActiveAndValid;
}
/// <summary>
/// [INTERNAL ONLY]
/// See also PauseBeams() and ResumeBeams() in Public API
/// Pause or unpause all Pooled, and Non-Pooled Beams in the scene
/// </summary>
/// <param name="isPause"></param>
private void PauseBeams(bool isPause)
{
if (isInitialised)
{
isBeamsPaused = isPause;
int numBeamTemplates = beamTemplateList == null ? 0 : beamTemplateList.Count;
// Pause or unpause all active Pooled Beams
for (int ptIdx = 0; ptIdx < numBeamTemplates; ptIdx++)
{
BeamTemplate _beamTemplate = beamTemplateList[ptIdx];
if (_beamTemplate != null && _beamTemplate.beamPrefab != null && _beamTemplate.beamPrefab.usePooling)
{
// Examine each of the beams in the pool
for (int pmIdx = 0; pmIdx < _beamTemplate.currentPoolSize; pmIdx++)
{
GameObject pmGO = _beamTemplate.beamPoolList[pmIdx];
if (pmGO.activeInHierarchy)
{
if (isPause) { pmGO.GetComponent<BeamModule>().DisableBeam(); }
else { pmGO.GetComponent<BeamModule>().EnableBeam(); }
}
}
}
}
// Pause or unpause all active Non-Pooled Beams (they should probably all be active in the heierarchy)
if (beamNonPooledTrfm != null)
{
BeamModule[] _beamModules = beamNonPooledTrfm.GetComponentsInChildren<BeamModule>(false);
int _numBeamModules = _beamModules == null ? 0 : _beamModules.Length;
for (int pmIdx = 0; pmIdx < _numBeamModules; pmIdx++)
{
if (isPause) { _beamModules[pmIdx].DisableBeam(); }
else { _beamModules[pmIdx].EnableBeam(); }
}
}
}
}
/// <summary>
/// [INTERNAL ONLY] - UNTESTED
/// See also PauseDestructs() and ResumeDestructs() in Public API
/// Pause or unpause all Pooled, and Non-Pooled Destruct Modules in the scene
/// </summary>
/// <param name="isPause"></param>
private void PauseDestructModules(bool isPause)
{
if (isInitialised)
{
isDestructsPaused = isPause;
int numDestructTemplates = destructTemplateList == null ? 0 : destructTemplateList.Count;
// Pause or unpause all active Pooled Destructs
for (int dtIdx = 0; dtIdx < numDestructTemplates; dtIdx++)
{
DestructTemplate _destructTemplate = destructTemplateList[dtIdx];
if (_destructTemplate != null && _destructTemplate.destructPrefab != null && _destructTemplate.destructPrefab.usePooling)
{
// Examine each of the destructs in the pool
for (int pmIdx = 0; pmIdx < _destructTemplate.currentPoolSize; pmIdx++)
{
GameObject pmGO = _destructTemplate.destructPoolList[pmIdx];
if (pmGO.activeInHierarchy)
{
if (isPause) { pmGO.GetComponent<DestructModule>().DisableDestruct(); }
else { pmGO.GetComponent<DestructModule>().EnableDestruct(); }
}
}
}
}
// Pause or unpause all active Non-Pooled Destructs (they should probably all be active in the heierarchy)
if (destructNonPooledTrfm != null)
{
DestructModule[] _destructModules = destructNonPooledTrfm.GetComponentsInChildren<DestructModule>(false);
int _numDestructModules = _destructModules == null ? 0 : _destructModules.Length;
for (int pmIdx = 0; pmIdx < _numDestructModules; pmIdx++)
{
if (isPause) { _destructModules[pmIdx].DisableDestruct(); }
else { _destructModules[pmIdx].EnableDestruct(); }
}
}
}
}
/// <summary>
/// [INTERNAL ONLY]
/// See also PauseProjectiles() and ResumeProjectiles() in Public API
/// Pause or unpause all DOTS, Pooled, and Non-Pooled Projectiles in the scene
/// </summary>
/// <param name="isPause"></param>
private void PauseProjectiles(bool isPause)
{
if (isInitialised)
{
// This will also stop (or resume) DOTS projectileSystem.Update() being called if applicable
isProjectilesPaused = isPause;
int numProjectileTemplates = projectileTemplatesList == null ? 0 : projectileTemplatesList.Count;
// Pause or unpause all active Pooled Projectiles
for (int ptIdx = 0; ptIdx < numProjectileTemplates; ptIdx++)
{
ProjectileTemplate _projectileTemplate = projectileTemplatesList[ptIdx];
if (_projectileTemplate != null && _projectileTemplate.projectilePrefab != null && _projectileTemplate.projectilePrefab.usePooling)
{
// Examine each of the projectiles in the pool
for (int pmIdx = 0; pmIdx < _projectileTemplate.currentPoolSize; pmIdx++)
{
GameObject pmGO = _projectileTemplate.projectilePool[pmIdx];
if (pmGO.activeInHierarchy)
{
if (isPause) { pmGO.GetComponent<ProjectileModule>().DisableProjectile(); }
else { pmGO.GetComponent<ProjectileModule>().EnableProjectile(); }
}
}
}
}
// Pause or unpause all active Non-Pooled Projectiles (they should probably all be active in the heierarchy)
if (projectileNonPooledTrfm != null)
{
ProjectileModule[] _projectileModules = projectileNonPooledTrfm.GetComponentsInChildren<ProjectileModule>(false);
int _numProjectileModules = _projectileModules == null ? 0 : _projectileModules.Length;
for (int pmIdx = 0; pmIdx < _numProjectileModules; pmIdx++)
{
if (isPause) { _projectileModules[pmIdx].DisableProjectile(); }
else { _projectileModules[pmIdx].EnableProjectile(); }
}
}
}
}
/// <summary>
/// [INTERNAL ONLY]
/// See also PauseEffectsObjects() and ResumeEffectsObjects() in Public API.
/// Pause or unpause all pooled and non-pooled effects objects in the scene.
/// </summary>
/// <param name="isPause"></param>
private void PauseEffectsObjects(bool isPause)
{
if (isInitialised)
{
isEffectsObjectsPaused = isPause;
int numEffectsTemplates = effectsObjectTemplatesList == null ? 0 : effectsObjectTemplatesList.Count;
// Pause or unpause all active Pooled Effects
for (int eotIdx = 0; eotIdx < numEffectsTemplates; eotIdx++)
{
EffectsObjectTemplate _effectsTemplate = effectsObjectTemplatesList[eotIdx];
if (_effectsTemplate != null && _effectsTemplate.effectsObjectPrefab != null && _effectsTemplate.effectsObjectPrefab.usePooling)
{
// Examine each of the effects modules in the pool
for (int emIdx = 0; emIdx < _effectsTemplate.currentPoolSize; emIdx++)
{
GameObject emGo = _effectsTemplate.effectsObjectPool[emIdx];
if (emGo.activeInHierarchy)
{
if (isPause) { emGo.GetComponent<EffectsModule>().DisableEffects(); }
else { emGo.GetComponent<EffectsModule>().EnableEffects(); }
}
}
}
}
// Pause or unpause all active Non-Pooled Effects (they should probably all be active in the heierarchy)
if (effectsNonPooledTrfm != null)
{
EffectsModule[] _effectsModules = effectsNonPooledTrfm.GetComponentsInChildren<EffectsModule>(false);
int _numEffectsModules = _effectsModules == null ? 0 : _effectsModules.Length;
for (int emIdx = 0; emIdx < _numEffectsModules; emIdx++)
{
if (isPause) { _effectsModules[emIdx].DisableEffects(); }
else { _effectsModules[emIdx].EnableEffects(); }
}
}
}
}
/// <summary>
/// Select or unselect all the Locations in a Path.
/// Always unselect in/out Controls for a valid Path Location
/// </summary>
/// <param name="pathData"></param>
/// <param name="isSelected"></param>
private void SelectPathLocations(PathData pathData, bool isSelected)
{
if (pathData != null)
{
int numLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
PathLocationData pathLocationData = null;
LocationData locationData = null;
for (int idx = 0; idx < numLocations; idx++)
{
pathLocationData = pathData.pathLocationDataList[idx];
locationData = pathLocationData == null ? null : pathLocationData.locationData;
if (locationData != null)
{
locationData.selectedInSceneView = isSelected;
pathLocationData.inControlSelectedInSceneView = false;
pathLocationData.outControlSelectedInSceneView = false;
}
}
}
}
//// For testing only
//#if SSC_ENTITIES
//void OnGUI()
//{
// GUI.Label(new Rect(10, 10, 100, 20), ProjectileSystem.GetTotalProjectiles.ToString());
// int numTemplates = projectileTemplatesList == null ? 0 : projectileTemplatesList.Count;
// if (numTemplates > 0) { GUI.Label(new Rect(10, 30, 100, 20), projectileTemplatesList[0].currentPoolSize.ToString()); }
//}
//#endif
#endregion
#region Events
/// <summary>
/// This gets called when SSCManager is selected in hierarchy and
/// scene view is visable both at design time and runtime.
/// This draws the Path lines in the scene. Locations and Path Location
/// tangents and control points are drawn in SSCManagerEditor.SceneGUI(..).
/// </summary>
private void OnDrawGizmosSelected()
{
int numPaths = pathDataList == null ? 0 : pathDataList.Count;
int _numPathSegments = 4;
// Remember current colour
Color gizmosColour = Gizmos.color;
// Ensure the length of each segment is sensible
float _segmentLength = pathDisplayResolution < 0.1f ? 0.1f : pathDisplayResolution;
// Loop through all paths
for (int pIdx = 0; pIdx < numPaths; pIdx++)
{
PathData pathData = pathDataList[pIdx];
if (pathData != null && pathData.showGizmosInSceneView)
{
int numLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
if (numLocations > 1)
{
int firstIdx = GetNextPathLocationIndex(pathData, -1, false);
int prevIdx = firstIdx;
int nextIdx = -1;
int lastAssignedIdx = -1; // Used with closed circuit
int numSections = 0;
// There needs to be at least 2 locations to join together
if (firstIdx >= 0 && firstIdx < numLocations - 1)
{
Vector3 from = pathData.pathLocationDataList[firstIdx].locationData.position;
Vector3 to = Vector3.zero;
Vector3 segmentFrom, segmentTo = Vector3.zero;
Gizmos.color = pathData.pathLineColour;
// Loop through all the Locations in the path
for (prevIdx = firstIdx; prevIdx < numLocations-1;)
{
nextIdx = GetNextPathLocationIndex(pathData, prevIdx, false);
if (nextIdx < 0) { break; }
else
{
// Remember last assigned Location for closed circuit
lastAssignedIdx = nextIdx;
PathLocationData pathLlocationData = pathData.pathLocationDataList[nextIdx];
to = pathLlocationData.locationData.position;
// Round up and have min 4 segments per section of the Path
_numPathSegments = (int)(0.5f + pathLlocationData.distanceFromPreviousLocation / _segmentLength);
if (_numPathSegments < 4) { _numPathSegments = 4; }
segmentFrom = from;
for (int sgIdx = 1; sgIdx < _numPathSegments + 1; sgIdx++)
{
if (SSCMath.GetPointOnPath(pathData, prevIdx, (float)sgIdx/_numPathSegments , ref segmentTo))
{
Gizmos.DrawLine(segmentFrom, segmentTo);
segmentFrom = segmentTo;
}
}
numSections++;
from = to;
prevIdx = nextIdx;
}
}
if (pathData.isClosedCircuit && numSections > 1)
{
// Draw the last section between the last point and the first to complete the circuit.
segmentFrom = to;
// Round up and have min 4 segments per section of the Path
_numPathSegments = (int)(0.5f + pathData.pathLocationDataList[firstIdx].distanceFromPreviousLocation / _segmentLength);
if (_numPathSegments < 4) { _numPathSegments = 4; }
for (int sgIdx = 1; sgIdx < _numPathSegments + 1; sgIdx++)
{
if (SSCMath.GetPointOnPath(pathData, lastAssignedIdx, (float)sgIdx / _numPathSegments, ref segmentTo))
{
Gizmos.DrawLine(segmentFrom, segmentTo);
segmentFrom = segmentTo;
}
}
}
}
}
}
}
// Reset colour back to original
Gizmos.color = gizmosColour;
}
private void OnDestroy()
{
#if SSC_ENTITIES && (UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER)
if (blobAssetStore != null) { blobAssetStore.Dispose(); }
#endif
}
#endregion
#region FixedUpdate
#if SSC_ENTITIES
private void FixedUpdate()
{
// Currently the (Job)ComponentSystem's Update occurs late in the Update
// cycle rather than in FixedUpdate. This may change in the future but for
// now ProjectileSystem is manually created in Initialise() then updated
// here where at least one Projectile prefab has DOTS/ECS enabled.
if (isProjectileSytemUpdateRequired && !isProjectilesPaused && projectileSystem != null)
{
projectileSystem.Update();
}
}
#endif
#endregion
#region Public API Static Methods
/// <summary>
/// Returns the current Ship Controller Manager instance for this scene. If one does not already exist, a new one is created.
/// If the manager is not initialised, it will be initialised.
/// </summary>
/// <returns></returns>
public static SSCManager GetOrCreateManager ()
{
// Check whether we have already found a manager for this scene
if (currentManager == null)
{
// Otherwise, check whether this scene already has a manager
currentManager = GameObject.FindObjectOfType<SSCManager>();
if (currentManager == null)
{
// If this scene does not already have a manager, create one
GameObject newManagerGameObject = new GameObject("SSC Manager");
newManagerGameObject.transform.position = Vector3.zero;
newManagerGameObject.transform.parent = null;
currentManager = newManagerGameObject.AddComponent<SSCManager>();
}
}
if (currentManager != null)
{
// Initialise the manager if it hasn't already been initialised
if (!currentManager.isInitialised) { currentManager.Initialise(); }
}
#if UNITY_EDITOR
// If currentManager is still null, log a warning to the console
else
{
Debug.LogWarning("SSCManager GetOrCreateManager() Warning: Could not find or create manager, so returned null.");
}
#endif
return currentManager;
}
/// <summary>
/// This method returns the index of the first assigned Location on a Path or -1 if
/// the path is null, there are no path points, or there are no assigned path points.
/// Locations on a Path can be assigned to a Location in the scene, or they
/// can be empty or "unassigned". These unassigned locations are typically ignored for things like
/// path following.
/// </summary>
/// <param name="pathData"></param>
/// <returns></returns>
public static int GetFirstAssignedLocationIdx(PathData pathData)
{
// Default to no first assigned location
int firstLocationIdx = -1;
if (pathData != null && pathData.pathLocationDataList != null)
{
int numPathLocations = pathData.pathLocationDataList.Count;
if (numPathLocations > 0)
{
PathLocationData tempPathLocationData = null;
for (int plIdx = 0; plIdx < numPathLocations; plIdx++)
{
tempPathLocationData = pathData.pathLocationDataList[plIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else { firstLocationIdx = plIdx; break; }
}
}
}
return firstLocationIdx;
}
/// <summary>
/// This method returns the index of the last assigned Location on a Path or -1 if
/// the path is null, there are no path points, or there are no assigned path points.
/// Locations on a Path can be assigned to a Location in the scene, or they
/// can be empty or "unassigned". These unassigned locations are typically ignored for things like
/// path following.
/// </summary>
/// <param name="pathData"></param>
/// <returns></returns>
public static int GetLastAssignedLocationIdx(PathData pathData)
{
// Default to no first assigned location
int lastLocationIdx = -1;
if (pathData != null && pathData.pathLocationDataList != null)
{
int numPathLocations = pathData.pathLocationDataList.Count;
if (numPathLocations > 0)
{
PathLocationData tempPathLocationData = null;
for (int plIdx = numPathLocations - 1; plIdx >= 0; plIdx--)
{
tempPathLocationData = pathData.pathLocationDataList[plIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else { lastLocationIdx = plIdx; break; }
}
}
}
return lastLocationIdx;
}
/// <summary>
/// Get the next Location that has been assigned to a Path, based on the 0-based
/// index position in list of PathLocationData items. May return null if no next found.
/// Wraps to start if isWrapEnabled is true.
/// If currentIdx = -1, it will attempt to find the first assigned Location.
/// e.g. LocationData locationData = GetNextLocation(pathData, prevIdx, false);
/// </summary>
/// <param name="pathData"></param>
/// <param name="currentIdx"></param>
/// <param name="isWrapEnabled"></param>
/// <returns></returns>
public static LocationData GetNextLocation(PathData pathData, int currentIdx, bool isWrapEnabled)
{
if (pathData == null || pathData.pathLocationDataList == null) { return null; }
else
{
int numPathLocations = pathData.pathLocationDataList.Count;
if (currentIdx < -1 || currentIdx > numPathLocations - 1) { return null; }
// If this is the last Location and isWrapEnbled is false, there is no next Location
else if (currentIdx == numPathLocations - 1 && !isWrapEnabled) { return null; }
else
{
// Start at the current position
int plIdx = currentIdx;
LocationData nextLocationData = null;
PathLocationData tempPathLocationData = null;
// If starting before the beginning of the Path we can iterate over the whole Path
// otherwise don't include the current position.
int numIterations = currentIdx < 0 ? numPathLocations : numPathLocations - 1;
// Loop through the Path a max of 1 time.
for (int i = 0; i < numIterations; i++)
{
plIdx++;
// If this is the last position, should we wrap around to the start?
if (plIdx >= numPathLocations)
{
if (!isWrapEnabled) { break; }
else { plIdx = 0; }
}
tempPathLocationData = pathData.pathLocationDataList[plIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else { nextLocationData = tempPathLocationData.locationData; break; }
}
return nextLocationData;
}
}
}
/// <summary>
/// Get the next PathLocation index that has been assigned to a Path, based on the 0-based
/// index position in list of PathLocationData items. May return -1 if no next found.
/// Wraps to start if isWrapEnabled is true.
/// If currentIdx = -1, it will attempt to find the first assigned Location.
/// </summary>
/// <param name="pathData"></param>
/// <param name="currentIdx"></param>
/// <param name="isWrapEnabled"></param>
/// <returns></returns>
public static int GetNextPathLocationIndex(PathData pathData, int currentIdx, bool isWrapEnabled)
{
if (pathData == null || pathData.pathLocationDataList == null) { return -1; }
else
{
int numPathLocations = pathData.pathLocationDataList.Count;
if (currentIdx < -1 || currentIdx > numPathLocations - 1) { return -1; }
// If this is the last Location and isWrapEnabled is false, there is no next Location
else if (currentIdx == numPathLocations - 1 && !isWrapEnabled) { return -1; }
else
{
// Start at the current position
int plIdx = currentIdx;
int nextLocationIndex = -1;
PathLocationData tempPathLocationData = null;
// If starting before the beginning of the Path we can iterate over the whole Path
// otherwise don't include the current position.
int numIterations = currentIdx < 0 ? numPathLocations : numPathLocations - 1;
// Loop through the Path a max of 1 time.
for (int i = 0; i < numIterations; i++)
{
plIdx++;
// If this is the last position, should we wrap around to the start?
if (plIdx >= numPathLocations)
{
if (!isWrapEnabled) { break; }
else { plIdx = 0; }
}
tempPathLocationData = pathData.pathLocationDataList[plIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else { nextLocationIndex = plIdx; break; }
}
return nextLocationIndex;
}
}
}
/// <summary>
/// Get the previous Location that has been assigned to a Path, based on the 0-based
/// index position in list of PathLocationData items. May return null if no previous found.
/// Wraps to end if isWrapEnabled is true.
/// If currentIdx == number of Locations in Path, the last assigned Location will be returned.
/// </summary>
/// <param name="pathData"></param>
/// <param name="currentIdx"></param>
/// <param name="isWrapEnabled"></param>
/// <returns></returns>
public static LocationData GetPreviousLocation(PathData pathData, int currentIdx, bool isWrapEnabled)
{
if (pathData == null || pathData.pathLocationDataList == null) { return null; }
else
{
int numPathLocations = pathData.pathLocationDataList.Count;
// It is possible to start 1 past the end of the Path so that the Last Location can be returned.
if (currentIdx < 0 || currentIdx > numPathLocations) { return null; }
// If this is the first Location and isWrapEnabled is false, there is no previous Location
else if (currentIdx == 0 && !isWrapEnabled) { return null; }
else
{
// Start at the current position
int plIdx = currentIdx;
LocationData prevLocationData = null;
PathLocationData tempPathLocationData = null;
// If starting past the end of the Path we can iterate over the whole Path
// otherwise don't include the current position.
int numIterations = currentIdx >= numPathLocations ? numPathLocations : numPathLocations - 1;
// Loop through the Path a max of 1 time.
for (int i = 0; i < numIterations; i++)
{
--plIdx;
// If this is the first position, should we wrap around to the last?
if (plIdx < 0)
{
if (!isWrapEnabled) { break; }
else { plIdx = numPathLocations - 1; }
}
tempPathLocationData = pathData.pathLocationDataList[plIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else { prevLocationData = tempPathLocationData.locationData; break; }
}
return prevLocationData;
}
}
}
/// <summary>
/// Get the previous PathLocation index that has been assigned to a Path, based on the 0-based
/// index position in list of PathLocationData items. May return -1 if no previous found.
/// Wraps to end if isWrapEnabled is true.
/// If currentIdx == number of Locations in Path, the last assigned Location will be returned.
/// </summary>
/// <param name="pathData"></param>
/// <param name="currentIdx"></param>
/// <param name="isWrapEnabled"></param>
/// <returns></returns>
public static int GetPreviousPathLocationIndex(PathData pathData, int currentIdx, bool isWrapEnabled)
{
if (pathData == null || pathData.pathLocationDataList == null) { return -1; }
else
{
int numPathLocations = pathData.pathLocationDataList.Count;
// It is possible to start 1 past the end of the Path so that the Last Location can be returned.
if (currentIdx < 0 || currentIdx > numPathLocations) { return -1; }
// If this is the first Location and isWrapEnabled is false, there is no previous Location
else if (currentIdx == 0 && !isWrapEnabled) { return -1; }
else
{
// Start at the current position
int plIdx = currentIdx;
int prevLocationIdx = -1;
PathLocationData tempPathLocationData = null;
// If starting past the end of the Path we can iterate over the whole Path
// otherwise don't include the current position.
int numIterations = currentIdx >= numPathLocations ? numPathLocations : numPathLocations - 1;
// Loop through the Path a max of 1 time.
for (int i = 0; i < numIterations; i++)
{
--plIdx;
// If this is the first position, should we wrap around to the last?
if (plIdx < 0)
{
if (!isWrapEnabled) { break; }
else { plIdx = numPathLocations - 1; }
}
tempPathLocationData = pathData.pathLocationDataList[plIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else { prevLocationIdx = plIdx; break; }
}
return prevLocationIdx;
}
}
}
/// <summary>
/// Import a json file from disk and return as PathData
/// </summary>
/// <param name="folderPath"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public static PathData ImportPathDataFromJson(string folderPath, string fileName)
{
PathData pathData = null;
if (!string.IsNullOrEmpty(folderPath) && !string.IsNullOrEmpty(fileName))
{
try
{
string filePath = System.IO.Path.Combine(folderPath, fileName);
if (System.IO.File.Exists(filePath))
{
string jsonText = System.IO.File.ReadAllText(filePath);
pathData = new PathData();
int pathGuidHash = pathData.guidHash;
JsonUtility.FromJsonOverwrite(jsonText, pathData);
if (pathData != null)
{
// make hash code unique
pathData.guidHash = pathGuidHash;
int numPathLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
// Give PathLocationData unique hash codes
for (int lIdx = 0; lIdx < numPathLocations; lIdx++)
{
pathData.pathLocationDataList[lIdx].guidHash = SSCMath.GetHashCodeFromGuid();
}
}
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("ERROR: Import PataData. Could not find file at " + filePath);
}
#endif
}
catch (System.Exception ex)
{
#if UNITY_EDITOR
Debug.LogWarning("ERROR: SSCManager - could not import path data from: " + folderPath + " PLEASE REPORT - " + ex.Message);
#else
// Keep compiler happy
if (ex != null) { }
#endif
}
}
return pathData;
}
/// <summary>
/// Save the PathData to a json file on disk.
/// </summary>
/// <param name="pathData"></param>
/// <param name="filePath"></param>
public static bool SavePathDataAsJson(PathData pathData, string filePath)
{
bool isSuccessful = false;
if (pathData != null && !string.IsNullOrEmpty(filePath))
{
try
{
string jsonPathData = JsonUtility.ToJson(pathData);
if (!string.IsNullOrEmpty(jsonPathData) && !string.IsNullOrEmpty(filePath))
{
System.IO.File.WriteAllText(filePath, jsonPathData);
isSuccessful = true;
}
}
catch (System.Exception ex)
{
#if UNITY_EDITOR
Debug.LogWarning("ERROR: SSCManager - could not export: " + pathData.name + " PLEASE REPORT - " + ex.Message);
#else
// Keep compiler happy
if (ex != null) { }
#endif
}
}
return isSuccessful;
}
/// <summary>
/// Get the Path distance between two Locations. If the Location index positions in the Path
/// are invalid, the method will return 0. If pathLocationIndex2 is less than pathLocationIndex1,
/// assume the path wraps around to the start again.
/// </summary>
/// <param name="pathData"></param>
/// <param name="pathLocationIndex1"></param>
/// <param name="pathLocationIndex2"></param>
/// <returns></returns>
public static float GetPathDistance(PathData pathData, int pathLocationIndex1, int pathLocationIndex2)
{
float deltaDistance = 0f;
if (pathData != null && pathLocationIndex1 != pathLocationIndex2)
{
int numPathLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
// Check locationData indexes are valid
if (pathLocationIndex1 >= 0 && pathLocationIndex1 < numPathLocations && pathLocationIndex2 >= 0 && pathLocationIndex2 < numPathLocations)
{
PathLocationData pathLocationData1 = pathData.pathLocationDataList[pathLocationIndex1];
PathLocationData pathLocationData2 = pathData.pathLocationDataList[pathLocationIndex2];
// Check Locations are valid
if (pathLocationData1 != null && pathLocationData2 != null)
{
// Check if locations are first Path points. If this is a closed circuit, the first Path point will store the total distance of the Path in distanceCumulative.
if (pathLocationIndex2 > pathLocationIndex1)
{
deltaDistance = (pathLocationIndex2 == 0 ? 0f : pathLocationData2.distanceCumulative) - (pathLocationIndex1 == 0 ? 0f : pathLocationData1.distanceCumulative);
}
else
{
// Distance to the end of the path + distance from start of path to second Location
deltaDistance = pathData.splineTotalDistance - (pathLocationIndex1 == 0 ? 0f : pathLocationData1.distanceCumulative) + (pathLocationIndex2 == 0 ? 0f : pathLocationData2.distanceCumulative);
}
}
}
}
return deltaDistance;
}
#endregion
#region Public API Member Methods - Beam, Destruct, Projectile and Effects Objects
/// <summary>
/// Get the effects pool for this prefab or create a new pool if one does not already exist.
/// Return the effectsObjectPrefabID for the pool or SSCManager.NoPrefabID.
/// See also InstantiateEffectsObject(ieParms), and InstantiateSoundFX(..).
/// </summary>
/// <param name="effectsObjectPrefab"></param>
/// <returns></returns>
public int GetorCreateEffectsPool (EffectsModule effectsObjectPrefab)
{
int effectsObjectTemplateIndex = NoPrefabID;
if (effectsObjectPrefab != null)
{
if (effectsObjectPrefab.usePooling)
{
// Get the transform instance ID for this effects object prefab
int effectsObjectTransformID = effectsObjectPrefab.transform.GetInstanceID();
// Search the effects object templates list to see if we already have an
// effects object prefab with the same instance ID
effectsObjectTemplateIndex = effectsObjectTemplatesList.FindIndex(e => e.instanceID == effectsObjectTransformID);
if (effectsObjectTemplateIndex == NoPrefabID)
{
// If no match was found, create a new effects object template for this prefab
effectsObjectTemplateIndex = AddEffectsObjectTemplate(effectsObjectPrefab, effectsObjectTransformID);
}
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("ERROR: sscManager.GetorCreateEffectsPool - Use Pooling is not enabled on " + effectsObjectPrefab.gameObject.name);
}
#endif
}
#if UNITY_EDITOR
else
{
Debug.LogWarning("ERROR: sscManager.GetorCreateEffectsPool - the effectsObjectPrefab is null");
}
#endif
// return the effectsObjectPrefabID
return effectsObjectTemplateIndex;
}
/// <summary>
/// Create any Effects pools that have not already been created using
/// a set of EffectsModule prefabs.
/// Populate a pre-created array of EffectsTemplate prefabIDs. The
/// length of effectsPrefabIDs must match number of effects slots.
/// </summary>
public bool CreateEffectsPools (SSCSoundFXSet sscEffectsSet, int[] effectsPrefabIDs)
{
int numEffects = sscEffectsSet == null || sscEffectsSet.effectsModuleList == null ? 0 : sscEffectsSet.effectsModuleList.Count;
int numEffectsPrefabIDs = effectsPrefabIDs == null ? 0 : effectsPrefabIDs.Length;
if (numEffects != numEffectsPrefabIDs) { return false; }
else
{
for (int dcIdx = 0; dcIdx < numEffects; dcIdx++)
{
effectsPrefabIDs[dcIdx] = GetorCreateEffectsPool(sscEffectsSet.effectsModuleList[dcIdx]);
#if UNITY_EDITOR
if (effectsPrefabIDs[dcIdx] == NoPrefabID)
{
Debug.LogWarning("ERROR SSCManager.CreateEffectsPools(..) " + sscEffectsSet.name + ", item " + (dcIdx+1).ToString("00") + ", does not contain a valid EffectsModule prefab.");
}
#endif
}
return true;
}
}
/// <summary>
/// Returns the prefab for a beam given its beam prefab ID.
/// </summary>
/// <param name="beamPrefabID"></param>
/// <returns></returns>
public BeamModule GetBeamPrefab (int beamPrefabID)
{
// Check that a valid beam prefab ID was supplied
if (beamPrefabID >= 0 && beamPrefabID < beamTemplateList.Count)
{
return beamTemplateList[beamPrefabID].beamPrefab;
}
else { return null; }
}
/// <summary>
/// Returns the prefab for an EffectsObject given its effects prefab ID.
/// </summary>
/// <param name="effectsObjectPrefabID"></param>
/// <returns></returns>
public EffectsModule GetEffectsObjectPrefab (int effectsObjectPrefabID)
{
// Check that a valid effects object prefab ID was supplied
if (effectsObjectPrefabID >= 0 && effectsObjectPrefabID < effectsObjectTemplatesList.Count)
{
return effectsObjectTemplatesList[effectsObjectPrefabID].effectsObjectPrefab;
}
else { return null; }
}
/// <summary>
/// Pause all Pooled, and Non-Pooled Beams in the scene
/// </summary>
public void PauseBeams()
{
PauseBeams(true);
}
/// <summary>
/// Resume all Pooled, and Non-Pooled beams in the scene
/// </summary>
public void ResumeBeams()
{
PauseBeams(false);
}
/// <summary>
/// Pause all Pooled, and Non-Pooled Destruct objects in the scene
/// </summary>
public void PauseDestructs()
{
PauseDestructModules(true);
}
/// <summary>
/// Resume all Pooled, and Non-Pooled Destruct objects in the scene
/// </summary>
public void ResumeDestructs()
{
PauseDestructModules(false);
}
/// <summary>
/// Returns the prefab for a projectile given its projectile prefab ID.
/// </summary>
/// <param name="projectileTemplateIndex"></param>
/// <returns></returns>
public ProjectileModule GetProjectilePrefab (int projectilePrefabID)
{
// Check that a valid projectile prefab ID was supplied
if (projectilePrefabID < projectileTemplatesList.Count)
{
return projectileTemplatesList[projectilePrefabID].projectilePrefab;
}
else { return null; }
}
/// <summary>
/// Pause all DOTS, Pooled, and Non-Pooled Projectiles in the scene
/// </summary>
public void PauseProjectiles()
{
PauseProjectiles(true);
}
/// <summary>
/// Resume all DOTS, Pooled, and Non-Pooled Projectiles in the scene
/// </summary>
public void ResumeProjectiles()
{
PauseProjectiles(false);
}
/// <summary>
/// Pause all Pooled and Non-Pooled Effects Objects in the scene
/// </summary>
public void PauseEffectsObjects()
{
PauseEffectsObjects(true);
}
/// <summary>
/// Resume all Pooled and Non-Pooled Effects Objects in the scene
/// </summary>
public void ResumeEffectsObjects()
{
PauseEffectsObjects(false);
}
/// <summary>
/// Instantiate a pooled EffectsModule which contains an audio source. If audioClip is null,
/// the existing one (if there is one) attached to the prefab will be used.
/// See also GetorCreateEffectsPool(..).
/// </summary>
/// <param name="sfxParms"></param>
/// <param name="audioClip"></param>
public void InstantiateSoundFX (InstantiateSoundFXParameters sfxParms, AudioClip audioClip)
{
if (isInitialised)
{
if (sfxParms.effectsObjectPrefabID >= 0 && sfxParms.effectsObjectPrefabID < effectsObjectTemplatesList.Count)
{
// Get the effects object template using its ID (this is just the index of it in the effects object template list)
effectsObjectTemplate = effectsObjectTemplatesList[sfxParms.effectsObjectPrefabID];
if (effectsObjectTemplate.effectsObjectPrefab.usePooling)
{
// If we are using pooling, find the first inactive effects object
if (effectsObjectTemplate.effectsObjectPool == null)
{
#if UNITY_EDITOR
Debug.LogWarning("SSCManager InstantiateSoundFX() effectsObjectPool is null. We do not support changing to usePooling for an effects object at runtime.");
#endif
}
else
{
EffectsModule _effectsModule = null;
int firstInactiveIndex = effectsObjectTemplate.effectsObjectPool.FindIndex(e => !e.activeInHierarchy);
if (firstInactiveIndex == -1)
{
// All effects objects in the pool are currently active
// So if we want to get a new one, we'll need to add a new one to the pool
// First check if we have reached the max pool size
if (effectsObjectTemplate.currentPoolSize < effectsObjectTemplate.effectsObjectPrefab.maxPoolSize)
{
// If we are still below the max pool size, add a new effects object instance to the pool
// Instantiate the effects object with the correct position and rotation
effectsObjectGameObjectInstance = Instantiate(effectsObjectTemplate.effectsObjectPrefab.gameObject,
sfxParms.position, Quaternion.identity);
// Set the object's parent to be the manager
effectsObjectGameObjectInstance.transform.SetParent(effectsPooledTrfm);
firstInactiveIndex = effectsObjectTemplate.currentPoolSize;
// Add the object to the list of pooled objects
effectsObjectTemplate.effectsObjectPool.Add(effectsObjectGameObjectInstance);
// Update the current pool size counter
effectsObjectTemplate.currentPoolSize++;
}
}
else
{
// Get the effects object
effectsObjectGameObjectInstance = effectsObjectTemplate.effectsObjectPool[firstInactiveIndex];
// Position the object
effectsObjectGameObjectInstance.transform.SetPositionAndRotation(sfxParms.position, Quaternion.identity);
// Set the object to active
effectsObjectGameObjectInstance.SetActive(true);
}
if (firstInactiveIndex >= 0)
{
// Initialise the effects object
_effectsModule = effectsObjectGameObjectInstance.GetComponent<EffectsModule>();
AudioSource audioSource = _effectsModule.GetAudioSource();
if (audioSource != null)
{
//AudioClip currentAudioclip = _effectsModule.GetAudioClip();
// Replace the clip?
if (audioClip != null)
{
_effectsModule.SetAudioClip(audioClip);
}
// Override the prefab volume?
if (sfxParms.useDefaultVolume) { audioSource.volume = _effectsModule.defaultVolume; }
else if (sfxParms.volume > 0f && sfxParms.volume <= 1f) { audioSource.volume = sfxParms.volume; }
_effectsModule.InitialiseEffectsObject();
//sfxParms.effectsObjectSequenceNumber = _effectsModule.InitialiseEffectsObject();
//sfxParms.effectsObjectPoolListIndex = firstInactiveIndex;
}
}
}
}
}
}
#if UNITY_EDITOR
else
{
// If the method is called before the manager is initialised, log a warning
Debug.LogWarning("SSCManager InstantiateSoundFX() Warning: Method was called before the manager was initialised.");
}
#endif
}
/// <summary>
/// Play an audio clip at the specified world-space position at a volume.
/// This uses the pooled EffectsModules created with GetOrCreateEffectsPool(..).
/// See also InstantiateSoundFX(..).
/// </summary>
/// <param name="audioClip"></param>
/// <param name="audioPosition"></param>
/// <param name="clipVolume"></param>
public void PlaySoundFX (int sfxObjectPrefabID, AudioClip audioClip, Vector3 audioPosition, float clipVolume)
{
// Check to see if the sound fx pool was created ok and the clip is not null.
if (sfxObjectPrefabID >= 0 && audioClip != null)
{
// Play the sound clip using a pool EffectsModule
InstantiateSoundFXParameters sfxParms = new InstantiateSoundFXParameters()
{
effectsObjectPrefabID = sfxObjectPrefabID,
position = audioPosition,
volume = clipVolume
};
InstantiateSoundFX(sfxParms, audioClip);
}
}
/// <summary>
/// Teleport or instantly move all active beams by an amount
/// in the x, y and z directions. This could be useful if changing
/// the origin or centre of your world to compensate for float-point
/// error.
/// </summary>
/// <param name="delta"></param>
public void TelePortBeams(Vector3 delta)
{
int numBeamTemplates = beamTemplateList == null ? 0 : beamTemplateList.Count;
// Teleport all active Pooled Beams
for (int ptIdx = 0; ptIdx < numBeamTemplates; ptIdx++)
{
BeamTemplate _beamTemplate = beamTemplateList[ptIdx];
if (_beamTemplate != null && _beamTemplate.beamPrefab != null && _beamTemplate.beamPrefab.usePooling)
{
// Examine each of the beams in the pool
for (int pmIdx = 0; pmIdx < _beamTemplate.currentPoolSize; pmIdx++)
{
GameObject pmGO = _beamTemplate.beamPoolList[pmIdx];
if (pmGO.activeInHierarchy)
{
pmGO.transform.position += delta;
BeamModule beamModule = pmGO.GetComponent<BeamModule>();
if (beamModule.isInitialised && beamModule.isBeamEnabled)
{
beamModule.transform.position += delta;
beamModule.lineRenderer.SetPosition(0, beamModule.lineRenderer.GetPosition(0) + delta);
beamModule.lineRenderer.SetPosition(1, beamModule.lineRenderer.GetPosition(1) + delta);
}
}
}
}
}
// Teleport all active Non-Pooled Beams (they should probably all be active in the heierarchy)
if (beamNonPooledTrfm != null)
{
BeamModule[] _beamModules = beamNonPooledTrfm.GetComponentsInChildren<BeamModule>(false);
int _numBeamModules = _beamModules == null ? 0 : _beamModules.Length;
for (int pmIdx = 0; pmIdx < _numBeamModules; pmIdx++)
{
BeamModule beamModule = _beamModules[pmIdx];
if (beamModule.isInitialised && beamModule.isBeamEnabled)
{
beamModule.transform.position += delta;
beamModule.lineRenderer.SetPosition(0, beamModule.lineRenderer.GetPosition(0) + delta);
beamModule.lineRenderer.SetPosition(1, beamModule.lineRenderer.GetPosition(1) + delta);
}
}
}
}
/// <summary>
/// Teleport or instantly move all active projectiles by an amount
/// in the x, y and z directions. This could be useful if changing
/// the origin or centre of your world to compensate for float-point
/// error.
/// </summary>
/// <param name="delta"></param>
public void TelePortProjectiles(Vector3 delta)
{
//if (isInitialised)
{
int numProjectileTemplates = projectileTemplatesList == null ? 0 : projectileTemplatesList.Count;
// Teleport all active Pooled Projectiles
for (int ptIdx = 0; ptIdx < numProjectileTemplates; ptIdx++)
{
ProjectileTemplate _projectileTemplate = projectileTemplatesList[ptIdx];
if (_projectileTemplate != null && _projectileTemplate.projectilePrefab != null && _projectileTemplate.projectilePrefab.usePooling)
{
// Examine each of the projectiles in the pool
for (int pmIdx = 0; pmIdx < _projectileTemplate.currentPoolSize; pmIdx++)
{
GameObject pmGO = _projectileTemplate.projectilePool[pmIdx];
if (pmGO.activeInHierarchy)
{
pmGO.transform.position += delta;
pmGO.GetComponent<ProjectileModule>().LastFramePosition += delta;
pmGO.GetComponent<ProjectileModule>().ThisFramePosition += delta;
}
}
}
}
// Teleport all active Non-Pooled Projectiles (they should probably all be active in the heierarchy)
if (projectileNonPooledTrfm != null)
{
ProjectileModule[] _projectileModules = projectileNonPooledTrfm.GetComponentsInChildren<ProjectileModule>(false);
int _numProjectileModules = _projectileModules == null ? 0 : _projectileModules.Length;
for (int pmIdx = 0; pmIdx < _numProjectileModules; pmIdx++)
{
_projectileModules[pmIdx].transform.position += delta;
_projectileModules[pmIdx].LastFramePosition += delta;
_projectileModules[pmIdx].ThisFramePosition += delta;
}
}
// Teleport DOTS projectiles
#if SSC_ENTITIES
// Are their potentially any DOTS projectiles to teleport?
if (isProjectileSytemUpdateRequired && !isProjectilesPaused && projectileSystem != null)
{
projectileSystem.TelePortProjectiles(delta);
}
#endif
}
}
#endregion
#region Public API Member Methods - Paths and Locations
/// <summary>
/// Add a new Location in the scene. The Location can then be added to one or more Paths.
/// Locations can be used with SSC AI and can appear on radar.
/// </summary>
/// <param name="locationData"></param>
/// <returns></returns>
public bool AddLocation (LocationData locationData)
{
if (locationDataList == null) { locationDataList = new List<LocationData>(10); }
if (locationDataList != null && locationData != null)
{
locationData.isUnassigned = false;
locationDataList.Add(locationData);
return true;
}
else { return false; }
}
/// <summary>
/// Same as calling AppendLocation(wsPosition, autoGenerateName)
/// </summary>
/// <param name="wsPosition"></param>
/// <param name="autoGenerateName"></param>
/// <returns></returns>
public LocationData AddLocation (Vector3 wsPosition, bool autoGenerateName)
{
return AppendLocation(wsPosition, autoGenerateName);
}
/// <summary>
/// Add a new Location in the scene. The Location can then be added to one or more Paths.
/// Locations can be used with SSC AI and can appear on radar.
/// If autoGenerateName is true, GC will be impacted.
/// </summary>
/// <param name="wsPosition"></param>
/// <param name="autoGenerateName"></param>
/// <returns></returns>
public LocationData AppendLocation(Vector3 wsPosition, bool autoGenerateName)
{
LocationData locationData = new LocationData()
{
position = wsPosition
};
if (locationDataList == null) { locationDataList = new List<LocationData>(10); }
if (locationDataList != null)
{
locationData.isUnassigned = false;
if (autoGenerateName) { locationData.name = "Location " + (locationDataList.Count+1).ToString(); }
locationDataList.Add(locationData);
}
return locationData;
}
/// <summary>
/// Add a new Location in the scene and add it to the Path.
/// If autoGenerateName is true, GC will be impacted.
/// refreshPath will update the path distances.
/// If adding many Locations, set refreshPath to false and call RefreshPathDistances(..)
/// for each Path manually.
/// </summary>
/// <param name="pathData"></param>
/// <param name="wsPosition"></param>
/// <param name="autoGenerateName"></param>
/// <param name="refreshPath"></param>
/// <returns></returns>
public LocationData AppendLocation(PathData pathData, Vector3 wsPosition, bool autoGenerateName, bool refreshPath)
{
LocationData locationData = AppendLocation(wsPosition, false);
if (locationData != null && pathData != null)
{
if (pathData.pathLocationDataList == null) { pathData.pathLocationDataList = new List<PathLocationData>(10); }
if (pathData.pathLocationDataList != null)
{
PathLocationData pathLocationData = new PathLocationData();
if (pathLocationData != null)
{
if (autoGenerateName)
{
// Give the new location a default name
locationData.name = (string.IsNullOrEmpty(pathData.name) ? "unknown" : pathData.name) + " " + (pathData.pathLocationDataList.Count+1).ToString();
}
pathLocationData.locationData = locationData;
pathLocationData.locationData.isUnassigned = false;
// Set the default tangent control points
// Get the last assigned point on the Path before the new Location is added
int prevLocationIdx = GetPreviousPathLocationIndex(pathData, pathData.pathLocationDataList.Count, pathData.isClosedCircuit);
if (prevLocationIdx >= 0)
{
LocationData prevLocation = pathData.pathLocationDataList[prevLocationIdx].locationData;
// Get the midpoint between the previous point and the new one
float midPointX = (wsPosition.x + prevLocation.position.x) / 2f;
float midPointY = (wsPosition.y + prevLocation.position.y) / 2f;
float midPointZ = (wsPosition.z + prevLocation.position.z) / 2f;
pathLocationData.inControlPoint.x = midPointX;
pathLocationData.inControlPoint.y = midPointY;
pathLocationData.inControlPoint.z = midPointZ;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - prevLocation.position) / 2f);
// Attempt to automatically fix control points on first point when adding second point
if (prevLocationIdx == 0)
{
PathLocationData firstLocationData = pathData.pathLocationDataList[prevLocationIdx];
if (firstLocationData != null)
{
firstLocationData.outControlPoint = pathLocationData.inControlPoint;
firstLocationData.inControlPoint = firstLocationData.locationData.position + ((firstLocationData.locationData.position - pathLocationData.inControlPoint).normalized * defaultPathControlOffset);
}
}
}
else
{
// default to left 2m and right 2m from position
pathLocationData.inControlPoint = wsPosition;
pathLocationData.inControlPoint.x -= defaultPathControlOffset;
pathLocationData.outControlPoint = wsPosition;
pathLocationData.outControlPoint.x += defaultPathControlOffset;
}
pathData.pathLocationDataList.Add(pathLocationData);
if (refreshPath) { RefreshPathDistances(pathData); }
else { pathData.isDirty = true; }
}
}
}
return locationData;
}
/// <summary>
/// Add a Location to the end of the Path by placing at the last Location on the Path.
/// Deselects the last Location and selects the new Location.
/// </summary>
/// <param name="pathData"></param>
/// <param name="autoGenerateName"></param>
/// <param name="refreshPath"></param>
/// <returns></returns>
public LocationData AppendLocation(PathData pathData, bool autoGenerateName, bool refreshPath)
{
LocationData locationData = null;
int numPathPoints = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
if (numPathPoints > 0)
{
// Get the last assigned Location in the Path
int lastIdx = GetPreviousPathLocationIndex(pathData, numPathPoints, false);
if (lastIdx >= 0)
{
PathLocationData lastPathLocationData = pathData.pathLocationDataList[lastIdx];
// Add a new Location to the end of the Path (don't refresh the Path before Control points are updated)
locationData = AppendLocation(pathData, lastPathLocationData.locationData.position, autoGenerateName, false);
if (locationData != null)
{
// Deselect the last Location
lastPathLocationData.locationData.selectedInSceneView = false;
lastPathLocationData.inControlSelectedInSceneView = false;
lastPathLocationData.outControlSelectedInSceneView = false;
locationData.selectedInSceneView = true;
PathLocationData newPathLocationData = pathData.pathLocationDataList[pathData.pathLocationDataList.Count-1];
newPathLocationData.inControlPoint = lastPathLocationData.inControlPoint;
newPathLocationData.outControlPoint = lastPathLocationData.outControlPoint;
if (refreshPath) { RefreshPathDistances(pathData); }
else { pathData.isDirty = true; }
}
}
}
return locationData;
}
/// <summary>
/// Add a new Location in the scene. The Location can then be added to one or more Paths.
/// Locations can be used with SSC AI.
/// </summary>
/// <param name="locationGameObject"></param>
/// <returns></returns>
public LocationData AppendLocation(GameObject locationGameObject)
{
if (locationGameObject != null)
{
LocationData locationData = new LocationData()
{
position = locationGameObject.transform.position,
// The gameobject instance id is not serialized
gameObjectInstanceId = locationGameObject.GetInstanceID()
};
if (locationDataList == null) { locationDataList = new List<LocationData>(10); }
if (locationDataList != null)
{
locationData.isUnassigned = false;
locationData.name = locationGameObject.name;
locationDataList.Add(locationData);
}
return locationData;
}
else { return null; }
}
/// <summary>
/// Add a path to the scene. Alternatively, use CreatePath().
/// </summary>
/// <param name="newPath"></param>
public void AddPath(PathData newPath)
{
if (newPath != null) { pathDataList.Add(newPath); }
#if UNITY_EDITOR
else { Debug.LogWarning("SSCManager.AddPath - the PathData parameter is null"); }
#endif
}
/// <summary>
/// Create a new path and return the reference to the PathData.
/// </summary>
/// <returns></returns>
public PathData CreatePath()
{
PathData newPath = new PathData();
if (newPath != null) { pathDataList.Add(newPath); }
return newPath;
}
/// <summary>
/// Delete a location and remove from radar if required
/// </summary>
/// <param name="locationData"></param>
public void DeleteLocation(LocationData locationData)
{
if (locationData != null)
{
// De-select all locations
int numLocations = locationDataList == null ? 0 : locationDataList.Count;
for (int idx = 0; idx < numLocations; idx++)
{
if (locationDataList[idx] != null) { locationDataList[idx].selectedInSceneView = false; }
}
locationData.selectedInSceneView = true;
// Do we need to tell the radar system to stop tracking this Location?
if (isInitialised && locationData.isRadarEnabled && locationData.RadarId >= 0 && sscRadar != null && sscRadar.IsInitialised)
{
sscRadar.RemoveItem(locationData.RadarId);
locationData.isRadarEnabled = false;
locationData.radarItemIndex = -1;
}
DeleteSelectedLocations(true);
}
}
/// <summary>
/// Insert a new Location before the zero-based point on the Path.
/// Has no effect if there are not no points in the Path.
/// </summary>
/// <param name="pathData"></param>
/// <param name="insertIndex"></param>
/// <param name="autoGenerateName"></param>
/// <param name="refreshPath"></param>
/// <returns></returns>
public LocationData InsertLocationAt (PathData pathData, int insertIndex, bool autoGenerateName, bool refreshPath)
{
LocationData locationData = null;
LocationData prevLocation = null;
LocationData nextLocation = null;
if (pathData != null)
{
if (pathData.pathLocationDataList == null) { pathData.pathLocationDataList = new List<PathLocationData>(10); }
int numPathLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
if (numPathLocations > 0)
{
// If attempting to insert past the end of the path, simply append.
if (insertIndex > numPathLocations)
{
locationData = AppendLocation(pathData, autoGenerateName, refreshPath);
}
else if (insertIndex >= 0)
{
// Get the prev assigned point on the Path
prevLocation = GetPreviousLocation(pathData, insertIndex, pathData.isClosedCircuit);
// The "next" location will be the insert point
nextLocation = pathData.pathLocationDataList[insertIndex].locationData;
if (prevLocation == null || nextLocation == null)
{
// Insert at beginning of path
locationData = AppendLocation(pathData.pathLocationDataList[0].locationData.position, false);
}
else
{
// Get the midpoint between the previous point and the next one
Vector3 wsPosition = new Vector3();
wsPosition.x = (nextLocation.position.x + prevLocation.position.x) / 2f;
wsPosition.y = (nextLocation.position.y + prevLocation.position.y) / 2f;
wsPosition.z = (nextLocation.position.z + prevLocation.position.z) / 2f;
locationData = AppendLocation(wsPosition, false);
}
}
// If we created a Location, now insert it into the Path
if (locationData != null)
{
// Unselect all locations on the path
SelectPathLocations(pathData, false);
PathLocationData pathLocationData = new PathLocationData();
if (pathLocationData != null)
{
if (autoGenerateName)
{
// Give the new location a default (inserted) name
locationData.name = (string.IsNullOrEmpty(pathData.name) ? "unknown" : pathData.name) + " " + insertIndex.ToString() + "a";
}
pathLocationData.locationData = locationData;
pathLocationData.locationData.isUnassigned = false;
Vector3 wsPosition = locationData.position;
// Set the default tangent control points
if (prevLocation != null)
{
// Get the midpoint between the previous point and the new one
float midPointX = (wsPosition.x + prevLocation.position.x) / 2f;
float midPointY = (wsPosition.y + prevLocation.position.y) / 2f;
float midPointZ = (wsPosition.z + prevLocation.position.z) / 2f;
pathLocationData.inControlPoint.x = midPointX;
pathLocationData.inControlPoint.y = midPointY;
pathLocationData.inControlPoint.z = midPointZ;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - prevLocation.position) / 2f);
}
else if (nextLocation != null)
{
// Get the midpoint between the insert point and the new one
float midPointX = (wsPosition.x + nextLocation.position.x) / 2f;
float midPointY = (wsPosition.y + nextLocation.position.y) / 2f;
float midPointZ = (wsPosition.z + nextLocation.position.z) / 2f;
pathLocationData.inControlPoint.x = midPointX;
pathLocationData.inControlPoint.y = midPointY;
pathLocationData.inControlPoint.z = midPointZ;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - nextLocation.position) / 2f);
}
else
{
// default to left 2m and right 2m from position
pathLocationData.inControlPoint = wsPosition;
pathLocationData.inControlPoint.x -= defaultPathControlOffset;
pathLocationData.outControlPoint = wsPosition;
pathLocationData.outControlPoint.x += defaultPathControlOffset;
}
if (insertIndex < numPathLocations)
{
pathData.pathLocationDataList.Insert(insertIndex, pathLocationData);
}
else { pathData.pathLocationDataList.Add(pathLocationData); }
if (refreshPath) { RefreshPathDistances(pathData); }
else { pathData.isDirty = true; }
}
else { locationData = null; }
}
}
}
return locationData;
}
/// <summary>
/// Update the Location's position. This also updates tangent control points for
/// any Paths that the Location is a member of.
/// refreshPath will update the path distances for all affected Paths.
/// If updating many Locations, set refreshPath to false and call RefreshPathDistances(..)
/// for each Path manually.
/// If enabled for radar, update the radar item.
/// </summary>
/// <param name="locationData"></param>
/// <param name="wsPosition"></param>
/// <param name="refreshPath"></param>
public void UpdateLocation(LocationData locationData, Vector3 wsPosition, bool refreshPath)
{
Vector3 currentPosition = locationData.position;
Vector3 deltaPosition = wsPosition - currentPosition;
bool isPathAffected = false;
// Update the Location before updating Paths so that it is correct
// when Path Distances are refreshed.
locationData.position = wsPosition;
// Check to see if radar is enabled, and update the wsPosition
// NOTE: This hasn't been fully tested...
if (locationData.isRadarEnabled && locationData.RadarId >= 0 && isInitialised && sscRadar != null && sscRadar.IsInitialised)
{
sscRadar.SetItemPosition(locationData.radarItemIndex, wsPosition);
}
int numPaths = pathDataList == null ? 0 : pathDataList.Count;
for (int pIdx = 0; pIdx < numPaths; pIdx++)
{
PathData _pathData = pathDataList[pIdx];
if (_pathData != null)
{
isPathAffected = false;
List<PathLocationData> _pathLocationList = _pathData.pathLocationDataList;
int numLocationsInList = _pathLocationList == null ? 0 : _pathLocationList.Count;
for (int lIdx = 0; lIdx < numLocationsInList; lIdx++)
{
// Get the location within the Path
PathLocationData _pathLocation = _pathLocationList[lIdx];
// If this Location in the Path is the same one being updated, then updated the control points
if (_pathLocation != null && _pathLocation.locationData != null && _pathLocation.locationData.guidHash == locationData.guidHash)
{
_pathLocation.inControlPoint += deltaPosition;
_pathLocation.outControlPoint += deltaPosition;
isPathAffected = true;
}
}
if (isPathAffected) { if (refreshPath) { RefreshPathDistances(_pathData); } else { _pathData.isDirty = true; } }
}
}
}
/// <summary>
/// Set the initial or starting positions of Locations and control points along a dynamic (moveable) Path
/// at runtime. Currently used with ShipDockingStation entry and exit paths that are attached
/// to a mothership.
/// NOTE: Assumes the Locations in each Path are not members of any other Paths in the scene.
/// Paths will only be initialised once.
/// </summary>
/// <param name="pathData"></param>
/// <param name="dockingStationPosition"></param>
/// <param name="offsetPosition">The offset from the original position of the docking station when the paths where setup in the scene</param>
/// <param name="deltaRotation">The amount the docking station has been rotated after the paths were setup in the scene</param>
/// <param name="pathVelocity"></param>
/// <param name="pathAngularVelocity"></param>
public void InitialiseLocations (PathData pathData, Vector3 dockingStationPosition, Vector3 offsetPosition, Quaternion deltaRotation, Vector3 pathVelocity, Vector3 pathAngularVelocity)
{
// Only update the locations if they Path hasn't already been updated.
// This prevents the Location position from potentially being offset from the original position
// multiple times.
if (pathData != null && !pathData.isDynamicPathInitialised)
{
int numLocationsInList = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
pathData.isDynamicPathInitialised = true;
pathData.anchorPoint = dockingStationPosition;
pathData.worldVelocity = pathVelocity;
pathData.worldAngularVelocity = pathAngularVelocity;
for (int lIdx = 0; lIdx < numLocationsInList; lIdx++)
{
PathLocationData _pathLocationData = pathData.pathLocationDataList[lIdx];
LocationData _locationData = GetLocation(_pathLocationData.locationData.guidHash);
if (_locationData != null)
{
// Add the position offset, then rotate the result around the pivot point (position) of the Docking Station
_locationData.initialPosition = (deltaRotation * (_locationData.position + offsetPosition - dockingStationPosition)) + dockingStationPosition;
_locationData.position = _locationData.initialPosition;
// Keep PathLocation and Location's in sync
_pathLocationData.locationData.position = _locationData.position;
// Add the position offset to the control points, then rotate the result around the pivot point (position) of the Docking Station
_pathLocationData.inInitialControlPoint = (deltaRotation * (_pathLocationData.inControlPoint + offsetPosition - dockingStationPosition)) + dockingStationPosition;
_pathLocationData.outInitialControlPoint = (deltaRotation * (_pathLocationData.outControlPoint + offsetPosition - dockingStationPosition)) + dockingStationPosition;
_pathLocationData.inControlPoint = _pathLocationData.inInitialControlPoint;
_pathLocationData.outControlPoint = _pathLocationData.outInitialControlPoint;
}
}
}
}
/// <summary>
/// Designed specifically for runtime movement of a Path, it assumes the Locations of the Path are NOT members of any other Path.
/// Position and Rotation deltas are from the CURRENT positions and rotations. Works similar to TelePort API methods.
/// </summary>
/// <param name="pathData"></param>
/// <param name="updateSeqNumber"></param>
/// <param name="deltaPosition">Difference between CURRENT position and new position</param>
/// <param name="deltaRotation">Difference between CURRENT rotation and new rotation</param>
public void MoveLocations (PathData pathData, float updateSeqNumber, Vector3 deltaPosition, Quaternion deltaRotation)
{
// Check if path is valid and hasn't already been updated this frame
if (pathData != null && pathData.updateSeqNumber != updateSeqNumber)
{
// Mark this path updated in this frame
pathData.updateSeqNumber = updateSeqNumber;
int numLocationsInList = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
for (int lIdx = 0; lIdx < numLocationsInList; lIdx++)
{
PathLocationData _pathLocationData = pathData.pathLocationDataList[lIdx];
LocationData _locationData = GetLocation(_pathLocationData.locationData.guidHash);
if (_locationData != null)
{
_locationData.position = deltaRotation * (_locationData.position + deltaPosition);
// Keep PathLocation and Location's in sync
_pathLocationData.locationData.position = _locationData.position;
_pathLocationData.inControlPoint = deltaRotation * (_pathLocationData.inControlPoint + deltaPosition);
_pathLocationData.outControlPoint = deltaRotation * (_pathLocationData.outControlPoint + deltaPosition);
}
}
}
}
/// <summary>
/// Designed specifically for runtime movement of a Path, it assumes the Locations of the Path are NOT members
/// of any other Path.
/// Position and Rotation deltas are from the INITIAL or starting positions and rotations.
/// See also InitialiseLocations(..).
/// </summary>
/// <param name="pathData"></param>
/// <param name="updateSeqNumber">Typically Time.time, it is used to determine if the Path has already been updated this frame</param>
/// <param name="dockingStationPosition"></param>
/// <param name="deltaPosition">Difference between initial or starting position and new position</param>
/// <param name="deltaRotation">Difference between initial or starting rotation and new rotation</param>
/// <param name="pathVelocity"></param>
/// <param name="pathAngularVelocity"></param>
public void MoveLocations(PathData pathData, float updateSeqNumber, Vector3 dockingStationPosition, Vector3 deltaPosition, Quaternion deltaRotation, Vector3 pathVelocity, Vector3 pathAngularVelocity)
{
// Check if path is valid and hasn't already been updated this frame
if (pathData != null && pathData.updateSeqNumber != updateSeqNumber)
{
// Mark this path updated in this frame
pathData.updateSeqNumber = updateSeqNumber;
pathData.anchorPoint = dockingStationPosition;
pathData.worldVelocity = pathVelocity;
pathData.worldAngularVelocity = pathAngularVelocity;
int numLocationsInList = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
for (int lIdx = 0; lIdx < numLocationsInList; lIdx++)
{
PathLocationData _pathLocationData = pathData.pathLocationDataList[lIdx];
LocationData _locationData = GetLocation(_pathLocationData.locationData.guidHash);
if (_locationData != null)
{
_locationData.position = (deltaRotation * (_locationData.initialPosition + deltaPosition - dockingStationPosition)) + dockingStationPosition;
// Keep PathLocation and Location's in sync
_pathLocationData.locationData.position = _locationData.position;
_pathLocationData.inControlPoint = (deltaRotation * (_pathLocationData.inInitialControlPoint + deltaPosition - dockingStationPosition)) + dockingStationPosition;
_pathLocationData.outControlPoint = (deltaRotation * (_pathLocationData.outInitialControlPoint + deltaPosition - dockingStationPosition)) + dockingStationPosition;
}
}
}
}
/// <summary>
/// Given a Location in a Path, being moved, update all other selected Locations in the same Path at the same time.
/// NOTE: As this uses delegates, it may incur some GC overhead.
/// </summary>
/// <param name="pathData"></param>
/// <param name="locationDataMoved"></param>
/// <param name="wsPosition"></param>
/// <param name="refreshPath"></param>
public void MoveLocations(PathData pathData, LocationData locationDataMoved, Vector3 wsPosition, bool refreshPath)
{
if (pathData != null && pathData.pathLocationDataList != null && locationDataMoved != null)
{
// Does the Location exist at least once in the Path?
if (pathData.pathLocationDataList.Exists(loc => loc.locationData != null && loc.locationData.guidHash == locationDataMoved.guidHash))
{
int numPathLocations = pathData.pathLocationDataList.Count;
Vector3 deltaPosition = wsPosition - locationDataMoved.position;
bool isPathAffected = false;
for (int plIdx = 0; plIdx < numPathLocations; plIdx++)
{
PathLocationData pathLocationData = pathData.pathLocationDataList[plIdx];
if (pathLocationData != null && pathLocationData.locationData != null && pathLocationData.locationData.selectedInSceneView && !pathLocationData.locationData.isUnassigned)
{
// Move Location
pathLocationData.locationData.position += deltaPosition;
// Update Control Points
pathLocationData.inControlPoint += deltaPosition;
pathLocationData.outControlPoint += deltaPosition;
isPathAffected = true;
}
}
if (isPathAffected) { if (refreshPath) { RefreshPathDistances(pathData); } else { pathData.isDirty = true; } }
}
}
}
/// <summary>
/// Given a Location being moved, update all other selected Locations in the scene at the same time.
/// If they are members of Paths, update those Paths.
/// </summary>
/// <param name="locationDataMoved"></param>
/// <param name="wsPosition"></param>
/// <param name="refreshPaths"></param>
public void MoveLocations(LocationData locationDataMoved, Vector3 wsPosition, bool refreshPaths)
{
if (locationDataMoved != null)
{
int numInList = locationDataList == null ? 0 : locationDataList.Count;
Vector3 deltaPosition = wsPosition - locationDataMoved.position;
// Update other selected Locations
for (int lIdx = 0; lIdx < numInList; lIdx++)
{
LocationData locationData = locationDataList[lIdx];
if (locationData != null && locationData.selectedInSceneView && locationData.guidHash != locationDataMoved.guidHash)
{
UpdateLocation(locationData, locationData.position + deltaPosition, false);
}
}
// Update the position of the Location being dragged in the scene
UpdateLocation(locationDataMoved, wsPosition, false);
if (refreshPaths)
{
// Refresh any stale Path distances
int numPaths = pathDataList == null ? 0 : pathDataList.Count;
for (int pIdx = 0; pIdx < numPaths; pIdx++)
{
if (pathDataList[pIdx].isDirty) { RefreshPathDistances(pathDataList[pIdx]); }
}
}
}
}
/// <summary>
/// Delete all the selected Locations, including any Location slots associated
/// with a Path.
/// refreshPath will update the path distances for all affected Paths.
/// </summary>
public void DeleteSelectedLocations(bool refreshPath)
{
int numInList = locationDataList == null ? 0 : locationDataList.Count;
bool isPathAffected = false;
// Loop backwards through the Locations
for (int idx = numInList - 1; idx >= 0; idx--)
{
LocationData locationData = locationDataList[idx];
if (locationData != null && locationData.selectedInSceneView)
{
// Remove any Location slots in Paths
// Loop through all the paths
int numPathsInList = pathDataList == null ? 0 : pathDataList.Count;
for (int pIdx = 0; pIdx < numPathsInList; pIdx++)
{
PathData _pathData = pathDataList[pIdx];
int numPathLocationsInList = _pathData != null && _pathData.pathLocationDataList != null ? _pathData.pathLocationDataList.Count : 0;
isPathAffected = false;
// Loop backwards through the list and remove Location slots where there is a matching Location
for (int plIdx = numPathLocationsInList-1; plIdx >= 0; plIdx--)
{
PathLocationData _pathLocationData = _pathData.pathLocationDataList[plIdx];
if (_pathLocationData != null && _pathLocationData.locationData != null && _pathLocationData.locationData.guidHash == locationData.guidHash)
{
// Remove the Path Location slot
_pathLocationData.locationData = null;
_pathData.pathLocationDataList.RemoveAt(plIdx);
isPathAffected = true;
}
}
if (isPathAffected) { if (refreshPath) { RefreshPathDistances(_pathData); } else { _pathData.isDirty = true; } }
}
// Delete the Location
locationData = null;
locationDataList.RemoveAt(idx);
}
}
}
/// <summary>
/// Attempt to snap selected Locations on a Path to the closes mesh within the mesh Layer Mask.
/// The location is placed with an offset from mesh. This 'snap' distance is the pathData.locationDefaultNormalOffset.
/// It can be set at runtime or in the SSCManager editor.
/// </summary>
/// <param name="pathData"></param>
/// <param name="upDirection">The up direction from the mesh being snapped to</param>
/// <param name="meshLayerMask">e.g. meshLayerMask = ~0;</param>
/// <param name="refreshPath"></param>
public void SnapPathSelectedLocationsToMesh(PathData pathData, Vector3 upDirection, LayerMask meshLayerMask, bool refreshPath)
{
bool isPathAffected = false;
int numPathLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
PathLocationData tempPathLocationData = null;
Vector3 locPosition;
Vector3 offsetVector = upDirection * pathData.locationDefaultNormalOffset;
Ray raycastRay = new Ray(Vector3.zero, Vector3.down);
RaycastHit raycastHitInfo;
float maxCheckDistance = pathData.snapMaxHeight - pathData.snapMinHeight;
raycastRay.direction = -upDirection;
for (int lIdx = 0; lIdx < numPathLocations; lIdx++)
{
// Only process assigned and selected Locations in the Path
tempPathLocationData = pathData.pathLocationDataList[lIdx];
if (tempPathLocationData != null && tempPathLocationData.locationData != null &&
!tempPathLocationData.locationData.isUnassigned && tempPathLocationData.locationData.selectedInSceneView)
{
locPosition = tempPathLocationData.locationData.position;
raycastRay.origin = locPosition + (upDirection * 0.001f);
if (Physics.Raycast(raycastRay, out raycastHitInfo, maxCheckDistance, meshLayerMask, QueryTriggerInteraction.Ignore))
{
// Update the actual location, not just the reference attached to the Path.
LocationData locationData = GetLocation(tempPathLocationData.locationData.guidHash);
Vector3 newPosition = raycastHitInfo.point + offsetVector;
// Ensure the new Location position is within acceptable limits
if (newPosition.y >= pathData.snapMinHeight && newPosition.y <= pathData.snapMaxHeight)
{
isPathAffected = true;
UpdateLocation(locationData, newPosition, false);
}
}
}
}
if (isPathAffected) { if (refreshPath) { RefreshPathDistances(pathData); } else { pathData.isDirty = true; } }
}
/// <summary>
/// Attempt to snap the Path locations to a mesh above or below the current location. The location is
/// placed with an offset from mesh. This 'snap' distance is the pathData.locationDefaultNormalOffset.
/// It can be set at runtime or in the SSCManager editor.
/// The method checks for a mesh between pathData.snapMinHeight and snapMaxHeight in the upDirection.
/// </summary>
/// <param name="pathData"></param>
/// <param name="upDirection">The up direction from the mesh being snapped to</param>
/// <param name="meshLayerMask">e.g. meshLayerMask = ~0;</param>
/// <param name="refreshPath"></param>
public void SnapPathToMesh(PathData pathData, Vector3 upDirection, LayerMask meshLayerMask, bool refreshPath)
{
if (pathData != null)
{
bool isPathAffected = false;
int numPathLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
PathLocationData tempPathLocationData = null;
Vector3 locPosition;
Vector3 rayOrigin = Vector3.zero + (upDirection * pathData.snapMaxHeight);
Vector3 offsetVector = upDirection * pathData.locationDefaultNormalOffset;
Ray raycastRay = new Ray(rayOrigin, Vector3.down);
RaycastHit raycastHitInfo;
float maxCheckDistance = pathData.snapMaxHeight - pathData.snapMinHeight;
raycastRay.direction = -upDirection;
for (int lIdx = 0; lIdx < numPathLocations; lIdx++)
{
tempPathLocationData = pathData.pathLocationDataList[lIdx];
if (tempPathLocationData == null || tempPathLocationData.locationData == null || tempPathLocationData.locationData.isUnassigned) { continue; }
else
{
isPathAffected = true;
locPosition = tempPathLocationData.locationData.position;
rayOrigin.x = locPosition.x;
rayOrigin.z = locPosition.z;
raycastRay.origin = rayOrigin;
if (Physics.Raycast(raycastRay, out raycastHitInfo, maxCheckDistance, meshLayerMask, QueryTriggerInteraction.Ignore))
{
// Update the actual location, not just the reference attached to the Path.
LocationData locationData = GetLocation(tempPathLocationData.locationData.guidHash);
UpdateLocation(locationData, raycastHitInfo.point + offsetVector, false);
}
}
}
if (isPathAffected) { if (refreshPath) { RefreshPathDistances(pathData); } else { pathData.isDirty = true; } }
}
}
/// <summary>
/// Attempt to snap the Location to a mesh above or below the current position. The location is
/// placed with an offset from mesh. This 'snap' distance is the offset.
/// </summary>
/// <param name="locationData"></param>
/// <param name="offset">The distance 'above' the mesh the Location should be placed</param>
/// <param name="snapMinHeight">The minimum height of the mesh to check for</param>
/// <param name="snapMaxHeight">The maximum height of the mesh to check for</param>
/// <param name="upDirection">The up direction from the mesh being snapped to</param>
/// <param name="meshLayerMask">e.g. meshLayerMask = ~0;</param>
/// <param name="refreshPaths">Refresh the lengths of all Paths that contain this Location</param>
public void SnapLocationToMesh(LocationData locationData, float offset, float snapMinHeight, float snapMaxHeight, Vector3 upDirection, LayerMask meshLayerMask, bool refreshPaths)
{
if (locationData != null)
{
Vector3 rayOrigin = Vector3.zero + (upDirection * snapMaxHeight);
Vector3 offsetVector = upDirection * offset;
Ray raycastRay = new Ray(rayOrigin, Vector3.down);
RaycastHit raycastHitInfo;
Vector3 locPosition = locationData.position;
rayOrigin.x = locPosition.x;
rayOrigin.z = locPosition.z;
raycastRay.origin = rayOrigin;
if (Physics.Raycast(raycastRay, out raycastHitInfo, snapMaxHeight - snapMinHeight, meshLayerMask, QueryTriggerInteraction.Ignore))
{
UpdateLocation(locationData, raycastHitInfo.point + offsetVector, refreshPaths);
}
}
}
/// <summary>
/// Get the first Location on a given Path
/// </summary>
/// <param name="pathData"></param>
/// <returns></returns>
public LocationData GetFirstLocation(PathData pathData)
{
// We use GetLocation(..) to ensure the path point contains an actual Location
if (pathData == null || pathData.pathLocationDataList == null || locationDataList == null || pathData.pathLocationDataList.Count < 1 || locationDataList.Count < 1) { return null; }
else { return GetLocation(pathData.pathLocationDataList[0].locationData.guidHash); }
}
/// <summary>
/// Get the last Location on a given Path
/// </summary>
/// <param name="pathData"></param>
/// <returns></returns>
public LocationData GetLastLocation(PathData pathData)
{
int numLocations = pathData == null || pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
// We use GetLocation(..) to ensure the path point contains an actual Location
if (numLocations < 1) { return null; }
else { return GetLocation(pathData.pathLocationDataList[numLocations-1].locationData.guidHash); }
}
/// <summary>
/// Get a Location given the unique guidHash code of the Location
/// </summary>
/// <param name="guidHash"></param>
/// <returns></returns>
public LocationData GetLocation(int guidHash)
{
if (locationDataList == null) { return null; }
else { return locationDataList.Find(loc => loc.guidHash == guidHash); }
}
/// <summary>
/// Get the first Location given the name of the Location. locationName is not case sensitive.
/// If the locationName parameter is empty or null, no Location will be returned even if
/// there are Locations without a name.
/// NOTE: When possible, use GetLocation(guidHash).
/// </summary>
/// <param name="locationName"></param>
/// <returns></returns>
public LocationData GetLocation(string locationName)
{
if (locationDataList == null || string.IsNullOrEmpty(locationName)) { return null; }
else { return locationDataList.Find(loc => !string.IsNullOrEmpty(loc.name) && loc.name.ToLower() == locationName.ToLower()); }
}
/// <summary>
/// If a location was added with AppendLocation(gameObject), it can also be retrieved at runtime with
/// this method by passing in the gameobject.GetInstanceID().
/// </summary>
/// <param name="gameObjectInstanceID"></param>
/// <returns></returns>
public LocationData GetLocationByGameObjectID(int gameObjectInstanceID)
{
LocationData locationData = null;
LocationData tempLocationData = null;
int _numLocations = locationDataList == null ? 0 : locationDataList.Count;
if (_numLocations > 0 && gameObjectInstanceID != 0)
{
for (int lIdx = 0; lIdx < _numLocations; lIdx++)
{
tempLocationData = locationDataList[lIdx];
if (tempLocationData != null && tempLocationData.gameObjectInstanceId == gameObjectInstanceID)
{
locationData = tempLocationData;
break;
}
}
}
return locationData;
}
/// <summary>
/// Get a Path given the unique guidHash code of the Path
/// </summary>
/// <param name="guidHash"></param>
/// <returns></returns>
public PathData GetPath(int guidHash)
{
if (pathDataList == null) { return null; }
else { return pathDataList.Find(p => p.guidHash == guidHash); }
}
/// <summary>
/// Get the first Path given the name of the Path. pathName is not case sensitive.
/// If the pathName parameter is empty or null, no Path will be returned even if
/// there are Paths without a name.
/// NOTE: When possible, use GetPath(guidHash).
/// </summary>
/// <param name="pathName"></param>
/// <returns></returns>
public PathData GetPath(string pathName)
{
if (pathDataList == null || string.IsNullOrEmpty(pathName)) { return null; }
else { return pathDataList.Find(p => !string.IsNullOrEmpty(p.name) && p.name.ToLower() == pathName.ToLower()); }
}
/// <summary>
/// Attempt to reverse the direction of a path
/// </summary>
/// <param name="pathData"></param>
public void ReversePath (PathData pathData)
{
if (pathData != null && pathData.pathLocationDataList != null)
{
int numPathLocations = pathData.pathLocationDataList.Count;
PathLocationData pathLocationData = null;
if (numPathLocations > 1)
{
pathData.pathLocationDataList.Reverse();
RefreshPathDistances(pathData);
// Reverse the control points
for (int plIdx = 0; plIdx < numPathLocations; plIdx++)
{
pathLocationData = pathData.pathLocationDataList[plIdx];
if (pathLocationData != null && pathLocationData.locationData != null && !pathLocationData.locationData.isUnassigned)
{
// Reverse Control Points
Vector3 inControlPoint = pathLocationData.inControlPoint;
pathLocationData.inControlPoint = pathLocationData.outControlPoint;
pathLocationData.outControlPoint = inControlPoint;
}
}
}
}
}
/// <summary>
/// Recalculate the distances that are stored in the PathLocationData instances.
/// </summary>
/// <param name="pathData"></param>
public void RefreshPathDistances(PathData pathData)
{
if (pathData != null && pathData.pathLocationDataList != null)
{
int numLocationsInList = pathData.pathLocationDataList.Count;
PathLocationData pathLocationData = null;
float cumulativeDistance = 0f;
if (numLocationsInList > 0)
{
pathLocationData = pathData.pathLocationDataList[0];
pathLocationData.distanceCumulative = 0f;
pathLocationData.distanceFromPreviousLocation = 0f;
float sectionDistance = 0f;
int numSegments = numPathSegmentsForEstimate;
for (int plIdx = 1; plIdx < numLocationsInList; plIdx++)
{
pathLocationData = pathData.pathLocationDataList[plIdx];
// If the length of the Path section is unknown, first do a estimate with only a few segments,
// else get the number of segments based on the previous length of the Path section.
numSegments = CalcPathSegments(pathData, plIdx - 1, pathLocationData.distanceFromPreviousLocation);
if (SSCMath.GetDistanceBetweenPathPoints(pathData, plIdx-1, numSegments, ref sectionDistance))
{
cumulativeDistance += sectionDistance;
pathLocationData.distanceCumulative = cumulativeDistance;
pathLocationData.distanceFromPreviousLocation = sectionDistance;
}
else
{
pathLocationData.distanceCumulative = 0f;
pathLocationData.distanceFromPreviousLocation = 0f;
}
}
// Deal with closed circuit
// You "might" want a "circuit" consisting of just 2 points.
// Most circuits should have at least 3 points.
if (pathData.isClosedCircuit && numLocationsInList > 1)
{
pathLocationData = pathData.pathLocationDataList[0];
numSegments = CalcPathSegments(pathData, numLocationsInList - 1, pathLocationData.distanceFromPreviousLocation);
if (SSCMath.GetDistanceBetweenPathPoints(pathData, numLocationsInList - 1, numSegments, ref sectionDistance))
{
cumulativeDistance += sectionDistance;
pathLocationData.distanceCumulative = cumulativeDistance;
pathLocationData.distanceFromPreviousLocation = sectionDistance;
}
}
}
pathData.splineTotalDistance = cumulativeDistance;
pathData.isDirty = false;
}
}
#endregion
#region Public API Member Methods - Radar
/// <summary>
/// Enable radar for a stationary Location if SSCManager is initialised.
/// </summary>
/// <param name="locationData"></param>
public void EnableRadar(LocationData locationData)
{
if (locationData != null && isInitialised)
{
// Not assigned in the radar system
locationData.radarItemIndex = -1;
if (sscRadar == null) { sscRadar = SSCRadar.GetOrCreateRadar(); }
if (sscRadar != null)
{
SSCRadarItem sscRadarItem = new SSCRadarItem();
sscRadarItem.radarItemType = SSCRadarItem.RadarItemType.Location;
sscRadarItem.isVisibleToRadar = true;
sscRadarItem.guidHash = locationData.guidHash;
sscRadarItem.position = locationData.position;
sscRadarItem.factionId = locationData.factionId;
sscRadarItem.squadronId = -1; // NOT SET
sscRadarItem.blipSize = locationData.radarBlipSize;
// Create a packet to be used to send data to the radar system
//SSCRadarPacket sscRadarPacket = new SSCRadarPacket();
locationData.radarItemIndex = sscRadar.AddItem(sscRadarItem);
locationData.isRadarEnabled = true;
}
}
}
/// <summary>
/// The Location will no longer be visible in the radar system.
/// If you want to change the visibility to other
/// radar consumers, consider changing the radar item data rather
/// than disabling the radar and (later) calling EnableRadar again.
/// </summary>
public void DisableRadar(LocationData locationData)
{
if (isInitialised)
{
if (locationData.isRadarEnabled && sscRadar != null)
{
sscRadar.RemoveItem(locationData.RadarId);
}
locationData.isRadarEnabled = false;
locationData.radarItemIndex = -1;
}
}
#endregion
}
#region ProjectileTemplate
/// <summary>
/// Class containing data for a projectile template. Used by SSCManager.
/// </summary>
public class ProjectileTemplate
{
public ProjectileModule projectilePrefab;
public int instanceID;
public int currentPoolSize;
public List<GameObject> projectilePool;
#if SSC_ENTITIES
public EntityArchetype projectileEntityArchetype;
public Entity projectilePrefabEntity;
#endif
// Class constructor
// SSCManager must be initialised before this is called when DOTS/ECS is used
public ProjectileTemplate (ProjectileModule prefab, int id)
{
this.projectilePrefab = prefab;
this.instanceID = id;
this.currentPoolSize = 0;
this.projectilePool = null;
#if SSC_ENTITIES
#region Construct Entity Archetype
if (prefab.useECS)
{
World defaultWorld = SSCManager.sscWorld;
// U2019.3 introduced Entities 0.2.0
#if UNITY_2019_3_OR_NEWER || UNITY_ENTITIES_0_2_0_OR_NEWER
GameObjectConversionSettings conversionSettings = GameObjectConversionSettings.FromWorld(defaultWorld, SSCManager.blobAssetStore);
projectilePrefabEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab.gameObject, conversionSettings);
#else
// U2019.1, 2019.2 Entities 0.012 preview 33 to 0.1.1 preview.
projectilePrefabEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab.gameObject, defaultWorld);
#endif
ComponentType[] archetypeComponentTypeArray = new ComponentType[4];
// With ConvertGameObjectHierarchy not sure if we need Translation and Rotation here given they are automatically added
archetypeComponentTypeArray[0] = typeof(Translation);
archetypeComponentTypeArray[1] = typeof(Rotation);
archetypeComponentTypeArray[2] = typeof(Projectile);
// NOTE: Requires hybrid renderer, has been tested using ECS 0.0.12 preview-30 and Hybrid Renderer 0.0.1 preview-10
archetypeComponentTypeArray[3] = typeof(RenderMesh);
this.projectileEntityArchetype = SSCManager.entityManager.CreateArchetype(archetypeComponentTypeArray);
}
#endregion
#endif
}
}
#endregion
#region BeamTemplate
/// <summary>
/// Class containing data for a beam template. Used by SSCManager.
/// </summary>
public class BeamTemplate
{
public BeamModule beamPrefab;
public int instanceID;
public int currentPoolSize;
public List<GameObject> beamPoolList;
// Class constructor
public BeamTemplate (BeamModule prefab, int id)
{
beamPrefab = prefab;
instanceID = id;
currentPoolSize = 0;
beamPoolList = null;
}
}
#endregion
#region DestructTemplate
/// <summary>
/// Class containing data for a destruct template. Used by SSCManager.
/// </summary>
public class DestructTemplate
{
public DestructModule destructPrefab;
public int instanceID;
public int currentPoolSize;
public List<GameObject> destructPoolList;
// Class constructor
public DestructTemplate (DestructModule prefab, int id)
{
destructPrefab = prefab;
instanceID = id;
currentPoolSize = 0;
destructPoolList = null;
}
}
#endregion
#region EffectsObjectTemplate
/// <summary>
/// Class containing data for an effects object template. Used by SSCManager.
/// </summary>
public class EffectsObjectTemplate
{
public EffectsModule effectsObjectPrefab;
public int instanceID;
public int currentPoolSize;
public List<GameObject> effectsObjectPool;
// Class constructor
public EffectsObjectTemplate (EffectsModule prefab, int id)
{
this.effectsObjectPrefab = prefab;
this.instanceID = id;
this.currentPoolSize = 0;
this.effectsObjectPool = null;
}
}
#endregion
#region Public Structures
/// <summary>
/// Parameters structure for use with sscManager.InstantiateProjectile(..).
/// struct members subject to change without notice.
/// </summary>
public struct InstantiateProjectileParameters
{
#region Public Variables
/// <summary>
/// The projectile template index
/// </summary>
public int projectilePrefabID;
/// <summary>
/// The world space position this projectile is fired from
/// </summary>
public Vector3 position;
/// <summary>
/// The world space direction the profile is fired in
/// </summary>
public Vector3 fwdDirection;
/// <summary>
/// The up direction of the projectile
/// </summary>
public Vector3 upDirection;
/// <summary>
/// Current velocity of the weapon that fired the projectile (which could include
/// the world velocity of the ship the weapon is attached to.
/// </summary>
public Vector3 weaponVelocity;
/// <summary>
/// Gravitational acceleration affecting the projectile in m/s^2
/// </summary>
public float gravity;
/// <summary>
/// Direction in worldspace that gravity is acting on the projectile
/// </summary>
public Vector3 gravityDirection;
/// <summary>
/// Ship that fired the projectile, else 0
/// </summary>
public int shipId;
/// <summary>
/// Squadron of the ship that fired the projectile, else -1.
/// </summary>
public int squadronId;
/// <summary>
/// This is the index in the SSCManager effectsObjectTemplatesList for the regular destruct FX
/// </summary>
public int effectsObjectPrefabID;
/// <summary>
/// This is the index in the SSCManager effectsObjectTemplatesList for the hit shield destruct FX
/// </summary>
public int shieldEffectsObjectPrefabID;
/// <summary>
/// The ship to target for guided projectiles
/// </summary>
public ShipControlModule targetShip;
/// <summary>
/// The gameobject to target for guided projectiles
/// </summary>
public GameObject targetGameObject;
/// <summary>
/// The unique guidHash for the target. Currently only used for ship damage regions.
/// If unset value is 0.
/// </summary>
public int targetguidHash;
/// <summary>
/// Return the spawned muzzle FX pooled template EffectsModule PrefabID.
/// Returns -1 if non-pooled or not spawned.
/// </summary>
public int muzzleEffectsObjectPrefabID;
/// <summary>
/// Return the spawned muzzle FX of the zero-based index in the current pool
/// Returns -1 if non-pooled or not spawned.
/// </summary>
public int muzzleEffectsObjectPoolListIndex;
#endregion
}
/// <summary>
/// Parameters structure for use with sscManager.InstantiateBeam(..).
/// struct members subject to change without notice.
/// </summary>
public struct InstantiateBeamParameters
{
#region Public Variables
/// <summary>
/// The beam template index
/// </summary>
public int beamPrefabID;
/// <summary>
/// The world space position this beam is fired from
/// </summary>
public Vector3 position;
/// <summary>
/// The world space direction the beam is fired in
/// </summary>
public Vector3 fwdDirection;
/// <summary>
/// The up direction of the beam
/// </summary>
public Vector3 upDirection;
/// <summary>
/// Ship that fired the beam, else 0
/// </summary>
public int shipId;
/// <summary>
/// Squadron of the ship that fired the beam, else -1.
/// </summary>
public int squadronId;
/// <summary>
/// The zero-based index of the weapon on the ship that fired the beam
/// </summary>
public int weaponIndex;
/// <summary>
/// The zero-based index of the fire position on the weapon that fired the beam
/// </summary>
public int firePositionIndex;
/// <summary>
/// This is the index in the SSCManager effectsObjectTemplatesList.
/// </summary>
public int effectsObjectPrefabID;
/// <summary>
/// Return value of the zero-based index in the current pool
/// Returns -1 if non-pooled.
/// </summary>
public int beamPoolListIndex;
/// <summary>
/// Return value of the unique number for the beam instance
/// </summary>
public uint beamSequenceNumber;
#endregion
}
/// <summary>
/// Parameters structure for use with sscManager.InstantiateDestruct(..).
/// struct members subject to change without notice.
/// </summary>
public struct InstantiateDestructParameters
{
#region Public variables
// The destruct object template index
public int destructPrefabID;
// The position where the destruct object will be instantiated
public Vector3 position;
// The rotation of the destruct object when instantiated
public Quaternion rotation;
/// <summary>
/// A 0 to 1 multiplier of the explosionPower of the Destruct Module
/// </summary>
public float explosionPowerFactor;
/// <summary>
/// A 0 to 1 multiplier of the explosionRadius of the Destruct Module
/// </summary>
public float explosionRadiusFactor;
/// <summary>
/// Return value of the zero-based index in the current pool
/// Returns -1 if non-pooled.
/// </summary>
public int destructPoolListIndex;
/// <summary>
/// Return value of the unique number for the destruct instance
/// </summary>
public uint destructSequenceNumber;
#endregion
}
/// <summary>
/// Parameters structure for use with sscManager.InstantiateEffectsObject(..).
/// struct members subject to change without notice.
/// </summary>
public struct InstantiateEffectsObjectParameters
{
#region Public variables
// The effects object template index
public int effectsObjectPrefabID;
// The position where the effects object will be instantiated
public Vector3 position;
// The rotation of the effects object when instantiated
public Quaternion rotation;
/// <summary>
/// Return value of the zero-based index in the current pool
/// Returns -1 if non-pooled.
/// </summary>
public int effectsObjectPoolListIndex;
/// <summary>
/// Return value of the unique number for the effects instance
/// </summary>
public uint effectsObjectSequenceNumber;
#endregion
}
/// <summary>
/// Parameters structure for use with sscManager.InstantiateSoundFX(..).
/// struct members subject to change without notice.
/// </summary>
public struct InstantiateSoundFXParameters
{
#region Public variables
// The effects object template index
public int effectsObjectPrefabID;
// The position where the sound will be instantiated
public Vector3 position;
/// <summary>
/// If the volume is > 0, it will override the volume of the prefab
/// </summary>
public float volume;
/// <summary>
/// If true, the volume will be set to the EffectModule.defaultVolume.
/// </summary>
public bool useDefaultVolume;
#endregion
}
/// <summary>
/// A struct used to uniquely identify a Beam Module
/// </summary>
public struct SSCBeamItemKey
{
#region Public variables
/// <summary>
/// -1 = unset
/// </summary>
public int beamTemplateListIndex;
/// <summary>
/// -1 = unset
/// </summary>
public int beamPoolListIndex;
/// <summary>
/// 0 = unset
/// </summary>
public uint beamSequenceNumber;
#endregion
#region Constructor
public SSCBeamItemKey(int templateId, int poolId, uint sequenceNumber)
{
beamTemplateListIndex = templateId;
beamPoolListIndex = poolId;
beamSequenceNumber = sequenceNumber;
}
#endregion
}
/// <summary>
/// A struct used to uniquely identify a Destruct Module
/// </summary>
public struct SSCDestructItemKey
{
#region Public variables
/// <summary>
/// -1 = unset
/// </summary>
public int destructTemplateListIndex;
/// <summary>
/// -1 = unset
/// </summary>
public int destructPoolListIndex;
/// <summary>
/// 0 = unset
/// </summary>
public uint destructSequenceNumber;
#endregion
#region Constructor
public SSCDestructItemKey(int templateId, int poolId, uint sequenceNumber)
{
destructTemplateListIndex = templateId;
destructPoolListIndex = poolId;
destructSequenceNumber = sequenceNumber;
}
#endregion
}
/// <summary>
/// A struct used to uniquely identity an EffectsModule
/// </summary>
public struct SSCEffectItemKey
{
#region Public variables
/// <summary>
/// -1 = unset
/// </summary>
public int effectsObjectTemplateListIndex;
/// <summary>
/// -1 = unset
/// </summary>
public int effectsObjectPoolListIndex;
/// <summary>
/// 0 = unset
/// </summary>
public uint effectsObjectSequenceNumber;
#endregion
#region Constructor
public SSCEffectItemKey(int templateId, int poolId, uint sequenceNumber)
{
effectsObjectTemplateListIndex = templateId;
effectsObjectPoolListIndex = poolId;
effectsObjectSequenceNumber = sequenceNumber;
}
#endregion
}
#endregion
}