using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { [AddComponentMenu("Sci-Fi Ship Controller/Weapon Components/Beam Module")] [HelpURL("http://scsmmedia.com/ssc-documentation")] public class BeamModule : MonoBehaviour { #region Public Enumerations #endregion #region Public Variables /// /// The speed the beam travels in metres per second /// public float speed = 200f; /// /// The type of damage the beam does. The amount of damage dealt to a ship upon collision is dependent /// on the ship's multiplier for this damage type (i.e. if a Type A beam with a damage amount of 10 hits a ship /// with a Type A damage multiplier of 2, a total damage of 20 will be done to the ship). If the damage type is set to Default, /// the damage multipliers are ignored i.e. the damage amount is unchanged. /// public ProjectileModule.DamageType damageType = ProjectileModule.DamageType.Default; /// /// The amount of damage this beam does, per second, to the ship or object it hits. NOTE: Non-ship objects need a DamageReceiver component. /// public float damageRate = 10f; /// /// Whether pooling is used when spawning beams of this type. /// Currently we don't support changing this at runtime. /// public bool usePooling = true; /// /// The starting size of the pool. Only relevant when usePooling is enabled. /// public int minPoolSize = 10; /// /// The maximum allowed size of the pool. Only relevant when usePooling is enabled. /// public int maxPoolSize = 100; /// /// The start width (in metres) of the beam on the local x-axis /// In this version the width will be the same for the entire length of the beam. /// [Range(0.001f, 5f)] public float beamStartWidth = 0.1f; /// /// The minimum amount of time, in seconds, the beam must be active /// [Range(0.1f, 5f)] public float minBurstDuration = 0.5f; /// /// The maximum amount of time, in seconds, the beam can be active in a single burst /// [Range(0.1f, 30f)] public float maxBurstDuration = 5f; /// /// The time (in seconds) it takes a single beam to discharge the beam weapon from full charge /// [Range(0.1f, 60f)] public float dischargeDuration = 10f; /// /// The ID number for this beam prefab (as assigned by the Ship Controller Manager in the scene). /// This is the index in the SSCManager beamTemplatesList. /// [INTERNAL USE ONLY] /// public int beamPrefabID; /// /// The sound or particle FX used when a collision occurs. /// If you modify this, call shipControlModule.ReinitialiseShipBeams() for each ship /// this beam is used on. /// public EffectsModule effectsObject = null; /// /// The ID number for this beam's effects object prefab (as assigned by the Ship Controller Manager in the scene). /// This is the index in the SSCManager effectsObjectTemplatesList. Not defined = -1. /// [INTERNAL USE ONLY] /// public int effectsObjectPrefabID = -1; /// /// The sound and/or particle FX used when a beam is fired from a weapon. /// If you modify this, call shipControlModule.ReinitialiseShipBeams() for each ship /// this beam is used on. /// public EffectsModule muzzleEffectsObject = null; /// /// The distance in local space that the muzzle Effects Object should be instantiated /// from the weapon firing point. Typically only the z-axis would be used when the beam /// is instantiated in front or forwards from the actual weapon. /// public Vector3 muzzleEffectsOffset = Vector3.zero; /// /// The ID number for this beam's muzzle object prefab (as assigned by the Ship Controller Manager in the scene). /// This is the index in the SSCManager effectsObjectTemplatesList. Not defined = -1. /// [INTERNAL USE ONLY] /// [System.NonSerialized] public int muzzleEffectsObjectPrefabID = -1; /// /// The Id of the ship that fired the beam /// [System.NonSerialized] public int sourceShipId = -1; /// /// The Squadron which the ship belonged to when it fired the beam /// [System.NonSerialized] public int sourceSquadronId = -1; #endregion #region Private and Internal Variables [System.NonSerialized] internal bool isInitialised = false; [System.NonSerialized] internal bool isBeamEnabled = false; [System.NonSerialized] internal float burstDuration = 0f; /// /// [INTERNAL ONLY] /// Reference to the LineRenderer component which should be on a child /// gameobject of this module. /// [System.NonSerialized] internal LineRenderer lineRenderer = null; /// /// [INTERNAL ONLY] /// Used to determine uniqueness /// [System.NonSerialized] internal uint itemSequenceNumber; /// /// The zero-based index of the weapon on the ship that fired the beam /// [System.NonSerialized] internal int weaponIndex; /// /// The zero-based index of the fire position on the weapon that fired the beam /// [System.NonSerialized] internal int firePositionIndex; /// /// The item key of the muzzle effects object (if any) firing from this beam /// [System.NonSerialized] internal SSCEffectItemKey muzzleEffectsItemKey; /// /// The item key of the effects object that is spawned when the beam hits something /// [System.NonSerialized] internal SSCEffectItemKey effectsItemKey; #endregion #region Internal Delegates internal delegate void CallbackOnMove(BeamModule beamModule); [System.NonSerialized] internal CallbackOnMove callbackOnMove = null; #endregion #region Internal Static Variables internal static uint nextSequenceNumber = 1; #endregion #region Enable/Disable Event Methods void OnDisable() { isInitialised = false; } #endregion #region Update Methods // FixedUpdate is called once per physics update (typically about 50 times per second) private void FixedUpdate() { if (isInitialised && isBeamEnabled) { burstDuration += Time.deltaTime; if (callbackOnMove != null) { callbackOnMove(this); } } } #endregion #region Private and Internal Methods /// /// Intialise the beam and return it's unique sequence number /// /// internal uint InitialiseBeam(InstantiateBeamParameters ibParms) { // Store the index to the BeamTemplate in the SSCManager beamTemplatesList // This is used with Beam FX when we know the BeamModule but not the parent BeamTemplate. this.beamPrefabID = ibParms.beamPrefabID; // Store the index to the EffectsObjectTemplate in sscManager.effectsObjectTemplatesList this.effectsObjectPrefabID = ibParms.effectsObjectPrefabID; // Store the details on what fired the beam this.sourceShipId = ibParms.shipId; this.sourceSquadronId = ibParms.squadronId; this.weaponIndex = ibParms.weaponIndex; this.firePositionIndex = ibParms.firePositionIndex; IncrementSequenceNumber(); muzzleEffectsItemKey = new SSCEffectItemKey(-1, -1, 0); effectsItemKey = new SSCEffectItemKey(-1, -1, 0); if (lineRenderer == null) { lineRenderer = GetComponentInChildren(); } if (lineRenderer != null) { lineRenderer.startWidth = beamStartWidth; lineRenderer.endWidth = beamStartWidth; lineRenderer.alignment = LineAlignment.View; lineRenderer.startColor = Color.white; lineRenderer.endColor = Color.white; // Our calculations in ship.MoveBeam(..) assume world-space positions lineRenderer.useWorldSpace = true; // After a given amount of time, automatically destroy this beam burstDuration = 0f; isBeamEnabled = gameObject.activeSelf; isInitialised = true; } return itemSequenceNumber; } /// /// Makes this BeamModule unique from all others that have gone before them. /// This is called every time beam is Initialised. /// internal void IncrementSequenceNumber() { itemSequenceNumber = nextSequenceNumber++; // if sequence number needs to be wrapped, do so to a high-ish number that is unlikely to be in use if (nextSequenceNumber > uint.MaxValue - 100) { nextSequenceNumber = 100000; } } /// /// [INTERNAL ONLY] /// Removes the beam. How this is done depends on what system is being used (i.e. pooling etc.). /// internal void DestroyBeam() { // Reset weaponIndex = -1; firePositionIndex = -1; isBeamEnabled = false; // Remove the reference callbackOnMove = null; SSCManager sscManager = null; // Stop muzzle fx if (muzzleEffectsItemKey.effectsObjectSequenceNumber > 0) { sscManager = SSCManager.GetOrCreateManager(); if (sscManager != null) { sscManager.DestroyEffectsObject(muzzleEffectsItemKey); } } // Stop hit fx if (effectsItemKey.effectsObjectSequenceNumber > 0) { if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); } if (sscManager != null) { sscManager.DestroyEffectsObject(effectsItemKey); } } if (usePooling) { // Deactivate the beam gameObject.SetActive(false); } else { // Destroy the beam Destroy(gameObject); } } #endregion #region Public API Static Methods /// /// Determine if a ship has been hit. Apply damage as required based on the damage rate, damage type /// and hit duration. /// Hit duration is the time in seconds that the beam has been in contact with the target object since /// it was last checked (typically the duration of the last frame or time step). /// /// /// Must not be null /// /// public static bool CheckShipHit (RaycastHit raycastHitInfo, BeamModule beamModule, float hitDuration) { bool isHit = false; Rigidbody hitRigidbody = raycastHitInfo.rigidbody; if (hitRigidbody != null && beamModule != null) { // Check if there is a ship control module attached to the hit object ShipControlModule shipControlModule = hitRigidbody.GetComponent(); if (shipControlModule != null) { float damageAmount = beamModule.damageRate * hitDuration; // Apply damage to the ship shipControlModule.shipInstance.ApplyNormalDamage(damageAmount, beamModule.damageType, raycastHitInfo.point); // If required, call the custom method if (shipControlModule.callbackOnHit != null) { // Get a reference to the SSCManager SSCManager sscManager = SSCManager.GetOrCreateManager(); // Create a struct with the necessary parameters CallbackOnShipHitParameters callbackOnShipHitParameters = new CallbackOnShipHitParameters { hitInfo = raycastHitInfo, projectilePrefab = null, beamPrefab = sscManager.GetBeamPrefab(beamModule.beamPrefabID), damageAmount = damageAmount, sourceSquadronId = beamModule.sourceSquadronId, sourceShipId = beamModule.sourceShipId }; // Call the custom callback shipControlModule.callbackOnHit(callbackOnShipHitParameters); } isHit = true; } } return isHit; } /// /// Determine if an object with a DamageReceiver component attached has been hit by a Beam. /// /// /// Must not be null /// public static bool CheckObjectHit (RaycastHit raycastHitInfo, BeamModule beamModule, float hitDuration) { bool isHit = false; Rigidbody hitRigidbody = raycastHitInfo.rigidbody; if (raycastHitInfo.collider != null) { DamageReceiver damageReceiver = raycastHitInfo.collider.GetComponent(); if (damageReceiver != null && damageReceiver.callbackOnHit != null) { // Get a reference to the SSCManager SSCManager sscManager = SSCManager.GetOrCreateManager(); // Create a struct with the necessary parameters CallbackOnObjectHitParameters callbackOnObjectHitParameters = new CallbackOnObjectHitParameters { hitInfo = raycastHitInfo, projectilePrefab = null, beamPrefab = sscManager.GetBeamPrefab(beamModule.beamPrefabID), damageAmount = beamModule.damageRate * hitDuration, sourceSquadronId = beamModule.sourceSquadronId }; // Call the custom callback damageReceiver.callbackOnHit(callbackOnObjectHitParameters); isHit = true; } } return isHit; } #endregion #region Public API Methods /// /// Re-enable a beam after it has been disabled with /// DisableBeam(). See also SSCManager.ResumeBeams() /// public void EnableBeam() { isBeamEnabled = true; } /// /// Useful when you want to pause the action in a game. /// Should always be called BEFORE setting Time.timeScale to 0. /// See also SSCManager.PauseBeams(). /// public void DisableBeam() { isBeamEnabled = false; } #endregion } }