using System; using System.Collections.Generic; using System.Linq; using Continentis.MainGame.Card; using Continentis.MainGame.Character; using DG.Tweening; using SLSFramework.General; using UniRx; using UnityEngine; using Random = UnityEngine.Random; namespace Continentis.MainGame.Commands { public class Cmd_ExhaustCards : CommandBase { private bool isPlayer; private readonly DeckSubmodule deck; private readonly List cardsToExhaust; private readonly float interval; private readonly float singleCardAnimationDuration = 0.5f; // 单张卡牌的动画时长 public Cmd_ExhaustCards(bool isPlayer, DeckSubmodule deck, List cards, float interval) { this.isPlayer = isPlayer; this.deck = deck; this.cardsToExhaust = cards; this.interval = interval; } protected override IObservable OnExecute(CommandContext outerContext) { if (cardsToExhaust == null || cardsToExhaust.Count == 0) { return Observable.Return(Unit.Default); } Func> exhaustAction = isPlayer ? PlayerExhaustCard : NpcExhaustCard; // --- 情况1:并行丢弃 (所有卡牌动画同时开始) --- if (interval <= 0f) { // 为每张卡牌创建一个“懒加载”的丢弃动画流。 var allDiscardAnimations = cardsToExhaust.Select(card => Observable.Defer(() => exhaustAction(card)) ); // 使用 WhenAll 等待所有的并行任务都完成。 // 只有当最后一个动画结束时,WhenAll 才会发出完成信号。 return Observable.WhenAll(allDiscardAnimations).AsUnitObservable(); } // --- 情况2:交错丢弃 (按固定间隔开始动画) --- else { var cardStream = cardsToExhaust.ToObservable(); var timerStream = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(interval)); return timerStream .Zip(cardStream, (_, card) => card) // 核心:不再使用 .Do(..).Subscribe(),而是使用 Select 将每个 card 转换为 // 其对应的动画流。这样主流程就“知道”了每个动画的存在。 .Select(card => exhaustAction(card)) // 使用 Merge 将所有交错开始的动画流合并为一个流。 // Merge 会同时运行所有这些动画,并在最后一个动画也完成时,它才会完成。 .Merge() // 使用 Last 来确保我们只在整个 Merge 流全部结束后,才发出最终的完成信号。 .Last() .AsUnitObservable(); } } private IObservable PlayerExhaustCard(CardInstance card) { deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.ExhaustPile, card); card.cardLogic.eventSubmodule.onExhaust.Invoke(); card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.exhaustPile); RectTransform cardTransform = card.handCardView.cardTransform; //Vector3 deltaMove = Vector3.zero - card.handCardView.cardTransform.localPosition; Vector3 randomLift = new Vector3(0, Random.Range(200f, 600f), 0); //cardTransform.DOBlendableLocalMoveBy(deltaMove, singleCardAnimationDuration).Play(); cardTransform.DOBlendableLocalMoveBy(randomLift, singleCardAnimationDuration * 0.5f).SetLoops(2, LoopType.Yoyo).Play(); //cardTransform.DOLocalRotate(new Vector3(0, 0, 720f), singleCardAnimationDuration, RotateMode.FastBeyond360).Play(); cardTransform.DOScale(Vector3.zero, singleCardAnimationDuration).SetEase(Ease.Linear).OnComplete(() => { cardTransform.anchoredPosition = Vector2.zero; }).Play(); return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable(); } private IObservable NpcExhaustCard(CardInstance card) { deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.ExhaustPile, card); card.cardLogic.eventSubmodule.onExhaust.Invoke(); return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable(); } } }