Working collisions on event-based triggers
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 315a624eb99600444a51bb1d37c51742
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d849a517ce3a41249ae9f37d2722cefa
|
||||
timeCreated: 1758109641
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user