Files
A-Fairytale-Gone-Bad-Briar-…/Assets/Scripts/Game/Levels/ChapterOne/LaxleyHouse/FireplaceLockbox/LockboxSlot.cs

295 lines
8.8 KiB
C#

using System;
using System.Threading;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Managers.Player.Data;
using Cysharp.Threading.Tasks;
using PrimeTween;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse.FireplaceLockbox
{
public class LockboxSlot : BaseItem
{
private const int MinDigit = 1;
private const int MaxDigit = 9;
[Header("Components")]
[SerializeField]
private Image _currentNumberImage;
[SerializeField]
private Image _nextNumberImage;
[Header("Animation")]
[SerializeField]
private float _spinPadding = 8f;
[SerializeField]
private float _spinDuration = 0.15f;
[SerializeField]
private Ease _spinEase = Ease.OutCubic;
[SerializeField]
[Range(0f, 1f)]
private float _fadedAlpha = 0.4f;
private RectTransform _currentRect;
private RectTransform _nextRect;
private Sequence _slotSequence;
private CancellationTokenSource _slotCancellationTokenSource;
private Func<int, Sprite> _spriteResolver;
private Action _onValueChanged;
public int CurrentValue { get; private set; } = MinDigit;
public void Initialize(Func<int, Sprite> spriteResolver, Action onValueChanged)
{
_spriteResolver = spriteResolver;
_onValueChanged = onValueChanged;
_currentRect = _currentNumberImage != null ? _currentNumberImage.rectTransform : null;
_nextRect = _nextNumberImage != null ? _nextNumberImage.rectTransform : null;
SyncHelperImageLayout();
ApplyValueImmediate(CurrentValue);
}
public void ApplyValueImmediate(int value)
{
CurrentValue = WrapValue(value);
CancelTweenIfRunning();
SyncHelperImageLayout();
var sprite = ResolveSprite(CurrentValue);
if (_currentNumberImage != null)
{
_currentNumberImage.sprite = sprite;
_currentNumberImage.enabled = sprite != null;
_currentNumberImage.color = Color.white;
}
if (_nextNumberImage != null)
{
_nextNumberImage.sprite = null;
_nextNumberImage.enabled = false;
_nextNumberImage.color = Color.white;
}
if (_currentRect != null)
_currentRect.anchoredPosition = Vector2.zero;
if (_nextRect != null)
_nextRect.anchoredPosition = GetHiddenPosition();
}
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (item != null)
return;
await SpinToNextValue();
}
public async UniTask SpinToNextValue()
{
if (_currentNumberImage == null || _nextNumberImage == null || _currentRect == null || _nextRect == null)
return;
int nextValue = WrapValue(CurrentValue + 1);
var nextSprite = ResolveSprite(nextValue);
if (nextSprite == null)
{
Debug.LogWarning($"[LockboxSlot] Missing sprite for value {nextValue}.", this);
return;
}
CancelTweenIfRunning();
SyncHelperImageLayout();
_slotCancellationTokenSource = new CancellationTokenSource();
_nextNumberImage.sprite = nextSprite;
_nextNumberImage.enabled = true;
_nextNumberImage.color = new Color(1f, 1f, 1f, _fadedAlpha);
_currentNumberImage.enabled = true;
_currentNumberImage.color = Color.white;
_currentRect.anchoredPosition = Vector2.zero;
_nextRect.anchoredPosition = GetHiddenPosition();
_slotSequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.UIAnchoredPosition(
_currentRect,
GetVisibleExitPosition(),
_spinDuration,
_spinEase,
useUnscaledTime: true))
.Group(Tween.UIAnchoredPosition(
_nextRect,
Vector2.zero,
_spinDuration,
_spinEase,
useUnscaledTime: true))
.Group(Tween.Alpha(
_currentNumberImage,
new TweenSettings<float>
{
endValue = _fadedAlpha,
settings = new TweenSettings
{
duration = _spinDuration,
useUnscaledTime = true
}
}))
.Group(Tween.Alpha(
_nextNumberImage,
new TweenSettings<float>
{
endValue = 1f,
settings = new TweenSettings
{
duration = _spinDuration,
useUnscaledTime = true
}
}));
try
{
await _slotSequence.ToUniTask(cancellationToken: _slotCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
if (_slotSequence.isAlive)
_slotSequence.Stop();
_slotSequence = default;
_slotCancellationTokenSource?.Dispose();
_slotCancellationTokenSource = null;
}
SwapImages();
CurrentValue = nextValue;
PrepareHiddenImage();
_onValueChanged?.Invoke();
}
private Sprite ResolveSprite(int value)
{
return _spriteResolver?.Invoke(WrapValue(value));
}
private int WrapValue(int value)
{
value %= MaxDigit;
if (value <= 0)
value += MaxDigit;
return value;
}
private void SwapImages()
{
(_currentNumberImage, _nextNumberImage) = (_nextNumberImage, _currentNumberImage);
(_currentRect, _nextRect) = (_nextRect, _currentRect);
}
private void PrepareHiddenImage()
{
if (_currentNumberImage != null)
{
_currentNumberImage.enabled = true;
_currentNumberImage.color = Color.white;
}
if (_currentRect != null)
_currentRect.anchoredPosition = Vector2.zero;
if (_nextNumberImage != null)
{
_nextNumberImage.sprite = null;
_nextNumberImage.enabled = false;
_nextNumberImage.color = Color.white;
}
if (_nextRect != null)
_nextRect.anchoredPosition = GetHiddenPosition();
}
private void SyncHelperImageLayout()
{
if (_currentRect == null || _nextRect == null)
return;
_nextRect.anchorMin = _currentRect.anchorMin;
_nextRect.anchorMax = _currentRect.anchorMax;
_nextRect.pivot = _currentRect.pivot;
_nextRect.sizeDelta = _currentRect.sizeDelta;
_nextRect.localScale = _currentRect.localScale;
_nextRect.localRotation = _currentRect.localRotation;
}
private Vector2 GetHiddenPosition()
{
float travelDistance = GetTravelDistance();
return new Vector2(0f, -travelDistance);
}
private Vector2 GetVisibleExitPosition()
{
float travelDistance = GetTravelDistance();
return new Vector2(0f, travelDistance);
}
private float GetTravelDistance()
{
if (_currentRect == null)
return _spinPadding;
float rectHeight = _currentRect.rect.height;
if (rectHeight <= 0f && _currentNumberImage != null)
rectHeight = _currentNumberImage.preferredHeight;
return rectHeight + _spinPadding;
}
private void CancelTweenIfRunning()
{
if (_slotCancellationTokenSource != null && !_slotCancellationTokenSource.IsCancellationRequested)
_slotCancellationTokenSource.Cancel();
if (_slotSequence.isAlive)
_slotSequence.Stop();
_slotSequence = default;
_slotCancellationTokenSource?.Dispose();
_slotCancellationTokenSource = null;
}
private void OnDisable()
{
CancelTweenIfRunning();
}
private void OnDestroy()
{
CancelTweenIfRunning();
}
}
}