using UnityEngine; using Core; using Core.Settings; using Core.Lifecycle; using AppleHillsCamera; namespace Minigames.BirdPooper { /// /// Spawns targets at regular intervals for Bird Pooper minigame. /// Uses Transform references for spawn and despawn positions. /// All targets are spawned at Y = 0 (EdgeAnchor positions them vertically). /// public class TargetSpawner : ManagedBehaviour { [Header("Spawn Configuration")] [Tooltip("Transform marking where targets spawn (off-screen right)")] [SerializeField] private Transform spawnPoint; [Tooltip("Transform marking where targets despawn (off-screen left)")] [SerializeField] private Transform despawnPoint; [Header("EdgeAnchor References")] [Tooltip("ScreenReferenceMarker to pass to spawned targets")] [SerializeField] private ScreenReferenceMarker referenceMarker; [Tooltip("CameraScreenAdapter to pass to spawned targets")] [SerializeField] private CameraScreenAdapter cameraAdapter; [Header("Target Prefabs")] [Tooltip("Array of target prefabs to spawn randomly")] [SerializeField] private GameObject[] targetPrefabs; private IBirdPooperSettings _settings; private float _spawnTimer; private bool _isSpawning; private float _currentTargetInterval = 1f; [Header("Spawn Timing")] [Tooltip("Minimum interval between target spawns (seconds)")] [SerializeField] private float minTargetSpawnInterval = 1f; [Tooltip("Maximum interval between target spawns (seconds)")] [SerializeField] private float maxTargetSpawnInterval = 2f; /// /// Initializes the target spawner by loading settings and validating references. /// Should be called once before spawning begins. /// private void Initialize() { // Load settings _settings = GameManager.GetSettingsObject(); if (_settings == null) { Debug.LogWarning("[TargetSpawner] BirdPooperSettings not found! Using inspector intervals."); } // Validate interval range if (minTargetSpawnInterval < 0f) minTargetSpawnInterval = 0f; if (maxTargetSpawnInterval < 0f) maxTargetSpawnInterval = 0f; if (minTargetSpawnInterval > maxTargetSpawnInterval) { float tmp = minTargetSpawnInterval; minTargetSpawnInterval = maxTargetSpawnInterval; maxTargetSpawnInterval = tmp; Debug.LogWarning("[TargetSpawner] minTargetSpawnInterval was greater than maxTargetSpawnInterval. Values were swapped."); } // Validate references if (spawnPoint == null) { Debug.LogError("[TargetSpawner] Spawn Point not assigned! Please assign a Transform in the Inspector."); } if (despawnPoint == null) { Debug.LogError("[TargetSpawner] Despawn Point not assigned! Please assign a Transform in the Inspector."); } if (targetPrefabs == null || targetPrefabs.Length == 0) { Debug.LogError("[TargetSpawner] No target prefabs assigned! Please assign at least one prefab in the Inspector."); } if (referenceMarker == null) { Debug.LogError("[TargetSpawner] ScreenReferenceMarker not assigned! Targets need this for EdgeAnchor positioning."); } if (cameraAdapter == null) { Debug.LogWarning("[TargetSpawner] CameraScreenAdapter not assigned. EdgeAnchor will attempt to auto-find camera."); } Debug.Log("[TargetSpawner] Initialized successfully"); } private void Update() { if (!_isSpawning) return; _spawnTimer += Time.deltaTime; if (_spawnTimer >= _currentTargetInterval) { SpawnTarget(); _spawnTimer = 0f; // pick next random interval _currentTargetInterval = Random.Range(minTargetSpawnInterval, maxTargetSpawnInterval); } } /// /// Spawns a random target prefab at the spawn point. /// Target is spawned at Y = 0, EdgeAnchor will position it vertically. /// private void SpawnTarget() { if (targetPrefabs == null || targetPrefabs.Length == 0 || spawnPoint == null) { Debug.LogError("[TargetSpawner] Cannot spawn target - missing prefabs or spawn point!"); return; } // Randomly select target prefab GameObject prefab = targetPrefabs[Random.Range(0, targetPrefabs.Length)]; // Spawn at spawn point X, but Y = 0 (EdgeAnchor will position vertically) Vector3 spawnPosition = new Vector3(spawnPoint.position.x, 0f, 0f); GameObject targetObj = Instantiate(prefab, spawnPosition, Quaternion.identity); // Initialize target Target target = targetObj.GetComponent(); if (target != null) { float despawnX = despawnPoint != null ? despawnPoint.position.x : -12f; target.Initialize(despawnX, referenceMarker, cameraAdapter); // Subscribe to target hit event to notify manager target.onTargetHit.AddListener(OnTargetHit); Debug.Log($"[TargetSpawner] Spawned target at {spawnPosition}"); } else { Debug.LogError($"[TargetSpawner] Spawned prefab '{prefab.name}' does not have Target component!"); Destroy(targetObj); } } /// /// Called when a target is hit by a projectile. /// Notifies the game manager for score tracking. /// private void OnTargetHit() { // Find and notify manager BirdPooperGameManager manager = BirdPooperGameManager.Instance; if (manager != null) { manager.OnTargetHit(); } else { Debug.LogWarning("[TargetSpawner] BirdPooperGameManager not found!"); } } /// /// Start spawning targets. /// Initializes the spawner if not already initialized, then begins spawning logic. /// public void StartSpawning() { // Initialize if not already done if (_settings == null) { Initialize(); } // Begin the spawning process BeginSpawningTargets(); } /// /// Internal method that handles the actual spawning startup logic. /// Sets initial state and computes first interval. /// private void BeginSpawningTargets() { _isSpawning = true; _spawnTimer = 0f; // Choose initial interval _currentTargetInterval = Random.Range(minTargetSpawnInterval, maxTargetSpawnInterval); Debug.Log($"[TargetSpawner] Started spawning targets with interval {_currentTargetInterval:F2}s"); } /// /// Stop spawning new targets (existing targets continue). /// public void StopSpawning() { _isSpawning = false; Debug.Log("[TargetSpawner] Stopped spawning targets"); } /// /// Check if spawner is currently spawning. /// public bool IsSpawning => _isSpawning; /// /// Draw gizmos to visualize spawn and despawn points in the editor. /// private void OnDrawGizmos() { if (spawnPoint != null) { Gizmos.color = Color.cyan; Gizmos.DrawLine( new Vector3(spawnPoint.position.x, -10f, 0f), new Vector3(spawnPoint.position.x, 10f, 0f) ); } if (despawnPoint != null) { Gizmos.color = Color.magenta; Gizmos.DrawLine( new Vector3(despawnPoint.position.x, -10f, 0f), new Vector3(despawnPoint.position.x, 10f, 0f) ); } } } }