From a3d2157925f9253494ad98253c61800d291ff430 Mon Sep 17 00:00:00 2001 From: MpakobecEx Date: Fri, 23 Oct 2026 16:20:31 +0500 Subject: [PATCH] Add guard choices and target 1920x1080 layout --- .../Vanguard/Runtime/App/VanguardApp.cs | 2 + .../Assets/Vanguard/Runtime/UI/BattleView.cs | 58 ++++++++++++++++++- .../Vanguard/Runtime/UI/Localization.cs | 4 ++ .../Vanguard/Tests/Editor/ClientSmokeTests.cs | 20 +++++++ client/ProjectSettings/ProjectSettings.asset | 2 +- docs/HANDOFF.md | 10 +++- docs/SESSION-STATE.md | 7 ++- 7 files changed, 96 insertions(+), 7 deletions(-) diff --git a/client/Assets/Vanguard/Runtime/App/VanguardApp.cs b/client/Assets/Vanguard/Runtime/App/VanguardApp.cs index 94003e4..6b1dd62 100644 --- a/client/Assets/Vanguard/Runtime/App/VanguardApp.cs +++ b/client/Assets/Vanguard/Runtime/App/VanguardApp.cs @@ -30,6 +30,8 @@ namespace Vanguard.App Instance = this; DontDestroyOnLoad(gameObject); Application.targetFrameRate = 60; + if (!Application.isEditor) + Screen.SetResolution(1920, 1080, FullScreenMode.FullScreenWindow); Loc.Initialize(); var config = new AppConfig(); diff --git a/client/Assets/Vanguard/Runtime/UI/BattleView.cs b/client/Assets/Vanguard/Runtime/UI/BattleView.cs index f1984aa..d9b0cf5 100644 --- a/client/Assets/Vanguard/Runtime/UI/BattleView.cs +++ b/client/Assets/Vanguard/Runtime/UI/BattleView.cs @@ -1247,6 +1247,54 @@ namespace Vanguard.UI UiKit.SetPreferredHeight(button, 46f); } + private void AddGuardButton(IReadOnlyList guards) + { + if (guards == null || guards.Count == 0) + return; + + var button = UiKit.CreateButton( + _intents, + "GuardAction", + "Защититься", + () => OpenGuardSelector(guards), + 16); + button.interactable = !_inputLock.IsLocked; + UiKit.SetPreferredHeight(button, 46f); + } + + private void OpenGuardSelector(IReadOnlyList guards) + { + var groups = guards + .GroupBy(guard => guard.PrintingId) + .ToList(); + if (groups.Count == 0) + return; + + if (groups.Count == 1) + { + var variants = groups[0].ToList(); + if (variants.Count > 1) + ShowGuardDiscardChoice(variants); + else + OnIntentClicked(variants[0]); + return; + } + + var items = groups + .Select(group => new CardActionMenuItem( + "Защититься: " + CardName(group.Key), + () => + { + var variants = group.ToList(); + if (variants.Count > 1) + ShowGuardDiscardChoice(variants); + else + OnIntentClicked(variants[0]); + })) + .ToList(); + ShowChoiceMenu("Защита", items); + } + private void OpenAbilitySelector(IReadOnlyList abilities) { var groups = abilities @@ -2155,8 +2203,11 @@ namespace Vanguard.UI var legal = view.LegalIntents ?? new List(); var playable = legal.Where(intent => intent is not ConcedeIntent).ToList(); var abilities = playable.OfType().ToList(); + var guards = playable.OfType().ToList(); var board = playable - .Where(intent => IsBoardIntent(intent) && intent is not ActivateAbilityIntent) + .Where(intent => IsBoardIntent(intent) + && intent is not ActivateAbilityIntent + && intent is not GuardIntent) .ToList(); var global = playable .Where(intent => !IsBoardIntent(intent) @@ -2271,6 +2322,9 @@ namespace Vanguard.UI if (abilities.Count > 0) AddAbilityButton(abilities); + if (guards.Count > 0) + AddGuardButton(guards); + foreach (var intent in global) AddIntentButton(intent); @@ -2374,7 +2428,7 @@ namespace Vanguard.UI + (attack.BoosterSlot >= 0 ? " + boost" : string.Empty); case GuardIntent guard: return "Guard: " + CardName(guard.PrintingId); case InterceptIntent intercept: return "Intercept: " + SlotName(intercept.Slot); - case PassGuardIntent _: return "Пропустить Guard"; + case PassGuardIntent _: return "Не защищаться"; case ResolveTriggerIntent trigger: return "Триггер: сила " + SlotName(trigger.PowerTarget) + ", эффект " + SlotName(trigger.EffectTarget); diff --git a/client/Assets/Vanguard/Runtime/UI/Localization.cs b/client/Assets/Vanguard/Runtime/UI/Localization.cs index a40a459..98d9acd 100644 --- a/client/Assets/Vanguard/Runtime/UI/Localization.cs +++ b/client/Assets/Vanguard/Runtime/UI/Localization.cs @@ -70,6 +70,10 @@ namespace Vanguard.UI ["Без сброса"] = "No discard", ["Guard без сброса"] = "Guard without discard", ["Perfect Guard: сбросить "] = "Perfect Guard: discard ", + ["Защититься"] = "Guard", + ["Защититься: "] = "Guard: ", + ["Защита"] = "Guard", + ["Не защищаться"] = "Do not guard", ["Действия"] = "Actions", ["Переместить"] = "Move", ["Подробнее"] = "Details", diff --git a/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs b/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs index 7a332c0..1f75bb1 100644 --- a/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs +++ b/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs @@ -123,6 +123,26 @@ namespace Vanguard.Tests Object.DestroyImmediate(go); } + [Test] + public void Canvas_Reference_Resolution_Is_1920x1080() + { + var canvas = UiKit.CreateCanvas("Reference Resolution Canvas"); + try + { + var scaler = canvas.GetComponent(); + Assert.IsNotNull(scaler); + Assert.AreEqual(1920f, scaler.referenceResolution.x); + Assert.AreEqual(1080f, scaler.referenceResolution.y); + } + finally + { + Object.DestroyImmediate(canvas.gameObject); + var eventSystem = Object.FindAnyObjectByType(); + if (eventSystem != null) + Object.DestroyImmediate(eventSystem.gameObject); + } + } + [Test] public void Battle_Layout_Fits_Reference_Resolution() { diff --git a/client/ProjectSettings/ProjectSettings.asset b/client/ProjectSettings/ProjectSettings.asset index 3485da0..eb7067e 100644 --- a/client/ProjectSettings/ProjectSettings.asset +++ b/client/ProjectSettings/ProjectSettings.asset @@ -85,7 +85,7 @@ PlayerSettings: androidAutoRotationBehavior: 1 androidPredictiveBackSupport: 1 androidApplicationEntry: 1 - defaultIsNativeResolution: 1 + defaultIsNativeResolution: 0 macRetinaSupport: 1 runInBackground: 0 callOnDisableOnAssetBundleUnload: 1 diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 436c042..ffd787b 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -959,7 +959,7 @@ Battle; Boot и общие сервисы остаются персистент - Добавлен `Battle_Layout_Fits_Reference_Resolution`: проверяет блоки зон, панель фаз и боевую плашку на 1920×1080 и отсутствие пересечений; при другом разрешении CanvasScaler масштабирует reference-раскладку. -- Проверки: клиент EditMode **29/29**, `SCENE_SMOKE success=True`, +- Проверки: клиент EditMode **30/30**, `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше: только 3D-модели/наполнение сцен, анимации retire/ghost и декбилдер; prefab-оверлеи и раскладка закрыты. @@ -992,8 +992,12 @@ Battle; Boot и общие сервисы остаются персистент кнопка «Активировать способность». Если целей/вариантов несколько — отдельный overlay, затем выбор карты для сброса (без duplicate-кнопок). - Guard: варианты Perfect Guard/сброса показываются отдельным overlay; - случайный первый интент больше не выбирается. + случайный первый интент больше не выбирается. В фазе защиты есть + «Защититься» (при наличии допустимых карт) и «Не защищаться». - Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X; поле по центру, без бокового смещения. +- UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight + = 0.5`; standalone принудительно `1920×1080 FullScreenWindow`, + `defaultIsNativeResolution = 0`. - Проверки: сервер **141** правил + **45** серверных, `validate` и - `translations check` OK; клиент EditMode **29/29**. + `translations check` OK; клиент EditMode **30/30**. diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index 828f814..d4c17ca 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -36,6 +36,9 @@ - Переходы — `SceneFlow`; очередь/онбординг — оверлеи **Lobby**. - 3D-окружение сейчас из примитивов (`SceneEnvironment`): камера, свет, пол, тематические кубы. UI — code-generated uGUI, пока gray-box. +- Интерфейс рассчитан на reference **1920×1080** (`CanvasScaler`, + `matchWidthOrHeight = 0.5`); standalone принудительно открывается в + 1920×1080 fullscreen, `defaultIsNativeResolution` выключен. - Login: тёмный неон + 3D-карточный стол, форма по центру на полупрозрачной панели. - Сервисы: `ApiClient`, `AuthService`, `MetaService`, `TokenStore`, @@ -85,6 +88,8 @@ нескольких вариантах открывается overlay выбора, затем — выбор карты для сброса. Perfect Guard также спрашивает карту для сброса отдельным overlay, а не выбирает случайную. +- В фазе защиты доступны «Защититься» (если есть допустимые карты) и + «Не защищаться»; выбор карты защиты и сброса идёт через overlay. - Статистика карт — отдельное UI-наложение: своя панель снизу слева, противника — сверху справа. Блоки `Рука/Колода/Дроп/Душа/Урон/Защита/Триггер` кликабельны и открывают общий горизонтальный `CardListView`; из списка @@ -181,7 +186,7 @@ Standalone Windows: -executeMethod Vanguard.Editor.SceneSmoke.RunPlaymat ``` -Клиентские проверки: **29/29** EditMode (сборка всех экранов, импорт арта, +Клиентские проверки: **30/30** EditMode (сборка всех экранов, импорт арта, перевод статических/динамических строк, live-переключение языка, рендер 3D-доски с уроном и отдыхающей картой, геометрия поля, правила перемещения, рука в поле зрения камеры, обмен колоды/дропа, триггер-зона,