218 lines
6.0 KiB
C#
218 lines
6.0 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using Vanguard.Battle;
|
||
using Vanguard.Board3D;
|
||
|
||
namespace Vanguard.Board3D
|
||
{
|
||
/// <summary>
|
||
/// Единый планировщик карточных перемещений. Карточки анимируются
|
||
/// Card3D, аниматор отвечает за stagger, очередь и InputLock.
|
||
/// </summary>
|
||
public sealed class CardMoveAnimator
|
||
{
|
||
private sealed class Request
|
||
{
|
||
public Card3D Card;
|
||
public Vector3 FromPosition;
|
||
public Quaternion FromRotation;
|
||
public Vector3 FromScale;
|
||
public float Duration;
|
||
public float ArcHeight;
|
||
}
|
||
|
||
private sealed class ActiveMove
|
||
{
|
||
public Card3D Card;
|
||
public float Remaining;
|
||
}
|
||
|
||
private readonly Queue<Request> _queue = new Queue<Request>();
|
||
private readonly List<ActiveMove> _active = new List<ActiveMove>();
|
||
private readonly InputLockService _inputLock;
|
||
private AnimationConfig _config;
|
||
private float _delay;
|
||
private bool _locked;
|
||
|
||
public CardMoveAnimator(
|
||
InputLockService inputLock,
|
||
AnimationConfig config)
|
||
{
|
||
_inputLock = inputLock;
|
||
_config = config;
|
||
}
|
||
|
||
public bool IsBusy => _queue.Count > 0 || _active.Count > 0;
|
||
|
||
public void SetConfig(AnimationConfig config)
|
||
{
|
||
_config = config;
|
||
}
|
||
|
||
public void Enqueue(
|
||
Card3D card,
|
||
Vector3 fromPosition,
|
||
Quaternion fromRotation,
|
||
Vector3 fromScale,
|
||
float duration,
|
||
float arcHeight)
|
||
{
|
||
if (card == null || card.IsDragging)
|
||
return;
|
||
|
||
EnsureLock();
|
||
_queue.Enqueue(new Request
|
||
{
|
||
Card = card,
|
||
FromPosition = fromPosition,
|
||
FromRotation = fromRotation,
|
||
FromScale = fromScale,
|
||
Duration = duration,
|
||
ArcHeight = arcHeight,
|
||
});
|
||
}
|
||
|
||
public void Tick(float deltaTime)
|
||
{
|
||
if (_active.Count > 0)
|
||
{
|
||
var delta = Mathf.Max(0f, deltaTime);
|
||
for (var i = _active.Count - 1; i >= 0; i--)
|
||
{
|
||
var active = _active[i];
|
||
var card = active?.Card;
|
||
if (active == null
|
||
|| card == null
|
||
|| !card.gameObject.activeInHierarchy)
|
||
{
|
||
if (card != null)
|
||
card.StopMove(snap: false);
|
||
_active.RemoveAt(i);
|
||
continue;
|
||
}
|
||
|
||
active.Remaining -= delta;
|
||
if (!card.IsMoving)
|
||
{
|
||
_active.RemoveAt(i);
|
||
continue;
|
||
}
|
||
|
||
// Страховка: карта могла быть снята с рендера до
|
||
// завершения Tick. Не держим InputLock вечно.
|
||
if (active.Remaining <= 0f)
|
||
{
|
||
card.StopMove(snap: true);
|
||
_active.RemoveAt(i);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_queue.Count > 0)
|
||
{
|
||
_delay -= Mathf.Max(0f, deltaTime);
|
||
if (_delay <= 0f)
|
||
StartNext();
|
||
}
|
||
|
||
if (!IsBusy)
|
||
ReleaseLock();
|
||
}
|
||
|
||
public void CancelAll(bool snapCards = false)
|
||
{
|
||
_queue.Clear();
|
||
foreach (var active in _active)
|
||
{
|
||
if (active?.Card != null)
|
||
active.Card.StopMove(snapCards);
|
||
}
|
||
|
||
_active.Clear();
|
||
_delay = 0f;
|
||
ReleaseLock();
|
||
}
|
||
|
||
public void CancelCard(Card3D card)
|
||
{
|
||
if (card == null)
|
||
return;
|
||
|
||
var remaining = new Queue<Request>();
|
||
while (_queue.Count > 0)
|
||
{
|
||
var request = _queue.Dequeue();
|
||
if (request.Card != card)
|
||
remaining.Enqueue(request);
|
||
}
|
||
|
||
while (remaining.Count > 0)
|
||
_queue.Enqueue(remaining.Dequeue());
|
||
|
||
for (var i = _active.Count - 1; i >= 0; i--)
|
||
{
|
||
if (_active[i]?.Card != card)
|
||
continue;
|
||
|
||
card.StopMove(snap: false);
|
||
_active.RemoveAt(i);
|
||
}
|
||
|
||
if (!IsBusy)
|
||
ReleaseLock();
|
||
}
|
||
|
||
private void StartNext()
|
||
{
|
||
if (_queue.Count == 0)
|
||
return;
|
||
|
||
var request = _queue.Dequeue();
|
||
var card = request.Card;
|
||
if (card == null || card.IsDragging)
|
||
{
|
||
_delay = 0f;
|
||
return;
|
||
}
|
||
|
||
card.StartArcMove(
|
||
request.FromPosition,
|
||
request.FromRotation,
|
||
request.FromScale,
|
||
request.Duration,
|
||
request.ArcHeight);
|
||
|
||
if (card.IsMoving)
|
||
{
|
||
_active.Add(new ActiveMove
|
||
{
|
||
Card = card,
|
||
Remaining = Mathf.Max(0.1f, request.Duration) + 0.05f,
|
||
});
|
||
}
|
||
|
||
_delay = Mathf.Max(0.01f, _config != null
|
||
? _config.moveDelayBetweenCards
|
||
: 0.18f);
|
||
}
|
||
|
||
private void EnsureLock()
|
||
{
|
||
if (_locked)
|
||
return;
|
||
|
||
_inputLock?.Acquire("card-move");
|
||
_locked = true;
|
||
}
|
||
|
||
private void ReleaseLock()
|
||
{
|
||
if (!_locked)
|
||
return;
|
||
|
||
_inputLock?.Release("card-move");
|
||
_locked = false;
|
||
}
|
||
}
|
||
}
|