using System.Collections; using System.Collections.Generic; using TMPro; using Unity.VisualScripting; using UnityEngine; using UnityEngine.UIElements; public class BulletSpawner : MonoBehaviour { public GameObject bulletPrefabBurst; public GameObject bulletPrefabSpiral; public GameObject bulletPrefabWave; public GameObject bulletPrefabChaosSpiral; public ScoreDataManager scoreDataManager; public Transform target; public List Bullets = new List(); public PlayerInput playerInput; public ScoreManager scoreManager; [Header("Base Settings")] public float spawnOffset = 2f; public int edgeSidesCount = 4; public float screenDepthZ = 10f; [Header("Patterns")] public float spreadAngle = 20f; public int burstBulletCount = 6; public int spiralGunCount = 12; public float spiralRadius = 6f; public int waveBulletCount = 12; [Header("Delays")] public float burstDelay = 0.22f; public float spiralDelay = 0.12f; public float waveDelay = 0.15f; public float betweenPatternsDelay = 1.2f; private float difficulty; public bool gameEnded = false; public Animator scoreAnimation; public Animator HeatButtomAnimation; public GameObject restartButton; public HealthManager healthManager; public Animator exitButonAnimator; public SFXManager sfxManager; void Start() { restartButton.SetActive(false); StartCoroutine(PatternLoop()); } void Update() { difficulty = scoreManager.GetDifficultyModifier(); } // ========================= // DIFFICULTY HELPERS // ========================= float FireRate() => Mathf.Lerp(1f, 0.35f, difficulty); int Scale(int baseValue, int extra) => baseValue + Mathf.RoundToInt(extra * difficulty); // ========================= // MAIN LOOP // ========================= public void RestartGame() { restartButton.SetActive(false); exitButonAnimator.Play("idle"); GameObject[] allObjects = GameObject.FindObjectsByType(FindObjectsInactive.Exclude); foreach (GameObject go in allObjects) { if (go.CompareTag("ToDestroy")) Destroy(go); } healthManager.hitpoints = healthManager.MaxHealth; ScoreManager.Instance.heatBar.gameObject.SetActive(true); ScoreManager.Instance.heatButton.gameObject.SetActive(true); ScoreManager.Instance.totalScore = 0; ScoreManager.Instance.scoreText.text = "00000000"; ScoreManager.Instance.heat = 0; ScoreManager.Instance.heatBar.value = 0; gameEnded = false; scoreAnimation.Play("Idle"); HeatButtomAnimation.Play("Idle"); foreach (TextMeshProUGUI scoreText in scoreDataManager.leaderboardScoreTexts) { scoreText.enabled = false; } sfxManager.StopSong(songs.gameOver); sfxManager.PlaySong(songs.gameplay); StartCoroutine(PatternLoop()); } IEnumerator PatternLoop() { yield return new WaitForSeconds(2f); // initial delay while (!gameEnded) { int pattern = Random.Range(0, 4); switch (pattern) { case 0: yield return Burst(); break; case 1: yield return Spiral(); break; case 2: yield return Wave(); break; case 3: yield return ChaosSpiral(); break; } yield return new WaitForSeconds(betweenPatternsDelay * FireRate()); } } // ========================= // BURST // ========================= IEnumerator Burst() { Vector3 pos = SpawnPos(); Vector3 dir = (target.position - pos).normalized; int count = Scale(burstBulletCount, 6); for (int i = 0; i < count; i++) { float offset = Random.Range(-spreadAngle, spreadAngle); sfxManager.PlaySFX(SFX.shot); Spawn(pos, Quaternion.Euler(0, 0, offset) * dir, bulletPrefabBurst); yield return new WaitForSeconds(burstDelay * FireRate()); } } // ========================= // CLASSIC SPIRAL (ORDERED) // ========================= IEnumerator Spiral() { int guns = Scale(spiralGunCount, 10); float step = 360f / guns; for (int i = 0; i < guns; i++) { float angle = i * step * Mathf.Deg2Rad; Vector3 pos = target.position + new Vector3( Mathf.Cos(angle), Mathf.Sin(angle), 0 ) * spiralRadius; Vector3 dir = (target.position - pos).normalized; sfxManager.PlaySFX(SFX.shot); Spawn(pos, dir, bulletPrefabSpiral); yield return new WaitForSeconds(spiralDelay * FireRate()); } } // ========================= // CHAOS SPIRAL (RANDOM ORDER) // ========================= IEnumerator ChaosSpiral() { int guns = Scale(spiralGunCount, 10); List angles = new List(); for (int i = 0; i < guns; i++) angles.Add(Random.Range(0f, 360f)); // shuffle for (int i = 0; i < angles.Count; i++) { int r = Random.Range(i, angles.Count); (angles[i], angles[r]) = (angles[r], angles[i]); } for (int i = 0; i < guns; i++) { float a = angles[i] * Mathf.Deg2Rad; Vector3 pos = target.position + new Vector3( Mathf.Cos(a), Mathf.Sin(a), 0 ) * spiralRadius; Vector3 dir = (target.position - pos).normalized; sfxManager.PlaySFX(SFX.shot); Spawn(pos, dir, bulletPrefabChaosSpiral); yield return new WaitForSeconds(spiralDelay * FireRate()); } } // ========================= // WAVE // ========================= IEnumerator Wave() { Vector3 pos = SpawnPos(); Vector3 dir = (target.position - pos).normalized; int count = Scale(waveBulletCount, 8); for (int i = 0; i < count; i++) { float wave = Mathf.Sin(i * 0.5f * difficulty) * spreadAngle; sfxManager.PlaySFX(SFX.shot); Spawn(pos, Quaternion.Euler(0, 0, wave) * dir, bulletPrefabWave); yield return new WaitForSeconds(waveDelay * FireRate()); } } // ========================= // SPAWN // ========================= void Spawn(Vector3 pos, Vector3 dir, GameObject bulletPrefab) { GameObject b = Instantiate(bulletPrefab, pos, Quaternion.identity); b.GetComponent().Init(dir); Bullets.Add(b); playerInput.bullets = Bullets; } // ========================= // SPAWN POSITION // ========================= Vector3 SpawnPos() { Camera cam = Camera.main; int side = Random.Range(0, edgeSidesCount); Vector3 pos = Vector3.zero; switch (side) { case 0: pos = cam.ViewportToWorldPoint(new Vector3(0, Random.value, screenDepthZ)); pos.x -= spawnOffset; break; case 1: pos = cam.ViewportToWorldPoint(new Vector3(1, Random.value, screenDepthZ)); pos.x += spawnOffset; break; case 2: pos = cam.ViewportToWorldPoint(new Vector3(Random.value, 1, screenDepthZ)); pos.y += spawnOffset; break; case 3: pos = cam.ViewportToWorldPoint(new Vector3(Random.value, 0, screenDepthZ)); pos.y -= spawnOffset; break; } pos.z = 0; return pos; } }