Move mulligan control to phase panel, shift battle UI, mirror trigger zone
CI / test (push) Successful in 1m52s Details

This commit is contained in:
MpakobecEx 2026-10-23 16:47:47 +05:00
parent 489d4eb394
commit 1c0cb4ecb0
6 changed files with 56 additions and 30 deletions

View File

@ -93,8 +93,8 @@ namespace Vanguard.Board3D
var x = (localSeat == 0 ? 1f : -1f) * _config.pileX; var x = (localSeat == 0 ? 1f : -1f) * _config.pileX;
var offset = index - (Mathf.Max(1, count) - 1) * 0.5f; var offset = index - (Mathf.Max(1, count) - 1) * 0.5f;
var sideSign = localSeat == 0 ? 1f : -1f; var sideSign = localSeat == 0 ? 1f : -1f;
// «Над колодой» — дальше от камеры, то есть выше на экране. // «Над колодой» — к центру стола на половине владельца.
var z = (ZSign(localSeat) * _config.frontRowZ) + 1.0f; var z = ZSign(localSeat) * Mathf.Max(0.25f, _config.frontRowZ - 1.0f);
return new Vector3(x + offset * 0.42f * sideSign, _config.cardLift, z); return new Vector3(x + offset * 0.42f * sideSign, _config.cardLift, z);
} }

View File

@ -26,6 +26,7 @@ namespace Vanguard.UI
private static readonly Color Card = new Color(0.10f, 0.13f, 0.18f, 1f); private static readonly Color Card = new Color(0.10f, 0.13f, 0.18f, 1f);
private static readonly Color Muted = new Color(0.62f, 0.68f, 0.82f); private static readonly Color Muted = new Color(0.62f, 0.68f, 0.82f);
private static readonly Color Accent = new Color(0.20f, 0.42f, 0.78f, 1f); private static readonly Color Accent = new Color(0.20f, 0.42f, 0.78f, 1f);
private const float IntentPanelShift = 456f;
private readonly MetaService _meta; private readonly MetaService _meta;
private readonly Action _onExit; private readonly Action _onExit;
@ -259,6 +260,7 @@ namespace Vanguard.UI
_intentPanelToggleLabel.text = "Скрыть панель"; _intentPanelToggleLabel.text = "Скрыть панель";
UiKit.MakeLiteral(_intentPanelToggleLabel); UiKit.MakeLiteral(_intentPanelToggleLabel);
} }
ApplyIntentPanelLayout();
_inputLock.ReleaseAll(); _inputLock.ReleaseAll();
_feedbackLockHeld = false; _feedbackLockHeld = false;
_logLines.Clear(); _logLines.Clear();
@ -882,6 +884,17 @@ namespace Vanguard.UI
: "Показать панель"; : "Показать панель";
UiKit.MakeLiteral(_intentPanelToggleLabel); UiKit.MakeLiteral(_intentPanelToggleLabel);
} }
ApplyIntentPanelLayout();
}
private void ApplyIntentPanelLayout()
{
var shift = _intentPanelVisible ? 0f : IntentPanelShift;
if (_phasePanel != null)
_phasePanel.anchoredPosition = new Vector2(-468f + shift, 0f);
if (_opponentZones?.Root != null)
_opponentZones.Root.anchoredPosition = new Vector2(-488f + shift, -108f);
} }
private void OnConcedeClicked() private void OnConcedeClicked()
@ -1689,6 +1702,19 @@ namespace Vanguard.UI
foreach (var intent in intents) foreach (var intent in intents)
{ {
var captured = intent; var captured = intent;
if (captured is FinishMulliganIntent && _mulliganSelected.Count > 0)
{
var exchange = UiKit.CreateButton(
_phasePanel,
"Phase_MulliganExchange",
"Заменить " + _mulliganSelected.Count + " карт",
SendMulligan,
14);
exchange.interactable = !_inputLock.IsLocked;
UiKit.SetPreferredHeight(exchange, 40f);
continue;
}
var button = UiKit.CreateButton( var button = UiKit.CreateButton(
_phasePanel, _phasePanel,
"Phase_" + captured.GetType().Name, "Phase_" + captured.GetType().Name,
@ -2368,33 +2394,6 @@ namespace Vanguard.UI
TextAnchor.UpperLeft); TextAnchor.UpperLeft);
mulliganHint.horizontalOverflow = HorizontalWrapMode.Wrap; mulliganHint.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(mulliganHint, 34f); UiKit.SetPreferredHeight(mulliganHint, 34f);
if (_mulliganSelected.Count > 0)
{
var exchange = UiKit.CreateButton(
_intents,
"MulliganExchange",
"Заменить " + _mulliganSelected.Count + " карт",
SendMulligan,
16);
UiKit.SetPreferredHeight(exchange, 44f);
}
else
{
var finishMulligan = (view.LegalIntents ?? new List<IntentPayload>())
.OfType<FinishMulliganIntent>()
.FirstOrDefault();
if (finishMulligan != null)
{
var finishButton = UiKit.CreateButton(
_intents,
"MulliganFinish",
"Закончить муллиган",
() => OnIntentClicked(finishMulligan),
16);
UiKit.SetPreferredHeight(finishButton, 44f);
}
}
} }
if (IsAbilityDamageTargeting()) if (IsAbilityDamageTargeting())

View File

@ -176,6 +176,18 @@ namespace Vanguard.Tests
Assert.IsFalse(opponent.Overlaps(phase)); Assert.IsFalse(opponent.Overlaps(phase));
Assert.IsFalse(opponent.Overlaps(stats)); Assert.IsFalse(opponent.Overlaps(stats));
Assert.IsFalse(phase.Overlaps(stats)); Assert.IsFalse(phase.Overlaps(stats));
var toggle = Resources.FindObjectsOfTypeAll<Button>()
.First(button => button.name == "ToggleIntentPanel"
&& button.gameObject.scene.IsValid());
toggle.onClick.Invoke();
var movedOpponent = ReferenceRect(FindRect("ZoneBlocksOpponent"));
var movedPhase = ReferenceRect(FindRect("PhasePanel"));
Assert.Greater(movedOpponent.xMin, opponent.xMin);
Assert.Greater(movedPhase.xMin, phase.xMin);
AssertWithinScreen(movedOpponent, "opponent zones after hide");
AssertWithinScreen(movedPhase, "phase panel after hide");
} }
finally finally
{ {

View File

@ -103,6 +103,11 @@ namespace Vanguard.Tests
var trigger = layout.TriggerPosition(0, 0, 1); var trigger = layout.TriggerPosition(0, 0, 1);
Assert.Greater(trigger.z, deck.z); Assert.Greater(trigger.z, deck.z);
Assert.AreEqual(deck.x, trigger.x, 0.0001f); Assert.AreEqual(deck.x, trigger.x, 0.0001f);
var opponentDeck = layout.DeckPosition(1);
var opponentTrigger = layout.TriggerPosition(1, 0, 1);
Assert.Less(opponentTrigger.z, opponentDeck.z);
Assert.AreEqual(opponentDeck.x, opponentTrigger.x, 0.0001f);
} }
[Test] [Test]

View File

@ -998,7 +998,12 @@ Battle; Boot и общие сервисы остаются персистент
«Защититься» (при наличии допустимых карт) и «Не защищаться». Сброс «Защититься» (при наличии допустимых карт) и «Не защищаться». Сброс
выбирается из карт в руке, а не по первому интенту. выбирается из карт в руке, а не по первому интенту.
- Правую панель действий можно временно скрыть/показать кнопкой в верхней - Правую панель действий можно временно скрыть/показать кнопкой в верхней
строке, чтобы видеть поле. строке, чтобы видеть поле. При скрытии панель фаз и UI противника
сдвигаются вправо.
- Кнопка муллигана в панели «Фазы» динамическая: без выбора — «Закончить
муллиган», с выбором — «Заменить X карт».
- Trigger-зона противника зеркальна: над его колодой в сторону центра,
а не за дальним краем.
- Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X; - Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X;
поле по центру, без бокового смещения. поле по центру, без бокового смещения.
- UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight - UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight

View File

@ -93,6 +93,11 @@
«Не защищаться»; выбор карты защиты и сброса идёт через overlay. «Не защищаться»; выбор карты защиты и сброса идёт через overlay.
- Правую панель действий можно временно скрыть кнопкой «Скрыть панель» - Правую панель действий можно временно скрыть кнопкой «Скрыть панель»
в верхней строке; поле остаётся видимым, вернуть — «Показать панель». в верхней строке; поле остаётся видимым, вернуть — «Показать панель».
При скрытии панель фаз и блоки противника сдвигаются вправо.
- Кнопка муллигана находится в панели «Фазы» и динамическая: без выбора —
«Закончить муллиган», с выбором — «Заменить X карт».
- Trigger-зона у противника расположена над его колодой по направлению
к центру стола (зеркально своей), а не за дальним краем.
- Статистика карт — отдельное UI-наложение: своя панель снизу слева, - Статистика карт — отдельное UI-наложение: своя панель снизу слева,
противника — сверху справа. Блоки `Рука/Колода/Дроп/Душа/Урон/Защита/Триггер` противника — сверху справа. Блоки `Рука/Колода/Дроп/Душа/Урон/Защита/Триггер`
кликабельны и открывают общий горизонтальный `CardListView`; из списка кликабельны и открывают общий горизонтальный `CardListView`; из списка