295 lines
8.8 KiB
C#
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();
|
|
}
|
|
}
|
|
} |