using UnityEngine; using UnityEngine.UI; using Pixelplacement; namespace Utils.UI { /// /// UI component that animates a screenshot flying away and disappearing. /// Uses Pixelplacement Tween library for smooth animations with curves. /// Attach to a UI prefab with frame (Image) and picture (Image) components. /// public class ScreenshotFlyawayAnimation : MonoBehaviour { [Header("UI Components")] [SerializeField] private Image _frame; [SerializeField] private Image _picture; [Header("Animation Settings")] [Tooltip("Target offset from starting position (in UI space)")] [SerializeField] private Vector2 _targetOffset = new Vector2(0, 300f); [Tooltip("Number of full rotations during animation")] [SerializeField] private float _spinRotations = 2f; [Tooltip("Duration of the entire animation in seconds")] [SerializeField] private float _duration = 1.5f; [Tooltip("Movement curve for position animation")] [SerializeField] private AnimationCurve _moveCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); [Tooltip("Scale curve for shrinking animation")] [SerializeField] private AnimationCurve _scaleCurve = AnimationCurve.EaseInOut(0, 1, 1, 0); [Tooltip("Rotation curve for spinning animation")] [SerializeField] private AnimationCurve _rotationCurve = AnimationCurve.Linear(0, 0, 1, 1); private RectTransform _rectTransform; private bool _isAnimating; private Transform _targetTransform; // Target to fly toward (optional) private void Awake() { _rectTransform = GetComponent(); // Hide initially - will be shown when animation plays gameObject.SetActive(false); // Validate components if (_frame == null) { Debug.LogWarning("[ScreenshotFlyawayAnimation] Frame image not assigned!"); } if (_picture == null) { Debug.LogWarning("[ScreenshotFlyawayAnimation] Picture image not assigned!"); } } /// /// Start the flyaway animation with the captured screenshot texture. /// Unparents to root canvas to survive viewfinder destruction. /// /// The screenshot texture to display /// Optional target transform to fly toward (if null, uses _targetOffset) public void PlayAnimation(Texture2D photo, Transform target = null) { if (_isAnimating) { Debug.LogWarning("[ScreenshotFlyawayAnimation] Animation already playing!"); return; } if (photo == null) { Debug.LogError("[ScreenshotFlyawayAnimation] Photo texture is null!"); Destroy(gameObject); return; } // Store target for StartAnimation _targetTransform = target; // Show the GameObject before playing animation gameObject.SetActive(true); // Unparent to root canvas to survive viewfinder destruction Canvas rootCanvas = GetComponentInParent(); if (rootCanvas != null) { rootCanvas = rootCanvas.rootCanvas; transform.SetParent(rootCanvas.transform, worldPositionStays: true); } // Assign texture to picture image if (_picture != null) { Sprite photoSprite = Sprite.Create( photo, new Rect(0, 0, photo.width, photo.height), new Vector2(0.5f, 0.5f) ); _picture.sprite = photoSprite; } _isAnimating = true; StartAnimation(); } /// /// Execute the animation using Pixelplacement Tween /// private void StartAnimation() { if (_rectTransform == null) return; // Get starting position Vector2 startPosition = _rectTransform.anchoredPosition; Vector2 endPosition; // Calculate end position based on target or offset if (_targetTransform != null) { // Convert target world position to local anchored position RectTransform targetRect = _targetTransform as RectTransform; if (targetRect != null && _rectTransform.parent is RectTransform parentRect) { // Convert target's position to parent's local space Vector2 targetLocalPos; RectTransformUtility.ScreenPointToLocalPointInRectangle( parentRect, RectTransformUtility.WorldToScreenPoint(null, targetRect.position), null, out targetLocalPos ); endPosition = targetLocalPos; } else { // Fallback: use offset if conversion fails endPosition = startPosition + _targetOffset; } } else { // Use offset if no target provided endPosition = startPosition + _targetOffset; } // Start rotation at 0 Vector3 startRotation = Vector3.zero; Vector3 endRotation = new Vector3(0, 0, 360f * _spinRotations); // Start scale at 1 Vector3 startScale = Vector3.one; Vector3 endScale = Vector3.zero; // Animate position using AnchoredPosition for UI Tween.AnchoredPosition( _rectTransform, startPosition, endPosition, _duration, 0f, // no delay _moveCurve, Tween.LoopType.None, null, // no start callback OnAnimationComplete // complete callback ); // Animate scale Tween.LocalScale( transform, startScale, endScale, _duration, 0f, _scaleCurve ); // Animate rotation (Z-axis spin) Tween.Rotation( transform, Quaternion.Euler(startRotation), Quaternion.Euler(endRotation), _duration, 0f, _rotationCurve, Tween.LoopType.None, null, null, true // obey timescale ); } /// /// Called when animation completes - destroy the GameObject /// private void OnAnimationComplete() { _isAnimating = false; // Clean up sprite to avoid memory leak if (_picture != null && _picture.sprite != null) { Sprite sprite = _picture.sprite; _picture.sprite = null; Destroy(sprite.texture); Destroy(sprite); } // Destroy the animation GameObject Destroy(gameObject); } } }