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 /// /// [READONLY] Are all the projectiles in the scene currently paused? /// See also PauseProjectiles() and ResumeProjectiles() /// public bool IsProjectilesPaused { get { return isProjectilesPaused; } } /// /// [READONLY] Are all the beams in the scene currently paused? /// See also PauseBeams() and ResumeBeams() /// public bool IsBeamsPaused { get { return isBeamsPaused; } } /// /// [READONLY] Are all the destructs in the scene currently paused? /// See also PauseDestructs() and ResumeDestructs() /// public bool IsDestructsPaused { get { return isDestructsPaused; } } /// /// [READONLY] Are all the effects objects in the scene currently paused? /// See also PauseEffectsObjects() and ResumeEffectsObjects() /// public bool IsEffectsObjectsPaused { get { return isEffectsObjectsPaused; } } public List ProjectileTemplatesList { get { return projectileTemplatesList; } } public List BeamTemplatesList { get { return beamTemplateList; } } public List DestructTemplatesList { get { return destructTemplateList; } } public List EffectsObjectTemplatesList { get { return effectsObjectTemplatesList; } } // Path data variables public List 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 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; /// /// [INTERNAL USE ONLY] Instead, call sscManager.EnableRadar() or DisableRadar(). /// public bool isRadarEnabled; #endregion #region Public Static Variables /// /// Used to denote that a prefab is not pooled. /// See GetorCreateEffectsPool(..). /// public static int NoPrefabID = -1; #endregion #region Public Delegates /// /// Optional callback for use with kinematic guided projectiles. Stored in SSCManager to /// avoid having one delegate for every projectile in the scene. /// /// public delegate void CallbackProjectileMoveTo(ProjectileModule projectileModule); /// /// 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. /// public CallbackProjectileMoveTo callbackProjectileMoveTo = null; #endregion #region Private Variables private List projectileTemplatesList; private ProjectileTemplate projectileTemplate; private GameObject projectileGameObjectInstance; private List beamTemplateList; private BeamTemplate beamTemplate; private GameObject beamGameObjectInstance; private List destructTemplateList; private DestructTemplate destructTemplate; private GameObject destructGameObjectInstance; private List 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; /// /// This is used to get a quick estimate of a Path Section length /// or distance. See CalcPathSegments(..). /// private int numPathSegmentsForEstimate = 8; /// /// The Path segment length used for maximum accuracy or precision. /// 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 /// /// This is only set when there are weapons that use DOTS-enabled projectile modules /// public bool isProjectileSytemUpdateRequired = false; #endif #region Public Methods /// /// Initialises the Ship Controller Manager instance. /// public void Initialise () { // Initialise the projectile templates list with an initial capacity of 10 projectile types projectileTemplatesList = new List(10); // Initialise the beam templates list with an initial capacity of 10 beam types beamTemplateList = new List(10); // Initialise the destruct templates list with an initial capacity of 10 destruct types destructTemplateList = new List(10); // Initialise the effects object templates list with an initial capacity of 25 effects object types effectsObjectTemplatesList = new List(25); // AI data if (locationDataList == null) { locationDataList = new List(10); } if (pathDataList == null) { pathDataList = new List(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(); } 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]); } } } } /// /// 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 /// /// 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 } /// /// 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 /// /// 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 } /// /// 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 /// /// 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 } /// /// Updates the DestructModule objects lists in the Ship Controller Manager given a Ship instance /// /// 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 } /// /// Updates the DestructModule objects lists in the Ship Controller Manager given a SurfaceTurretModule instance /// /// 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 } /// /// Updates the DestructModule objects lists in the Ship Controller Manager given a DestructibleObjectModule instance /// /// 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 } /// /// 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 /// /// 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 } /// /// Updates the effects objects lists in the Ship Controller Manager given a DestructibleObjectModule instance, /// and updates the IDs for: /// - The damage effects object /// /// 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 } /// /// 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. /// /// 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(); 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(); 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(); 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; } /// /// 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. /// /// /// /// 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(); 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(); 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(); // 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); } /// /// 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. /// /// 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().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().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().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 } /// /// 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). /// /// /// /// [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); } /// /// 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). /// /// /// 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(); 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(); 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(); 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 /// /// Adds a new projectile template to the list. Returns the projectile template index. /// /// /// 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(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; } /// /// Adds a new beam template to the list. Returns the beam template index. /// /// /// /// 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(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; } /// /// Adds a new destruct template to the list. /// TODO: consider initialising when first adding to the pool /// TODO: turn off isExplodeOnStart /// /// /// 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(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; } /// /// Adds a new effects object template to the list. /// /// /// 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(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; } /// /// 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. /// /// 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; } /// /// 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. /// /// 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; } /// /// 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. /// /// /// /// /// 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; } /// /// Deactivate a beam /// /// 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(); // 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(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(); } } } } /// /// Destroy the effects Object or return it to the pool. /// /// 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(); // 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(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(); } } } } /// /// 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. /// /// /// /// /// 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; } } /// /// Change the transform position and rotation of an EffectsObject. /// It is faster to not validate sequence numbers for pooled effects. /// /// /// /// /// 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().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(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; } /// /// [INTERNAL ONLY] /// See also PauseBeams() and ResumeBeams() in Public API /// Pause or unpause all Pooled, and Non-Pooled Beams in the scene /// /// 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().DisableBeam(); } else { pmGO.GetComponent().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(false); int _numBeamModules = _beamModules == null ? 0 : _beamModules.Length; for (int pmIdx = 0; pmIdx < _numBeamModules; pmIdx++) { if (isPause) { _beamModules[pmIdx].DisableBeam(); } else { _beamModules[pmIdx].EnableBeam(); } } } } } /// /// [INTERNAL ONLY] - UNTESTED /// See also PauseDestructs() and ResumeDestructs() in Public API /// Pause or unpause all Pooled, and Non-Pooled Destruct Modules in the scene /// /// 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().DisableDestruct(); } else { pmGO.GetComponent().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(false); int _numDestructModules = _destructModules == null ? 0 : _destructModules.Length; for (int pmIdx = 0; pmIdx < _numDestructModules; pmIdx++) { if (isPause) { _destructModules[pmIdx].DisableDestruct(); } else { _destructModules[pmIdx].EnableDestruct(); } } } } } /// /// [INTERNAL ONLY] /// See also PauseProjectiles() and ResumeProjectiles() in Public API /// Pause or unpause all DOTS, Pooled, and Non-Pooled Projectiles in the scene /// /// 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().DisableProjectile(); } else { pmGO.GetComponent().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(false); int _numProjectileModules = _projectileModules == null ? 0 : _projectileModules.Length; for (int pmIdx = 0; pmIdx < _numProjectileModules; pmIdx++) { if (isPause) { _projectileModules[pmIdx].DisableProjectile(); } else { _projectileModules[pmIdx].EnableProjectile(); } } } } } /// /// [INTERNAL ONLY] /// See also PauseEffectsObjects() and ResumeEffectsObjects() in Public API. /// Pause or unpause all pooled and non-pooled effects objects in the scene. /// /// 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().DisableEffects(); } else { emGo.GetComponent().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(false); int _numEffectsModules = _effectsModules == null ? 0 : _effectsModules.Length; for (int emIdx = 0; emIdx < _numEffectsModules; emIdx++) { if (isPause) { _effectsModules[emIdx].DisableEffects(); } else { _effectsModules[emIdx].EnableEffects(); } } } } } /// /// Select or unselect all the Locations in a Path. /// Always unselect in/out Controls for a valid Path Location /// /// /// 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 /// /// 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(..). /// 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 /// /// 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. /// /// 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(); 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(); } } 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; } /// /// 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. /// /// /// 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; } /// /// 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. /// /// /// 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; } /// /// 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); /// /// /// /// /// 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; } } } /// /// 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. /// /// /// /// /// 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; } } } /// /// 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. /// /// /// /// /// 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; } } } /// /// 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. /// /// /// /// /// 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; } } } /// /// Import a json file from disk and return as PathData /// /// /// /// 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; } /// /// Save the PathData to a json file on disk. /// /// /// 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; } /// /// 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. /// /// /// /// /// 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 /// /// 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(..). /// /// /// 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; } /// /// 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. /// 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; } } /// /// Returns the prefab for a beam given its beam prefab ID. /// /// /// 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; } } /// /// Returns the prefab for an EffectsObject given its effects prefab ID. /// /// /// 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; } } /// /// Pause all Pooled, and Non-Pooled Beams in the scene /// public void PauseBeams() { PauseBeams(true); } /// /// Resume all Pooled, and Non-Pooled beams in the scene /// public void ResumeBeams() { PauseBeams(false); } /// /// Pause all Pooled, and Non-Pooled Destruct objects in the scene /// public void PauseDestructs() { PauseDestructModules(true); } /// /// Resume all Pooled, and Non-Pooled Destruct objects in the scene /// public void ResumeDestructs() { PauseDestructModules(false); } /// /// Returns the prefab for a projectile given its projectile prefab ID. /// /// /// 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; } } /// /// Pause all DOTS, Pooled, and Non-Pooled Projectiles in the scene /// public void PauseProjectiles() { PauseProjectiles(true); } /// /// Resume all DOTS, Pooled, and Non-Pooled Projectiles in the scene /// public void ResumeProjectiles() { PauseProjectiles(false); } /// /// Pause all Pooled and Non-Pooled Effects Objects in the scene /// public void PauseEffectsObjects() { PauseEffectsObjects(true); } /// /// Resume all Pooled and Non-Pooled Effects Objects in the scene /// public void ResumeEffectsObjects() { PauseEffectsObjects(false); } /// /// 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(..). /// /// /// 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(); 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 } /// /// Play an audio clip at the specified world-space position at a volume. /// This uses the pooled EffectsModules created with GetOrCreateEffectsPool(..). /// See also InstantiateSoundFX(..). /// /// /// /// 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); } } /// /// 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. /// /// 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(); 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(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); } } } } /// /// 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. /// /// 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().LastFramePosition += delta; pmGO.GetComponent().ThisFramePosition += delta; } } } } // Teleport all active Non-Pooled Projectiles (they should probably all be active in the heierarchy) if (projectileNonPooledTrfm != null) { ProjectileModule[] _projectileModules = projectileNonPooledTrfm.GetComponentsInChildren(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 /// /// 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. /// /// /// public bool AddLocation (LocationData locationData) { if (locationDataList == null) { locationDataList = new List(10); } if (locationDataList != null && locationData != null) { locationData.isUnassigned = false; locationDataList.Add(locationData); return true; } else { return false; } } /// /// Same as calling AppendLocation(wsPosition, autoGenerateName) /// /// /// /// public LocationData AddLocation (Vector3 wsPosition, bool autoGenerateName) { return AppendLocation(wsPosition, autoGenerateName); } /// /// 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. /// /// /// /// public LocationData AppendLocation(Vector3 wsPosition, bool autoGenerateName) { LocationData locationData = new LocationData() { position = wsPosition }; if (locationDataList == null) { locationDataList = new List(10); } if (locationDataList != null) { locationData.isUnassigned = false; if (autoGenerateName) { locationData.name = "Location " + (locationDataList.Count+1).ToString(); } locationDataList.Add(locationData); } return locationData; } /// /// 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. /// /// /// /// /// /// 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(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; } /// /// 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. /// /// /// /// /// 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; } /// /// 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. /// /// /// 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(10); } if (locationDataList != null) { locationData.isUnassigned = false; locationData.name = locationGameObject.name; locationDataList.Add(locationData); } return locationData; } else { return null; } } /// /// Add a path to the scene. Alternatively, use CreatePath(). /// /// 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 } /// /// Create a new path and return the reference to the PathData. /// /// public PathData CreatePath() { PathData newPath = new PathData(); if (newPath != null) { pathDataList.Add(newPath); } return newPath; } /// /// Delete a location and remove from radar if required /// /// 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); } } /// /// Insert a new Location before the zero-based point on the Path. /// Has no effect if there are not no points in the Path. /// /// /// /// /// /// 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(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; } /// /// 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. /// /// /// /// 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 _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; } } } } } /// /// 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. /// /// /// /// The offset from the original position of the docking station when the paths where setup in the scene /// The amount the docking station has been rotated after the paths were setup in the scene /// /// 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; } } } } /// /// 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. /// /// /// /// Difference between CURRENT position and new position /// Difference between CURRENT rotation and new rotation 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); } } } } /// /// 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(..). /// /// /// Typically Time.time, it is used to determine if the Path has already been updated this frame /// /// Difference between initial or starting position and new position /// Difference between initial or starting rotation and new rotation /// /// 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; } } } } /// /// 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. /// /// /// /// /// 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; } } } } } /// /// 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. /// /// /// /// 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]); } } } } } /// /// Delete all the selected Locations, including any Location slots associated /// with a Path. /// refreshPath will update the path distances for all affected Paths. /// 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); } } } /// /// 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. /// /// /// The up direction from the mesh being snapped to /// e.g. meshLayerMask = ~0; /// 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; } } } /// /// 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. /// /// /// The up direction from the mesh being snapped to /// e.g. meshLayerMask = ~0; /// 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; } } } } /// /// 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. /// /// /// The distance 'above' the mesh the Location should be placed /// The minimum height of the mesh to check for /// The maximum height of the mesh to check for /// The up direction from the mesh being snapped to /// e.g. meshLayerMask = ~0; /// Refresh the lengths of all Paths that contain this Location 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); } } } /// /// Get the first Location on a given Path /// /// /// 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); } } /// /// Get the last Location on a given Path /// /// /// 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); } } /// /// Get a Location given the unique guidHash code of the Location /// /// /// public LocationData GetLocation(int guidHash) { if (locationDataList == null) { return null; } else { return locationDataList.Find(loc => loc.guidHash == guidHash); } } /// /// 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). /// /// /// 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()); } } /// /// If a location was added with AppendLocation(gameObject), it can also be retrieved at runtime with /// this method by passing in the gameobject.GetInstanceID(). /// /// /// 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; } /// /// Get a Path given the unique guidHash code of the Path /// /// /// public PathData GetPath(int guidHash) { if (pathDataList == null) { return null; } else { return pathDataList.Find(p => p.guidHash == guidHash); } } /// /// 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). /// /// /// 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()); } } /// /// Attempt to reverse the direction of a path /// /// 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; } } } } } /// /// Recalculate the distances that are stored in the PathLocationData instances. /// /// 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 /// /// Enable radar for a stationary Location if SSCManager is initialised. /// /// 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; } } } /// /// 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. /// 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 /// /// Class containing data for a projectile template. Used by SSCManager. /// public class ProjectileTemplate { public ProjectileModule projectilePrefab; public int instanceID; public int currentPoolSize; public List 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 /// /// Class containing data for a beam template. Used by SSCManager. /// public class BeamTemplate { public BeamModule beamPrefab; public int instanceID; public int currentPoolSize; public List beamPoolList; // Class constructor public BeamTemplate (BeamModule prefab, int id) { beamPrefab = prefab; instanceID = id; currentPoolSize = 0; beamPoolList = null; } } #endregion #region DestructTemplate /// /// Class containing data for a destruct template. Used by SSCManager. /// public class DestructTemplate { public DestructModule destructPrefab; public int instanceID; public int currentPoolSize; public List destructPoolList; // Class constructor public DestructTemplate (DestructModule prefab, int id) { destructPrefab = prefab; instanceID = id; currentPoolSize = 0; destructPoolList = null; } } #endregion #region EffectsObjectTemplate /// /// Class containing data for an effects object template. Used by SSCManager. /// public class EffectsObjectTemplate { public EffectsModule effectsObjectPrefab; public int instanceID; public int currentPoolSize; public List 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 /// /// Parameters structure for use with sscManager.InstantiateProjectile(..). /// struct members subject to change without notice. /// public struct InstantiateProjectileParameters { #region Public Variables /// /// The projectile template index /// public int projectilePrefabID; /// /// The world space position this projectile is fired from /// public Vector3 position; /// /// The world space direction the profile is fired in /// public Vector3 fwdDirection; /// /// The up direction of the projectile /// public Vector3 upDirection; /// /// Current velocity of the weapon that fired the projectile (which could include /// the world velocity of the ship the weapon is attached to. /// public Vector3 weaponVelocity; /// /// Gravitational acceleration affecting the projectile in m/s^2 /// public float gravity; /// /// Direction in worldspace that gravity is acting on the projectile /// public Vector3 gravityDirection; /// /// Ship that fired the projectile, else 0 /// public int shipId; /// /// Squadron of the ship that fired the projectile, else -1. /// public int squadronId; /// /// This is the index in the SSCManager effectsObjectTemplatesList for the regular destruct FX /// public int effectsObjectPrefabID; /// /// This is the index in the SSCManager effectsObjectTemplatesList for the hit shield destruct FX /// public int shieldEffectsObjectPrefabID; /// /// The ship to target for guided projectiles /// public ShipControlModule targetShip; /// /// The gameobject to target for guided projectiles /// public GameObject targetGameObject; /// /// The unique guidHash for the target. Currently only used for ship damage regions. /// If unset value is 0. /// public int targetguidHash; /// /// Return the spawned muzzle FX pooled template EffectsModule PrefabID. /// Returns -1 if non-pooled or not spawned. /// public int muzzleEffectsObjectPrefabID; /// /// Return the spawned muzzle FX of the zero-based index in the current pool /// Returns -1 if non-pooled or not spawned. /// public int muzzleEffectsObjectPoolListIndex; #endregion } /// /// Parameters structure for use with sscManager.InstantiateBeam(..). /// struct members subject to change without notice. /// public struct InstantiateBeamParameters { #region Public Variables /// /// The beam template index /// public int beamPrefabID; /// /// The world space position this beam is fired from /// public Vector3 position; /// /// The world space direction the beam is fired in /// public Vector3 fwdDirection; /// /// The up direction of the beam /// public Vector3 upDirection; /// /// Ship that fired the beam, else 0 /// public int shipId; /// /// Squadron of the ship that fired the beam, else -1. /// public int squadronId; /// /// The zero-based index of the weapon on the ship that fired the beam /// public int weaponIndex; /// /// The zero-based index of the fire position on the weapon that fired the beam /// public int firePositionIndex; /// /// This is the index in the SSCManager effectsObjectTemplatesList. /// public int effectsObjectPrefabID; /// /// Return value of the zero-based index in the current pool /// Returns -1 if non-pooled. /// public int beamPoolListIndex; /// /// Return value of the unique number for the beam instance /// public uint beamSequenceNumber; #endregion } /// /// Parameters structure for use with sscManager.InstantiateDestruct(..). /// struct members subject to change without notice. /// 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; /// /// A 0 to 1 multiplier of the explosionPower of the Destruct Module /// public float explosionPowerFactor; /// /// A 0 to 1 multiplier of the explosionRadius of the Destruct Module /// public float explosionRadiusFactor; /// /// Return value of the zero-based index in the current pool /// Returns -1 if non-pooled. /// public int destructPoolListIndex; /// /// Return value of the unique number for the destruct instance /// public uint destructSequenceNumber; #endregion } /// /// Parameters structure for use with sscManager.InstantiateEffectsObject(..). /// struct members subject to change without notice. /// 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; /// /// Return value of the zero-based index in the current pool /// Returns -1 if non-pooled. /// public int effectsObjectPoolListIndex; /// /// Return value of the unique number for the effects instance /// public uint effectsObjectSequenceNumber; #endregion } /// /// Parameters structure for use with sscManager.InstantiateSoundFX(..). /// struct members subject to change without notice. /// 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; /// /// If the volume is > 0, it will override the volume of the prefab /// public float volume; /// /// If true, the volume will be set to the EffectModule.defaultVolume. /// public bool useDefaultVolume; #endregion } /// /// A struct used to uniquely identify a Beam Module /// public struct SSCBeamItemKey { #region Public variables /// /// -1 = unset /// public int beamTemplateListIndex; /// /// -1 = unset /// public int beamPoolListIndex; /// /// 0 = unset /// public uint beamSequenceNumber; #endregion #region Constructor public SSCBeamItemKey(int templateId, int poolId, uint sequenceNumber) { beamTemplateListIndex = templateId; beamPoolListIndex = poolId; beamSequenceNumber = sequenceNumber; } #endregion } /// /// A struct used to uniquely identify a Destruct Module /// public struct SSCDestructItemKey { #region Public variables /// /// -1 = unset /// public int destructTemplateListIndex; /// /// -1 = unset /// public int destructPoolListIndex; /// /// 0 = unset /// public uint destructSequenceNumber; #endregion #region Constructor public SSCDestructItemKey(int templateId, int poolId, uint sequenceNumber) { destructTemplateListIndex = templateId; destructPoolListIndex = poolId; destructSequenceNumber = sequenceNumber; } #endregion } /// /// A struct used to uniquely identity an EffectsModule /// public struct SSCEffectItemKey { #region Public variables /// /// -1 = unset /// public int effectsObjectTemplateListIndex; /// /// -1 = unset /// public int effectsObjectPoolListIndex; /// /// 0 = unset /// public uint effectsObjectSequenceNumber; #endregion #region Constructor public SSCEffectItemKey(int templateId, int poolId, uint sequenceNumber) { effectsObjectTemplateListIndex = templateId; effectsObjectPoolListIndex = poolId; effectsObjectSequenceNumber = sequenceNumber; } #endregion } #endregion }