using UnityEngine; using Pooling; namespace Minigames.DivingForPictures { /// /// Complete floating obstacle component that handles data, movement, collision detection, and pooling. /// Obstacles move upward toward the surface and detect collisions with the player. /// public class FloatingObstacle : MonoBehaviour, IPoolable { [Header("Obstacle Properties")] [Tooltip("Index of the prefab this obstacle was created from")] [SerializeField] private int prefabIndex; [Tooltip("Damage this obstacle deals to the player")] [SerializeField] private float damage = 1f; [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("Collision Detection")] [Tooltip("Layer mask for player detection - should match Player layer")] [SerializeField] private LayerMask playerLayerMask = 1 << 7; // Player layer [Tooltip("How often to check for collisions (in seconds)")] [SerializeField] private float collisionCheckInterval = 0.1f; [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 Damage { get => damage; set => damage = value; } public float MoveSpeed { get => moveSpeed; set => moveSpeed = value; } public bool HasDealtDamage => _hasDealtDamage; // Private fields private Collider2D _collider; private float _collisionCheckTimer; private bool _hasDealtDamage; 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(); } HandleCollisionDetection(); CheckIfOffScreen(); } /// /// Moves the obstacle upward based on its speed /// private void HandleMovement() { transform.position += Vector3.up * (moveSpeed * Time.deltaTime); } /// /// Checks for collisions with the player at regular intervals /// private void HandleCollisionDetection() { if (_hasDealtDamage || _collider == null) return; _collisionCheckTimer -= Time.deltaTime; if (_collisionCheckTimer <= 0f) { _collisionCheckTimer = collisionCheckInterval; CheckForPlayerCollision(); } } /// /// Checks if this obstacle is colliding with the player /// private void CheckForPlayerCollision() { Collider2D[] overlapping = new Collider2D[5]; ContactFilter2D filter = new ContactFilter2D(); filter.SetLayerMask(playerLayerMask); filter.useTriggers = true; int count = _collider.Overlap(filter, overlapping); if (count > 0 && !_hasDealtDamage) { // Found collision with player OnPlayerCollision(overlapping[0]); } } /// /// Called when this obstacle collides with the player /// /// The player's collider private void OnPlayerCollision(Collider2D playerCollider) { _hasDealtDamage = true; // Trigger damage through events (following the existing pattern) Debug.Log($"[FloatingObstacle] Obstacle dealt {damage} damage to player"); // Broadcast damage event using the static method PlayerCollisionBehavior.TriggerDamageStart(); // Continue moving upward - don't destroy or stop the obstacle Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} hit player and continues moving"); } /// /// 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() { _hasDealtDamage = false; _collisionCheckTimer = 0f; _screenTop = 0f; // Reset cached screen bounds // 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() { _hasDealtDamage = false; _collisionCheckTimer = 0f; Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} despawned"); } /// /// Public method to manually trigger return to pool (for external systems) /// public void ForceReturnToPool() { ReturnToPool(); } } }