using UnityEngine; using System; using System.Collections; using AppleHills.Core.Settings; using Core; using UnityEngine.Audio; namespace Minigames.DivingForPictures.PictureCamera { /// /// Manages the camera viewfinder visual representation and animation /// Responsible only for visual aspects with no game logic /// public class CameraViewfinderManager : MonoBehaviour { // Singleton instance private static CameraViewfinderManager _instance; public static CameraViewfinderManager Instance { get { if (_instance == null && Application.isPlaying) { _instance = FindAnyObjectByType(); if (_instance == null) { var go = new GameObject("CameraViewfinderManager"); _instance = go.AddComponent(); } } return _instance; } } [Header("References")] [Tooltip("The Canvas to spawn the viewfinder UI on")] [SerializeField] private Canvas targetCanvas; // References private GameObject viewfinderInstance; private RectTransform viewfinderRectTransform; private Viewfinder viewfinderComponent; private UnityEngine.Camera mainCamera; private AudioSource _audioSource; // Animation state private float animationProgress = 0f; private bool isAnimating = false; private bool isReversePhase = false; // Whether we're in the zoom-out phase private float currentProximity = 0f; // How close we are to the target (0=far, 1=directly over target) private Transform targetTransform; private Coroutine animationCoroutine; // Target position and size (calculated once at start) private Vector3 targetScreenPosition; private float targetViewfinderSize; private Vector2 targetAnchoredPosition; // target position in canvas units // Store settings private IDivingMinigameSettings settings; // New field to store the current input mode private PhotoInputModes currentInputMode; public AudioResource flashSound; public AudioResource focusSound; // Events for progress milestones public event Action OnProgressUpdated; // Continuous progress updates (0-1) public event Action OnAnimationStarted; public event Action OnAnimationCompleted; public event Action OnReverseAnimationStarted; // New event for when zoom-out phase starts public event Action OnProximityUpdated; // New event for proximity updates public event Action OnProgressThresholdReached; // Fires at configured thresholds (25%, 50%, etc.) public event Action OnViewfinderTapped; // Event when viewfinder is tapped during normal display public event Action OnViewfinderTappedDuringAnimation; // Event when viewfinder is tapped during animation (with proximity) private void Awake() { if (_instance == null) { _instance = this; } else if (_instance != this) { Destroy(gameObject); } } private void Start() { settings = GameManager.GetSettingsObject(); mainCamera = UnityEngine.Camera.main; _audioSource = GetComponent(); // Get the photo input mode from settings currentInputMode = settings.PhotoInputMode; } /// /// Begin the viewfinder animation targeting a specific transform /// /// The transform to focus on (usually the monster) public void StartViewfinderAnimation(Transform target) { if (isAnimating) { StopViewfinderAnimation(); } if (settings.ViewfinderPrefab == null) { Debug.LogError("[CameraViewfinderManager] No viewfinder prefab assigned!"); return; } if (targetCanvas == null) { Debug.LogError("[CameraViewfinderManager] No canvas assigned!"); return; } targetTransform = target; // Calculate target screen position and size only once at the start CalculateTargetScreenPositionAndSize(); // Create viewfinder as UI element viewfinderInstance = Instantiate(settings.ViewfinderPrefab, targetCanvas.transform); viewfinderRectTransform = viewfinderInstance.GetComponent(); viewfinderComponent = viewfinderInstance.GetComponent(); if (viewfinderRectTransform == null) { Debug.LogError("[CameraViewfinderManager] Viewfinder prefab doesn't have a RectTransform component!"); Destroy(viewfinderInstance); return; } // Initialize viewfinder with a reference to this manager if (viewfinderComponent != null) { viewfinderComponent.Initialize(this); viewfinderComponent.SetupInputOverride(); InitializeViewfinder(viewfinderComponent); } // Reset state animationProgress = 0f; isAnimating = true; // Determine canvas width for CanvasScaler-consistent sizing RectTransform canvasRect = targetCanvas.transform as RectTransform; float canvasWidth = canvasRect != null ? canvasRect.rect.width : Screen.width; // Initialize viewfinder size and position to full canvas width (square) at center float startSize = canvasWidth; viewfinderRectTransform.sizeDelta = new Vector2(startSize, startSize); viewfinderRectTransform.anchorMin = new Vector2(0.5f, 0.5f); viewfinderRectTransform.anchorMax = new Vector2(0.5f, 0.5f); viewfinderRectTransform.pivot = new Vector2(0.5f, 0.5f); viewfinderRectTransform.anchoredPosition = Vector2.zero; // Compute target anchored position in canvas units from previously calculated screen position targetAnchoredPosition = ScreenToAnchoredPosition(targetScreenPosition); // Fire starting event OnAnimationStarted?.Invoke(); // Start animation coroutine animationCoroutine = StartCoroutine(AnimateViewfinder()); } /// /// Calculates the target screen position and size based on monster's sprite bounds /// private void CalculateTargetScreenPositionAndSize() { if (targetTransform == null || mainCamera == null) return; RectTransform canvasRect = targetCanvas != null ? targetCanvas.transform as RectTransform : null; if (canvasRect == null) { Debug.LogError("[CameraViewfinderManager] Target canvas RectTransform not found."); return; } // Choose UI camera for coordinate conversion Camera uiCamera = null; if (targetCanvas.renderMode == RenderMode.ScreenSpaceCamera || targetCanvas.renderMode == RenderMode.WorldSpace) { uiCamera = targetCanvas.worldCamera; } // Get sprite renderer from the monster SpriteRenderer spriteRenderer = targetTransform.GetComponent(); if (spriteRenderer == null || spriteRenderer.sprite == null) { // Fallback to transform position and default size if no sprite renderer targetScreenPosition = mainCamera.WorldToScreenPoint(targetTransform.position); // Convert to anchored UI position RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, targetScreenPosition, uiCamera, out targetAnchoredPosition); // Default size: fraction of canvas width (canvas units) float canvasWidth = canvasRect.rect.width; targetViewfinderSize = canvasWidth * 0.25f; Logging.Warning("[CameraViewfinderManager] No SpriteRenderer found on target, using default size"); return; } // Calculate world bounds of the sprite and apply padding Bounds spriteBounds = spriteRenderer.bounds; Vector3 paddedSize = spriteBounds.size * settings.PaddingFactor; Bounds paddedBounds = new Bounds(spriteBounds.center, paddedSize); // Convert bounds corners to screen space Vector3[] worldCorners = new Vector3[4]; worldCorners[0] = new Vector3(paddedBounds.min.x, paddedBounds.min.y, paddedBounds.center.z); // BL worldCorners[1] = new Vector3(paddedBounds.max.x, paddedBounds.min.y, paddedBounds.center.z); // BR worldCorners[2] = new Vector3(paddedBounds.min.x, paddedBounds.max.y, paddedBounds.center.z); // TL worldCorners[3] = new Vector3(paddedBounds.max.x, paddedBounds.max.y, paddedBounds.center.z); // TR // Convert screen-space corners to canvas local points (canvas units) bool anyFailed = false; Vector2[] localCorners = new Vector2[4]; for (int i = 0; i < 4; i++) { Vector3 screenPos = mainCamera.WorldToScreenPoint(worldCorners[i]); if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPos, uiCamera, out localCorners[i])) { anyFailed = true; } } // Fallback: if conversion failed for some reason, keep original behavior (pixels -> clamp -> convert later) if (anyFailed) { Vector2 minScreen = new Vector2(float.MaxValue, float.MaxValue); Vector2 maxScreen = new Vector2(float.MinValue, float.MinValue); foreach (Vector3 corner in worldCorners) { Vector3 sp = mainCamera.WorldToScreenPoint(corner); minScreen.x = Mathf.Min(minScreen.x, sp.x); minScreen.y = Mathf.Min(minScreen.y, sp.y); maxScreen.x = Mathf.Max(maxScreen.x, sp.x); maxScreen.y = Mathf.Max(maxScreen.y, sp.y); } float widthPx = maxScreen.x - minScreen.x; float heightPx = maxScreen.y - minScreen.y; float canvasWidth = canvasRect.rect.width; float scaleX = Screen.width > 0 ? canvasWidth / Screen.width : 1f; targetViewfinderSize = Mathf.Max(widthPx, heightPx) * scaleX; // approximate conversion float minCanvas = canvasWidth * settings.MinSizePercent; float maxCanvas = canvasWidth * settings.MaxSizePercent; targetViewfinderSize = Mathf.Clamp(targetViewfinderSize, minCanvas, maxCanvas); // Target position targetScreenPosition = mainCamera.WorldToScreenPoint(spriteBounds.center); RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, targetScreenPosition, uiCamera, out targetAnchoredPosition); return; } // Compute width/height in canvas units from local corners float minX = float.MaxValue, minY = float.MaxValue, maxX = float.MinValue, maxY = float.MinValue; for (int i = 0; i < 4; i++) { minX = Mathf.Min(minX, localCorners[i].x); minY = Mathf.Min(minY, localCorners[i].y); maxX = Mathf.Max(maxX, localCorners[i].x); maxY = Mathf.Max(maxY, localCorners[i].y); } float widthCanvas = Mathf.Max(0f, maxX - minX); float heightCanvas = Mathf.Max(0f, maxY - minY); float canvasSquare = Mathf.Max(widthCanvas, heightCanvas); // Clamp using canvas width float canvasW = canvasRect.rect.width; float minSizeCanvas = canvasW * settings.MinSizePercent; float maxSizeCanvas = canvasW * settings.MaxSizePercent; targetViewfinderSize = Mathf.Clamp(canvasSquare, minSizeCanvas, maxSizeCanvas); // Target position in both screen and canvas units targetScreenPosition = mainCamera.WorldToScreenPoint(spriteBounds.center); RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, targetScreenPosition, uiCamera, out targetAnchoredPosition); Logging.Debug( $"[CameraViewfinderManager] Target size (canvas): {targetViewfinderSize}, target anchored: {targetAnchoredPosition}"); } /// /// Stop the current viewfinder animation and clean up /// public void StopViewfinderAnimation() { if (animationCoroutine != null) { StopCoroutine(animationCoroutine); animationCoroutine = null; } if (viewfinderInstance != null) { Destroy(viewfinderInstance); viewfinderInstance = null; } isAnimating = false; targetTransform = null; } /// /// Coroutine that handles the viewfinder animation /// private IEnumerator AnimateViewfinder() { RectTransform canvasRect = targetCanvas.transform as RectTransform; float canvasWidth = canvasRect != null ? canvasRect.rect.width : Screen.width; // IMPORTANT: We're animating from full canvas width DOWN to the target size float startSize = canvasWidth; // Always start with full canvas width float endSize = targetViewfinderSize; // End at the calculated target size float elapsedTime = 0f; bool[] thresholdTriggered = new bool[settings.ViewfinderProgressThresholds.Length]; // Debug the actual values Logging.Debug($"[CameraViewfinderManager] Animation starting: startSize={startSize}, endSize={endSize}, " + $"current sizeDelta={viewfinderRectTransform.sizeDelta}"); // Verify the initial size is set correctly viewfinderRectTransform.sizeDelta = new Vector2(startSize, startSize); while (elapsedTime < settings.ViewfinderShrinkDuration) { if (targetTransform == null) { StopViewfinderAnimation(); yield break; } elapsedTime += Time.unscaledDeltaTime; animationProgress = Mathf.Clamp01(elapsedTime / settings.ViewfinderShrinkDuration); // The curve value might be inverted - ensure we're shrinking not growing float curveValue = settings.ViewfinderShrinkCurve.Evaluate(animationProgress); // FIX: Ensure we're interpolating from large (start) to small (end) // This is critical - we must ensure the start and end are in the right order float currentSize = Mathf.Lerp(startSize, endSize, curveValue); // Additional check to ensure size is actually shrinking if (startSize > endSize && currentSize > startSize) { Logging.Warning($"[CameraViewfinderManager] Animation curve producing wrong direction! " + $"progress={animationProgress:F2}, curveValue={curveValue:F2}"); // Force correct behavior curveValue = animationProgress; // Override with linear interpolation currentSize = Mathf.Lerp(startSize, endSize, curveValue); } viewfinderRectTransform.sizeDelta = new Vector2(currentSize, currentSize); // Move in UI space towards the anchored target position Vector2 currentPos = viewfinderRectTransform.anchoredPosition; Vector2 newPos = Vector2.Lerp(currentPos, targetAnchoredPosition, settings.ViewfinderMoveSpeed * Time.unscaledDeltaTime); viewfinderRectTransform.anchoredPosition = newPos; // Log the animation state occasionally for debugging if (animationProgress % 0.25f <= 0.01f || animationProgress >= 0.99f) { Logging.Debug($"[CameraViewfinderManager] Animation progress: {animationProgress:F2}, " + $"curveValue={curveValue:F2}, currentSize={currentSize:F2}, currentPos={newPos}"); } for (int i = 0; i < settings.ViewfinderProgressThresholds.Length; i++) { if (!thresholdTriggered[i] && animationProgress >= settings.ViewfinderProgressThresholds[i]) { thresholdTriggered[i] = true; OnProgressThresholdReached?.Invoke(settings.ViewfinderProgressThresholds[i]); } } OnProgressUpdated?.Invoke(animationProgress); yield return null; } // Ensure we reach the exact end size viewfinderRectTransform.sizeDelta = new Vector2(endSize, endSize); animationProgress = 1f; OnProgressUpdated?.Invoke(animationProgress); OnAnimationCompleted?.Invoke(); // Log final state to confirm we reached the target size Logging.Debug( $"[CameraViewfinderManager] Animation completed: final size={viewfinderRectTransform.sizeDelta}"); yield return new WaitForSecondsRealtime(0.5f); StopViewfinderAnimation(); } // Converts a screen-space point to a RectTransform anchoredPosition for the target canvas private Vector2 ScreenToAnchoredPosition(Vector3 screenPosition) { if (targetCanvas == null) { return new Vector2(screenPosition.x, screenPosition.y); } RectTransform canvasRect = targetCanvas.transform as RectTransform; Camera uiCamera = null; if (targetCanvas.renderMode == RenderMode.ScreenSpaceCamera || targetCanvas.renderMode == RenderMode.WorldSpace) { uiCamera = targetCanvas.worldCamera; } Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPosition, uiCamera, out localPoint); return localPoint; } private void OnDestroy() { StopViewfinderAnimation(); if (_instance == this) { _instance = null; } } /// /// Handles tap event from the viewfinder and forwards it /// private void HandleViewfinderTapped() { // Forward the tap event to any listeners OnViewfinderTapped?.Invoke(); } /// /// Hides the currently displayed viewfinder /// public void HideViewfinder() { if (viewfinderInstance != null) { viewfinderComponent.RemoveInputOverride(); // Unsubscribe from all viewfinder events if (viewfinderComponent != null) { viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTapped; viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTappedDuringAnimation; viewfinderComponent.OnViewfinderHoldStarted -= HandleViewfinderHoldStarted; viewfinderComponent.OnViewfinderHoldEnded -= HandleViewfinderHoldEnded; } Destroy(viewfinderInstance); viewfinderInstance = null; viewfinderComponent = null; viewfinderRectTransform = null; Logging.Debug("[CameraViewfinderManager] Hid viewfinder"); } } /// /// Shows the viewfinder in the middle of the screen at full size without animation /// This is the first step in the two-step process, showing the UI without targeting a monster yet /// public void ShowFullScreenViewfinder() { if (viewfinderInstance != null) { // Already showing a viewfinder, destroy it first Destroy(viewfinderInstance); } if (settings.ViewfinderPrefab == null) { Debug.LogError("[CameraViewfinderManager] No viewfinder prefab assigned!"); return; } if (targetCanvas == null) { Debug.LogError("[CameraViewfinderManager] No canvas assigned!"); return; } isAnimating = false; // Create viewfinder as UI element viewfinderInstance = Instantiate(settings.ViewfinderPrefab, targetCanvas.transform); viewfinderRectTransform = viewfinderInstance.GetComponent(); viewfinderComponent = viewfinderInstance.GetComponent(); if (viewfinderRectTransform == null) { Debug.LogError("[CameraViewfinderManager] Viewfinder prefab doesn't have a RectTransform component!"); Destroy(viewfinderInstance); return; } // Initialize viewfinder with a reference to this manager if (viewfinderComponent != null) { viewfinderComponent.Initialize(this); viewfinderComponent.SetupInputOverride(); InitializeViewfinder(viewfinderComponent); } // Determine canvas width for full-screen sizing RectTransform canvasRect = targetCanvas.transform as RectTransform; float canvasWidth = canvasRect != null ? canvasRect.rect.width : Screen.width; // Set up the viewfinder in the center of the screen at full canvas width viewfinderRectTransform.anchorMin = new Vector2(0.5f, 0.5f); viewfinderRectTransform.anchorMax = new Vector2(0.5f, 0.5f); viewfinderRectTransform.pivot = new Vector2(0.5f, 0.5f); viewfinderRectTransform.sizeDelta = new Vector2(canvasWidth, canvasWidth); viewfinderRectTransform.anchoredPosition = Vector2.zero; Logging.Debug($"[CameraViewfinderManager] Showed full-screen viewfinder with size {canvasWidth}"); } /// /// Starts the complete viewfinder animation sequence (zoom in, then zoom out) /// /// The transform to focus on (usually the monster) public void StartViewfinderSequence(Transform target) { if (isAnimating) { StopViewfinderAnimation(); } if (settings.ViewfinderPrefab == null) { Debug.LogError("[CameraViewfinderManager] No viewfinder prefab assigned!"); return; } if (targetCanvas == null) { Debug.LogError("[CameraViewfinderManager] No canvas assigned!"); return; } targetTransform = target; isReversePhase = false; currentProximity = 0f; PlayFocusSound(); // Calculate target screen position and size based on monster's sprite bounds CalculateTargetScreenPositionAndSize(); // Reuse existing viewfinder instance if it exists if (viewfinderInstance == null) { // Create viewfinder as UI element if it doesn't exist yet viewfinderInstance = Instantiate(settings.ViewfinderPrefab, targetCanvas.transform); viewfinderRectTransform = viewfinderInstance.GetComponent(); viewfinderComponent = viewfinderInstance.GetComponent(); if (viewfinderRectTransform == null) { Debug.LogError("[CameraViewfinderManager] Viewfinder prefab doesn't have a RectTransform component!"); Destroy(viewfinderInstance); return; } } else { // Unsubscribe from any existing event to prevent multiple subscriptions if (viewfinderComponent != null) { viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTapped; viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTappedDuringAnimation; } // Subscribe to the viewfinder's tap event for interrupting the animation viewfinderComponent.OnViewfinderTapped += HandleViewfinderTappedDuringAnimation; } // Initialize viewfinder with appropriate event handlers based on input mode if (viewfinderComponent != null) { viewfinderComponent.Initialize(this); viewfinderComponent.SetupInputOverride(); InitializeViewfinder(viewfinderComponent); // For animation sequence, we need additional handling for tapping during animation if (currentInputMode == PhotoInputModes.Tap) { viewfinderComponent.OnViewfinderTapped += HandleViewfinderTappedDuringAnimation; } } // Reset state animationProgress = 0f; isAnimating = true; // Determine canvas width for CanvasScaler-consistent sizing RectTransform canvasRect = targetCanvas.transform as RectTransform; float canvasWidth = canvasRect != null ? canvasRect.rect.width : Screen.width; // Initialize viewfinder size and position to full canvas width (square) at center float startSize = canvasWidth; viewfinderRectTransform.sizeDelta = new Vector2(startSize, startSize); viewfinderRectTransform.anchorMin = new Vector2(0.5f, 0.5f); viewfinderRectTransform.anchorMax = new Vector2(0.5f, 0.5f); viewfinderRectTransform.pivot = new Vector2(0.5f, 0.5f); viewfinderRectTransform.anchoredPosition = Vector2.zero; // Compute target anchored position in canvas units from previously calculated screen position targetAnchoredPosition = ScreenToAnchoredPosition(targetScreenPosition); // Fire starting event OnAnimationStarted?.Invoke(); // Start sequence coroutine animationCoroutine = StartCoroutine(AnimateViewfinderSequence()); } /// /// Coroutine that handles the complete viewfinder animation sequence (zoom in, then zoom out) /// private IEnumerator AnimateViewfinderSequence() { RectTransform canvasRect = targetCanvas.transform as RectTransform; float canvasWidth = canvasRect != null ? canvasRect.rect.width : Screen.width; // Phase 1: Zoom in (from full screen to monster) float startSize = canvasWidth; // Always start with full canvas width float endSize = targetViewfinderSize; // End at the calculated target size Vector2 startPos = Vector2.zero; // Center of screen Vector2 endPos = targetAnchoredPosition; // Target position float elapsedTime = 0f; bool[] thresholdTriggered = new bool[settings.ViewfinderProgressThresholds.Length]; // Debug the actual values Logging.Debug($"[CameraViewfinderManager] Animation sequence starting: startSize={startSize}, endSize={endSize}"); // Verify the initial size is set correctly viewfinderRectTransform.sizeDelta = new Vector2(startSize, startSize); // Phase 1: Zoom In while (elapsedTime < settings.ViewfinderShrinkDuration && !isReversePhase) { if (targetTransform == null) { StopViewfinderAnimation(); yield break; } elapsedTime += Time.unscaledDeltaTime; animationProgress = Mathf.Clamp01(elapsedTime / settings.ViewfinderShrinkDuration); // Calculate proximity - it increases as we get closer to the target currentProximity = animationProgress; OnProximityUpdated?.Invoke(currentProximity); // The curve value might be inverted - ensure we're shrinking not growing float curveValue = settings.ViewfinderShrinkCurve.Evaluate(animationProgress); // Interpolate size float currentSize = Mathf.Lerp(startSize, endSize, curveValue); viewfinderRectTransform.sizeDelta = new Vector2(currentSize, currentSize); // Interpolate position Vector2 currentPos = viewfinderRectTransform.anchoredPosition; Vector2 newPos = Vector2.Lerp(startPos, endPos, curveValue); viewfinderRectTransform.anchoredPosition = newPos; // Log the animation state occasionally for debugging if (animationProgress % 0.25f <= 0.01f || animationProgress >= 0.99f) { Logging.Debug($"[CameraViewfinderManager] Animation progress: {animationProgress:F2}, " + $"curveValue={curveValue:F2}, currentSize={currentSize:F2}, currentPos={newPos}, proximity={currentProximity:F2}"); } for (int i = 0; i < settings.ViewfinderProgressThresholds.Length; i++) { if (!thresholdTriggered[i] && animationProgress >= settings.ViewfinderProgressThresholds[i]) { thresholdTriggered[i] = true; OnProgressThresholdReached?.Invoke(settings.ViewfinderProgressThresholds[i]); } } OnProgressUpdated?.Invoke(animationProgress); yield return null; } // Ensure we reach the exact end size and position for phase 1 viewfinderRectTransform.sizeDelta = new Vector2(endSize, endSize); viewfinderRectTransform.anchoredPosition = endPos; currentProximity = 1.0f; // At target OnProximityUpdated?.Invoke(currentProximity); // Brief pause at target yield return new WaitForSecondsRealtime(0.2f); // Start phase 2 - reverse animation isReversePhase = true; OnReverseAnimationStarted?.Invoke(); // Reset for reverse phase elapsedTime = 0f; animationProgress = 0f; // Phase 2: Zoom Out (from monster back to full screen) while (elapsedTime < settings.ViewfinderShrinkDuration && isReversePhase) { if (targetTransform == null) { StopViewfinderAnimation(); yield break; } elapsedTime += Time.unscaledDeltaTime; animationProgress = Mathf.Clamp01(elapsedTime / settings.ViewfinderShrinkDuration); // Calculate proximity - it decreases as we move away from target currentProximity = 1.0f - animationProgress; OnProximityUpdated?.Invoke(currentProximity); // The curve value for zooming out float curveValue = settings.ViewfinderShrinkCurve.Evaluate(animationProgress); // Interpolate size (now growing) float currentSize = Mathf.Lerp(endSize, startSize, curveValue); viewfinderRectTransform.sizeDelta = new Vector2(currentSize, currentSize); // Interpolate position (now returning to center) Vector2 currentPos = viewfinderRectTransform.anchoredPosition; Vector2 newPos = Vector2.Lerp(endPos, startPos, curveValue); viewfinderRectTransform.anchoredPosition = newPos; // Log the animation state occasionally for debugging if (animationProgress % 0.25f <= 0.01f || animationProgress >= 0.99f) { Logging.Debug($"[CameraViewfinderManager] Reverse animation progress: {animationProgress:F2}, " + $"curveValue={curveValue:F2}, currentSize={currentSize:F2}, currentPos={newPos}, proximity={currentProximity:F2}"); } OnProgressUpdated?.Invoke(animationProgress); yield return null; } // Animation completed (both phases) HandleViewfinderTappedDuringAnimation(); } /// /// Handle tap events during animation (for capturing at current proximity) /// private void HandleViewfinderTappedDuringAnimation() { if (isAnimating) { // Fire event with current proximity value for scoring OnViewfinderTappedDuringAnimation?.Invoke(currentProximity); // Complete the animation immediately if (animationCoroutine != null) { StopCoroutine(animationCoroutine); animationCoroutine = null; } // Fire completed event OnAnimationCompleted?.Invoke(); isAnimating = false; isReversePhase = false; // Hide the viewfinder on the second tap HideViewfinder(); } else { // Regular tap handling when not animating HandleViewfinderTapped(); } } /// /// Initialize the viewfinder with appropriate event handlers based on input mode /// /// The viewfinder component to initialize public void InitializeViewfinder(Viewfinder viewfinder) { if (viewfinder != null) { // Clean up any existing event subscriptions viewfinder.OnViewfinderTapped -= HandleViewfinderTapped; viewfinder.OnViewfinderHoldStarted -= HandleViewfinderHoldStarted; viewfinder.OnViewfinderHoldEnded -= HandleViewfinderHoldEnded; // Set up handlers based on the current input mode switch (currentInputMode) { case PhotoInputModes.Tap: // For tap mode, only subscribe to tap events viewfinder.OnViewfinderTapped += HandleViewfinderTapped; break; case PhotoInputModes.Hold: // For hold mode, subscribe to hold start/end events viewfinder.OnViewfinderHoldStarted += HandleViewfinderHoldStarted; viewfinder.OnViewfinderHoldEnded += HandleViewfinderHoldEnded; break; } } } /// /// Handles hold start event from the viewfinder /// private void HandleViewfinderHoldStarted() { if (currentInputMode == PhotoInputModes.Hold) { // Start the photo sequence when hold begins (same behavior as first tap in tap mode) OnViewfinderTapped?.Invoke(); Logging.Debug("[CameraViewfinderManager] Hold started - initiating photo sequence"); } } /// /// Handles hold end event from the viewfinder /// private void HandleViewfinderHoldEnded() { if (currentInputMode == PhotoInputModes.Hold && isAnimating) { // Complete the sequence when hold ends (same behavior as second tap in tap mode) OnViewfinderTappedDuringAnimation?.Invoke(currentProximity); Logging.Debug("[CameraViewfinderManager] Hold ended - completing photo sequence with proximity: " + currentProximity); // Complete the animation immediately if (animationCoroutine != null) { StopCoroutine(animationCoroutine); animationCoroutine = null; } // Fire completed event OnAnimationCompleted?.Invoke(); isAnimating = false; isReversePhase = false; HideViewfinder(); } } public void PlayShutterSound() { _audioSource.Stop(); _audioSource.resource = flashSound; _audioSource.Play(); } private void PlayFocusSound() { _audioSource.Stop(); _audioSource.resource = focusSound; _audioSource.Play(); } } }