using UnityEngine; using System; namespace Minigames.DivingForPictures { /// /// Base class for handling player collisions with world obstacles. /// Detects collisions between Player layer (7) and WorldObstacle layer (6). /// 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 - should match WorldObstacle layer")] [SerializeField] protected LayerMask obstacleLayerMask = 1 << 6; // WorldObstacle layer [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; // Events for damage state changes public static event Action OnDamageStart; public static event Action OnDamageEnd; /// /// Public static method to trigger damage start event from external classes /// public static void TriggerDamageStart() { OnDamageStart?.Invoke(); } /// /// Public static method to trigger damage end event from external classes /// public static void TriggerDamageEnd() { OnDamageEnd?.Invoke(); } protected bool isImmune; protected float immunityTimer; protected Collider2D playerCollider; protected bool wasInputBlocked; protected virtual void Awake() { // Auto-assign if not set in inspector if (playerCharacter == null) playerCharacter = gameObject; if (playerController == null) playerController = GetComponent(); // Look for collider on this GameObject first, then in children playerCollider = GetComponent(); if (playerCollider == null) { playerCollider = GetComponentInChildren(); if (playerCollider != null) { Debug.Log($"[{GetType().Name}] Found collider on child object: {playerCollider.gameObject.name}"); } } if (playerCollider == null) { Debug.LogError($"[{GetType().Name}] No Collider2D found on this GameObject or its children!"); } } protected virtual void Update() { // Handle immunity timer if (isImmune) { immunityTimer -= Time.deltaTime; if (immunityTimer <= 0f) { isImmune = false; OnImmunityEnd(); } } // Check for collisions if not immune if (!isImmune && playerCollider != null) { CheckForCollisions(); } } /// /// Checks for collisions with obstacle layer objects /// protected virtual void CheckForCollisions() { // Get all colliders overlapping with the player Collider2D[] overlapping = new Collider2D[10]; ContactFilter2D filter = new ContactFilter2D(); filter.SetLayerMask(obstacleLayerMask); filter.useTriggers = true; int count = playerCollider.Overlap(filter, overlapping); if (count > 0) { // Found collision, trigger response OnCollisionDetected(overlapping[0]); } } /// /// Called when a collision with an obstacle is detected /// /// The obstacle collider that was hit protected virtual void OnCollisionDetected(Collider2D obstacle) { if (isImmune) return; // Start immunity period isImmune = true; immunityTimer = damageImmunityDuration; // Call the specific collision response HandleCollisionResponse(obstacle); // Notify about immunity start OnImmunityStart(); } /// /// 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 /// protected virtual void OnImmunityStart() { Debug.Log($"[{GetType().Name}] Damage immunity started for {damageImmunityDuration} seconds"); // Block input if specified if (blockInputDuringImmunity) { BlockPlayerInput(); } // Broadcast damage start event OnDamageStart?.Invoke(); } /// /// Called when damage immunity ends /// protected virtual void OnImmunityEnd() { Debug.Log($"[{GetType().Name}] Damage immunity ended"); // Restore input if it was blocked if (wasInputBlocked) { RestorePlayerInput(); } // Broadcast damage end event OnDamageEnd?.Invoke(); } /// /// 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); } } } /// /// Public property to check if player is currently immune /// public bool IsImmune => isImmune; /// /// Remaining immunity time /// public float RemainingImmunityTime => immunityTimer; } }