Danro-Jump/Assets/Scripts/Runtime/UI/Modals/ModalWindowBase.cs
Danil Iarosh 1f6884c5ac Build gameplay systems and service settings
Подробные изменения:
- Добавлены и доработаны модальные окна главного меню, сервисные настройки, шаблоны UI Toolkit и единый scene-flow сервис для переходов между сценами.
- Расширена gameplay-архитектура: сессия забега, HUD, спавн контента платформ, collectible-предметы, оборудование, хрупкие и движущиеся платформы.
- Добавлена база противников: типы спавна, патрулирование, процедурный спавнер, правила размещения через ScriptableObject-сложности и убийство врагов прыжком сверху.
- Исправлены смерть и respawn игрока, финальный GameOver, отскоки от врагов, пружин и boost-предметов, а также проход сквозь врагов при усиленном подъеме.
- Проведен рефакторинг взаимодействия систем: общий landing context, единые правила player-contact, NonLazy gameplay session, fail-fast installer и централизованный выбор точек спавна.
- Обновлены сцены, prefab-ассеты, difficulty stages, ресурсы главного меню, настройки проекта, roadmap и аппаратная COM-интеграция.
- Добавлены ассеты летучей мыши, врагов, collectibles, TextMesh Pro, ProjectContext и gameplay UI.

Проверки:
- dotnet build .\\Assembly-CSharp.csproj --no-restore
- Unity validation ключевых gameplay/UI/scene-flow скриптов
- Unity Console после force refresh без ошибок
2026-05-27 16:34:51 +03:00

187 lines
6.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using DanroJump.UI.Focus;
using UnityEngine;
using UnityEngine.UIElements;
namespace DanroJump.UI.Modals
{
/// <summary>
/// Базовый класс UI Toolkit-модалки с управлением видимостью и локальным кольцом фокуса.
/// </summary>
public abstract class ModalWindowBase
{
private readonly UiFocusRing focusRing = new();
/// <summary>
/// Создает модальное окно поверх корневого VisualElement.
/// </summary>
protected ModalWindowBase(VisualElement root)
{
Root = root;
}
/// <summary>
/// Корневой VisualElement модального окна.
/// </summary>
public VisualElement Root { get; }
/// <summary>
/// Последний элемент внутри модалки, на котором был фокус.
/// </summary>
public VisualElement LastFocusedElement { get; private set; }
/// <summary>
/// Разрешает закрытие окна по Escape/Back.
/// </summary>
public virtual bool CanCloseByBack => true;
/// <summary>
/// Показывает, открыто ли модальное окно сейчас.
/// </summary>
public bool IsOpen { get; private set; }
public event System.Action<ModalWindowBase> Opened;
public event System.Action<ModalWindowBase> Closed;
public event System.Action<ModalWindowBase> CloseRequested;
/// <summary>
/// Показывает модальное окно и пересобирает доступные элементы фокуса.
/// </summary>
public virtual void Open()
{
if (Root == null)
{
return;
}
Root.style.display = DisplayStyle.Flex;
Root.SetEnabled(true);
IsOpen = true;
RebuildFocusRing();
Opened?.Invoke(this);
}
/// <summary>
/// Скрывает модальное окно и сбрасывает запомненный фокус.
/// </summary>
public virtual void Close()
{
if (Root == null)
{
return;
}
Root.style.display = DisplayStyle.None;
Root.SetEnabled(false);
IsOpen = false;
LastFocusedElement = null;
Closed?.Invoke(this);
}
/// <summary>
/// Просит внешний стек закрыть это окно с учетом порядка модалок.
/// </summary>
public void RequestClose()
{
CloseRequested?.Invoke(this);
}
/// <summary>
/// Обновляет список элементов, между которыми можно перемещать фокус.
/// </summary>
public void RebuildFocusRing()
{
focusRing.Rebuild(Root);
}
/// <summary>
/// Проверяет, принадлежит ли элемент визуальному дереву модалки.
/// </summary>
public bool Contains(VisualElement element)
{
return ContainsElement(Root, element);
}
/// <summary>
/// Проверяет, известен ли элемент текущему кольцу фокуса.
/// </summary>
public bool IsKnownFocusable(VisualElement element)
{
return focusRing.Contains(element);
}
/// <summary>
/// Запоминает последний сфокусированный элемент внутри модалки.
/// </summary>
public void RememberFocusedElement(VisualElement element)
{
if (Contains(element))
{
LastFocusedElement = element;
}
}
/// <summary>
/// Возвращает фокус на последний активный элемент или на элемент по умолчанию.
/// </summary>
public void FocusDefault()
{
RebuildFocusRing();
(LastFocusedElement != null && focusRing.Contains(LastFocusedElement)
? LastFocusedElement
: GetDefaultFocusElement() ?? focusRing.GetFirst())?.Focus();
}
/// <summary>
/// Сдвигает фокус внутри окна на один элемент вперед или назад.
/// </summary>
public bool FocusRelativeTo(VisualElement current, int direction)
{
RebuildFocusRing();
return focusRing.FocusRelativeTo(current, direction);
}
/// <summary>
/// Позволяет конкретной модалке перехватить UI Toolkit navigation-событие.
/// </summary>
/// <param name="current">Элемент, с которого пришла навигация.</param>
/// <param name="direction">Направление навигации.</param>
/// <returns>True, если модалка обработала событие сама.</returns>
public virtual bool HandleNavigationMove(VisualElement current, NavigationMoveEvent.Direction direction)
{
return false;
}
/// <summary>
/// Позволяет конкретной модалке перехватить клавиатурную навигацию.
/// </summary>
/// <param name="current">Элемент, с которого пришла клавиша.</param>
/// <param name="keyCode">Код нажатой клавиши.</param>
/// <returns>True, если модалка обработала клавишу сама.</returns>
public virtual bool HandleNavigationKey(VisualElement current, KeyCode keyCode)
{
return false;
}
/// <summary>
/// Позволяет конкретной модалке выбрать стартовый элемент фокуса.
/// </summary>
protected virtual VisualElement GetDefaultFocusElement()
{
return null;
}
private static bool ContainsElement(VisualElement root, VisualElement element)
{
for (var current = element; current != null; current = current.parent)
{
if (current == root)
{
return true;
}
}
return false;
}
}
}