Files
AppleHillsProduction/Assets/Scripts/Minigames/DivingForPictures/Player/PlayerController.cs
2025-10-16 23:19:33 +02:00

321 lines
12 KiB
C#

using AppleHills.Core.Interfaces;
using AppleHills.Core.Settings;
using AppleHillsCamera;
using Core;
using Input;
using UnityEngine;
namespace Minigames.DivingForPictures.Player
{
/// <summary>
/// Handles endless descender movement in response to tap and hold input events.
/// Moves the character horizontally to follow the finger or tap position.
/// </summary>
public class PlayerController : MonoBehaviour, ITouchInputConsumer, IPausable
{
[Tooltip("Reference to the edge anchor that this player should follow for Y position")]
[SerializeField] private EdgeAnchor edgeAnchor;
// Settings reference
private IDivingMinigameSettings settings;
private float targetFingerX;
private bool isTouchActive;
private float originY;
// Tap impulse system variables
private float tapImpulseStrength = 0f;
private float tapDirection = 0f;
// Initialization flag
private bool isInitialized = false;
void Awake()
{
originY = transform.position.y;
// Get settings from GameManager
settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
if (settings == null)
{
Debug.LogError("[PlayerController] Failed to load diving minigame settings!");
}
}
void OnEnable()
{
// Register as a pausable component with DivingGameManager
DivingGameManager.Instance.RegisterPausableComponent(this);
}
void Start()
{
// Initialize target to current position
targetFingerX = transform.position.x;
isTouchActive = false;
// Try to find edge anchor if not assigned
if (edgeAnchor == null)
{
// First try to find edge anchor on the same object or parent
edgeAnchor = GetComponentInParent<EdgeAnchor>();
// If not found, find any edge anchor in the scene
if (edgeAnchor == null)
{
edgeAnchor = FindFirstObjectByType<EdgeAnchor>();
if (edgeAnchor == null)
{
Logging.Warning("[PlayerController] No EdgeAnchor found in scene. Origin Y position won't update with camera changes.");
}
else
{
Logging.Debug($"[PlayerController] Auto-connected to EdgeAnchor on {edgeAnchor.gameObject.name}");
}
}
}
// Subscribe to edge anchor events if it exists
if (edgeAnchor != null)
{
// Unsubscribe first to prevent duplicate subscriptions
edgeAnchor.OnPositionUpdated -= UpdateOriginYFromAnchor;
edgeAnchor.OnPositionUpdated += UpdateOriginYFromAnchor;
// Update origin Y based on current anchor position
UpdateOriginYFromAnchor();
}
DivingGameManager.Instance.OnGameInitialized += Initialize;
// If game is already initialized, initialize immediately
if (DivingGameManager.Instance.GetType().GetField("_isGameInitialized",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance)?.GetValue(DivingGameManager.Instance) is bool isInitialized && isInitialized)
{
Initialize();
}
}
/// <summary>
/// Initializes the player controller when triggered by DivingGameManager
/// </summary>
private void Initialize()
{
if (isInitialized) return;
// Register as default consumer for input
InputManager.Instance?.SetDefaultConsumer(this);
isInitialized = true;
Logging.Debug("[PlayerController] Initialized");
}
private void OnDestroy()
{
DivingGameManager.Instance.OnGameInitialized -= Initialize;
// Unregister as a pausable component
DivingGameManager.Instance.UnregisterPausableComponent(this);
// Unsubscribe from edge anchor events
if (edgeAnchor != null)
{
edgeAnchor.OnPositionUpdated -= UpdateOriginYFromAnchor;
}
}
/// <summary>
/// Handles tap input. Applies an impulse in the tapped direction.
/// </summary>
public void OnTap(Vector2 worldPosition)
{
// Ignore input when paused
if (isPaused) return;
// Logging.Debug($"[EndlessDescenderController] OnTap at {worldPosition}");
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);
// Set impulse strength to full
tapImpulseStrength = 1.0f;
// Store target X for animation purposes
targetFingerX = targetX;
// Do not set _isTouchActive for taps anymore
// _isTouchActive = true; - Removed to prevent continuous movement
}
/// <summary>
/// Handles the start of a hold input. Begins tracking the finger.
/// </summary>
public void OnHoldStart(Vector2 worldPosition)
{
// Ignore input when paused
if (isPaused) return;
// Logging.Debug($"[EndlessDescenderController] OnHoldStart at {worldPosition}");
targetFingerX = Mathf.Clamp(worldPosition.x, settings.ClampXMin, settings.ClampXMax);
isTouchActive = true;
}
/// <summary>
/// Handles hold move input. Updates the target X position as the finger moves.
/// </summary>
public void OnHoldMove(Vector2 worldPosition)
{
// Ignore input when paused
if (isPaused) return;
// Logging.Debug($"[EndlessDescenderController] OnHoldMove at {worldPosition}");
targetFingerX = Mathf.Clamp(worldPosition.x, settings.ClampXMin, settings.ClampXMax);
}
/// <summary>
/// Handles the end of a hold input. Stops tracking.
/// </summary>
public void OnHoldEnd(Vector2 worldPosition)
{
// Ignore input when paused
if (isPaused) return;
// Logging.Debug($"[EndlessDescenderController] OnHoldEnd at {worldPosition}");
isTouchActive = false;
}
void Update()
{
// Skip movement processing if paused
if (isPaused) return;
// Handle hold movement
if (isTouchActive)
{
float currentX = transform.position.x;
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);
float absOffset = Mathf.Abs(offset);
float t = Mathf.Pow(absOffset / maxOffset, exponent); // Non-linear drop-off
float moveStep = Mathf.Sign(offset) * maxOffset * t * Time.deltaTime * lerpSpeed;
// Prevent overshooting
moveStep = Mathf.Clamp(moveStep, -absOffset, absOffset);
float newX = currentX + moveStep;
newX = Mathf.Clamp(newX, settings.ClampXMin, settings.ClampXMax);
UpdatePosition(newX);
}
// Handle tap impulse movement
else if (tapImpulseStrength > 0)
{
float currentX = transform.position.x;
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.TapMaxDistance * tapImpulseStrength);
// Apply movement in tap direction
float newX = currentX + (moveDistance * tapDirection);
newX = Mathf.Clamp(newX, settings.ClampXMin, settings.ClampXMax);
// Reduce impulse strength over time
tapImpulseStrength -= Time.deltaTime * settings.TapDecelerationRate;
if (tapImpulseStrength < 0.01f)
{
tapImpulseStrength = 0f;
}
UpdatePosition(newX);
}
}
/// <summary>
/// Updates the player's position with the given X coordinate
/// </summary>
private void UpdatePosition(float newX)
{
float newY = originY;
// Add vertical offset from WobbleBehavior if present
WobbleBehavior wobble = GetComponent<WobbleBehavior>();
if (wobble != null)
{
newY += wobble.VerticalOffset;
}
transform.position = new Vector3(newX, newY, transform.position.z);
}
/// <summary>
/// Updates the origin Y position based on camera adjustments
/// </summary>
public void UpdateOriginY(float newOriginY)
{
originY = newOriginY;
}
/// <summary>
/// Updates the origin Y position based on the current position of the player
/// This method is intended to be called by the camera adapter when the camera is adjusted.
/// </summary>
private void UpdateOriginYFromCurrentPosition()
{
originY = transform.position.y;
}
/// <summary>
/// Updates the origin Y position based on the current position of the edge anchor
/// This method is intended to be called by the edge anchor when its position is updated.
/// </summary>
private void UpdateOriginYFromAnchor()
{
originY = edgeAnchor.transform.position.y;
}
#region IPausable Implementation
private bool isPaused = false;
/// <summary>
/// Pauses the player controller, blocking all input processing
/// </summary>
public void Pause()
{
if (isPaused) return;
isPaused = true;
// If we're being paused, stop any active touch and tap impulse
isTouchActive = false;
tapImpulseStrength = 0f;
Logging.Debug("[PlayerController] Paused");
}
/// <summary>
/// Resumes the player controller, allowing input processing again
/// </summary>
public void DoResume()
{
if (!isPaused) return;
isPaused = false;
Logging.Debug("[PlayerController] Resumed");
}
/// <summary>
/// Returns whether the player controller is currently paused
/// </summary>
public bool IsPaused => isPaused;
#endregion
}
}