using UnityEngine; using System; using System.Collections; namespace Minigames.DivingForPictures { /// /// Base class for handling player collisions with world obstacles. /// Uses trigger-based collision detection with shared immunity state across all collision behaviors. /// public abstract class PlayerCollisionBehavior : MonoBehaviour { [Header("Collision Settings")] [Tooltip("Duration in seconds of damage immunity after being hit")] [SerializeField] protected float damageImmunityDuration = 1.0f; [Tooltip("Layer mask for obstacle detection - configure which layers contain obstacles")] [SerializeField] protected LayerMask obstacleLayerMask = -1; [Header("Input Blocking")] [Tooltip("Whether to block player input during damage immunity period")] [SerializeField] protected bool blockInputDuringImmunity; [Header("References")] [Tooltip("The player character GameObject (auto-assigned if empty)")] [SerializeField] protected GameObject playerCharacter; [Tooltip("Reference to the PlayerController component (auto-assigned if empty)")] [SerializeField] protected PlayerController playerController; // Static shared immunity state across all collision behaviors private static bool _isGloballyImmune; private static Coroutine _globalImmunityCoroutine; private static MonoBehaviour _coroutineRunner; private static Collider2D _sharedPlayerCollider; // Events for immunity and damage state changes public static event Action OnImmunityStarted; public static event Action OnImmunityEnded; public static event Action OnDamageTaken; // Instance tracking for shared state management private static readonly System.Collections.Generic.HashSet _allInstances = new System.Collections.Generic.HashSet(); /// /// Public static method to trigger immunity start event from external classes /// public static void TriggerImmunityStarted() { OnImmunityStarted?.Invoke(); } /// /// Public static method to trigger immunity end event from external classes /// public static void TriggerImmunityEnded() { OnImmunityEnded?.Invoke(); } /// /// Public static method to trigger damage taken event from external classes /// public static void TriggerDamageTaken() { OnDamageTaken?.Invoke(); } protected bool wasInputBlocked; protected virtual void Awake() { // Auto-assign if not set in inspector if (playerCharacter == null) playerCharacter = gameObject; if (playerController == null) playerController = GetComponent(); // Set up shared collider reference (only once) if (_sharedPlayerCollider == null) { _sharedPlayerCollider = GetComponent(); if (_sharedPlayerCollider == null) { _sharedPlayerCollider = GetComponentInChildren(); if (_sharedPlayerCollider != null) { Debug.Log($"[PlayerCollisionBehavior] Found collider on child object: {_sharedPlayerCollider.gameObject.name}"); } } if (_sharedPlayerCollider == null) { Debug.LogError($"[PlayerCollisionBehavior] No Collider2D found on this GameObject or its children!"); } } // Set up coroutine runner (use first instance) if (_coroutineRunner == null) { _coroutineRunner = this; } // Register this instance _allInstances.Add(this); } private void OnDestroy() { // Unregister this instance _allInstances.Remove(this); // Clean up static references if this was the coroutine runner if (_coroutineRunner == this) { if (_globalImmunityCoroutine != null) { StopCoroutine(_globalImmunityCoroutine); _globalImmunityCoroutine = null; } _coroutineRunner = null; // Find a new coroutine runner if there are other instances foreach (var instance in _allInstances) { if (instance != null) { _coroutineRunner = instance; break; } } } } /// /// Called when another collider enters this trigger collider /// /// The other collider that entered the trigger private void OnTriggerEnter2D(Collider2D other) { Debug.Log($"[{GetType().Name}] OnTriggerEnter2D called with collider: {other.gameObject.name} on layer: {other.gameObject.layer}"); // Check if the other collider is on one of our obstacle layers and we're not immune if (IsObstacleLayer(other.gameObject.layer) && !_isGloballyImmune) { OnCollisionDetected(other); } } /// /// Called when a collision with an obstacle is detected /// /// The obstacle collider that was hit protected virtual void OnCollisionDetected(Collider2D obstacle) { if (_isGloballyImmune) return; // Trigger damage taken event first OnDamageTaken?.Invoke(); // Start shared immunity period StartGlobalImmunity(); // Call the specific collision response HandleCollisionResponse(obstacle); } /// /// Starts the shared immunity period across all collision behaviors /// private void StartGlobalImmunity() { if (_isGloballyImmune) return; // Already immune _isGloballyImmune = true; // Disable the shared collider to prevent further collisions if (_sharedPlayerCollider != null) { _sharedPlayerCollider.enabled = false; } // Stop any existing immunity coroutine if (_globalImmunityCoroutine != null && _coroutineRunner != null) { _coroutineRunner.StopCoroutine(_globalImmunityCoroutine); } // Start new immunity coroutine if (_coroutineRunner != null) { _globalImmunityCoroutine = _coroutineRunner.StartCoroutine(ImmunityCoroutine()); } // Notify all instances about immunity start foreach (var instance in _allInstances) { if (instance != null) { instance.OnImmunityStart(); } } // Broadcast immunity start event OnImmunityStarted?.Invoke(); } /// /// Coroutine that handles the immunity timer /// private IEnumerator ImmunityCoroutine() { Debug.Log($"[PlayerCollisionBehavior] Starting immunity coroutine for {damageImmunityDuration} seconds"); yield return new WaitForSeconds(damageImmunityDuration); Debug.Log($"[PlayerCollisionBehavior] Immunity period ended"); // End immunity _isGloballyImmune = false; _globalImmunityCoroutine = null; // Re-enable the shared collider if (_sharedPlayerCollider != null) { _sharedPlayerCollider.enabled = true; } // Notify all instances about immunity end foreach (var instance in _allInstances) { if (instance != null) { instance.OnImmunityEnd(); } } // Broadcast immunity end event OnImmunityEnded?.Invoke(); } /// /// Override this method to implement specific collision response behavior /// /// The obstacle that was collided with protected abstract void HandleCollisionResponse(Collider2D obstacle); /// /// Called when damage immunity starts (called on all instances) /// protected virtual void OnImmunityStart() { Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds"); // Block input if specified if (blockInputDuringImmunity) { BlockPlayerInput(); } } /// /// Called when damage immunity ends (called on all instances) /// protected virtual void OnImmunityEnd() { Debug.Log($"[{GetType().Name}] Damage immunity ended"); // Restore input if it was blocked if (wasInputBlocked) { RestorePlayerInput(); } } /// /// Restores player input after immunity /// protected virtual void RestorePlayerInput() { if (playerController != null && wasInputBlocked) { playerController.enabled = true; wasInputBlocked = false; // Update the controller's target position to current position to prevent snapping UpdateControllerTarget(); Debug.Log($"[{GetType().Name}] Player input restored after immunity"); } } /// /// Blocks player input during immunity /// protected virtual void BlockPlayerInput() { if (playerController != null && playerController.enabled) { playerController.enabled = false; wasInputBlocked = true; Debug.Log($"[{GetType().Name}] Player input blocked during immunity"); } } /// /// Updates the PlayerController's internal target to match current position /// protected virtual void UpdateControllerTarget() { if (playerController != null && playerCharacter != null) { // Use reflection to update the private _targetFingerX field var targetField = typeof(PlayerController) .GetField("_targetFingerX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (targetField != null) { targetField.SetValue(playerController, playerCharacter.transform.position.x); } } } /// /// Checks if the given layer is included in our obstacle layer mask /// /// The layer to check /// True if the layer is included in the obstacle layer mask private bool IsObstacleLayer(int layer) { return (obstacleLayerMask.value & (1 << layer)) != 0; } /// /// Public property to check if player is currently immune (shared across all instances) /// public static bool IsImmune => _isGloballyImmune; /// /// Public method to manually end immunity (affects all collision behaviors) /// public static void EndImmunity() { if (_isGloballyImmune && _globalImmunityCoroutine != null && _coroutineRunner != null) { _coroutineRunner.StopCoroutine(_globalImmunityCoroutine); _globalImmunityCoroutine = null; _isGloballyImmune = false; // Re-enable the shared collider if (_sharedPlayerCollider != null) { _sharedPlayerCollider.enabled = true; } // Notify all instances foreach (var instance in _allInstances) { if (instance != null) { instance.OnImmunityEnd(); } } OnImmunityEnded?.Invoke(); } } } }