Add battle targeting, action groups, and event log
CI / test (push) Successful in 9m52s
Details
CI / test (push) Successful in 9m52s
Details
This commit is contained in:
parent
0e528586b4
commit
0e4737ac22
|
|
@ -276,7 +276,8 @@
|
|||
- [x] Процедурные карты: цвет/градиент клана, рубашка, имя, ранг, триггер,
|
||||
сила/крит; REST/скрытая карта.
|
||||
- [x] Старт: маллиган (сервер пока даёт только «закончить маллиган»).
|
||||
- [x] Ввод: кнопки легальных интентов; подсветка целей — дальше.
|
||||
- [x] Ввод: кнопки легальных интентов; клик по картам/зонам,
|
||||
подсветка целей, группировка действий и журнал боя.
|
||||
- [x] Отображение силы/крита из `MatchView`; модификаторы уже посчитаны сервером.
|
||||
- [ ] Очередь анимаций строго по событиям сервера: ride, call, attack, drive, damage, retire, boost, guard.
|
||||
- [ ] Скип/ускорение анимаций.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ namespace Vanguard.UI
|
|||
private readonly Text _concedeLabel;
|
||||
private readonly Button _reconnect;
|
||||
private readonly RectTransform _result;
|
||||
private RectTransform _logContent;
|
||||
private Text _resultText;
|
||||
private readonly List<string> _logLines = new List<string>();
|
||||
|
||||
private GameConnection _connection;
|
||||
private CancellationTokenSource _cts;
|
||||
|
|
@ -52,9 +54,12 @@ namespace Vanguard.UI
|
|||
private bool _snapshotPending;
|
||||
private bool _finished;
|
||||
private bool _confirmConcede;
|
||||
private bool _showAllIntents;
|
||||
private float _confirmDeadline;
|
||||
private long _clockServerMs;
|
||||
private long _clockReceivedMs;
|
||||
private readonly List<IntentPayload> _pendingIntents = new List<IntentPayload>();
|
||||
private readonly HashSet<string> _highlightKeys = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
public BattleView(Transform parent, MetaService meta, Action onExit)
|
||||
{
|
||||
|
|
@ -116,7 +121,17 @@ namespace Vanguard.UI
|
|||
_status = UiKit.CreateText(
|
||||
right, "Status", string.Empty, 16, Muted, TextAnchor.UpperLeft);
|
||||
_status.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UiKit.SetPreferredHeight(_status, 110f);
|
||||
UiKit.SetPreferredHeight(_status, 74f);
|
||||
|
||||
var logTitle = UiKit.CreateText(
|
||||
right, "LogTitle", "Журнал", 16, new Color(0.72f, 0.80f, 0.94f), TextAnchor.MiddleLeft);
|
||||
logTitle.fontStyle = FontStyle.Bold;
|
||||
UiKit.SetPreferredHeight(logTitle, 22f);
|
||||
|
||||
var logScroll = UiKit.CreateScrollView(right, "LogScroll", out _logContent, 3f);
|
||||
var logElement = logScroll.gameObject.AddComponent<LayoutElement>();
|
||||
logElement.preferredHeight = 142f;
|
||||
logElement.minHeight = 80f;
|
||||
|
||||
_reconnect = UiKit.CreateButton(right, "Reconnect", "Переподключиться", OnReconnectClicked, 18);
|
||||
UiKit.SetPreferredHeight(_reconnect, 46f);
|
||||
|
|
@ -138,6 +153,11 @@ namespace Vanguard.UI
|
|||
_concede.interactable = true;
|
||||
_reconnect.gameObject.SetActive(false);
|
||||
_result.gameObject.SetActive(false);
|
||||
_pendingIntents.Clear();
|
||||
_highlightKeys.Clear();
|
||||
_showAllIntents = false;
|
||||
_logLines.Clear();
|
||||
RebuildLog();
|
||||
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
|
||||
SetStatus("Подключаемся к матчу…", Muted);
|
||||
_root.gameObject.SetActive(true);
|
||||
|
|
@ -286,6 +306,8 @@ namespace Vanguard.UI
|
|||
|
||||
_view = snapshot.View;
|
||||
_finished = _view.IsFinished;
|
||||
_pendingIntents.Clear();
|
||||
_highlightKeys.Clear();
|
||||
if (_view.Clock != null)
|
||||
{
|
||||
_clockServerMs = _view.Clock.ServerTimeMs;
|
||||
|
|
@ -297,9 +319,78 @@ namespace Vanguard.UI
|
|||
|
||||
private void OnEvents(ServerEvents events)
|
||||
{
|
||||
if (events?.Events != null)
|
||||
{
|
||||
foreach (var envelope in events.Events)
|
||||
{
|
||||
var line = Describe(envelope.Event);
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
_logLines.Add(line);
|
||||
}
|
||||
|
||||
while (_logLines.Count > 40)
|
||||
_logLines.RemoveAt(0);
|
||||
|
||||
RebuildLog();
|
||||
}
|
||||
|
||||
_ = RequestSnapshotAsync();
|
||||
}
|
||||
|
||||
private void RebuildLog()
|
||||
{
|
||||
if (_logContent == null)
|
||||
return;
|
||||
|
||||
ClearChildren(_logContent);
|
||||
var start = Mathf.Max(0, _logLines.Count - 8);
|
||||
for (var i = start; i < _logLines.Count; i++)
|
||||
{
|
||||
var line = UiKit.CreateText(
|
||||
_logContent, "LogLine", _logLines[i], 13,
|
||||
i == _logLines.Count - 1
|
||||
? new Color(0.92f, 0.94f, 1f)
|
||||
: new Color(0.60f, 0.68f, 0.82f),
|
||||
TextAnchor.MiddleLeft);
|
||||
line.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UiKit.SetPreferredHeight(line, 30f);
|
||||
}
|
||||
}
|
||||
|
||||
private string Describe(EventView view)
|
||||
{
|
||||
if (view == null)
|
||||
return null;
|
||||
|
||||
return view switch
|
||||
{
|
||||
UnitRodeView ride => "Ride: " + CardName(ride.PrintingId),
|
||||
UnitCalledView call => "Call: " + CardName(call.PrintingId) + " → " + SlotName(call.Slot),
|
||||
UnitMovedView move => "Move: " + SlotName(move.FromSlot) + " → " + SlotName(move.ToSlot),
|
||||
AttackDeclaredView attack => "Атака: " + SlotName(attack.AttackerSlot)
|
||||
+ " → " + SlotName(attack.TargetSlot),
|
||||
GuardAddedView guard => "Guard: " + CardName(guard.PrintingId)
|
||||
+ " (+" + guard.Shield + ")",
|
||||
PerfectGuardView perfect => "Perfect Guard: " + CardName(perfect.SentinelPrintingId),
|
||||
DriveCheckedView drive => "Drive: " + CardName(drive.PrintingId)
|
||||
+ " · " + drive.Trigger,
|
||||
TriggerResolvedView trigger => "Триггер: " + CardName(trigger.PrintingId),
|
||||
BattleResolvedView battle => battle.Hit ? "Атака прошла." : "Атака отбита.",
|
||||
DamageTakenView damage => "Урон: " + PlayerName(damage.Player)
|
||||
+ " +" + damage.Amount + " (всего " + damage.Total + ")",
|
||||
HealedView heal => "Heal: " + PlayerName(heal.Player),
|
||||
UnitRetiredView retire => "Retire: " + SlotName(retire.Slot),
|
||||
InterceptedView intercept => "Intercept: " + CardName(intercept.PrintingId),
|
||||
CardDrawnView draw => "Карта: " + PlayerName(draw.Player),
|
||||
PhaseChangedView phase => "Фаза: " + phase.Phase,
|
||||
GameFinishedView finish => "Партия завершена.",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private string PlayerName(int player) =>
|
||||
_view == null ? "P" + player : player == _view.You ? "Вы" : "Противник";
|
||||
|
||||
private void OnClock(ServerClock clock)
|
||||
{
|
||||
if (!_root.gameObject.activeSelf || clock?.Clock == null)
|
||||
|
|
@ -358,6 +449,8 @@ namespace Vanguard.UI
|
|||
if (_sending || _connection == null || _cts == null || _view == null)
|
||||
return;
|
||||
|
||||
_pendingIntents.Clear();
|
||||
_highlightKeys.Clear();
|
||||
_sending = true;
|
||||
SetStatus("Отправляем: " + Label(intent), Muted);
|
||||
|
||||
|
|
@ -504,13 +597,13 @@ namespace Vanguard.UI
|
|||
rowElement.minHeight = 166f;
|
||||
UiKit.AddHorizontalLayout(row, 3, 7);
|
||||
|
||||
AddCardBox(row, player.Vanguard, "V", compact: false);
|
||||
AddCardBox(row, player.Vanguard, "V", compact: false, player.Index, -1);
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var card = player.RearGuards != null && i < player.RearGuards.Length
|
||||
? player.RearGuards[i]
|
||||
: null;
|
||||
AddCardBox(row, card, "R" + (i + 1), compact: false);
|
||||
AddCardBox(row, card, "R" + (i + 1), compact: false, player.Index, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +681,7 @@ namespace Vanguard.UI
|
|||
}
|
||||
|
||||
foreach (var card in self.Hand)
|
||||
AddCardBox(content, card, "Рука", compact: true);
|
||||
AddCardBox(content, card, "Рука", compact: true, self.Index, -2);
|
||||
}
|
||||
|
||||
private void AddInfoLine(Transform parent, string text)
|
||||
|
|
@ -598,7 +691,13 @@ namespace Vanguard.UI
|
|||
UiKit.SetPreferredHeight(line, 22f);
|
||||
}
|
||||
|
||||
private void AddCardBox(Transform parent, CardView card, string slot, bool compact)
|
||||
private void AddCardBox(
|
||||
Transform parent,
|
||||
CardView card,
|
||||
string slot,
|
||||
bool compact,
|
||||
int playerIndex,
|
||||
int slotIndex)
|
||||
{
|
||||
var meta = card != null && _catalog.TryGetValue(card.PrintingId, out var found)
|
||||
? found
|
||||
|
|
@ -619,9 +718,19 @@ namespace Vanguard.UI
|
|||
element.preferredHeight = compact ? 142f : 162f;
|
||||
element.flexibleWidth = 0f;
|
||||
|
||||
var highlighted = _highlightKeys.Contains(
|
||||
TargetKey(playerIndex, slotIndex, card != null ? card.PrintingId : null));
|
||||
var button = panel.gameObject.AddComponent<Button>();
|
||||
button.targetGraphic = panel.GetComponent<Image>();
|
||||
button.onClick.AddListener(() => OnBoardTargetClicked(new BoardTarget(
|
||||
playerIndex, slotIndex, card != null ? card.PrintingId : null,
|
||||
card != null ? card.InstanceId : 0, slotIndex == -2)));
|
||||
|
||||
var outline = panel.gameObject.AddComponent<Outline>();
|
||||
outline.effectColor = frameColor;
|
||||
outline.effectDistance = new Vector2(2f, -2f);
|
||||
outline.effectColor = highlighted ? new Color(1f, 0.82f, 0.28f) : frameColor;
|
||||
outline.effectDistance = highlighted
|
||||
? new Vector2(3f, -3f)
|
||||
: new Vector2(2f, -2f);
|
||||
UiKit.AddVerticalLayout(panel, 4, 2);
|
||||
|
||||
var slotText = UiKit.CreateText(
|
||||
|
|
@ -734,12 +843,233 @@ namespace Vanguard.UI
|
|||
private string CardName(string printingId) =>
|
||||
_catalog.TryGetValue(printingId, out var meta) ? meta.NameEn : printingId;
|
||||
|
||||
private void OnBoardTargetClicked(BoardTarget target)
|
||||
{
|
||||
if (_view == null || _sending)
|
||||
return;
|
||||
|
||||
var legal = _view.LegalIntents ?? new List<IntentPayload>();
|
||||
|
||||
if (_pendingIntents.Count > 0)
|
||||
{
|
||||
var matches = _pendingIntents
|
||||
.Where(intent => TryGetTarget(intent, out var candidate) && candidate.Matches(target))
|
||||
.OrderBy(intent => intent is AttackIntent attack && attack.BoosterSlot >= 0 ? 1 : 0)
|
||||
.ToList();
|
||||
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
ClearSelection(rebuild: false);
|
||||
OnIntentClicked(matches[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var sources = legal
|
||||
.Where(intent => TryGetSource(intent, out var source) && source.Matches(target))
|
||||
.ToList();
|
||||
|
||||
if (sources.Count == 0)
|
||||
{
|
||||
SetStatus("Для этой карты сейчас нет доступного действия.", Muted);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sources.Count == 1)
|
||||
{
|
||||
ClearSelection(rebuild: false);
|
||||
OnIntentClicked(sources[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
var withTargets = sources
|
||||
.Where(intent => TryGetTarget(intent, out _))
|
||||
.ToList();
|
||||
|
||||
if (withTargets.Count == 0)
|
||||
{
|
||||
ClearSelection(rebuild: false);
|
||||
OnIntentClicked(sources[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingIntents.Clear();
|
||||
_pendingIntents.AddRange(withTargets);
|
||||
RebuildHighlights();
|
||||
RebuildIntents(_view);
|
||||
SetStatus("Выберите цель на поле.", new Color(0.98f, 0.82f, 0.42f));
|
||||
}
|
||||
|
||||
private void ClearSelection(bool rebuild = true)
|
||||
{
|
||||
_pendingIntents.Clear();
|
||||
_highlightKeys.Clear();
|
||||
if (rebuild && _view != null)
|
||||
RebuildIntents(_view);
|
||||
}
|
||||
|
||||
private void RebuildHighlights()
|
||||
{
|
||||
_highlightKeys.Clear();
|
||||
foreach (var intent in _pendingIntents)
|
||||
{
|
||||
if (TryGetTarget(intent, out var target))
|
||||
_highlightKeys.Add(TargetKey(target.Player, target.Slot, target.PrintingId));
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetSource(IntentPayload intent, out BoardTarget source)
|
||||
{
|
||||
source = default;
|
||||
if (_view == null)
|
||||
return false;
|
||||
|
||||
switch (intent)
|
||||
{
|
||||
case RideIntent ride:
|
||||
source = new BoardTarget(_view.You, -2, ride.PrintingId, 0, true);
|
||||
return true;
|
||||
case CallIntent call:
|
||||
source = new BoardTarget(_view.You, -2, call.PrintingId, 0, true);
|
||||
return true;
|
||||
case GuardIntent guard:
|
||||
source = new BoardTarget(_view.You, -2, guard.PrintingId, 0, true);
|
||||
return true;
|
||||
case MoveIntent move:
|
||||
source = FieldTarget(_view.You, move.FromSlot);
|
||||
return true;
|
||||
case AttackIntent attack:
|
||||
source = FieldTarget(_view.You, attack.AttackerSlot);
|
||||
return true;
|
||||
case InterceptIntent intercept:
|
||||
source = FieldTarget(_view.You, intercept.Slot);
|
||||
return true;
|
||||
case ActivateAbilityIntent activate:
|
||||
source = InstanceTarget(_view.You, activate.InstanceId);
|
||||
return source.InstanceId != 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetTarget(IntentPayload intent, out BoardTarget target)
|
||||
{
|
||||
target = default;
|
||||
if (_view == null)
|
||||
return false;
|
||||
|
||||
switch (intent)
|
||||
{
|
||||
case CallIntent call:
|
||||
target = FieldTarget(_view.You, call.Slot);
|
||||
return true;
|
||||
case MoveIntent move:
|
||||
target = FieldTarget(_view.You, move.ToSlot);
|
||||
return true;
|
||||
case AttackIntent attack:
|
||||
target = FieldTarget(1 - _view.You, attack.TargetSlot);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private BoardTarget FieldTarget(int player, int slot)
|
||||
{
|
||||
var card = CardAt(player, slot);
|
||||
return new BoardTarget(player, slot, card != null ? card.PrintingId : null,
|
||||
card != null ? card.InstanceId : 0, false);
|
||||
}
|
||||
|
||||
private BoardTarget InstanceTarget(int player, int instanceId)
|
||||
{
|
||||
var card = InstanceAt(player, instanceId);
|
||||
if (card == null)
|
||||
return default;
|
||||
|
||||
if (_view != null
|
||||
&& _view.Players.FirstOrDefault(item => item.Index == player)?.Vanguard is { } vanguard
|
||||
&& vanguard.InstanceId == instanceId)
|
||||
{
|
||||
return new BoardTarget(player, -1, card.PrintingId, instanceId, false);
|
||||
}
|
||||
|
||||
var field = _view?.Players.FirstOrDefault(item => item.Index == player);
|
||||
if (field?.RearGuards != null)
|
||||
{
|
||||
for (var i = 0; i < field.RearGuards.Length; i++)
|
||||
{
|
||||
if (field.RearGuards[i]?.InstanceId == instanceId)
|
||||
return new BoardTarget(player, i, card.PrintingId, instanceId, false);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private CardView CardAt(int player, int slot)
|
||||
{
|
||||
var side = _view?.Players.FirstOrDefault(item => item.Index == player);
|
||||
if (side == null)
|
||||
return null;
|
||||
|
||||
if (slot == -1)
|
||||
return side.Vanguard;
|
||||
|
||||
return side.RearGuards != null && slot >= 0 && slot < side.RearGuards.Length
|
||||
? side.RearGuards[slot]
|
||||
: null;
|
||||
}
|
||||
|
||||
private CardView InstanceAt(int player, int instanceId)
|
||||
{
|
||||
var side = _view?.Players.FirstOrDefault(item => item.Index == player);
|
||||
if (side == null)
|
||||
return null;
|
||||
|
||||
if (side.Vanguard?.InstanceId == instanceId)
|
||||
return side.Vanguard;
|
||||
|
||||
return side.RearGuards?.FirstOrDefault(card => card?.InstanceId == instanceId);
|
||||
}
|
||||
|
||||
private static string TargetKey(int player, int slot, string printingId) =>
|
||||
player + ":" + slot + ":" + (printingId ?? string.Empty);
|
||||
|
||||
private static bool IsBoardIntent(IntentPayload intent) =>
|
||||
intent is RideIntent or CallIntent or MoveIntent or AttackIntent
|
||||
or GuardIntent or InterceptIntent or ActivateAbilityIntent;
|
||||
|
||||
private void RebuildIntents(MatchView view)
|
||||
{
|
||||
ClearChildren(_intents);
|
||||
|
||||
var legal = view.LegalIntents ?? new List<IntentPayload>();
|
||||
var playable = legal.Where(intent => intent is not ConcedeIntent).ToList();
|
||||
var board = playable.Where(IsBoardIntent).ToList();
|
||||
var global = playable.Where(intent => !IsBoardIntent(intent)).ToList();
|
||||
|
||||
if (_pendingIntents.Count > 0)
|
||||
{
|
||||
var prompt = UiKit.CreateText(
|
||||
_intents,
|
||||
"TargetPrompt",
|
||||
"Выберите цель на поле.\nНажмите «Отмена», чтобы снять выбор.",
|
||||
16,
|
||||
new Color(0.98f, 0.82f, 0.42f),
|
||||
TextAnchor.UpperLeft);
|
||||
prompt.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UiKit.SetPreferredHeight(prompt, 64f);
|
||||
|
||||
var cancel = UiKit.CreateButton(
|
||||
_intents, "CancelTarget", "Отмена", () =>
|
||||
{
|
||||
ClearSelection();
|
||||
Render();
|
||||
}, 17);
|
||||
UiKit.SetPreferredHeight(cancel, 44f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (playable.Count == 0)
|
||||
{
|
||||
|
|
@ -753,7 +1083,42 @@ namespace Vanguard.UI
|
|||
return;
|
||||
}
|
||||
|
||||
foreach (var intent in playable)
|
||||
if (board.Count > 0)
|
||||
{
|
||||
var hint = UiKit.CreateText(
|
||||
_intents,
|
||||
"BoardHint",
|
||||
"Нажмите на карту в руке или на поле.\nДопустимые цели подсветятся.",
|
||||
15,
|
||||
new Color(0.72f, 0.80f, 0.94f),
|
||||
TextAnchor.UpperLeft);
|
||||
hint.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UiKit.SetPreferredHeight(hint, 52f);
|
||||
|
||||
var toggle = UiKit.CreateButton(
|
||||
_intents,
|
||||
"ToggleAllIntents",
|
||||
_showAllIntents ? "Скрыть все действия" : "Показать все действия",
|
||||
() =>
|
||||
{
|
||||
_showAllIntents = !_showAllIntents;
|
||||
RebuildIntents(_view);
|
||||
},
|
||||
15);
|
||||
UiKit.SetPreferredHeight(toggle, 38f);
|
||||
}
|
||||
|
||||
foreach (var intent in global)
|
||||
AddIntentButton(intent);
|
||||
|
||||
if (_showAllIntents)
|
||||
{
|
||||
foreach (var intent in board)
|
||||
AddIntentButton(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddIntentButton(IntentPayload intent)
|
||||
{
|
||||
var captured = intent;
|
||||
var button = UiKit.CreateButton(
|
||||
|
|
@ -761,9 +1126,8 @@ namespace Vanguard.UI
|
|||
"Intent_" + captured.GetType().Name,
|
||||
Label(captured),
|
||||
() => OnIntentClicked(captured),
|
||||
17);
|
||||
UiKit.SetPreferredHeight(button, 50f);
|
||||
}
|
||||
16);
|
||||
UiKit.SetPreferredHeight(button, 46f);
|
||||
}
|
||||
|
||||
private void ShowResult(MatchView view)
|
||||
|
|
@ -879,5 +1243,40 @@ namespace Vanguard.UI
|
|||
_status.text = text ?? string.Empty;
|
||||
_status.color = color;
|
||||
}
|
||||
|
||||
private readonly struct BoardTarget
|
||||
{
|
||||
public BoardTarget(
|
||||
int player,
|
||||
int slot,
|
||||
string printingId,
|
||||
int instanceId,
|
||||
bool isHand)
|
||||
{
|
||||
Player = player;
|
||||
Slot = slot;
|
||||
PrintingId = printingId;
|
||||
InstanceId = instanceId;
|
||||
IsHand = isHand;
|
||||
}
|
||||
|
||||
public int Player { get; }
|
||||
public int Slot { get; }
|
||||
public string PrintingId { get; }
|
||||
public int InstanceId { get; }
|
||||
public bool IsHand { get; }
|
||||
|
||||
public bool Matches(BoardTarget other)
|
||||
{
|
||||
if (IsHand || other.IsHand)
|
||||
{
|
||||
return IsHand && other.IsHand
|
||||
&& Player == other.Player
|
||||
&& string.Equals(PrintingId, other.PrintingId, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
return Player == other.Player && Slot == other.Slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ http://localhost:5080
|
|||
интенты, ответы на ping, закрытие и переподключение.
|
||||
- Боевой gray-box: доска, рука, часы, кнопки всех легальных интентов,
|
||||
сдача с подтверждением и экран результата.
|
||||
- Боевой UX: клик по картам/зонам, подсветка целей, сгруппированные
|
||||
действия, журнал событий и подсказки.
|
||||
- Карты в бою и коллекции: процедурные арты по клану, рамка, имя, ранг,
|
||||
триггер, сила/крит, REST и рубашка для скрытых карт.
|
||||
- Экран входа и главный экран с балансами, MMR и переходами в разделы.
|
||||
|
|
|
|||
|
|
@ -745,5 +745,7 @@ Battle; Boot и общие сервисы остаются персистент
|
|||
рубашку; `BattleView` показывает рамки, имена, ранги, триггеры,
|
||||
силу/крит, REST и скрытые карты; поля обоих игроков, рука и панель
|
||||
действий. Коллекция использует те же арты.
|
||||
- Боевой UX: клик по карте/зоне, подсветка целей, отдельная группировка
|
||||
действий, журнал последних событий и подсказки выбора.
|
||||
- Проверки визуального блока: Unity EditMode 2/2, `SCENE_SMOKE success=True`,
|
||||
live-практика с ботом на стенде.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@
|
|||
- Боевой экран: процедурные карты с арт-градиентом по клану, рамкой,
|
||||
именем, рангом, триггером, силой/критом и рубашкой; поля Vanguard +
|
||||
5 Арьергардов у обоих, рука, damage/soul/drop, REST/скрытые карты.
|
||||
- Взаимодействие в бою: клик по картам/зонам, подсветка целей,
|
||||
группировка действий (карты → цели, глобальные кнопки отдельно),
|
||||
журнал последних событий.
|
||||
- Отдельная сцена `Collection` и `CollectionView` удалены; коллекция
|
||||
открывается из Lobby кнопкой «Коллекция» (сцена `DeckBuilder`).
|
||||
- Не готово: реальные арты/модели/анимации, локализация RU/EN, настройки,
|
||||
|
|
|
|||
Loading…
Reference in New Issue