Working collisions on event-based triggers
This commit is contained in:
@@ -1,36 +1,47 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Minigames.DivingForPictures
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for handling player collisions with world obstacles.
|
||||
/// Detects collisions between Player layer (7) and WorldObstacle layer (6).
|
||||
/// Uses trigger-based collision detection with shared immunity state across all collision behaviors.
|
||||
/// </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
|
||||
|
||||
|
||||
[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 damage state changes
|
||||
public static event Action OnDamageStart;
|
||||
public static event Action OnDamageEnd;
|
||||
|
||||
|
||||
// Instance tracking for shared state management
|
||||
private static readonly System.Collections.Generic.HashSet<PlayerCollisionBehavior> _allInstances =
|
||||
new System.Collections.Generic.HashSet<PlayerCollisionBehavior>();
|
||||
|
||||
/// <summary>
|
||||
/// Public static method to trigger damage start event from external classes
|
||||
/// </summary>
|
||||
@@ -38,7 +49,7 @@ namespace Minigames.DivingForPictures
|
||||
{
|
||||
OnDamageStart?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Public static method to trigger damage end event from external classes
|
||||
/// </summary>
|
||||
@@ -46,137 +57,212 @@ namespace Minigames.DivingForPictures
|
||||
{
|
||||
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)
|
||||
|
||||
// Set up shared collider reference (only once)
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
playerCollider = GetComponentInChildren<Collider2D>();
|
||||
if (playerCollider != null)
|
||||
_sharedPlayerCollider = GetComponent<Collider2D>();
|
||||
if (_sharedPlayerCollider == null)
|
||||
{
|
||||
Debug.Log($"[{GetType().Name}] Found collider on child object: {playerCollider.gameObject.name}");
|
||||
_sharedPlayerCollider = GetComponentInChildren<Collider2D>();
|
||||
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!");
|
||||
}
|
||||
}
|
||||
|
||||
if (playerCollider == null)
|
||||
|
||||
// Set up coroutine runner (use first instance)
|
||||
if (_coroutineRunner == null)
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}] No Collider2D found on this GameObject or its children!");
|
||||
_coroutineRunner = this;
|
||||
}
|
||||
|
||||
// Register this instance
|
||||
_allInstances.Add(this);
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Handle immunity timer
|
||||
if (isImmune)
|
||||
// Unregister this instance
|
||||
_allInstances.Remove(this);
|
||||
|
||||
// Clean up static references if this was the coroutine runner
|
||||
if (_coroutineRunner == this)
|
||||
{
|
||||
immunityTimer -= Time.deltaTime;
|
||||
if (immunityTimer <= 0f)
|
||||
if (_globalImmunityCoroutine != null)
|
||||
{
|
||||
isImmune = false;
|
||||
OnImmunityEnd();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for collisions if not immune
|
||||
if (!isImmune && playerCollider != null)
|
||||
{
|
||||
CheckForCollisions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks for collisions with obstacle layer objects
|
||||
/// Called when another collider enters this trigger collider
|
||||
/// </summary>
|
||||
protected virtual void CheckForCollisions()
|
||||
/// <param name="other">The other collider that entered the trigger</param>
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Get all colliders overlapping with the player
|
||||
Collider2D[] overlapping = new Collider2D[10];
|
||||
ContactFilter2D filter = new ContactFilter2D();
|
||||
filter.SetLayerMask(obstacleLayerMask);
|
||||
filter.useTriggers = true;
|
||||
Debug.Log($"[{GetType().Name}] OnTriggerEnter2D called with collider: {other.gameObject.name} on layer: {other.gameObject.layer}");
|
||||
|
||||
int count = playerCollider.Overlap(filter, overlapping);
|
||||
|
||||
if (count > 0)
|
||||
// Check if the other collider is on one of our obstacle layers and we're not immune
|
||||
if (IsObstacleLayer(other.gameObject.layer) && !_isGloballyImmune)
|
||||
{
|
||||
// Found collision, trigger response
|
||||
OnCollisionDetected(overlapping[0]);
|
||||
OnCollisionDetected(other);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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;
|
||||
|
||||
if (_isGloballyImmune) return;
|
||||
|
||||
// Start shared immunity period
|
||||
StartGlobalImmunity();
|
||||
|
||||
// Call the specific collision response
|
||||
HandleCollisionResponse(obstacle);
|
||||
|
||||
// Notify about immunity start
|
||||
OnImmunityStart();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Starts the shared immunity period across all collision behaviors
|
||||
/// </summary>
|
||||
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 damage start event
|
||||
OnDamageStart?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the immunity timer
|
||||
/// </summary>
|
||||
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 damage end event
|
||||
OnDamageEnd?.Invoke();
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// Called when damage immunity starts (called on all instances)
|
||||
/// </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
|
||||
/// Called when damage immunity ends (called on all instances)
|
||||
/// </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>
|
||||
@@ -186,14 +272,14 @@ namespace Minigames.DivingForPictures
|
||||
{
|
||||
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>
|
||||
@@ -206,7 +292,7 @@ namespace Minigames.DivingForPictures
|
||||
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates the PlayerController's internal target to match current position
|
||||
/// </summary>
|
||||
@@ -217,22 +303,57 @@ namespace Minigames.DivingForPictures
|
||||
// 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
|
||||
/// Checks if the given layer is included in our obstacle layer mask
|
||||
/// </summary>
|
||||
public bool IsImmune => isImmune;
|
||||
|
||||
/// <param name="layer">The layer to check</param>
|
||||
/// <returns>True if the layer is included in the obstacle layer mask</returns>
|
||||
private bool IsObstacleLayer(int layer)
|
||||
{
|
||||
return (obstacleLayerMask.value & (1 << layer)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remaining immunity time
|
||||
/// Public property to check if player is currently immune (shared across all instances)
|
||||
/// </summary>
|
||||
public float RemainingImmunityTime => immunityTimer;
|
||||
public static bool IsImmune => _isGloballyImmune;
|
||||
|
||||
/// <summary>
|
||||
/// Public method to manually end immunity (affects all collision behaviors)
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
OnDamageEnd?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user