Files
AppleHillsProduction/Assets/Scripts/Utils/UI/ScreenshotFlyawayAnimation.cs

218 lines
7.7 KiB
C#
Raw Normal View History

2025-12-18 10:52:12 +01:00
using UnityEngine;
using UnityEngine.UI;
using Pixelplacement;
namespace Utils.UI
{
/// <summary>
/// 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.
/// </summary>
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<RectTransform>();
// 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!");
}
}
/// <summary>
/// Start the flyaway animation with the captured screenshot texture.
/// Unparents to root canvas to survive viewfinder destruction.
/// </summary>
/// <param name="photo">The screenshot texture to display</param>
/// <param name="target">Optional target transform to fly toward (if null, uses _targetOffset)</param>
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<Canvas>();
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();
}
/// <summary>
/// Execute the animation using Pixelplacement Tween
/// </summary>
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
);
}
/// <summary>
/// Called when animation completes - destroy the GameObject
/// </summary>
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);
}
}
}