The feel is fine

This commit is contained in:
2025-09-24 15:16:31 +02:00
parent 783541a776
commit aeaa977294
19 changed files with 494 additions and 344 deletions

View File

@@ -0,0 +1,69 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Collision behavior that handles damage from mobile obstacles.
/// Uses trigger-based collision detection with shared immunity state.
/// </summary>
public class ObstacleCollision : PlayerCollisionBehavior
{
protected override void OnEnable()
{
base.OnEnable();
// Subscribe to immunity events
OnImmunityStarted += HandleImmunityStarted;
OnImmunityEnded += HandleImmunityEnded;
}
protected override void OnDisable()
{
// Unsubscribe from immunity events
OnImmunityStarted -= HandleImmunityStarted;
OnImmunityEnded -= HandleImmunityEnded;
base.OnDisable();
}
protected override void HandleCollisionResponse(Collider2D obstacle)
{
// Check if the obstacle is on the ObstacleLayer
if (obstacle.gameObject.layer != _devSettings.ObstacleLayer)
{
// If not on the obstacle layer, don't process the collision
Debug.Log($"[ObstacleCollision] Ignored collision with object on layer {obstacle.gameObject.layer} (expected {_devSettings.ObstacleLayer})");
return;
}
// Mark the obstacle as having dealt damage to prevent multiple hits
FloatingObstacle obstacleComponent = obstacle.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
obstacleComponent.MarkDamageDealt();
}
Debug.Log($"[ObstacleCollision] Player hit by obstacle {obstacle.gameObject.name}");
}
/// <summary>
/// Handler for immunity started event - replaces OnImmunityStart method
/// </summary>
private void HandleImmunityStarted()
{
Debug.Log($"[ObstacleCollision] Damage immunity started for {_gameSettings.DamageImmunityDuration} seconds");
// Don't block input for obstacle damage - let player keep moving
// The shared immunity system will handle the collision prevention
}
/// <summary>
/// Handler for immunity ended event - replaces OnImmunityEnd method
/// </summary>
private void HandleImmunityEnded()
{
Debug.Log($"[ObstacleCollision] Damage immunity ended");
// No special handling needed - shared immunity system handles collider re-enabling
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c9c18dbd013d42ae8c221e6205e4d49c
timeCreated: 1758116850

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections;
using AppleHills.Core.Settings;
using AppleHills.Utilities;
namespace Minigames.DivingForPictures
{
@@ -27,6 +28,7 @@ namespace Minigames.DivingForPictures
private static Coroutine _globalImmunityCoroutine;
private static MonoBehaviour _coroutineRunner;
private static Collider2D _sharedPlayerCollider;
private static bool wasInputBlocked = false; // Track if input was blocked
// Events for immunity and damage state changes
public static event Action OnImmunityStarted;
@@ -117,8 +119,8 @@ namespace Minigames.DivingForPictures
if (_isGloballyImmune)
return;
// Check if the collider is an obstacle
if ((_devSettings.PlayerObstacleLayerMask.value & (1 << other.gameObject.layer)) != 0)
// Use our extension method to check if the collider's layer is in the obstacle layer mask
if (_devSettings.PlayerObstacleLayerMask.Contains(other.gameObject))
{
HandleObstacleCollision(other);
}
@@ -179,6 +181,8 @@ namespace Minigames.DivingForPictures
if (_devSettings.BlockInputDuringImmunity && playerController != null)
{
// Notify player controller to block input
BlockPlayerInput();
wasInputBlocked = true;
}
// Trigger event for all listeners
@@ -200,13 +204,57 @@ namespace Minigames.DivingForPictures
private IEnumerator ImmunityTimerCoroutine()
{
// Wait for the immunity duration
yield return new WaitForSeconds(_gameSettings.EndlessDescenderDamageImmunityDuration);
yield return new WaitForSeconds(_gameSettings.DamageImmunityDuration);
// Reset immunity state
_isGloballyImmune = false;
// Restore player input if it was blocked
if (_devSettings.BlockInputDuringImmunity)
{
RestorePlayerInput();
}
// Trigger event for all listeners
OnImmunityEnded?.Invoke();
}
/// <summary>
/// Blocks player input during immunity
/// </summary>
protected virtual void BlockPlayerInput()
{
if (playerController != null && playerController.enabled)
{
playerController.enabled = false;
wasInputBlocked = true;
Debug.Log($"[{GetType().Name}] Player input blocked during immunity");
}
}
/// <summary>
/// Restores player input after immunity
/// </summary>
protected virtual void RestorePlayerInput()
{
if (playerController != null && wasInputBlocked)
{
playerController.enabled = true;
wasInputBlocked = false;
// Update the controller's target position to current position to prevent snapping
UpdateControllerTarget();
Debug.Log($"[{GetType().Name}] Player input restored after immunity");
}
}
/// <summary>
/// Updates the player controller's target position to the current position to prevent snapping
/// </summary>
protected virtual void UpdateControllerTarget()
{
// This would normally be implemented in derived classes if needed
}
}
}

View File

@@ -47,7 +47,7 @@ namespace Minigames.DivingForPictures
public void OnTap(Vector2 worldPosition)
{
// Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}");
float targetX = Mathf.Clamp(worldPosition.x, _settings.EndlessDescenderClampXMin, _settings.EndlessDescenderClampXMax);
float targetX = Mathf.Clamp(worldPosition.x, _settings.ClampXMin, _settings.ClampXMax);
// Calculate tap direction (+1 for right, -1 for left)
_tapDirection = Mathf.Sign(targetX - transform.position.x);
@@ -68,7 +68,7 @@ namespace Minigames.DivingForPictures
public void OnHoldStart(Vector2 worldPosition)
{
// Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, _settings.EndlessDescenderClampXMin, _settings.EndlessDescenderClampXMax);
_targetFingerX = Mathf.Clamp(worldPosition.x, _settings.ClampXMin, _settings.ClampXMax);
_isTouchActive = true;
}
@@ -78,7 +78,7 @@ namespace Minigames.DivingForPictures
public void OnHoldMove(Vector2 worldPosition)
{
// Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}");
_targetFingerX = Mathf.Clamp(worldPosition.x, _settings.EndlessDescenderClampXMin, _settings.EndlessDescenderClampXMax);
_targetFingerX = Mathf.Clamp(worldPosition.x, _settings.ClampXMin, _settings.ClampXMax);
}
/// <summary>
@@ -96,9 +96,9 @@ namespace Minigames.DivingForPictures
if (_isTouchActive)
{
float currentX = transform.position.x;
float lerpSpeed = _settings.EndlessDescenderLerpSpeed;
float maxOffset = _settings.EndlessDescenderMaxOffset;
float exponent = _settings.EndlessDescenderSpeedExponent;
float lerpSpeed = _settings.LerpSpeed;
float maxOffset = _settings.MaxOffset;
float exponent = _settings.SpeedExponent;
float targetX = _targetFingerX;
float offset = targetX - currentX;
offset = Mathf.Clamp(offset, -maxOffset, maxOffset);
@@ -108,7 +108,7 @@ namespace Minigames.DivingForPictures
// Prevent overshooting
moveStep = Mathf.Clamp(moveStep, -absOffset, absOffset);
float newX = currentX + moveStep;
newX = Mathf.Clamp(newX, _settings.EndlessDescenderClampXMin, _settings.EndlessDescenderClampXMax);
newX = Mathf.Clamp(newX, _settings.ClampXMin, _settings.ClampXMax);
UpdatePosition(newX);
}
@@ -116,21 +116,21 @@ namespace Minigames.DivingForPictures
else if (_tapImpulseStrength > 0)
{
float currentX = transform.position.x;
float maxOffset = _settings.EndlessDescenderMaxOffset;
float lerpSpeed = _settings.EndlessDescenderLerpSpeed;
float maxOffset = _settings.MaxOffset;
float lerpSpeed = _settings.LerpSpeed;
// Calculate move distance based on impulse strength
float moveDistance = maxOffset * _tapImpulseStrength * Time.deltaTime * lerpSpeed;
// Limit total movement from single tap
moveDistance = Mathf.Min(moveDistance, _settings.EndlessDescenderTapMaxDistance * _tapImpulseStrength);
moveDistance = Mathf.Min(moveDistance, _settings.TapMaxDistance * _tapImpulseStrength);
// Apply movement in tap direction
float newX = currentX + (moveDistance * _tapDirection);
newX = Mathf.Clamp(newX, _settings.EndlessDescenderClampXMin, _settings.EndlessDescenderClampXMax);
newX = Mathf.Clamp(newX, _settings.ClampXMin, _settings.ClampXMax);
// Reduce impulse strength over time
_tapImpulseStrength -= Time.deltaTime * _settings.EndlessDescenderTapDecelerationRate;
_tapImpulseStrength -= Time.deltaTime * _settings.TapDecelerationRate;
if (_tapImpulseStrength < 0.01f)
{
_tapImpulseStrength = 0f;

View File

@@ -12,157 +12,139 @@ namespace Minigames.DivingForPictures
{
private bool _isBumping;
private Coroutine _bumpCoroutine;
private bool _bumpInputBlocked; // Tracks bump-specific input blocking
protected override void HandleCollisionResponse(Collider2D obstacle)
{
// Check if the obstacle is on the TrenchTileLayer
if (obstacle.gameObject.layer != _devSettings.TrenchTileLayer)
{
// If not on the trench tile layer, don't process the collision
Debug.Log($"[TileBumpCollision] Ignored collision with object on layer {obstacle.gameObject.layer} (expected {_devSettings.TrenchTileLayer})");
return;
}
// Use bump mode from developer settings
switch (_devSettings.BumpMode)
{
case 0: // Impulse mode
case BumpMode.Impulse:
StartImpulseBump();
break;
case 1: // SmoothToCenter mode
case BumpMode.SmoothToCenter:
StartSmoothMoveToCenter();
break;
}
Debug.Log($"[TileBumpCollision] Collision handled with {(_devSettings.BumpMode == 0 ? "Impulse" : "SmoothToCenter")} mode");
Debug.Log($"[TileBumpCollision] Collision handled with {_devSettings.BumpMode} mode");
}
/// <summary>
/// Applies an impulse force toward center
/// Starts an impulse bump toward the center with force-based distance
/// </summary>
private void StartImpulseBump()
{
if (_isBumping || playerCharacter == null)
return;
if (playerCharacter == null) return;
_isBumping = true;
float currentX = playerCharacter.transform.position.x;
// Block input during bump if configured
if (_gameSettings.EndlessDescenderBlockInputDuringBump && playerController != null)
// Calculate bump distance based on force and current position
float directionToCenter = currentX > 0 ? -1f : 1f; // Direction toward center
// Calculate target position - closer to center based on bump force
float bumpDistance = _gameSettings.BumpForce * 0.2f; // Scale factor for distance
float targetX = currentX + (directionToCenter * bumpDistance);
// Clamp to center if we would overshoot
if ((currentX > 0 && targetX < 0) || (currentX < 0 && targetX > 0))
{
_bumpInputBlocked = true;
// TODO: Implement input blocking
targetX = 0f;
}
// Calculate direction to center (X = 0)
float directionToCenter = Mathf.Sign(-playerCharacter.transform.position.x);
float bumpDuration = 0.5f; // Fixed duration for impulse
// Start impulse bump coroutine
if (_bumpCoroutine != null)
StopCoroutine(_bumpCoroutine);
StartBump(currentX, targetX, bumpDuration);
_bumpCoroutine = StartCoroutine(ImpulseBumpCoroutine(directionToCenter));
Debug.Log($"[TileBumpCollision] Started impulse bump with force {_gameSettings.EndlessDescenderBumpForce} in direction {directionToCenter}");
Debug.Log($"[TileBumpCollision] Starting impulse bump from X={currentX} to X={targetX} (force={_gameSettings.BumpForce})");
}
/// <summary>
/// Smoothly moves the player to center
/// Starts smooth movement to the center
/// </summary>
private void StartSmoothMoveToCenter()
{
if (_isBumping || playerCharacter == null)
return;
if (playerCharacter == null) return;
float currentX = playerCharacter.transform.position.x;
float distanceToCenter = Mathf.Abs(currentX);
float targetX = 0f; // Always move to center
float bumpDuration = distanceToCenter / _gameSettings.SmoothMoveSpeed; // Duration based on distance and speed
StartBump(currentX, targetX, bumpDuration);
Debug.Log($"[TileBumpCollision] Starting smooth move to center from X={currentX} (speed={_gameSettings.SmoothMoveSpeed}, duration={bumpDuration:F2}s)");
}
/// <summary>
/// Common bump initialization using coroutines
/// </summary>
private void StartBump(float startX, float targetX, float duration)
{
// Stop any existing bump
if (_bumpCoroutine != null)
{
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = null;
}
_isBumping = true;
// Block input during bump if configured
if (_gameSettings.EndlessDescenderBlockInputDuringBump && playerController != null)
{
_bumpInputBlocked = true;
// TODO: Implement input blocking
}
// Start smooth move coroutine
if (_bumpCoroutine != null)
StopCoroutine(_bumpCoroutine);
_bumpCoroutine = StartCoroutine(SmoothMoveToCenterCoroutine());
Debug.Log($"[TileBumpCollision] Started smooth move to center with speed {_gameSettings.EndlessDescenderSmoothMoveSpeed}");
// Start bump coroutine
_bumpCoroutine = StartCoroutine(BumpCoroutine(startX, targetX, duration));
}
/// <summary>
/// Coroutine to handle impulse bump movement
/// Coroutine that handles the bump movement over time
/// </summary>
private IEnumerator ImpulseBumpCoroutine(float direction)
private IEnumerator BumpCoroutine(float startX, float targetX, float duration)
{
float elapsedTime = 0f;
float bumpDuration = 0.5f; // Fixed duration for impulse
Vector3 startPosition = playerCharacter.transform.position;
while (elapsedTime < bumpDuration)
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
// Use evaluation time from curve for non-linear movement
float t = elapsedTime / bumpDuration;
float curveValue = _devSettings.BumpCurve.Evaluate(t);
// Calculate progress and apply curve
float progress = elapsedTime / duration;
float curveValue = _devSettings.BumpCurve.Evaluate(progress);
// Calculate movement distance based on force and curve
float distance = _gameSettings.EndlessDescenderBumpForce * curveValue * Time.deltaTime;
// Interpolate position
float currentX = Mathf.Lerp(startX, targetX, curveValue);
// Move the player toward center
Vector3 newPosition = playerCharacter.transform.position;
newPosition.x += direction * distance;
// Clamp to valid range
newPosition.x = Mathf.Clamp(newPosition.x, _gameSettings.EndlessDescenderClampXMin, _gameSettings.EndlessDescenderClampXMax);
// Apply the position
playerCharacter.transform.position = newPosition;
// Apply the position to the player character
if (playerCharacter != null)
{
Vector3 currentPos = playerCharacter.transform.position;
currentPos.x = Mathf.Clamp(currentX, _gameSettings.ClampXMin, _gameSettings.ClampXMax);
playerCharacter.transform.position = currentPos;
}
yield return null;
}
// Finish bump
_isBumping = false;
_bumpInputBlocked = false;
_bumpCoroutine = null;
}
/// <summary>
/// Coroutine to handle smooth movement to center
/// </summary>
private IEnumerator SmoothMoveToCenterCoroutine()
{
Vector3 startPosition = playerCharacter.transform.position;
Vector3 targetPosition = new Vector3(0f, startPosition.y, startPosition.z);
// Calculate distance to center and expected duration
float distanceToCenter = Mathf.Abs(startPosition.x);
float expectedDuration = distanceToCenter / _gameSettings.EndlessDescenderSmoothMoveSpeed;
float elapsedTime = 0f;
// Move until we reach the center
while (elapsedTime < expectedDuration)
// Ensure we end exactly at target
if (playerCharacter != null)
{
elapsedTime += Time.deltaTime;
// Calculate progress based on time and curve
float t = elapsedTime / expectedDuration;
float curveValue = _devSettings.BumpCurve.Evaluate(t);
// Calculate interpolated position
Vector3 newPosition = Vector3.Lerp(startPosition, targetPosition, curveValue);
// Apply the position
playerCharacter.transform.position = newPosition;
yield return null;
Vector3 currentPos = playerCharacter.transform.position;
float clampedTargetX = Mathf.Clamp(targetX, _gameSettings.ClampXMin, _gameSettings.ClampXMax);
playerCharacter.transform.position = new Vector3(clampedTargetX, currentPos.y, currentPos.z);
}
// Ensure we end at exactly the center
playerCharacter.transform.position = targetPosition;
// Finish bump
// Bump finished
_isBumping = false;
_bumpInputBlocked = false;
_bumpCoroutine = null;
Debug.Log("[TileBumpCollision] Bump movement completed");
}
}
}