239 lines
8.3 KiB
C#
239 lines
8.3 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using System;
|
|||
|
|
|
|||
|
|
namespace Minigames.DivingForPictures
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Base class for handling player collisions with world obstacles.
|
|||
|
|
/// Detects collisions between Player layer (7) and WorldObstacle layer (6).
|
|||
|
|
/// </summary>
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Public static method to trigger damage start event from external classes
|
|||
|
|
/// </summary>
|
|||
|
|
public static void TriggerDamageStart()
|
|||
|
|
{
|
|||
|
|
OnDamageStart?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Public static method to trigger damage end event from external classes
|
|||
|
|
/// </summary>
|
|||
|
|
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<PlayerController>();
|
|||
|
|
|
|||
|
|
// Look for collider on this GameObject first, then in children
|
|||
|
|
playerCollider = GetComponent<Collider2D>();
|
|||
|
|
if (playerCollider == null)
|
|||
|
|
{
|
|||
|
|
playerCollider = GetComponentInChildren<Collider2D>();
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Checks for collisions with obstacle layer objects
|
|||
|
|
/// </summary>
|
|||
|
|
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]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Called when a collision with an obstacle is detected
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="obstacle">The obstacle collider that was hit</param>
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Override this method to implement specific collision response behavior
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="obstacle">The obstacle that was collided with</param>
|
|||
|
|
protected abstract void HandleCollisionResponse(Collider2D obstacle);
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Called when damage immunity starts
|
|||
|
|
/// </summary>
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Called when damage immunity ends
|
|||
|
|
/// </summary>
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Restores player input after immunity
|
|||
|
|
/// </summary>
|
|||
|
|
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");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Blocks player input during immunity
|
|||
|
|
/// </summary>
|
|||
|
|
protected virtual void BlockPlayerInput()
|
|||
|
|
{
|
|||
|
|
if (playerController != null && playerController.enabled)
|
|||
|
|
{
|
|||
|
|
playerController.enabled = false;
|
|||
|
|
wasInputBlocked = true;
|
|||
|
|
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Updates the PlayerController's internal target to match current position
|
|||
|
|
/// </summary>
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Public property to check if player is currently immune
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsImmune => isImmune;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Remaining immunity time
|
|||
|
|
/// </summary>
|
|||
|
|
public float RemainingImmunityTime => immunityTimer;
|
|||
|
|
}
|
|||
|
|
}
|