using UnityEngine; using Pooling; namespace Minigames.DivingForPictures { /// /// Complete floating obstacle component that handles movement and pooling. /// Obstacles move upward toward the surface. Collision detection is handled by the player. /// Once an obstacle hits the player, its collider is disabled to prevent further collisions. /// public class FloatingObstacle : MonoBehaviour, IPoolable { [Header("Obstacle Properties")] [Tooltip("Index of the prefab this obstacle was created from")] [SerializeField] private int prefabIndex; [Tooltip("Movement speed of this obstacle")] [SerializeField] private float moveSpeed = 2f; [Header("Movement")] [Tooltip("Whether this obstacle moves (can be disabled for static obstacles)")] [SerializeField] private bool enableMovement = true; [Header("References")] [Tooltip("Reference to the spawner that created this obstacle")] [SerializeField] private ObstacleSpawner spawner; // Public properties public int PrefabIndex { get => prefabIndex; set => prefabIndex = value; } public float MoveSpeed { get => moveSpeed; set => moveSpeed = value; } // Private fields private Collider2D _collider; private Camera _mainCamera; private float _screenTop; private void Awake() { _collider = GetComponent(); if (_collider == null) { _collider = GetComponentInChildren(); } if (_collider == null) { Debug.LogError($"[FloatingObstacle] No Collider2D found on {gameObject.name}!"); } _mainCamera = Camera.main; } private void Update() { if (enableMovement) { HandleMovement(); } CheckIfOffScreen(); } /// /// Moves the obstacle upward based on its speed /// private void HandleMovement() { transform.position += Vector3.up * (moveSpeed * Time.deltaTime); } /// /// Disables the collider after hitting the player to prevent further collisions /// This is more performant than tracking hit state /// public void MarkDamageDealt() { if (_collider != null && _collider.enabled) { _collider.enabled = false; Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} hit player - collider disabled"); } } /// /// Checks if the obstacle has moved off-screen and should be despawned /// private void CheckIfOffScreen() { if (_mainCamera == null) return; // Calculate screen top if not cached if (_screenTop == 0f) { Vector3 topWorldPoint = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.nearClipPlane)); _screenTop = topWorldPoint.y; } // Check if obstacle is above screen if (transform.position.y > _screenTop + 2f) // Extra buffer for safety { ReturnToPool(); } } /// /// Returns this obstacle to the spawner's pool /// private void ReturnToPool() { if (spawner != null) { spawner.ReturnObstacleToPool(gameObject, prefabIndex); } else { Debug.LogWarning($"[FloatingObstacle] Cannot return {gameObject.name} to pool - missing spawner reference"); Destroy(gameObject); } } /// /// Sets the spawner reference for this obstacle /// /// The spawner that created this obstacle public void SetSpawner(ObstacleSpawner obstacleSpawner) { spawner = obstacleSpawner; } /// /// Called when the obstacle is retrieved from the pool /// public void OnSpawn() { _screenTop = 0f; // Reset cached screen bounds // Re-enable the collider for reuse if (_collider != null) { _collider.enabled = true; } // Ensure the obstacle is active and visible gameObject.SetActive(true); Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} spawned"); } /// /// Called when the obstacle is returned to the pool /// public void OnDespawn() { // Re-enable collider for next use (in case it was disabled) if (_collider != null) { _collider.enabled = true; } Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} despawned"); } /// /// Public method to manually trigger return to pool (for external systems) /// public void ForceReturnToPool() { ReturnToPool(); } } }