using System.Collections; using System.Collections.Generic; using UnityEngine; public class Shooter : MonoBehaviour, IEnemy { //Set up serialized variables and others [SerializeField] private GameObject bulletPrefab; [SerializeField] private float bulletMoveSpeed; [SerializeField] private float restTime = 1f; [SerializeField] private int burstCount; [SerializeField] private float timeBetweenBursts; [SerializeField] private int projectilesPerBurst; [SerializeField][Range(0, 359)] private float angleSpread; [SerializeField] private float startingDistance = 0.1f; [SerializeField] private bool stagger; [SerializeField] private bool oscillate; //Set is shooting enemy bool variable to false private bool isShooting = false; /// /// This method method in Unity is a special method that gets called when the script is loaded or a value in the inspector is changed. /// It is typically used to ensure that serialized field values are within acceptable ranges or to enforce specific relationships between variables. /// This method is particularly useful for maintaining consistency and avoiding invalid values being set in the Unity Editor. /// private void OnValidate(){ //Ensure all serialized fields maintain valid and consistent values if (oscillate) { stagger = true; } if (!oscillate){ stagger = false; } if (projectilesPerBurst < 1) { projectilesPerBurst = 1; } if (burstCount < 1){ burstCount = 1; } if (timeBetweenBursts < 0.1f){ timeBetweenBursts = 0.1f; } if (restTime < 0.1f){ restTime = 0.1f; } if (startingDistance < 0.1f){ startingDistance = 0.1f; } if (angleSpread == 0){ projectilesPerBurst = 1;} if (bulletMoveSpeed <= 0){ bulletMoveSpeed = 0.1f; } } /// /// This method Attacks if the condition is shooting is true and /// it starts the coroutine for the shoot routine. /// public void Attack(){ //Checks if is shooting is true if (!isShooting){ //If condition is met, start coroutine and call on the method below StartCoroutine(ShootRoutine()); } } /// /// This routine shoots projectiles in bursts, with options for staggering shots /// oscillating angles, and resting between bursts. /// /// private IEnumerator ShootRoutine(){ //Set shooting to true meaning player is shooting isShooting = true; //Initialize other float variables float startAngle, currentAngle, angleStep, endAngle; float timeBetweenProjectiles = 0f; //Call on the Target Cone Of Influence method TargetConeOfInfluence(out startAngle, out currentAngle, out angleStep, out endAngle); //Calculates time interval between projectiles in a burst if(stagger) { timeBetweenProjectiles = timeBetweenBursts / projectilesPerBurst; } //For loop while burst count is small for (int i = 0; i < burstCount; i++){ //check if oscillate is flase and call the target cone of influence method if (!oscillate){ TargetConeOfInfluence(out startAngle, out currentAngle, out angleStep, out endAngle); } //check if oscillate is true and call the target cone of influence method if (oscillate && i % 2 != 1){ TargetConeOfInfluence(out startAngle, out currentAngle, out angleStep, out endAngle); } //check if oscillate is true and set angles else if (oscillate){ currentAngle = endAngle; endAngle = startAngle; startAngle = currentAngle; angleStep *= -1; } //Another for loop while projectiles per burst are small for (int j = 0; j < projectilesPerBurst; j++){ //Set vector 2 position of the bullet spawn Vector2 pos = FindBulletSpawnPos(currentAngle); //Set bullet game object GameObject newBullet = Instantiate(bulletPrefab, pos, Quaternion.identity); newBullet.transform.right = newBullet.transform.position - transform.position; //check if bullet component is active if (newBullet.TryGetComponent(out Projectile projectile)){ //Update the speed in which the projectile (bullet) is moving projectile.UpdateMoveSpeed(bulletMoveSpeed); } currentAngle += angleStep; //pause coroutine if stagger is true if (stagger) { yield return new WaitForSeconds(timeBetweenProjectiles); } } currentAngle = startAngle; //pause for a few seconds if stagger is false if (!stagger) { yield return new WaitForSeconds(timeBetweenBursts); } } //pause for a few seconds and set shooting to false yield return new WaitForSeconds(restTime); isShooting = false; } /// /// This method calculates the angles for projecting the projectiles /// (bullets) when shooting. /// /// angle the projectile starts /// current angle of the projectile /// angle step of the projectile /// angle the projectile ends at private void TargetConeOfInfluence(out float startAngle, out float currentAngle, out float angleStep, out float endAngle) { //Set different angles for the projectile shoot Vector2 targetDirection = PlayerController.Instance.transform.position - transform.position; float targetAngle = Mathf.Atan2(targetDirection.y, targetDirection.x) * Mathf.Rad2Deg; startAngle = targetAngle; endAngle = targetAngle; currentAngle = targetAngle; float halfAngleSpread = 0f; angleStep = 0; if (angleSpread != 0){ angleStep = angleSpread / (projectilesPerBurst - 1); halfAngleSpread = angleSpread / 2f; startAngle = targetAngle - halfAngleSpread; endAngle = targetAngle + halfAngleSpread; currentAngle = startAngle; } } /// /// This method calculates and returns the position that a bullet /// will be spawned according to the angle. /// /// /// private Vector2 FindBulletSpawnPos(float currentAngle){ //Set x and y position for the bullet spawn float x = transform.position.x + startingDistance * Mathf.Cos(currentAngle * Mathf.Deg2Rad); float y = transform.position.y + startingDistance * Mathf.Sin(currentAngle * Mathf.Deg2Rad); //Set gector 2 position x and y Vector2 pos = new Vector2(x, y); return pos; } }