Add backbone for card creation and implement Camera minigame mechanics
This commit is contained in:
@@ -0,0 +1,776 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
|
||||
namespace Minigames.DivingForPictures.PictureCamera
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the camera viewfinder visual representation and animation
|
||||
/// Responsible only for visual aspects with no game logic
|
||||
/// </summary>
|
||||
public class CameraViewfinderManager : MonoBehaviour
|
||||
{
|
||||
// Singleton instance
|
||||
private static CameraViewfinderManager _instance;
|
||||
private static bool _isQuitting = false;
|
||||
|
||||
public static CameraViewfinderManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null && Application.isPlaying && !_isQuitting)
|
||||
{
|
||||
_instance = FindAnyObjectByType<CameraViewfinderManager>();
|
||||
if (_instance == null)
|
||||
{
|
||||
var go = new GameObject("CameraViewfinderManager");
|
||||
_instance = go.AddComponent<CameraViewfinderManager>();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Events for progress milestones
|
||||
public event Action<float> 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<float> OnProximityUpdated; // New event for proximity updates
|
||||
public event Action<float> OnProgressThresholdReached; // Fires at configured thresholds (25%, 50%, etc.)
|
||||
public event Action OnViewfinderTapped; // Event when viewfinder is tapped during normal display
|
||||
public event Action<float> 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 OnApplicationQuit()
|
||||
{
|
||||
_isQuitting = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
||||
mainCamera = UnityEngine.Camera.main;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin the viewfinder animation targeting a specific transform
|
||||
/// </summary>
|
||||
/// <param name="target">The transform to focus on (usually the monster)</param>
|
||||
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<RectTransform>();
|
||||
viewfinderComponent = viewfinderInstance.GetComponent<Viewfinder>();
|
||||
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();
|
||||
viewfinderComponent.OnViewfinderTapped += HandleViewfinderTapped;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the target screen position and size based on monster's sprite bounds
|
||||
/// </summary>
|
||||
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<SpriteRenderer>();
|
||||
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;
|
||||
|
||||
Debug.LogWarning("[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);
|
||||
|
||||
Debug.Log(
|
||||
$"[CameraViewfinderManager] Target size (canvas): {targetViewfinderSize}, target anchored: {targetAnchoredPosition}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the current viewfinder animation and clean up
|
||||
/// </summary>
|
||||
public void StopViewfinderAnimation()
|
||||
{
|
||||
if (animationCoroutine != null)
|
||||
{
|
||||
StopCoroutine(animationCoroutine);
|
||||
animationCoroutine = null;
|
||||
}
|
||||
|
||||
if (viewfinderInstance != null)
|
||||
{
|
||||
Destroy(viewfinderInstance);
|
||||
viewfinderInstance = null;
|
||||
}
|
||||
|
||||
isAnimating = false;
|
||||
targetTransform = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the viewfinder animation
|
||||
/// </summary>
|
||||
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
|
||||
Debug.Log($"[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)
|
||||
{
|
||||
Debug.LogWarning($"[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)
|
||||
{
|
||||
Debug.Log($"[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
|
||||
Debug.Log(
|
||||
$"[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles tap event from the viewfinder and forwards it
|
||||
/// </summary>
|
||||
private void HandleViewfinderTapped()
|
||||
{
|
||||
// Forward the tap event to any listeners
|
||||
OnViewfinderTapped?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the currently displayed viewfinder
|
||||
/// </summary>
|
||||
public void HideViewfinder()
|
||||
{
|
||||
if (viewfinderInstance != null)
|
||||
{
|
||||
// Unsubscribe from the viewfinder's tap event
|
||||
if (viewfinderComponent != null)
|
||||
{
|
||||
viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTapped;
|
||||
}
|
||||
|
||||
Destroy(viewfinderInstance);
|
||||
viewfinderInstance = null;
|
||||
viewfinderComponent = null;
|
||||
viewfinderRectTransform = null;
|
||||
Debug.Log("[CameraViewfinderManager] Hid viewfinder");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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<RectTransform>();
|
||||
viewfinderComponent = viewfinderInstance.GetComponent<Viewfinder>();
|
||||
|
||||
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();
|
||||
viewfinderComponent.OnViewfinderTapped += HandleViewfinderTapped;
|
||||
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
Debug.Log($"[CameraViewfinderManager] Showed full-screen viewfinder with size {canvasWidth}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the complete viewfinder animation sequence (zoom in, then zoom out)
|
||||
/// </summary>
|
||||
/// <param name="target">The transform to focus on (usually the monster)</param>
|
||||
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;
|
||||
|
||||
// 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<RectTransform>();
|
||||
viewfinderComponent = viewfinderInstance.GetComponent<Viewfinder>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that handles the complete viewfinder animation sequence (zoom in, then zoom out)
|
||||
/// </summary>
|
||||
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
|
||||
Debug.Log($"[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)
|
||||
{
|
||||
Debug.Log($"[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)
|
||||
{
|
||||
Debug.Log($"[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle tap events during animation (for capturing at current proximity)
|
||||
/// </summary>
|
||||
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
|
||||
viewfinderComponent.RemoveInputOverride();
|
||||
HideViewfinder();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular tap handling when not animating
|
||||
HandleViewfinderTapped();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user