Working collisions on event-based triggers

This commit is contained in:
Michal Pikulski
2025-09-18 12:42:05 +02:00
parent 50070651c5
commit 44c1e48b82
12 changed files with 530 additions and 428 deletions

View File

@@ -0,0 +1,154 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4743746373562280435
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7889589254526244618}
- component: {fileID: 2565520060777160406}
- component: {fileID: 586678365535466516}
- component: {fileID: 3635110434097976059}
m_Layer: 11
m_Name: FloatingObstacle
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7889589254526244618
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.62358, y: 4.23222, z: 0}
m_LocalScale: {x: 5, y: 5, z: 5}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2565520060777160406
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 32718083aef44be2a4318681fcdf5b2e, type: 3}
m_Name:
m_EditorClassIdentifier:
prefabIndex: 0
damage: 1
moveSpeed: 2
enableMovement: 1
spawner: {fileID: 0}
--- !u!212 &586678365535466516
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0}
m_Color: {r: 0.9150943, g: 0, b: 0, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 0.16, y: 0.16}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!61 &3635110434097976059
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4743746373562280435}
m_Enabled: 1
serializedVersion: 3
m_Density: 1
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_ForceSendLayers:
serializedVersion: 2
m_Bits: 4294967295
m_ForceReceiveLayers:
serializedVersion: 2
m_Bits: 4294967295
m_ContactCaptureLayers:
serializedVersion: 2
m_Bits: 4294967295
m_CallbackLayers:
serializedVersion: 2
m_Bits: 4294967295
m_IsTrigger: 0
m_UsedByEffector: 0
m_CompositeOperation: 0
m_CompositeOrder: 0
m_Offset: {x: 0, y: 0}
m_SpriteTilingProperty:
border: {x: 0.049999997, y: 0.049999997, z: 0.049999997, w: 0.049999997}
pivot: {x: 0.5, y: 0.5}
oldSize: {x: 0.16, y: 0.16}
newSize: {x: 0.16, y: 0.16}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 0.16, y: 0.16}
m_EdgeRadius: 0

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 315a624eb99600444a51bb1d37c51742
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -4,8 +4,8 @@ using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Complete floating obstacle component that handles data, movement, collision detection, and pooling.
/// Obstacles move upward toward the surface and detect collisions with the player.
/// Complete floating obstacle component that handles data, movement, and pooling.
/// Obstacles move upward toward the surface. Collision detection is now handled by the player.
/// </summary>
public class FloatingObstacle : MonoBehaviour, IPoolable
{
@@ -23,13 +23,6 @@ namespace Minigames.DivingForPictures
[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;
@@ -57,7 +50,6 @@ namespace Minigames.DivingForPictures
// Private fields
private Collider2D _collider;
private float _collisionCheckTimer;
private bool _hasDealtDamage;
private Camera _mainCamera;
private float _screenTop;
@@ -86,7 +78,6 @@ namespace Minigames.DivingForPictures
HandleMovement();
}
HandleCollisionDetection();
CheckIfOffScreen();
}
@@ -99,57 +90,17 @@ namespace Minigames.DivingForPictures
}
/// <summary>
/// Checks for collisions with the player at regular intervals
/// Marks this obstacle as having dealt damage (called by PlayerDamageCollisionBehavior)
/// </summary>
private void HandleCollisionDetection()
public void MarkDamageDealt()
{
if (_hasDealtDamage || _collider == null) return;
_collisionCheckTimer -= Time.deltaTime;
if (_collisionCheckTimer <= 0f)
if (!_hasDealtDamage)
{
_collisionCheckTimer = collisionCheckInterval;
CheckForPlayerCollision();
_hasDealtDamage = true;
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} dealt {damage} damage to player");
}
}
/// <summary>
/// Checks if this obstacle is colliding with the player
/// </summary>
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]);
}
}
/// <summary>
/// Called when this obstacle collides with the player
/// </summary>
/// <param name="playerCollider">The player's collider</param>
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");
}
/// <summary>
/// Checks if the obstacle has moved off-screen and should be despawned
/// </summary>
@@ -202,7 +153,6 @@ namespace Minigames.DivingForPictures
public void OnSpawn()
{
_hasDealtDamage = false;
_collisionCheckTimer = 0f;
_screenTop = 0f; // Reset cached screen bounds
// Ensure the obstacle is active and visible
@@ -217,7 +167,6 @@ namespace Minigames.DivingForPictures
public void OnDespawn()
{
_hasDealtDamage = false;
_collisionCheckTimer = 0f;
Debug.Log($"[FloatingObstacle] Obstacle {gameObject.name} despawned");
}

View File

@@ -4,10 +4,9 @@ namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that handles damage from mobile obstacles.
/// Unlike bump collisions, this only deals damage without physical response.
/// Detects collisions between Player layer (7) and QuarryObstacle layer (11).
/// Uses trigger-based collision detection with shared immunity state.
/// </summary>
public class PlayerDamageCollisionBehavior : PlayerCollisionBehavior
public class ObstacleCollision : PlayerCollisionBehavior
{
[Header("Damage Settings")]
[Tooltip("Base damage amount dealt by obstacles")]
@@ -16,14 +15,6 @@ namespace Minigames.DivingForPictures
[Tooltip("Whether to use the obstacle's individual damage value or the base damage")]
[SerializeField] private bool useObstacleDamageValue = true;
protected override void Awake()
{
base.Awake();
// Override the obstacle layer mask to target QuarryObstacle layer (11)
obstacleLayerMask = 1 << 11; // QuarryObstacle layer
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
float damageAmount = baseDamage;
@@ -35,13 +26,16 @@ namespace Minigames.DivingForPictures
if (obstacleComponent != null)
{
damageAmount = obstacleComponent.Damage;
// Mark the obstacle as having dealt damage to prevent multiple hits
obstacleComponent.MarkDamageDealt();
}
}
// Apply damage (this could be extended to integrate with a health system)
ApplyDamage(damageAmount);
Debug.Log($"[PlayerDamageCollisionBehavior] Player took {damageAmount} damage from obstacle {obstacle.gameObject.name}");
Debug.Log($"[ObstacleCollision] Player took {damageAmount} damage from obstacle {obstacle.gameObject.name}");
}
/// <summary>
@@ -53,7 +47,7 @@ namespace Minigames.DivingForPictures
{
// For now, just log the damage
// In a full implementation, this would reduce player health, trigger UI updates, etc.
Debug.Log($"[PlayerDamageCollisionBehavior] Applied {damage} damage to player");
Debug.Log($"[ObstacleCollision] Applied {damage} damage to player");
// TODO: Integrate with health system when available
// Example: playerHealth.TakeDamage(damage);
@@ -65,22 +59,19 @@ namespace Minigames.DivingForPictures
/// </summary>
protected override void OnImmunityStart()
{
Debug.Log($"[PlayerDamageCollisionBehavior] Damage immunity started for {damageImmunityDuration} seconds");
Debug.Log($"[ObstacleCollision] Damage immunity started for {damageImmunityDuration} seconds");
// Don't block input for obstacle damage - let player keep moving
// Only broadcast the damage event
TriggerDamageStart();
// The shared immunity system will handle the collision prevention
}
/// <summary>
/// Override to handle immunity end without input restoration
/// Override to handle immunity end
/// </summary>
protected override void OnImmunityEnd()
{
Debug.Log($"[PlayerDamageCollisionBehavior] Damage immunity ended");
// Broadcast damage end event
TriggerDamageEnd();
Debug.Log($"[ObstacleCollision] Damage immunity ended");
// No special handling needed - shared immunity system handles collider re-enabling
}
/// <summary>

View File

@@ -60,8 +60,11 @@ namespace Minigames.DivingForPictures
[SerializeField] private int totalMaxPoolSize = 15;
[Header("Layer Settings")]
[Tooltip("Layer mask for tile collision detection - should match WorldObstacle layer")]
[SerializeField] private LayerMask tileLayerMask = 1 << 6; // WorldObstacle layer
[Tooltip("Layer mask for tile collision detection during spawn position validation")]
[SerializeField] private LayerMask tileLayerMask = -1; // Let user configure which layers to avoid
[Tooltip("Target layer for spawned obstacles - obstacles will be placed on this layer")]
[SerializeField] private int obstacleLayer = 11; // Default to layer 11, but configurable
[Header("Events")]
[Tooltip("Called when an obstacle is spawned")]
@@ -117,11 +120,11 @@ namespace Minigames.DivingForPictures
obstaclePrefabs[i].AddComponent<FloatingObstacle>();
}
// Ensure the prefab is on the correct layer
if (obstaclePrefabs[i].layer != 11) // QuarryObstacle layer
// Ensure the prefab is on the correct layer (using configurable obstacleLayer)
if (obstaclePrefabs[i].layer != obstacleLayer)
{
Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on QuarryObstacle layer (11). Setting layer automatically.");
SetLayerRecursively(obstaclePrefabs[i], 11);
Debug.LogWarning($"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({obstacleLayer}). Setting layer automatically.");
SetLayerRecursively(obstaclePrefabs[i], obstacleLayer);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -31,7 +31,7 @@ namespace Minigames.DivingForPictures
/// </summary>
public void OnTap(Vector2 worldPosition)
{
Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}");
// Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
_isTouchActive = true;
}
@@ -41,7 +41,7 @@ namespace Minigames.DivingForPictures
/// </summary>
public void OnHoldStart(Vector2 worldPosition)
{
Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}");
// Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
_isTouchActive = true;
}
@@ -51,7 +51,7 @@ namespace Minigames.DivingForPictures
/// </summary>
public void OnHoldMove(Vector2 worldPosition)
{
Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}");
// Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
}
@@ -60,7 +60,7 @@ namespace Minigames.DivingForPictures
/// </summary>
public void OnHoldEnd(Vector2 worldPosition)
{
Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}");
// Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}");
_isTouchActive = false;
}

View File

@@ -1,180 +0,0 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that grants temporary immunity and allows the player to phase through obstacles.
/// During immunity, the player can pass through all obstacles without further collision detection.
/// </summary>
public class PlayerPhaseCollisionBehavior : PlayerCollisionBehavior
{
[Header("Phase Settings")]
[Tooltip("Whether to disable the player's collider during immunity to allow phasing through obstacles")]
[SerializeField] private bool disableColliderDuringImmunity = true;
[Tooltip("How fast the player sprite blinks during phase mode (seconds between blinks)")]
[SerializeField] private float visualFeedbackBlinkRate = 0.1f;
private SpriteRenderer _spriteRenderer;
private bool _originalColliderState;
private bool _isBlinking;
private float _blinkTimer;
private Color _originalColor;
private float _originalAlpha;
protected override void Awake()
{
base.Awake();
// Get sprite renderer from player character
if (playerCharacter != null)
{
_spriteRenderer = playerCharacter.GetComponent<SpriteRenderer>();
}
if (_spriteRenderer != null)
{
_originalColor = _spriteRenderer.color;
_originalAlpha = _originalColor.a;
}
if (playerCollider != null)
{
_originalColliderState = playerCollider.enabled;
}
}
protected override void Update()
{
base.Update();
// Handle visual feedback blinking during immunity
if (_isBlinking && _spriteRenderer != null)
{
_blinkTimer -= Time.deltaTime;
if (_blinkTimer <= 0f)
{
// Toggle visibility
Color currentColor = _spriteRenderer.color;
currentColor.a = currentColor.a > 0.5f ? 0.3f : _originalAlpha;
_spriteRenderer.color = currentColor;
_blinkTimer = visualFeedbackBlinkRate;
}
}
}
protected override void CheckForCollisions()
{
// Override to skip collision detection entirely during immunity if collider is disabled
if (isImmune && disableColliderDuringImmunity)
{
return; // Skip collision detection completely
}
base.CheckForCollisions();
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
Debug.Log("[PlayerPhaseCollisionBehavior] Collision detected - entering phase mode");
// No immediate physical response - just start immunity period
// The immunity will be handled by the base class
}
protected override void OnImmunityStart()
{
base.OnImmunityStart();
// Disable collider to allow phasing through obstacles
if (disableColliderDuringImmunity && playerCollider != null)
{
playerCollider.enabled = false;
Debug.Log("[PlayerPhaseCollisionBehavior] Collider disabled - entering phase mode");
}
// Start visual feedback
StartVisualFeedback();
}
protected override void OnImmunityEnd()
{
base.OnImmunityEnd();
// Re-enable collider
if (playerCollider != null)
{
playerCollider.enabled = _originalColliderState;
Debug.Log("[PlayerPhaseCollisionBehavior] Collider re-enabled - exiting phase mode");
}
// Stop visual feedback
StopVisualFeedback();
}
/// <summary>
/// Starts the visual feedback to indicate immunity/phase mode
/// </summary>
private void StartVisualFeedback()
{
if (_spriteRenderer != null)
{
_isBlinking = true;
_blinkTimer = 0f;
// Start with reduced opacity
Color immunityColor = _originalColor;
immunityColor.a = 0.3f;
_spriteRenderer.color = immunityColor;
}
}
/// <summary>
/// Stops the visual feedback and restores original appearance
/// </summary>
private void StopVisualFeedback()
{
_isBlinking = false;
if (_spriteRenderer != null)
{
// Restore original color and alpha
_spriteRenderer.color = _originalColor;
}
}
/// <summary>
/// Public method to toggle collider behavior during immunity
/// </summary>
public void SetColliderDisabling(bool disable)
{
disableColliderDuringImmunity = disable;
}
/// <summary>
/// Check if player is currently in phase mode
/// </summary>
public bool IsPhasing => isImmune && disableColliderDuringImmunity;
/// <summary>
/// Manually trigger phase mode (useful for testing or special abilities)
/// </summary>
public void TriggerPhaseMode(float duration = -1f)
{
if (duration > 0f)
{
isImmune = true;
immunityTimer = duration;
OnImmunityStart();
}
else
{
isImmune = true;
immunityTimer = damageImmunityDuration;
OnImmunityStart();
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: d849a517ce3a41249ae9f37d2722cefa
timeCreated: 1758109641

View File

@@ -1,12 +1,13 @@
using UnityEngine;
using System.Collections;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that bumps the player toward the center of the trench.
/// Provides two modes: impulse (force-based push) or smooth movement to center.
/// Uses trigger-based collision detection with coroutine-based bump timing.
/// </summary>
public class PlayerBumpCollisionBehavior : PlayerCollisionBehavior
public class TileBumpCollision : PlayerCollisionBehavior
{
[Header("Bump Settings")]
[Tooltip("Type of bump response: Impulse pushes with force, SmoothToCenter moves directly to center")]
@@ -31,55 +32,9 @@ namespace Minigames.DivingForPictures
}
private bool _isBumping;
private float _bumpTimer;
private float _bumpStartX;
private float _bumpTargetX;
private float _bumpDuration;
private Coroutine _bumpCoroutine;
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
protected override void Update()
{
base.Update();
// Handle bump movement
if (_isBumping)
{
_bumpTimer -= Time.deltaTime;
if (_bumpTimer <= 0f)
{
// Bump finished
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
// Ensure we end exactly at target
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(_bumpTargetX, currentPos.y, currentPos.z);
}
}
else
{
// Apply bump movement
float progress = 1f - (_bumpTimer / _bumpDuration);
float curveValue = bumpCurve.Evaluate(progress);
float currentX = Mathf.Lerp(_bumpStartX, _bumpTargetX, curveValue);
// Apply the position to the player character
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(currentX, currentPos.y, currentPos.z);
}
}
}
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
switch (bumpMode)
@@ -93,7 +48,7 @@ namespace Minigames.DivingForPictures
break;
}
Debug.Log($"[PlayerBumpCollisionBehavior] Collision handled with {bumpMode} mode");
Debug.Log($"[TileBumpCollision] Collision handled with {bumpMode} mode");
}
/// <summary>
@@ -118,14 +73,11 @@ namespace Minigames.DivingForPictures
targetX = 0f;
}
// Set bump parameters
_bumpStartX = currentX;
_bumpTargetX = targetX;
_bumpDuration = 0.5f; // Fixed duration for impulse
float bumpDuration = 0.5f; // Fixed duration for impulse
StartBump();
StartBump(currentX, targetX, bumpDuration);
Debug.Log($"[PlayerBumpCollisionBehavior] Starting impulse bump from X={_bumpStartX} to X={_bumpTargetX} (force={bumpForce})");
Debug.Log($"[TileBumpCollision] Starting impulse bump from X={currentX} to X={targetX} (force={bumpForce})");
}
/// <summary>
@@ -138,31 +90,85 @@ namespace Minigames.DivingForPictures
float currentX = playerCharacter.transform.position.x;
float distanceToCenter = Mathf.Abs(currentX);
// Set bump parameters
_bumpStartX = currentX;
_bumpTargetX = 0f; // Always move to center
_bumpDuration = distanceToCenter / smoothMoveSpeed; // Duration based on distance and speed
float targetX = 0f; // Always move to center
float bumpDuration = distanceToCenter / smoothMoveSpeed; // Duration based on distance and speed
StartBump();
StartBump(currentX, targetX, bumpDuration);
Debug.Log($"[PlayerBumpCollisionBehavior] Starting smooth move to center from X={_bumpStartX} (speed={smoothMoveSpeed}, duration={_bumpDuration:F2}s)");
Debug.Log($"[TileBumpCollision] Starting smooth move to center from X={currentX} (speed={smoothMoveSpeed}, duration={bumpDuration:F2}s)");
}
/// <summary>
/// Common bump initialization
/// Common bump initialization using coroutines
/// </summary>
private void StartBump()
private void StartBump(float startX, float targetX, float duration)
{
// Stop any existing bump
if (_bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
}
_isBumping = true;
_bumpTimer = _bumpDuration;
// Block player input if enabled (use bump-specific blocking)
if (blockInputDuringBump && playerController != null && playerController.enabled)
{
playerController.enabled = false;
_bumpInputBlocked = true;
Debug.Log("[PlayerBumpCollisionBehavior] Player input blocked during bump");
Debug.Log("[TileBumpCollision] Player input blocked during bump");
}
// Start bump coroutine
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
}
/// <summary>
/// Coroutine that handles the bump movement over time
/// </summary>
private IEnumerator BumpCoroutine(float startX, float targetX, float duration)
{
float elapsedTime = 0f;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
// Calculate progress and apply curve
float progress = elapsedTime / duration;
float curveValue = bumpCurve.Evaluate(progress);
// Interpolate position
float currentX = Mathf.Lerp(startX, targetX, curveValue);
// Apply the position to the player character
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(currentX, currentPos.y, currentPos.z);
}
yield return null;
}
// Ensure we end exactly at target
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
playerCharacter.transform.position = new Vector3(targetX, currentPos.y, currentPos.z);
}
// Bump finished
_isBumping = false;
_bumpCoroutine = null;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
Debug.Log("[TileBumpCollision] Bump movement completed");
}
/// <summary>
@@ -178,22 +184,56 @@ namespace Minigames.DivingForPictures
// Update the controller's target position to current position to prevent snapping
UpdateControllerTarget();
Debug.Log("[PlayerBumpCollisionBehavior] Player input restored after bump");
Debug.Log("[TileBumpCollision] Player input restored after bump");
}
}
/// <summary>
/// Override to handle bump-specific input blocking during immunity
/// </summary>
protected override void OnImmunityStart()
{
Debug.Log($"[TileBumpCollision] Damage immunity started for {damageImmunityDuration} seconds");
// Block input if specified (in addition to any bump input blocking)
if (blockInputDuringImmunity && !_bumpInputBlocked)
{
BlockPlayerInput();
}
}
/// <summary>
/// Override to handle immunity end and bump cleanup
/// </summary>
protected override void OnImmunityEnd()
{
base.OnImmunityEnd();
// Stop any ongoing bump if immunity ends
if (_isBumping)
if (_isBumping && _bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
Debug.Log("[TileBumpCollision] Bump interrupted by immunity end");
}
}
/// <summary>
/// Called when component is destroyed - cleanup coroutines
/// </summary>
private void OnDestroy()
{
if (_bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
}
}
@@ -230,5 +270,25 @@ namespace Minigames.DivingForPictures
/// Check if input is currently blocked by bump
/// </summary>
public bool IsBumpInputBlocked => _bumpInputBlocked;
/// <summary>
/// Public method to manually stop bump movement
/// </summary>
public void StopBump()
{
if (_isBumping && _bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
_isBumping = false;
if (_bumpInputBlocked)
{
RestoreBumpInput();
}
Debug.Log("[TileBumpCollision] Bump manually stopped");
}
}
}
}