架构大更
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Continentis.MainGame.Card;
|
||||
using Continentis.MainGame.Character;
|
||||
using DG.Tweening;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace Continentis.MainGame.Commands
|
||||
{
|
||||
private readonly DeckSubmodule deck;
|
||||
private readonly List<CardInstance> cardsToDiscard;
|
||||
private readonly float interval;
|
||||
private readonly float singleCardAnimationDuration = 0.5f; // 单张卡牌的动画时长
|
||||
|
||||
private readonly bool isInitiative;
|
||||
|
||||
|
||||
private float interval;
|
||||
private const float SingleCardAnimationDuration = 0.5f;
|
||||
|
||||
public Cmd_DiscardCards(DeckSubmodule deck, List<CardInstance> cards, bool isInitiative, float interval)
|
||||
{
|
||||
this.deck = deck;
|
||||
@@ -27,75 +27,57 @@ namespace Continentis.MainGame.Commands
|
||||
this.isInitiative = isInitiative;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToDiscard == null || cardsToDiscard.Count == 0)
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
|
||||
// --- 情况1:并行丢弃 (所有卡牌动画同时开始) ---
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToDiscard == null || cardsToDiscard.Count == 0) return;
|
||||
|
||||
if (interval <= 0f)
|
||||
{
|
||||
// 为每张卡牌创建一个“懒加载”的丢弃动画流。
|
||||
var allDiscardAnimations = cardsToDiscard.Select(card =>
|
||||
Observable.Defer(() => DiscardCard(card))
|
||||
);
|
||||
|
||||
// 使用 WhenAll 等待所有的并行任务都完成。
|
||||
// 只有当最后一个动画结束时,WhenAll 才会发出完成信号。
|
||||
return Observable.WhenAll(allDiscardAnimations).AsUnitObservable();
|
||||
await UniTask.WhenAll(cardsToDiscard.Select(card => DiscardCardAsync(card)));
|
||||
}
|
||||
|
||||
// --- 情况2:交错丢弃 (按固定间隔开始动画) ---
|
||||
else
|
||||
{
|
||||
var cardStream = cardsToDiscard.ToObservable();
|
||||
var timerStream = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(interval));
|
||||
|
||||
return timerStream
|
||||
.Zip(cardStream, (_, card) => card)
|
||||
// 核心:不再使用 .Do(..).Subscribe(),而是使用 Select 将每个 card 转换为
|
||||
// 其对应的动画流。这样主流程就“知道”了每个动画的存在。
|
||||
.Select(card => DiscardCard(card))
|
||||
// 使用 Merge 将所有交错开始的动画流合并为一个流。
|
||||
// Merge 会同时运行所有这些动画,并在最后一个动画也完成时,它才会完成。
|
||||
.Merge()
|
||||
// 使用 Last 来确保我们只在整个 Merge 流全部结束后,才发出最终的完成信号。
|
||||
.Last()
|
||||
.AsUnitObservable();
|
||||
var tasks = new UniTask[cardsToDiscard.Count];
|
||||
for (int i = 0; i < cardsToDiscard.Count; i++)
|
||||
{
|
||||
CardInstance captured = cardsToDiscard[i];
|
||||
tasks[i] = DiscardCardWithDelayAsync(captured, i * interval);
|
||||
}
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
private IObservable<Unit> DiscardCard(CardInstance card)
|
||||
private async UniTask DiscardCardWithDelayAsync(CardInstance card, float delay)
|
||||
{
|
||||
if (isInitiative)
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
await DiscardCardAsync(card);
|
||||
}
|
||||
|
||||
private async UniTask DiscardCardAsync(CardInstance card)
|
||||
{
|
||||
if (isInitiative && card.eventSubmodule.onInitiativeDiscard.GetChecks().Any())
|
||||
{
|
||||
if (card.eventSubmodule.onInitiativeDiscard.GetChecks().Any()) // 如果主动弃牌后,有触发条件,则打断弃牌,直接触发效果
|
||||
{
|
||||
CommandQueueManager.Instance.AddCommand(new Cmd_Function(() =>
|
||||
{
|
||||
card.eventSubmodule.onInitiativeDiscard.GetEffects().ForEach(effect => effect.Invoke());
|
||||
}));
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
CommandQueueManager.Instance.AddCommand(Cmd.Do(() =>
|
||||
card.eventSubmodule.onInitiativeDiscard.GetEffects().ForEach(effect => effect.Invoke())));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.DiscardPile, card);
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.discardPile);
|
||||
|
||||
|
||||
RectTransform cardTransform = card.handCardView.cardTransform;
|
||||
Vector3 deltaMove = Vector3.zero - card.handCardView.cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), 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).Play();
|
||||
Vector3 deltaMove = Vector3.zero - cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0f);
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
cardTransform.DOBlendableLocalMoveBy(deltaMove, SingleCardAnimationDuration).Play();
|
||||
cardTransform.DOBlendableLocalMoveBy(randomLift, SingleCardAnimationDuration * 0.5f)
|
||||
.SetLoops(2, LoopType.Yoyo).Play();
|
||||
cardTransform.DOLocalRotate(new Vector3(0f, 0f, 720f), SingleCardAnimationDuration, RotateMode.FastBeyond360).Play();
|
||||
cardTransform.DOScale(Vector3.zero, SingleCardAnimationDuration).SetEase(Ease.Linear).Play();
|
||||
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Continentis.MainGame.Card;
|
||||
using Continentis.MainGame.Character;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DG.Tweening;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
@@ -14,122 +14,122 @@ namespace Continentis.MainGame.Commands
|
||||
public class Cmd_DrawCards : CommandBase
|
||||
{
|
||||
private readonly DeckSubmodule deck;
|
||||
|
||||
private readonly bool isCustomDraw;
|
||||
private readonly int drawCount;
|
||||
private readonly float interval;
|
||||
private readonly float singleCardAnimationDuration = 0.4f; // 单张卡牌的动画时长
|
||||
|
||||
private readonly List<CardInstance> customDrawCards;
|
||||
|
||||
private readonly bool isCustomDraw;
|
||||
|
||||
private const float SingleCardAnimationDuration = 0.4f;
|
||||
|
||||
public Cmd_DrawCards(DeckSubmodule deck, int drawCount, float interval = 0.1f)
|
||||
{
|
||||
this.isCustomDraw = false;
|
||||
this.deck = deck;
|
||||
this.drawCount = drawCount;
|
||||
this.interval = interval;
|
||||
this.customDrawCards = null;
|
||||
isCustomDraw = false;
|
||||
customDrawCards = null;
|
||||
}
|
||||
|
||||
public Cmd_DrawCards(DeckSubmodule deck, List<CardInstance> customDrawCards, float interval = 0.1f)
|
||||
{
|
||||
this.isCustomDraw = true;
|
||||
this.deck = deck;
|
||||
this.drawCount = customDrawCards.Count;
|
||||
this.interval = interval;
|
||||
this.customDrawCards = customDrawCards;
|
||||
isCustomDraw = true;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (!isCustomDraw)
|
||||
{
|
||||
// 确定最终能抽的数量
|
||||
int finalDrawCount = Mathf.Min(drawCount, deck.DrawPile.Count);
|
||||
|
||||
if (finalDrawCount <= 0)
|
||||
{
|
||||
Debug.Log("无牌可抽。");
|
||||
outerContext.context["DrawnCards"] = new List<CardLogicBase>();
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"最终抽取 {finalDrawCount} 张卡牌。");
|
||||
Debug.Log("[Cmd_DrawCards] 无牌可抽。");
|
||||
outerContext.Set(CommandContextKeys.DrawnCards, new List<CardLogicBase>());
|
||||
return;
|
||||
}
|
||||
|
||||
// 从抽牌堆顶部取出卡牌
|
||||
List<CardInstance> drawnCards = deck.DrawPile.Take(finalDrawCount).ToList();
|
||||
// 快照:在动画开始前记录将要抽的牌(顺序与 DrawPile 一致)
|
||||
var drawnCards = deck.DrawPile.Take(finalDrawCount).ToList();
|
||||
outerContext.Set(CommandContextKeys.DrawnCards, drawnCards);
|
||||
Debug.Log($"[Cmd_DrawCards] 抽取 {finalDrawCount} 张牌。");
|
||||
|
||||
// --- 关键:将结果存入上下文 ---
|
||||
// 这替代了 'out' 参数,让后续指令可以访问到这次抽到的牌。
|
||||
outerContext.context["DrawnCards"] = drawnCards;
|
||||
Debug.Log($"抽取 {drawnCards.Count} 张卡牌,并将列表存入DrawnCards。");
|
||||
|
||||
|
||||
// --- 2. 异步的动画阶段 ---
|
||||
|
||||
// 创建一个交错的动画流,和我们修正后的 Cmd_DiscardCards 完全一样
|
||||
return drawnCards.ToObservable()
|
||||
.Zip(Observable.Interval(TimeSpan.FromSeconds(interval)), (card, _) => card)
|
||||
// 使用 Select 将每个 card 和它的索引传递给动画方法
|
||||
.Select((card, index) => Draw(index, drawnCards.Count))
|
||||
.Merge() // 并行执行所有交错开始的动画
|
||||
.Last() // 等待最后一个动画流完成
|
||||
.AsUnitObservable();
|
||||
await DrawStaggeredAsync(finalDrawCount, drawnCards.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
outerContext.context["DrawnCards"] = customDrawCards;
|
||||
Debug.Log($"抽取 {customDrawCards.Count} 张指定卡牌,并将列表存入DrawnCards。");
|
||||
return customDrawCards.ToObservable()
|
||||
.Zip(Observable.Interval(TimeSpan.FromSeconds(interval)), (card, _) => card)
|
||||
// 使用 Select 将每个 card 和它的索引传递给动画方法
|
||||
.Select((card, index) => Draw(card, index, customDrawCards.Count))
|
||||
.Merge() // 并行执行所有交错开始的动画
|
||||
.Last() // 等待最后一个动画流完成
|
||||
.AsUnitObservable();
|
||||
outerContext.Set(CommandContextKeys.DrawnCards, customDrawCards);
|
||||
Debug.Log($"[Cmd_DrawCards] 抽取 {customDrawCards.Count} 张指定牌。");
|
||||
|
||||
await DrawCustomStaggeredAsync(customDrawCards);
|
||||
}
|
||||
}
|
||||
|
||||
private IObservable<Unit> Draw(int index, int totalCount)
|
||||
|
||||
// 标准抽牌:每次从 DrawPile[0] 取牌,交错并行动画
|
||||
private async UniTask DrawStaggeredAsync(int count, int totalCount)
|
||||
{
|
||||
CardInstance card = deck.DrawPile[0];
|
||||
|
||||
var tasks = new UniTask[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
tasks[i] = DrawOneWithDelay(i, totalCount, i * interval);
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async UniTask DrawOneWithDelay(int index, int totalCount, float delay)
|
||||
{
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
|
||||
var card = deck.DrawPile[0];
|
||||
deck.TransferCard(deck.DrawPile, deck.HandPile, card);
|
||||
card.eventSubmodule.onDraw.Invoke();
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.handPile);
|
||||
|
||||
Vector3 targetPosition = CombatUIManager.Instance.combatMainPage.handPile.GetCardPosition(index, totalCount);
|
||||
Quaternion targetRotation = CombatUIManager.Instance.combatMainPage.handPile.GetCardRotation(index, totalCount);
|
||||
Vector3 deltaMove = targetPosition - card.handCardView.cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0);
|
||||
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(deltaMove, singleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(randomLift, singleCardAnimationDuration * 0.5f).SetLoops(2, LoopType.Yoyo).Play();
|
||||
card.handCardView.cardTransform.DOLocalRotateQuaternion(targetRotation, singleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform.DOScale(Vector3.one, singleCardAnimationDuration).Play();
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
|
||||
PlayDrawAnimation(card, index, totalCount);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
|
||||
private IObservable<Unit> Draw(CardInstance card, int index, int totalCount)
|
||||
|
||||
// 指定牌抽牌:交错并行动画
|
||||
private async UniTask DrawCustomStaggeredAsync(List<CardInstance> cards)
|
||||
{
|
||||
var tasks = new UniTask[cards.Count];
|
||||
for (int i = 0; i < cards.Count; i++)
|
||||
{
|
||||
CardInstance captured = cards[i];
|
||||
int idx = i;
|
||||
tasks[i] = DrawCustomOneWithDelay(captured, idx, cards.Count, idx * interval);
|
||||
}
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async UniTask DrawCustomOneWithDelay(CardInstance card, int index, int totalCount, float delay)
|
||||
{
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
|
||||
deck.TransferCard(card.cardLocation.pileName, "Hand", card);
|
||||
card.eventSubmodule.onDraw.Invoke();
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.handPile);
|
||||
|
||||
Vector3 targetPosition = CombatUIManager.Instance.combatMainPage.handPile.GetCardPosition(index, totalCount);
|
||||
Quaternion targetRotation = CombatUIManager.Instance.combatMainPage.handPile.GetCardRotation(index, totalCount);
|
||||
Vector3 deltaMove = targetPosition - card.handCardView.cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0);
|
||||
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(deltaMove, singleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(randomLift, singleCardAnimationDuration * 0.5f).SetLoops(2, LoopType.Yoyo).Play();
|
||||
card.handCardView.cardTransform.DOLocalRotateQuaternion(targetRotation, singleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform.DOScale(Vector3.one, singleCardAnimationDuration).Play();
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
|
||||
PlayDrawAnimation(card, index, totalCount);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
|
||||
private void PlayDrawAnimation(CardInstance card, int index, int totalCount)
|
||||
{
|
||||
var handPile = CombatUIManager.Instance.combatMainPage.handPile;
|
||||
var targetPosition = handPile.GetCardPosition(index, totalCount);
|
||||
var targetRotation = handPile.GetCardRotation(index, totalCount);
|
||||
var deltaMove = targetPosition - card.handCardView.cardTransform.localPosition;
|
||||
var randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0f);
|
||||
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(deltaMove, SingleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(randomLift, SingleCardAnimationDuration * 0.5f)
|
||||
.SetLoops(2, LoopType.Yoyo).Play();
|
||||
card.handCardView.cardTransform.DOLocalRotateQuaternion(targetRotation, SingleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform.DOScale(Vector3.one, SingleCardAnimationDuration).Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,24 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Continentis.MainGame.Card;
|
||||
using Continentis.MainGame.Character;
|
||||
using Cysharp.Threading.Tasks;
|
||||
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 bool isPlayer;
|
||||
private readonly DeckSubmodule deck;
|
||||
private readonly List<CardInstance> cardsToExhaust;
|
||||
private readonly float interval;
|
||||
private readonly float singleCardAnimationDuration = 0.5f; // 单张卡牌的动画时长
|
||||
|
||||
|
||||
private const float SingleCardAnimationDuration = 0.5f;
|
||||
|
||||
public Cmd_ExhaustCards(bool isPlayer, DeckSubmodule deck, List<CardInstance> cards, float interval)
|
||||
{
|
||||
this.isPlayer = isPlayer;
|
||||
@@ -26,76 +28,64 @@ namespace Continentis.MainGame.Commands
|
||||
this.cardsToExhaust = cards;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToExhaust == null || cardsToExhaust.Count == 0)
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
|
||||
Func<CardInstance, IObservable<Unit>> exhaustAction = isPlayer ? PlayerExhaustCard : NpcExhaustCard;
|
||||
|
||||
// --- 情况1:并行丢弃 (所有卡牌动画同时开始) ---
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToExhaust == null || cardsToExhaust.Count == 0) return;
|
||||
|
||||
if (interval <= 0f)
|
||||
{
|
||||
// 为每张卡牌创建一个“懒加载”的丢弃动画流。
|
||||
var allDiscardAnimations = cardsToExhaust.Select(card =>
|
||||
Observable.Defer(() => exhaustAction(card))
|
||||
);
|
||||
|
||||
// 使用 WhenAll 等待所有的并行任务都完成。
|
||||
// 只有当最后一个动画结束时,WhenAll 才会发出完成信号。
|
||||
return Observable.WhenAll(allDiscardAnimations).AsUnitObservable();
|
||||
await UniTask.WhenAll(cardsToExhaust.Select(card => ExhaustCardAsync(card)));
|
||||
}
|
||||
|
||||
// --- 情况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();
|
||||
var tasks = new UniTask[cardsToExhaust.Count];
|
||||
for (int i = 0; i < cardsToExhaust.Count; i++)
|
||||
{
|
||||
CardInstance captured = cardsToExhaust[i];
|
||||
tasks[i] = ExhaustCardWithDelayAsync(captured, i * interval);
|
||||
}
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
private IObservable<Unit> PlayerExhaustCard(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.ExhaustPile, card);
|
||||
card.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 async UniTask ExhaustCardWithDelayAsync(CardInstance card, float delay)
|
||||
{
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
await ExhaustCardAsync(card);
|
||||
}
|
||||
|
||||
private IObservable<Unit> NpcExhaustCard(CardInstance card)
|
||||
private async UniTask ExhaustCardAsync(CardInstance card)
|
||||
{
|
||||
if (isPlayer)
|
||||
await PlayerExhaustCardAsync(card);
|
||||
else
|
||||
await NpcExhaustCardAsync(card);
|
||||
}
|
||||
|
||||
private async UniTask PlayerExhaustCardAsync(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.ExhaustPile, card);
|
||||
card.eventSubmodule.onExhaust.Invoke();
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.exhaustPile);
|
||||
|
||||
RectTransform cardTransform = card.handCardView.cardTransform;
|
||||
Vector3 randomLift = new Vector3(0f, Random.Range(200f, 600f), 0f);
|
||||
|
||||
cardTransform.DOBlendableLocalMoveBy(randomLift, SingleCardAnimationDuration * 0.5f)
|
||||
.SetLoops(2, LoopType.Yoyo).Play();
|
||||
cardTransform.DOScale(Vector3.zero, SingleCardAnimationDuration).SetEase(Ease.Linear)
|
||||
.OnComplete(() => cardTransform.anchoredPosition = Vector2.zero).Play();
|
||||
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
|
||||
private async UniTask NpcExhaustCardAsync(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.ExhaustPile, card);
|
||||
card.eventSubmodule.onExhaust.Invoke();
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,108 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Continentis.MainGame.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// 同步/延迟函数命令。新代码请改用 <see cref="Cmd.Do"/> / <see cref="Cmd.Wait"/> / <see cref="Cmd.After"/>。
|
||||
/// </summary>
|
||||
[Obsolete("请改用 Cmd.Do() / Cmd.Wait() / Cmd.After()。")]
|
||||
public class Cmd_Function : CommandBase
|
||||
{
|
||||
private readonly float functionDuration;
|
||||
private readonly UnityAction function;
|
||||
private readonly bool executeAtStart;
|
||||
|
||||
public Cmd_Function(float functionDuration, UnityAction function, bool executeAtStart = true) : base(null)
|
||||
|
||||
public Cmd_Function(float functionDuration, UnityAction function, bool executeAtStart = true)
|
||||
{
|
||||
this.functionDuration = functionDuration;
|
||||
this.function = function;
|
||||
this.executeAtStart = executeAtStart;
|
||||
}
|
||||
|
||||
public Cmd_Function(UnityAction function) : base(null)
|
||||
|
||||
public Cmd_Function(UnityAction function)
|
||||
{
|
||||
this.functionDuration = 0;
|
||||
this.functionDuration = 0f;
|
||||
this.function = function;
|
||||
this.executeAtStart = true;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (functionDuration > 0)
|
||||
if (functionDuration > 0f)
|
||||
{
|
||||
if (executeAtStart) //在持续时间开始时执行
|
||||
if (executeAtStart)
|
||||
{
|
||||
function?.Invoke();
|
||||
return Observable.Timer(TimeSpan.FromSeconds(functionDuration)).AsUnitObservable();
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(functionDuration));
|
||||
}
|
||||
else
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(functionDuration));
|
||||
function?.Invoke();
|
||||
}
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(functionDuration))
|
||||
.Do(_ => function?.Invoke())
|
||||
.AsUnitObservable(); //在持续时间结束时执行
|
||||
}
|
||||
|
||||
function?.Invoke();
|
||||
return Observable.Return(Unit.Default); //如果持续时间为0,立即完成
|
||||
else
|
||||
{
|
||||
function?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 带参数的函数命令,参数由 selfContext 中的 "Target" 注入。
|
||||
/// 新代码请改用闭包捕获参数,通过 <see cref="Cmd.Do"/> 执行。
|
||||
/// </summary>
|
||||
[Obsolete("请改用闭包捕获参数并通过 Cmd.Do() 执行。")]
|
||||
public class Cmd_ParamFunction<T> : CommandBase where T : class
|
||||
{
|
||||
private readonly float functionDuration;
|
||||
private readonly UnityAction<T> function;
|
||||
private readonly bool executeAtStart;
|
||||
|
||||
public Cmd_ParamFunction(UnityAction<T> function, CommandContext selfContext = null, bool executeAtStart = true)
|
||||
:base(selfContext)
|
||||
|
||||
public Cmd_ParamFunction(UnityAction<T> function, CommandContext selfContext = null, bool executeAtStart = true)
|
||||
: base(selfContext)
|
||||
{
|
||||
this.functionDuration = 0;
|
||||
this.functionDuration = 0f;
|
||||
this.function = function;
|
||||
this.executeAtStart = executeAtStart;
|
||||
}
|
||||
|
||||
public Cmd_ParamFunction(float functionDuration, UnityAction<T> function, CommandContext selfContext = null, bool executeAtStart = true)
|
||||
:base(selfContext)
|
||||
|
||||
public Cmd_ParamFunction(float functionDuration, UnityAction<T> function, CommandContext selfContext = null, bool executeAtStart = true)
|
||||
: base(selfContext)
|
||||
{
|
||||
this.functionDuration = functionDuration;
|
||||
this.function = function;
|
||||
this.executeAtStart = executeAtStart;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (selfContext.context.Count != 1)
|
||||
if (!selfContext.TryGet<T>(CommandContextKeys.Target, out T param))
|
||||
{
|
||||
Debug.LogWarning("Cmd_Function 期望 selfContext 只包含一个参数,作为 function 的输入。");
|
||||
Debug.LogWarning($"Cmd_ParamFunction<{typeof(T).Name}> 未能从 selfContext 中获取 Target 参数。");
|
||||
}
|
||||
|
||||
T param = selfContext.context["Target"] as T;
|
||||
|
||||
if (param == null)
|
||||
|
||||
if (functionDuration > 0f)
|
||||
{
|
||||
Debug.LogWarning("Cmd_Function 未能从 selfContext 中获取到有效的参数。");
|
||||
}
|
||||
|
||||
if (functionDuration > 0)
|
||||
{
|
||||
if (executeAtStart) //在持续时间开始时执行
|
||||
if (executeAtStart)
|
||||
{
|
||||
function?.Invoke(param);
|
||||
return Observable.Timer(TimeSpan.FromSeconds(functionDuration)).AsUnitObservable();
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(functionDuration));
|
||||
}
|
||||
else
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(functionDuration));
|
||||
function?.Invoke(param);
|
||||
}
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(functionDuration))
|
||||
.Do(_ => function?.Invoke(param))
|
||||
.AsUnitObservable(); //在持续时间结束时执行
|
||||
}
|
||||
|
||||
function?.Invoke(param);
|
||||
return Observable.Return(Unit.Default); //如果持续时间为0,立即完成
|
||||
else
|
||||
{
|
||||
function?.Invoke(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Continentis.MainGame.Character;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Continentis.MainGame.Commands
|
||||
{
|
||||
@@ -13,105 +12,109 @@ namespace Continentis.MainGame.Commands
|
||||
{
|
||||
private readonly CombatCharacterViewBase characterView;
|
||||
private readonly Animator animator;
|
||||
private bool waitForFinish;
|
||||
private float overrideDuration;
|
||||
private string animationName;
|
||||
private int layer;
|
||||
private readonly bool waitForFinish;
|
||||
private readonly float overrideDuration;
|
||||
private readonly string animationName;
|
||||
private readonly int layer;
|
||||
|
||||
//在动画的normalizedTime执行函数
|
||||
private AnimationClip clip;
|
||||
private float clipScaledLength => clip.length / animator.speed;
|
||||
private Dictionary<float, Action> animationActions;
|
||||
|
||||
public Cmd_PlayAnimation(CombatCharacterViewBase characterView, string animationName,
|
||||
bool waitForFinish = true, float overrideDuration = -1, int layer = 0) : base(null)
|
||||
private float ClipScaledLength => clip.length / animator.speed;
|
||||
private readonly Dictionary<float, Action> animationActions = new Dictionary<float, Action>();
|
||||
|
||||
public Cmd_PlayAnimation(CombatCharacterViewBase characterView, string animationName,
|
||||
bool waitForFinish = true, float overrideDuration = -1f, int layer = 0)
|
||||
{
|
||||
this.characterView = characterView;
|
||||
this.animator = characterView.animator;
|
||||
this.animationName = animationName;
|
||||
this.clip = null;
|
||||
characterView.animations.TryGetValue(animationName, out clip);
|
||||
this.waitForFinish = waitForFinish;
|
||||
this.overrideDuration = overrideDuration;
|
||||
this.layer = layer;
|
||||
this.animationActions = new Dictionary<float, Action>();
|
||||
characterView.animations.TryGetValue(animationName, out clip);
|
||||
}
|
||||
|
||||
public Cmd_PlayAnimation AddAction(float normalizedDuration, Action action)
|
||||
/// <summary>在动画的指定归一化时间点(0~1)执行 Action。</summary>
|
||||
public Cmd_PlayAnimation AddAction(float normalizedTime, Action action)
|
||||
{
|
||||
animationActions[normalizedDuration] = action;
|
||||
animationActions[normalizedTime] = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>在动画的指定帧执行 Action。</summary>
|
||||
public Cmd_PlayAnimation AddAction(int frame, Action action)
|
||||
{
|
||||
float normalizedDuration = frame / (clip.frameRate * clip.length);
|
||||
return AddAction(normalizedDuration, action);
|
||||
if (clip == null) return this;
|
||||
float normalizedTime = frame / (clip.frameRate * clip.length);
|
||||
return AddAction(normalizedTime, action);
|
||||
}
|
||||
|
||||
public Cmd_PlayAnimation AddAction<T>(float normalizedDuration, string selfContextKey, Action<T> action)
|
||||
|
||||
/// <summary>在动画的指定归一化时间点执行带强类型参数的 Action,参数从 selfContext 读取。</summary>
|
||||
public Cmd_PlayAnimation AddAction<T>(float normalizedTime, string selfContextKey, Action<T> action)
|
||||
{
|
||||
T param = selfContext.GetInfo<T>(selfContextKey);
|
||||
animationActions[normalizedDuration] = () => action(param);
|
||||
T param = selfContext.Get<T>(selfContextKey);
|
||||
animationActions[normalizedTime] = () => action(param);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>在动画的指定帧执行带强类型参数的 Action,参数从 selfContext 读取。</summary>
|
||||
public Cmd_PlayAnimation AddAction<T>(int frame, string selfContextKey, Action<T> action)
|
||||
{
|
||||
float normalizedDuration = frame / (clip.frameRate * clip.length);
|
||||
return AddAction(normalizedDuration, selfContextKey, action);
|
||||
if (clip == null) return this;
|
||||
float normalizedTime = frame / (clip.frameRate * clip.length);
|
||||
return AddAction(normalizedTime, selfContextKey, action);
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (animator == null || clip == null || string.IsNullOrEmpty(animationName))
|
||||
if (animator == null || string.IsNullOrEmpty(animationName))
|
||||
{
|
||||
Debug.LogWarning("Animator or stateName is null or empty.");
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
|
||||
string finalAnimationName = animationName;
|
||||
if (!characterView.animations.ContainsKey(animationName))
|
||||
{
|
||||
finalAnimationName = "Action";
|
||||
Debug.LogWarning("[Cmd_PlayAnimation] Animator 或动画名称为空。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (characterView.animations.TryGetValue(finalAnimationName, out clip))
|
||||
// 确认播放目标动画,回退到 "Action"
|
||||
string finalName = characterView.animations.ContainsKey(animationName) ? animationName : "Action";
|
||||
|
||||
if (!characterView.animations.TryGetValue(finalName, out clip))
|
||||
{
|
||||
characterView.animatorPlus2D.Play(clip);
|
||||
Debug.LogWarning($"[Cmd_PlayAnimation] 找不到动画片段:{finalName}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Animation clip not found for state: {finalAnimationName}");
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
|
||||
//监听动画进度以执行函数,独立Observable
|
||||
|
||||
characterView.animatorPlus2D.Play(clip);
|
||||
|
||||
// 帧轮询动画事件(fire-and-forget,不阻塞命令流)
|
||||
if (animationActions.Count > 0)
|
||||
{
|
||||
Observable.EveryUpdate().TakeUntil(Observable.Timer(TimeSpan.FromSeconds(clipScaledLength))).Subscribe(_ =>
|
||||
{
|
||||
float normalizedTime = animator.GetCurrentAnimatorStateInfo(layer).normalizedTime % 1f;
|
||||
foreach (var kvp in animationActions.ToList())
|
||||
{
|
||||
if (normalizedTime >= kvp.Key)
|
||||
{
|
||||
kvp.Value?.Invoke();
|
||||
animationActions.Remove(kvp.Key); //确保只执行一次
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PollAnimationActionsAsync().Forget();
|
||||
|
||||
if (waitForFinish)
|
||||
{
|
||||
float animationDuration = overrideDuration >= 0 ? overrideDuration / animator.speed : clipScaledLength;
|
||||
return Observable.Timer(TimeSpan.FromSeconds(animationDuration)).AsUnitObservable();
|
||||
float duration = overrideDuration >= 0f ? overrideDuration / animator.speed : ClipScaledLength;
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(duration));
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
private async UniTaskVoid PollAnimationActionsAsync()
|
||||
{
|
||||
float elapsed = 0f;
|
||||
float totalDuration = ClipScaledLength;
|
||||
var pending = new Dictionary<float, Action>(animationActions);
|
||||
|
||||
while (elapsed < totalDuration && pending.Count > 0)
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
await UniTask.Yield(PlayerLoopTiming.Update);
|
||||
elapsed += Time.deltaTime;
|
||||
float normalizedTime = animator.GetCurrentAnimatorStateInfo(layer).normalizedTime % 1f;
|
||||
|
||||
foreach (float key in pending.Keys.ToList())
|
||||
{
|
||||
if (normalizedTime >= key)
|
||||
{
|
||||
pending[key]?.Invoke();
|
||||
pending.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +1,66 @@
|
||||
using System;
|
||||
using Continentis.MainGame.Character;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using SLSFramework.General;
|
||||
using SLSFramework.UModAssistance;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Continentis.MainGame.Commands
|
||||
{
|
||||
public class Cmd_PlaySFX : CommandBase
|
||||
{
|
||||
public AudioClip sfxClip;
|
||||
public bool useTargetPosition;
|
||||
public Vector3 playPosition;
|
||||
public Vector3 playPositionOffset;
|
||||
public float volume;
|
||||
public float overrideDuration;
|
||||
public bool willWaitUntilFinish;
|
||||
|
||||
public Cmd_PlaySFX(string sfxClipID, bool useTargetPosition = true, Vector3 positionOrOffset = default,
|
||||
private readonly AudioClip sfxClip;
|
||||
private readonly CharacterBase target;
|
||||
private readonly Vector3 fixedPosition;
|
||||
private readonly Vector3 positionOffset;
|
||||
private readonly float volume;
|
||||
private readonly float overrideDuration;
|
||||
private readonly bool willWaitUntilFinish;
|
||||
|
||||
/// <summary>在目标角色位置播放音效。</summary>
|
||||
public Cmd_PlaySFX(string sfxClipID, CharacterBase target, Vector3 positionOffset = default,
|
||||
bool willWaitUntilFinish = false, float volume = 1f, float overrideDuration = -1f)
|
||||
{
|
||||
this.sfxClip = ModManager.GetAsset<AudioClip>(sfxClipID);
|
||||
this.useTargetPosition = useTargetPosition;
|
||||
|
||||
if (useTargetPosition)
|
||||
{
|
||||
this.playPosition = Vector3.zero;
|
||||
this.playPositionOffset = positionOrOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.playPosition = positionOrOffset;
|
||||
this.playPositionOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
this.positionOffset = positionOffset;
|
||||
this.fixedPosition = Vector3.zero;
|
||||
this.volume = volume;
|
||||
this.overrideDuration = overrideDuration;
|
||||
this.willWaitUntilFinish = willWaitUntilFinish;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
|
||||
/// <summary>在世界坐标固定位置播放音效。</summary>
|
||||
public Cmd_PlaySFX(string sfxClipID, Vector3 position = default,
|
||||
bool willWaitUntilFinish = false, float volume = 1f, float overrideDuration = -1f)
|
||||
{
|
||||
this.sfxClip = ModManager.GetAsset<AudioClip>(sfxClipID);
|
||||
this.target = null;
|
||||
this.fixedPosition = position;
|
||||
this.positionOffset = Vector3.zero;
|
||||
this.volume = volume;
|
||||
this.overrideDuration = overrideDuration;
|
||||
this.willWaitUntilFinish = willWaitUntilFinish;
|
||||
}
|
||||
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (sfxClip == null)
|
||||
{
|
||||
Debug.LogWarning("SFX Clip is null.");
|
||||
return Observable.Return(Unit.Default);
|
||||
Debug.LogWarning("[Cmd_PlaySFX] SFX Clip 为空。");
|
||||
return;
|
||||
}
|
||||
|
||||
return base.OnExecute(outerContext);
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> CoreExecute(CommandContext outerContext)
|
||||
{
|
||||
if (useTargetPosition)
|
||||
{
|
||||
if (selfContext.context["Target"] is CharacterBase character)
|
||||
{
|
||||
playPosition = character.characterView.centerPoint.transform.position;
|
||||
}
|
||||
}
|
||||
Vector3 playPosition = target != null
|
||||
? target.characterView.centerPoint.transform.position + positionOffset
|
||||
: fixedPosition;
|
||||
|
||||
AudioManager.PlaySFX(sfxClip, playPosition + playPositionOffset, volume);
|
||||
float sfxDuration = overrideDuration > 0 ? overrideDuration : sfxClip.length;
|
||||
AudioManager.PlaySFX(sfxClip, playPosition, volume);
|
||||
|
||||
if (willWaitUntilFinish)
|
||||
{
|
||||
return Observable.Timer(TimeSpan.FromSeconds(sfxDuration)).AsUnitObservable();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
float duration = overrideDuration > 0f ? overrideDuration : sfxClip.length;
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Continentis.MainGame.Card;
|
||||
using Continentis.MainGame.Character;
|
||||
using DG.Tweening;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
@@ -13,75 +12,62 @@ namespace Continentis.MainGame.Commands
|
||||
{
|
||||
public class Cmd_ReboundCards : CommandBase
|
||||
{
|
||||
private readonly DeckSubmodule deck;
|
||||
private readonly List<CardInstance> cardsToRebound;
|
||||
private readonly DeckSubmodule deck;
|
||||
private readonly float interval;
|
||||
private readonly float singleCardAnimationDuration = 0.5f; // 单张卡牌的动画时长
|
||||
|
||||
|
||||
private const float SingleCardAnimationDuration = 0.5f;
|
||||
|
||||
public Cmd_ReboundCards(DeckSubmodule deck, List<CardInstance> cards, float interval)
|
||||
{
|
||||
this.deck = deck;
|
||||
this.cardsToRebound = cards;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToRebound == null || cardsToRebound.Count == 0)
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
|
||||
// --- 情况1:并行丢弃 (所有卡牌动画同时开始) ---
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToRebound == null || cardsToRebound.Count == 0) return;
|
||||
|
||||
if (interval <= 0f)
|
||||
{
|
||||
// 为每张卡牌创建一个“懒加载”的丢弃动画流。
|
||||
var allDiscardAnimations = cardsToRebound.Select(card =>
|
||||
Observable.Defer(() => DiscardCard(card))
|
||||
);
|
||||
|
||||
// 使用 WhenAll 等待所有的并行任务都完成。
|
||||
// 只有当最后一个动画结束时,WhenAll 才会发出完成信号。
|
||||
return Observable.WhenAll(allDiscardAnimations).AsUnitObservable();
|
||||
await UniTask.WhenAll(System.Linq.Enumerable.Select(cardsToRebound, card => ReboundCardAsync(card)));
|
||||
}
|
||||
|
||||
// --- 情况2:交错丢弃 (按固定间隔开始动画) ---
|
||||
else
|
||||
{
|
||||
var cardStream = cardsToRebound.ToObservable();
|
||||
var timerStream = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(interval));
|
||||
|
||||
return timerStream
|
||||
.Zip(cardStream, (_, card) => card)
|
||||
// 核心:不再使用 .Do(..).Subscribe(),而是使用 Select 将每个 card 转换为
|
||||
// 其对应的动画流。这样主流程就“知道”了每个动画的存在。
|
||||
.Select(card => DiscardCard(card))
|
||||
// 使用 Merge 将所有交错开始的动画流合并为一个流。
|
||||
// Merge 会同时运行所有这些动画,并在最后一个动画也完成时,它才会完成。
|
||||
.Merge()
|
||||
// 使用 Last 来确保我们只在整个 Merge 流全部结束后,才发出最终的完成信号。
|
||||
.Last()
|
||||
.AsUnitObservable();
|
||||
var tasks = new UniTask[cardsToRebound.Count];
|
||||
for (int i = 0; i < cardsToRebound.Count; i++)
|
||||
{
|
||||
CardInstance captured = cardsToRebound[i];
|
||||
tasks[i] = ReboundCardWithDelayAsync(captured, i * interval);
|
||||
}
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
private IObservable<Unit> DiscardCard(CardInstance card)
|
||||
private async UniTask ReboundCardWithDelayAsync(CardInstance card, float delay)
|
||||
{
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
await ReboundCardAsync(card);
|
||||
}
|
||||
|
||||
private async UniTask ReboundCardAsync(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.DrawPile, card);
|
||||
//card.cardLogic.eventSubmodule.onInitiativeDiscard.Invoke();
|
||||
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.drawPile);
|
||||
|
||||
RectTransform cardTransform = card.handCardView.cardTransform;
|
||||
Vector3 deltaMove = Vector3.zero - card.handCardView.cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), 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).Play();
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
RectTransform cardTransform = card.handCardView.cardTransform;
|
||||
Vector3 deltaMove = Vector3.zero - cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0f);
|
||||
|
||||
cardTransform.DOBlendableLocalMoveBy(deltaMove, SingleCardAnimationDuration).Play();
|
||||
cardTransform.DOBlendableLocalMoveBy(randomLift, SingleCardAnimationDuration * 0.5f)
|
||||
.SetLoops(2, LoopType.Yoyo).Play();
|
||||
cardTransform.DOLocalRotate(new Vector3(0f, 0f, 720f), SingleCardAnimationDuration, RotateMode.FastBeyond360).Play();
|
||||
cardTransform.DOScale(Vector3.zero, SingleCardAnimationDuration).SetEase(Ease.Linear).Play();
|
||||
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Continentis.MainGame.Card;
|
||||
using Continentis.MainGame.Character;
|
||||
using DG.Tweening;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
@@ -14,51 +14,57 @@ namespace Continentis.MainGame.Commands
|
||||
{
|
||||
private readonly DeckSubmodule deck;
|
||||
private readonly float interval;
|
||||
private readonly float singleCardAnimationDuration = 0.5f; // 单张卡牌的动画时长
|
||||
|
||||
|
||||
private const float SingleCardAnimationDuration = 0.5f;
|
||||
|
||||
public Cmd_ReshuffleDeck(DeckSubmodule deck, float interval)
|
||||
{
|
||||
this.deck = deck;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
var cardStream = deck.DiscardPile.ToObservable();
|
||||
var timerStream = Observable.Interval(TimeSpan.FromSeconds(interval));
|
||||
var cards = new List<CardInstance>(deck.DiscardPile);
|
||||
if (cards.Count == 0) return;
|
||||
|
||||
return timerStream
|
||||
.Zip(cardStream, (_, card) => card)
|
||||
.Select(card => MoveCardToDrawPile(card))
|
||||
.Merge()
|
||||
.Last()
|
||||
.Do(_ => deck.DrawPile.Shuffle())
|
||||
.AsUnitObservable();
|
||||
// 使用 Interval 语义:第一张卡在 t=interval 开始(与旧版 Observable.Interval 一致)
|
||||
var tasks = new UniTask[cards.Count];
|
||||
for (int i = 0; i < cards.Count; i++)
|
||||
{
|
||||
CardInstance captured = cards[i];
|
||||
tasks[i] = MoveCardWithDelayAsync(captured, (i + 1) * interval);
|
||||
}
|
||||
await UniTask.WhenAll(tasks);
|
||||
|
||||
deck.DrawPile.Shuffle();
|
||||
}
|
||||
|
||||
private IObservable<Unit> MoveCardToDrawPile(CardInstance card)
|
||||
|
||||
private async UniTask MoveCardWithDelayAsync(CardInstance card, float delay)
|
||||
{
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
await MoveCardToDrawPileAsync(card);
|
||||
}
|
||||
|
||||
private async UniTask MoveCardToDrawPileAsync(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.DiscardPile, deck.DrawPile, card);
|
||||
//card.cardLogic.eventSubmodule.onInitiativeDiscard.Invoke();
|
||||
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.drawPile);
|
||||
|
||||
|
||||
Vector3 deltaMove = Vector3.zero - card.handCardView.cardTransform.localPosition;
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0);
|
||||
|
||||
Vector3 randomLift = new Vector3(Random.Range(-200f, 200f), Random.Range(200f, 600f), 0f);
|
||||
|
||||
card.handCardView.cardOrb.gameObject.SetActive(true);
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(deltaMove, singleCardAnimationDuration)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
card.handCardView.cardOrb.gameObject.SetActive(false);
|
||||
}).Play();
|
||||
|
||||
card.handCardView.cardTransform.DOBlendableLocalMoveBy(randomLift, singleCardAnimationDuration * 0.5f).SetLoops(2, LoopType.Yoyo).Play();
|
||||
|
||||
card.handCardView.cardTransform.DOScale(Vector3.zero, singleCardAnimationDuration).Play();
|
||||
card.handCardView.cardTransform
|
||||
.DOBlendableLocalMoveBy(deltaMove, SingleCardAnimationDuration)
|
||||
.OnComplete(() => card.handCardView.cardOrb.gameObject.SetActive(false)).Play();
|
||||
card.handCardView.cardTransform
|
||||
.DOBlendableLocalMoveBy(randomLift, SingleCardAnimationDuration * 0.5f)
|
||||
.SetLoops(2, LoopType.Yoyo).Play();
|
||||
card.handCardView.cardTransform.DOScale(Vector3.zero, SingleCardAnimationDuration).Play();
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,99 +1,91 @@
|
||||
using System;
|
||||
using Continentis.MainGame.Character;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Lean.Pool;
|
||||
using SLSFramework.General;
|
||||
using SLSFramework.UModAssistance;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Continentis.MainGame.Commands
|
||||
{
|
||||
public class Cmd_SpawnVFX : CommandBase
|
||||
{
|
||||
public VisualEffectBase vfxPrefab;
|
||||
public bool useTargetPosition;
|
||||
public Vector3 spawnPosition;
|
||||
public Vector3 spawnPositionOffset;
|
||||
public bool willWaitUntilFinish;
|
||||
public float overrideDuration;
|
||||
|
||||
|
||||
public Cmd_SpawnVFX(string vfxID, bool useTargetPosition = true, Vector3 positionOrOffset = default,
|
||||
bool willWaitUntilFinish = false, float overrideDuration = -1f) : base(null)
|
||||
private readonly VisualEffectBase vfxPrefab;
|
||||
private readonly CharacterBase target;
|
||||
private readonly Vector3 fixedPosition;
|
||||
private readonly Vector3 positionOffset;
|
||||
private readonly bool willWaitUntilFinish;
|
||||
private readonly float overrideDuration;
|
||||
|
||||
/// <summary>在目标角色位置生成 VFX。</summary>
|
||||
public Cmd_SpawnVFX(string vfxID, CharacterBase target, Vector3 positionOffset = default,
|
||||
bool willWaitUntilFinish = false, float overrideDuration = -1f)
|
||||
{
|
||||
this.vfxPrefab = ModManager.GetAsset<GameObject>(vfxID).GetComponent<VisualEffectBase>();
|
||||
this.useTargetPosition = useTargetPosition;
|
||||
|
||||
if (useTargetPosition)
|
||||
{
|
||||
this.spawnPosition = Vector3.zero;
|
||||
this.spawnPositionOffset = positionOrOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.spawnPosition = positionOrOffset;
|
||||
this.spawnPositionOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
this.positionOffset = positionOffset;
|
||||
this.fixedPosition = Vector3.zero;
|
||||
this.willWaitUntilFinish = willWaitUntilFinish;
|
||||
this.overrideDuration = overrideDuration;
|
||||
}
|
||||
|
||||
public Cmd_SpawnVFX(GameObject prefab, bool useTargetPosition = true, Vector3 positionOrOffset = default,
|
||||
bool willWaitUntilFinish = false, float overrideDuration = -1f) : base(null)
|
||||
|
||||
/// <summary>在世界坐标固定位置生成 VFX。</summary>
|
||||
public Cmd_SpawnVFX(string vfxID, Vector3 position = default,
|
||||
bool willWaitUntilFinish = false, float overrideDuration = -1f)
|
||||
{
|
||||
this.vfxPrefab = ModManager.GetAsset<GameObject>(vfxID).GetComponent<VisualEffectBase>();
|
||||
this.target = null;
|
||||
this.fixedPosition = position;
|
||||
this.positionOffset = Vector3.zero;
|
||||
this.willWaitUntilFinish = willWaitUntilFinish;
|
||||
this.overrideDuration = overrideDuration;
|
||||
}
|
||||
|
||||
/// <summary>在目标角色位置生成 VFX(直接传入 Prefab GameObject)。</summary>
|
||||
public Cmd_SpawnVFX(GameObject prefab, CharacterBase target, Vector3 positionOffset = default,
|
||||
bool willWaitUntilFinish = false, float overrideDuration = -1f)
|
||||
{
|
||||
this.vfxPrefab = prefab.GetComponent<VisualEffectBase>();
|
||||
this.useTargetPosition = useTargetPosition;
|
||||
|
||||
if (useTargetPosition)
|
||||
{
|
||||
this.spawnPosition = Vector3.zero;
|
||||
this.spawnPositionOffset = positionOrOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.spawnPosition = positionOrOffset;
|
||||
this.spawnPositionOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
this.positionOffset = positionOffset;
|
||||
this.fixedPosition = Vector3.zero;
|
||||
this.willWaitUntilFinish = willWaitUntilFinish;
|
||||
this.overrideDuration = overrideDuration;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
|
||||
/// <summary>在世界坐标固定位置生成 VFX(直接传入 Prefab GameObject)。</summary>
|
||||
public Cmd_SpawnVFX(GameObject prefab, Vector3 position = default,
|
||||
bool willWaitUntilFinish = false, float overrideDuration = -1f)
|
||||
{
|
||||
this.vfxPrefab = prefab.GetComponent<VisualEffectBase>();
|
||||
this.target = null;
|
||||
this.fixedPosition = position;
|
||||
this.positionOffset = Vector3.zero;
|
||||
this.willWaitUntilFinish = willWaitUntilFinish;
|
||||
this.overrideDuration = overrideDuration;
|
||||
}
|
||||
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (vfxPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("VFX Prefab is null.");
|
||||
return Observable.Return(Unit.Default);
|
||||
Debug.LogWarning("[Cmd_SpawnVFX] VFX Prefab 为空。");
|
||||
return;
|
||||
}
|
||||
|
||||
return base.OnExecute(outerContext);
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> CoreExecute(CommandContext outerContext)
|
||||
{
|
||||
if (useTargetPosition)
|
||||
{
|
||||
if (selfContext.context["Target"] is CharacterBase character)
|
||||
{
|
||||
spawnPosition = character.characterView.centerPoint.transform.position;
|
||||
}
|
||||
}
|
||||
|
||||
VisualEffectBase spawnedVFX = LeanPool.Spawn(vfxPrefab, spawnPosition + spawnPositionOffset, Quaternion.identity);
|
||||
|
||||
if(!spawnedVFX.isAutoDespawn) Debug.LogWarning("Spawned VFX is not set to auto-despawn. This may lead to memory leaks.");
|
||||
|
||||
float vfxDuration = overrideDuration > 0 ? overrideDuration : spawnedVFX.autoDespawnTime;
|
||||
Vector3 spawnPosition = target != null
|
||||
? target.characterView.centerPoint.transform.position + positionOffset
|
||||
: fixedPosition;
|
||||
|
||||
VisualEffectBase spawnedVFX = LeanPool.Spawn(vfxPrefab, spawnPosition, Quaternion.identity);
|
||||
|
||||
if (!spawnedVFX.isAutoDespawn)
|
||||
Debug.LogWarning("[Cmd_SpawnVFX] 生成的 VFX 未设置自动销毁,可能导致内存泄漏。");
|
||||
|
||||
if (willWaitUntilFinish)
|
||||
{
|
||||
return Observable.Timer(TimeSpan.FromSeconds(vfxDuration)).AsUnitObservable();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
float duration = overrideDuration > 0f ? overrideDuration : spawnedVFX.autoDespawnTime;
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Continentis.MainGame.Card;
|
||||
using Continentis.MainGame.Character;
|
||||
using DG.Tweening;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
@@ -13,12 +13,13 @@ namespace Continentis.MainGame.Commands
|
||||
{
|
||||
public class Cmd_UsePowerCards : CommandBase
|
||||
{
|
||||
private bool isPlayer;
|
||||
private readonly bool isPlayer;
|
||||
private readonly DeckSubmodule deck;
|
||||
private readonly List<CardInstance> cardsToUse;
|
||||
private readonly float interval;
|
||||
private readonly float singleCardAnimationDuration = 0.5f; // 单张卡牌的动画时长
|
||||
|
||||
|
||||
private const float SingleCardAnimationDuration = 0.5f;
|
||||
|
||||
public Cmd_UsePowerCards(bool isPlayer, DeckSubmodule deck, List<CardInstance> cards, float interval)
|
||||
{
|
||||
this.isPlayer = isPlayer;
|
||||
@@ -26,71 +27,61 @@ namespace Continentis.MainGame.Commands
|
||||
this.cardsToUse = cards;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToUse == null || cardsToUse.Count == 0)
|
||||
{
|
||||
return Observable.Return(Unit.Default);
|
||||
}
|
||||
|
||||
Func<CardInstance, IObservable<Unit>> exhaustAction = isPlayer ? PlayerUsePowers : NpcUsePowers;
|
||||
|
||||
// --- 情况1:并行丢弃 (所有卡牌动画同时开始) ---
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (cardsToUse == null || cardsToUse.Count == 0) return;
|
||||
|
||||
if (interval <= 0f)
|
||||
{
|
||||
// 为每张卡牌创建一个“懒加载”的丢弃动画流。
|
||||
var allDiscardAnimations = cardsToUse.Select(card =>
|
||||
Observable.Defer(() => exhaustAction(card))
|
||||
);
|
||||
|
||||
// 使用 WhenAll 等待所有的并行任务都完成。
|
||||
// 只有当最后一个动画结束时,WhenAll 才会发出完成信号。
|
||||
return Observable.WhenAll(allDiscardAnimations).AsUnitObservable();
|
||||
await UniTask.WhenAll(cardsToUse.Select(card => UsePowerCardAsync(card)));
|
||||
}
|
||||
|
||||
// --- 情况2:交错丢弃 (按固定间隔开始动画) ---
|
||||
else
|
||||
{
|
||||
var cardStream = cardsToUse.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();
|
||||
var tasks = new UniTask[cardsToUse.Count];
|
||||
for (int i = 0; i < cardsToUse.Count; i++)
|
||||
{
|
||||
CardInstance captured = cardsToUse[i];
|
||||
tasks[i] = UsePowerCardWithDelayAsync(captured, i * interval);
|
||||
}
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IObservable<Unit> PlayerUsePowers(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.GravePile, card);
|
||||
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.gravePile);
|
||||
|
||||
RectTransform cardTransform = card.handCardView.cardTransform;
|
||||
Vector2 userViewPosition = card.user.characterView.hudContainer.GetComponent<RectTransform>().position;
|
||||
cardTransform.DOMove(userViewPosition, singleCardAnimationDuration).SetEase(Ease.Linear).Play();
|
||||
cardTransform.DOScale(Vector3.zero, singleCardAnimationDuration).SetEase(Ease.Linear).OnComplete(() =>
|
||||
{
|
||||
cardTransform.anchoredPosition = Vector2.zero;
|
||||
}).Play();
|
||||
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
private async UniTask UsePowerCardWithDelayAsync(CardInstance card, float delay)
|
||||
{
|
||||
if (delay > 0f)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(delay));
|
||||
await UsePowerCardAsync(card);
|
||||
}
|
||||
|
||||
private IObservable<Unit> NpcUsePowers(CardInstance card)
|
||||
private async UniTask UsePowerCardAsync(CardInstance card)
|
||||
{
|
||||
if (isPlayer)
|
||||
await PlayerUsePowerAsync(card);
|
||||
else
|
||||
await NpcUsePowerAsync(card);
|
||||
}
|
||||
|
||||
private async UniTask PlayerUsePowerAsync(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.GravePile, card);
|
||||
return Observable.Timer(TimeSpan.FromSeconds(singleCardAnimationDuration)).AsUnitObservable();
|
||||
card.handCardView.TransferCardView(CombatUIManager.Instance.combatMainPage.gravePile);
|
||||
|
||||
RectTransform cardTransform = card.handCardView.cardTransform;
|
||||
Vector2 userViewPosition = card.user.characterView.hudContainer.GetComponent<RectTransform>().position;
|
||||
|
||||
cardTransform.DOMove(userViewPosition, SingleCardAnimationDuration).SetEase(Ease.Linear).Play();
|
||||
cardTransform.DOScale(Vector3.zero, SingleCardAnimationDuration).SetEase(Ease.Linear)
|
||||
.OnComplete(() => cardTransform.anchoredPosition = Vector2.zero).Play();
|
||||
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
|
||||
private async UniTask NpcUsePowerAsync(CardInstance card)
|
||||
{
|
||||
deck.TransferCard(deck.Pile(card.cardLocation.pileName), deck.GravePile, card);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(SingleCardAnimationDuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using SLSFramework.General;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Continentis.MainGame.Commands
|
||||
@@ -8,31 +7,27 @@ namespace Continentis.MainGame.Commands
|
||||
public class Cmd_WaitForUI : CommandBase
|
||||
{
|
||||
private readonly WaitableUIElement waitableUI;
|
||||
private bool autoHide;
|
||||
|
||||
private readonly bool autoHide;
|
||||
|
||||
public Cmd_WaitForUI(WaitableUIElement waitableUI, bool autoHide = true)
|
||||
{
|
||||
this.waitableUI = waitableUI;
|
||||
this.autoHide = autoHide;
|
||||
}
|
||||
|
||||
protected override IObservable<Unit> OnExecute(CommandContext outerContext)
|
||||
|
||||
protected override async UniTask ExecuteAsync(CommandContext outerContext)
|
||||
{
|
||||
if (waitableUI == null)
|
||||
{
|
||||
Debug.LogError($"指令无法找到UI元素,指令将立即完成以避免队列卡死。");
|
||||
return Observable.Return(Unit.Default);
|
||||
Debug.LogError("[Cmd_WaitForUI] UI 元素为空,命令立即完成以避免队列卡死。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 显示UI面板
|
||||
waitableUI.Show();
|
||||
|
||||
// 2. 获取该面板的确认事件流,并附加一个副作用:
|
||||
// 在确认事件发生后(即玩家点击按钮后),自动隐藏该面板。
|
||||
return waitableUI.OnConfirm().Do(_ =>
|
||||
{
|
||||
if(autoHide) waitableUI.Hide();
|
||||
});
|
||||
await waitableUI.OnConfirmAsync();
|
||||
|
||||
if (autoHide)
|
||||
waitableUI.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user