Как выбрать нужное всплывающее окно
Всплывающее окно прерывает игру. Прежде чем выбирать, ответьте на один вопрос: должен ли игрок ответить, прежде чем игра продолжится? Если да — блокируйте. Если нет — никогда не блокируйте: карточка, которая останавливает игру ради новости, о которой игрок не просил, — самый быстрый способ заставить его закрыть вашу игру.
- Вопрос, на который нужен ответ — купить, удалить, выйти, перезаписать →
GoDialogs.confirm(). То, что нужно просто принять к сведению →alert(). Один из нескольких вариантов →choose(). - Отдельный экран — настройки, лист персонажа, товар в магазине →
GoSurfaceпосередине экрана. - Длинный список или страница управления — сумка, почта, друзья →
GoSheetснизу на телефоне,GoDrawerсбоку на широком экране. - Что-то о только что нажатом элементе — характеристики предмета, действия с ним →
GoPopoverилиGoContextMenuрядом с ним. - Новости, при которых игра идёт дальше — «Сохранено», «Выброшено», «Вас пригласили» →
GoSnackbar,GoNoticeилиGoPromptCard. Они никогда не забирают экран. - Обучение управлению →
GoCoachMark. Читы на время разработки →GoConsole.
Все всплывающие окна в одной таблице
| Окно | Останавливает игру | «Назад» / Escape | Касание снаружи | Для чего |
|---|---|---|---|---|
GoDialogs confirm · alert | Да | Закрывает — confirm отвечает false, alert возвращается | Закрывает только с dismiss_on_scrim | Вопросы, на которые нужен ответ |
GoDialogs.choose | Да | Закрывает — отвечает -1 | Закрывает только с dismiss_on_scrim | Выбор одного из нескольких |
GoSurface | Да | Просит закрыть самое верхнее | Просит закрыть при dismiss_on_scrim | Ваши собственные окна |
GoSheet | Да | Закрывает весь лист | Закрывает | Сумка, список магазина, почта |
GoDrawer | Да | Только «Назад» на Android | Закрывает | Боковой список на широком экране |
GoPopover | Пока не закроется | Закрывает | Закрывает — и дальше не проходит | Описание предметов и навыков |
GoCombobox — список | Пока не закроется | Закрывает | Закрывает | Список выбора с поиском |
GoContextMenu · выпадающее меню · список выбора | Меню движка | Меню движка | Закрывает | Действия, короткие списки |
GoSnackbar | Нет | — | Доходит до игры | «Сохранено» · «Отменить» · «Повторить» |
GoPromptCard | Нет | — | Доходит до игры | Приглашение, предложение обмена |
GoNotice | Никогда | — | Доходит до игры | Постоянная строка в HUD |
GoBanner | Нет — он часть страницы | — | — | «Нет сети. Повторить» |
GoCoachMark | Только своей карточкой | Завершает тур | Доходит до игры | Тур при первом запуске |
GoConsole | Только своей панелью | Закрывается клавишей Escape | Доходит до игры | Отладочные команды |
На каком слое открывается каждое окно и почему именно в таком порядке — в разделе Слои, «Назад» и ввод.

GoDialogs
GoSurface
GoSheet
GoDrawer
GoPopover
GoContextMenu
GoSnackbar
GoPromptCard
GoCoachMark
GoConsoleКаждый снимок на этой странице — одна и та же маленькая игра: здоровье и мана слева вверху, монеты справа вверху, стик и три
быстрых слота внизу — в пресете default_dark, с одним открытым поверх неё всплывающим окном. То же
окно во всех четырнадцати обликах →
Из чего состоит всплывающее окно
Почти каждое блокирующее всплывающее окно в gohud — диалоги, листы, карточки с привязкой, список комбобокса и ваши
собственные окна — это один узел, GoSurface. Разберитесь в его частях один раз — и вы знаете их все.

| Часть | Что это | Как до неё добраться |
|---|---|---|
| Слой | CanvasLayer, на котором живёт окно. Его номер решает, что окно перекрывает. | Ваш — для GoSurface; у остальных встроен (таблица) |
| Подложка | Вуаль поверх всего, что позади. Она ловит касания мимо карточки. | scrim_transparent, dismiss_on_scrim, цвет scrim из темы |
| Карточка | Само окно — лицевая панель из темы: скруглённая, срезанная, кованая или желейная, в зависимости от облика. | card, alpha |
| Шапка | Необязательная кнопка «Назад», заголовок (не больше двух строк, дальше «…»), кнопка закрытия. | set_title() · set_title_key() · set_back() · close_enabled · show_header |
| Панель инструментов | Закреплённый ряд под шапкой — поле поиска, чипы фильтров. Скрыта, пока вы ею не воспользуетесь. | toolbar |
| Тело | Прокручиваемая часть. Содержимое кладите сюда. | body · scroll_body · clear() |
| Строка состояния | Закреплённая строка между телом и подвалом — «Имя не прошло проверку», «Сохранение…». | set_status_text() · set_status_key() · clear_status() |
| Подвал | Закреплённые кнопки внизу. Скрыт, пока вы им не воспользуетесь. | footer |
toolbar, строку состояния и footer — они остаются на месте, пока тело прокручивается.
Диалоги, которые можно await
GoDialogs спрашивает и ждёт. Один узел, три вопроса — и ответ возвращается на той же строке, так что
код читается в том же порядке, в каком всё происходит с игроком.
var dialogs := GoDialogs.new()
add_child(dialogs)
if await dialogs.confirm("Buy the starter pack?", "300 gems for a sword, a shield and 10 potions.",
"Buy", "Not now"):
buy_starter_pack()

confirm()
confirm(…, true) — необратимое действие
alert()
choose()
ActionLayout.HORIZONTALТри вопроса
confirm(title, body, ok_text := "", cancel_text := "", extra := "",
args := {}, destructive := false) -> bool
alert(title, body, ok_text := "", extra := "", args := {}) # сделайте await, чтобы дождаться
choose(title, options: Array, cancel_text := "", translate := false) -> int
confirm_key(…) · alert_key(…) # ключи перевода
| Вызов | Возвращает | Для чего |
|---|---|---|
confirm() | true или false | Да или нет — купить, удалить, выйти, перезаписать. |
alert() | ничего — сделайте await, чтобы дождаться | То, что игрок обязан увидеть: награда, потеря соединения. |
choose() | индекс варианта или -1 | Один из нескольких. Вариант — это String или {text, icon, danger, subtitle}. |
confirm_key() · alert_key() | как выше | То же самое с ключами перевода — слова следуют за сменой языка, даже пока диалог открыт. |
Пустые подписи кнопок заменяются собственными словами набора (confirm, cancel в GoConfig.text_keys).
extra добавляется к телу с новой строки. args заполняет {placeholders} в заголовке
и в теле: confirm("Buy {item}?", "It costs {price} gold.", "Buy", "", "", {"item": name, "price": 300}).
У каждого выхода есть ответ
| Игрок… | confirm | alert | choose |
|---|---|---|---|
| нажимает OK / вариант | true | возвращается | индекс варианта |
| нажимает «Отмена» | false | — | -1 |
| нажимает ×, Escape или «Назад» на Android | false | возвращается | -1 |
касается подложки (только с dismiss_on_scrim) | false | возвращается | -1 |
узел GoDialogs покидает дерево | false | возвращается | -1 |
Поэтому код после await выполняется всегда — ни один ответ не повисает, даже когда сцена меняется прямо под диалогом.
Необратимые действия
Передайте destructive = true для того, что нельзя отменить. Кнопка OK становится залитой кнопкой
Tone.DANGER_SOLID — белое на красном в любом облике, тогда как подкрашенной подписи опасного действия в светлой теме
пришлось бы темнеть почти до чёрного, чтобы оставаться читаемой. Тон сбрасывается при каждом вызове, потому что окно переиспользуется.
if await dialogs.confirm("Delete this save?", "Slot 2 — Aria, level 12. This cannot be undone.",
"Delete", "Keep it", "", {}, true):
delete_save(2)
Раскладка кнопок
action_layout | Что делает |
|---|---|
VERTICAL (по умолчанию) | Кнопки друг под другом, во всю ширину. Безопасно для любого языка — длинная немецкая или русская подпись никогда не сжимается. |
HORIZONTAL | Один ряд, каждой кнопке по половине ширины. |
AUTO | Один ряд, только когда обе подписи умещаются в половину карточки одной строкой; иначе столбиком. Решается заново при смене языка или размера. |
action_gap — расстояние между кнопками, body_gap — расстояние над ними; сделайте его больше, чем
зазор между кнопками, чтобы вопрос и выбор читались как две части. Только для одного диалога вызовите
set_next_action_layout() перед его открытием. Единственная кнопка оповещения всегда занимает всю ширину.
По одному
Второй confirm(), пока открыт первый, сразу возвращает false — диалог никогда не ложится на диалог
случайно. Включите queue_when_busy, и он вместо этого дождётся своей очереди; alert() ждёт всегда.
queue_limit (8) ограничивает очередь, а при переполнении самый старый запрос получает ответ false.
pending() считает ожидающие запросы, а clear_pending() отвечает им всем false.
choose() никогда не встаёт в очередь: если диалог уже открыт, он возвращает -1.
Телефон и компьютер
choose() открывается на Android и iOS как лист снизу — там, где большой палец, — а на компьютере как карточка
посередине. Подтверждение ограничено шириной max_width (420dp) и стоит по центру. Задавайте layer_index,
max_width и остальные экспортируемые свойства до add_child: они применяются, когда узел входит в дерево.
var picked := await dialogs.choose("Sort the bag by", [
{"text": "Newest first", "icon": GoIconSet.CLOCK},
{"text": "Rarity", "icon": GoIconSet.STAR},
{"text": "Drop everything", "icon": GoIconSet.TRASH, "danger": true}])
if picked >= 0:
sort_bag(picked)
GoSurface — ваше собственное окно
Когда всплывающее окно — больше чем вопрос (настройки, лист персонажа, товар для покупки), стройте его на
GoSurface. Безопасная область, клавиатура, закреплённые ряды, прокрутка, «Назад», фокус и изменение размера уже сделаны.
var layer := CanvasLayer.new()
layer.layer = 50
add_child(layer)
var window := GoSurface.new()
window.set_title("Settings")
window.close_requested.connect(layer.queue_free)
window.body.add_child(GoStyle.toggle("Music", false))
window.body.add_child(GoStyle.slider(0.0, 1.0, 0.05))
var row := GoStyle.row()
row.add_child(GoStyle.button("Cancel", window.request_close))
row.add_child(GoStyle.button("Save", save_and_close, GoStyle.Tone.PRIMARY))
window.footer.add_child(row)
window.footer.visible = true
layer.add_child(window)
close_requested. Скрыть или
освободить окно — работа владеющего экрана: только он знает, что значит «закрыть» — сохранить, отбросить или сначала спросить.
Три размещения

CENTER — подтверждения, настройки
BOTTOM — списки, страницы управления
ANCHOR — рядом с элементом управления
GoDatePicker в окне CENTERANCHOR требует anchor_control. Карточка встаёт справа от него, если помещается anchor_min_width,
иначе — на более широкую сторону, иначе — внутрь экрана, и открывается вверх, когда сверху больше места. Она следует за движущимся
якорем каждый кадр. Размер задают anchor_width (320) и anchor_max_height (520).
Насколько оно вырастает
| Мера | Правило | Чем менять |
|---|---|---|
| Место | Безопасная область минус клавиатура минус поле экрана (16dp, но не больше 10% короткой стороны). | — |
| Ширина | 94% места в книжной ориентации, 72% в альбомной, не больше 480dp. | max_width · GoConfig.surface_width_ratio_* |
| Высота | По высоте содержимого (fit_content), до 68% места, с потолком 72%. Карточка по центру без изменения размера может вырасти до 94%, чтобы обойтись без прокрутки. | height_ratio · max_height_ratio · max_height |
| Плотность | Плотная раскладка, когда задан compact, экран ниже 420dp (телефон, повёрнутый набок) или содержимое не помещается: меньше отступов, меньше зазоры, меньше заголовок. Окно измеряет оба варианта и оставляет более просторный из тех, что помещаются, — без дрожания. | compact |
| Изменение размера | С resizable заголовок становится ручкой: тяните его между 30% и потолком. height_changed(ratio) сообщает результат. | resizable |
Доля выше потолка урезается, с одним предупреждением в отладочной сборке. Клавиатура поднимает карточку и прокручивает поле с фокусом в зону видимости; безопасная область держит её подальше от выреза и полосы жестов на Android и iOS.
Свойства
| Свойство | По умолчанию | Что делает |
|---|---|---|
placement | CENTER | CENTER, BOTTOM или ANCHOR |
dismiss_on_scrim | GoConfig.dismiss_on_scrim (false) | Касание подложки просит закрыть окно. Перетаскивание, начатое внутри карточки, — нет. |
scrim_transparent | false | Оставить игру видимой — подложка всё равно ловит касания снаружи. |
close_enabled | true | Показать × и позволить Escape и «Назад» просить о закрытии. |
show_header · scroll_body · resizable | true · true · false | Ряд шапки, прокручиваемое тело, ручка перетаскивания. |
fit_content · compact | true · false | Подгонять размер под содержимое; начинать с плотной раскладки. |
fade_in | GoConfig.surface_fade_in (false) | Плавно проявлять карточку при открытии. При reduce_motion — мгновенно. |
alpha | -1 (из темы) | Непрозрачность лицевой панели карточки, долей. Прозрачнеет только панель — никогда не текст. |
initial_focus | нет | Элемент, который получает фокус при открытии. |
add_child: scrim_transparent, show_header, resizable и
scroll_body применяются, когда окно входит в дерево. dismiss_on_scrim, close_enabled,
fade_in и initial_focus читаются на лету.
Сигналы и вызовы
| Имя | Что это |
|---|---|
close_requested | ×, Escape, «Назад» на Android или касание подложки — а также request_close(). Испускается только самым верхним окном. |
back_requested · set_back(action) | Кнопка «Назад» в шапке — для окна со страницами. Пустой Callable её прячет. |
height_changed(ratio) | После перетаскивания ручки. |
is_top() · GoSurface.is_any_open() | Это ли окно получает «Назад» — и открыто ли вообще хоть одно окно. |
relayout() | Разложить заново прямо сейчас. Изменение свойства размера уже делает это за вас. |
Листы и боковые панели
Список, которому нужно место, поднимается снизу на телефоне в книжной ориентации — там, где большой палец, — и выезжает сбоку на планшете или компьютере, где лист накрыл бы игру.

GoSheet
GoDrawer · LEFT
GoDrawer · RIGHTGoSheet — страницы снизу
var sheet := GoSheet.new()
add_child(sheet)
func show_bag(items: Array) -> void:
sheet.open("Bag")
var search := GoStyle.line_edit("Search the bag…")
search.text_changed.connect(filter_bag)
sheet.toolbar().add_child(search)
sheet.toolbar().visible = true
for item in items:
sheet.body.add_child(GoStyle.list_button(GoIconSet.BOX, item.name, show_item.bind(item),
Color.TRANSPARENT, item.description, false, GoIconSet.CHEVRON_RIGHT))
sheet.add_footer(GoStyle.button("Close", sheet.close, GoStyle.Tone.PRIMARY))
- Это
CanvasLayer(слой 10), в котором лежитBOTTOM-GoSurfaceс изменяемым размером, — поэтому он лежит поверх HUD, а игрок может вытянуть его выше. open(title)начинает новую страницу: очищает тело, панель инструментов и кнопку «Назад» и освобождает то, что добавилadd_footer(). Вызовите его снова, чтобы показать другую страницу в том же листе;set_back()показывает кнопку «Назад» для пути обратно.open_key()принимает ключ перевода.close()скрывает лист — он не освобождается, поэтому следующийopen()мгновенный. Когда это происходит, сообщаютclosedиpage_changed.height_ratio(0.6) — высота при открытии;max_height_ratioподнимает потолок;dismissable(true) закрывает лист касанием подложки.
set_back(), — на страницу назад ведёт только экранная
кнопка «Назад». Если «Назад» должна возвращать на шаг, обработайте close_requested у sheet.surface сами.
GoDrawer — сбоку
var drawer := GoDrawer.new()
drawer.side = GoDrawer.Side.LEFT
drawer.follow_text_direction = true # сторона начала: слева, а в арабском — справа
add_child(drawer)
drawer.body.add_child(GoNavBar.drawer_list([{"icon": GoIconSet.HOME, "text": "Town"},
{"icon": GoIconSet.MAP, "text": "World map"}], 0, go_to))
drawer.open("Menu")
- Слой 80. Ширина — 42% экрана (
width_ratio), междуmin_width(320dp) иmax_width(420dp): на телефоне панель накрывает бо́льшую часть экрана, поэтому там лучше выбрать лист. - Панель доходит до края экрана; внутрь безопасной области отступает только её содержимое, поэтому скруглённый угол никогда не срезает первую строку.
LEFTозначает левый край экрана даже в арабском. Включитеfollow_text_direction, когда смысл именно в «стороне начала»;effective_side()скажет, куда панель в итоге встала.open(title)сохраняет тело — заполните его один раз.set_title("")прячет шапку.motion_seconds(0.2) задаёт выезд;reduce_motionделает его мгновенным.
drawer.close() из собственной привязки клавиш — и учтите, что GoSurface.is_any_open() боковую
панель не считает; спрашивайте drawer.is_open().
Карточки и меню рядом с элементом управления
То, что игрок только что нажал, заслуживает ответа прямо у пальца, а не посередине экрана.

GoPopover
GoContextMenu
GoStyle.dropdown()
GoStyle.select()
GoComboboxGoPopover — карточка у нажатого элемента
slot.pressed.connect(func() -> void:
var card := GoStyle.item_card({"icon": GoIconSet.POTION, "title": "Health potion",
"subtitle": "Heals 50 HP · 12 left"}, false)
GoPopover.open(slot, card, {"width": 280}))
Поверхность ANCHOR, где слой, якорь и уборка уже связаны, — те десять строк, которые каждая игра пишет снова и снова.
По одной: открытие второй карточки закрывает первую, потому что сложенные стопкой карточки хоронят экран и уже не понять, какая
из них какому слоту принадлежит. Подложка прозрачная — карточка, открытая, чтобы сравнить снаряжение, не должна это снаряжение
затемнять, — но она всё равно ловит первое касание снаружи, которое закрывает карточку и дальше не проходит.
| Параметр | По умолчанию | Что делает |
|---|---|---|
title | "" | Шапка с ×. Пусто — шапки нет. |
width · max_height | 320 · 520 | Размер карточки. |
dismissable | true | Касание снаружи закрывает её. |
compact · alpha · translate | false · -1 · false | Плотные отступы, непрозрачность лицевой панели, заголовок как ключ перевода. |
layer | 95 | CanvasLayer, на котором она открывается. |
GoPopover.close() и GoPopover.is_open() — статические. open() возвращает поверхность — или
null, когда якоря нет в дереве, так что проверьте результат, прежде чем делать await shown.close_requested.
dismissable = false и без заголовка на экране нет ничего, что её закроет, — только Escape, «Назад» или ваш собственный вызов.
GoContextMenu — долгое нажатие и правый клик
var menu := [
{"text": "Use", "icon": GoIconSet.POTION, "action": use},
{"separator": true},
{"text": "Drop", "icon": GoIconSet.TRASH, "action": drop, "danger": true}]
GoContextMenu.attach(slot, menu)
var more := GoStyle.icon_button(GoIconSet.MORE, Callable(), -1, &"more")
more.pressed.connect(func() -> void: GoContextMenu.open_at(more, menu))
- Правый клик открывает меню сразу. Палец, удержанный полсекунды (
HOLD_SECONDS), тоже открывает его; сдвиг больше чем на 12dp отменяет — поэтому прокрутка списка никогда не вызывает меню. - Пункт — это
{text, action, icon, disabled, checked, separator, danger, translate}. ПередайтеCallableвместо массива, когда пункты зависят от того, на что нажали; он вызывается при каждом открытии меню. dangerставит «⚠ » перед текстом — меню движка умеет окрашивать только все свои пункты сразу.- Это
PopupMenuдвижка, оформленноеGoStyle.style_popup(). Открытой поверхностью оно не считается.
⋯
(open_at(), как выше) или один раз научите ему обучающей подсказкой.
Выпадающие меню, списки выбора и разделённая кнопка
var sort := GoStyle.dropdown("Sort", ["Highest score", "Most wins", {"text": "Friends only", "disabled": true}],
func(index: int) -> void: sort_board(index))
var quality := GoStyle.select(["Low", "Medium", "High", "Ultra"], "Quality")
quality.item_selected.connect(func(index: int) -> void: settings.quality = index)
var send := GoSplitButton.make("Send", send_now, ["Send later", "Save as draft"])
Все три открывают всплывающее меню движка: dropdown() — это MenuButton, которая запускает действие,
select() — OptionButton, которая хранит значение, GoSplitButton — основное действие плюс меню на
стрелке (chosen(index)). Меню намеренно остаются полностью непрозрачными: меню, показанное в собственном окне ОС,
превращает полупрозрачные пиксели в чёрные.
GoCombobox — список с поиском
var friend := GoCombobox.make(friend_names, -1, "Find a friend")
friend.picked.connect(func(index: int) -> void: invite(friend_names[index]))
По нажатию он открывает привязанный список на слое 96 с прозрачной подложкой. Начиная с search_threshold (8) пунктов
появляется поле поиска и забирает фокус; оно ищет совпадение в любом месте текста или подсказки без учёта регистра, а пустой результат
так и сообщает. Ширину списка задаёт list_width (0 = ширина кнопки, но не меньше 180dp).
Всплывающие окна, которые никогда не останавливают игру
Новости, которые игрок может прочитать, не прерывая игру. Ни у одного из этих окон нет подложки; касание где угодно, кроме самой карточки, уходит в игру.

GoSnackbar
GoNotice
GoPromptCard
GoStyle.tooltip_node()GoSnackbar — сообщение с «Отменить»
var snack := GoSnackbar.new()
add_child(snack)
if await snack.post({"text": "Dropped {name}", "args": {"name": item.name}, "icon": GoIconSet.TRASH,
"actions": ["Undo"], "seconds": 5.0}) == 0:
restore(item)
Ключ post() | Что делает |
|---|---|
text · title · args · translate | Сообщение, необязательная строка жирным, {placeholders}, ключи перевода. |
tone · icon | Акцентный цвет (GoTheme.SUCCESS, WARNING…) и иконка. |
actions · closable | Кнопки — строки или {text, action} — и ×. |
seconds | Сколько он держится. Отрицательное значение — notice_duration_ms из темы. 0 — пока его не закроют; тогда дайте ему кнопку. |
post() возвращает индекс нажатой кнопки или -1, если время вышло или снекбар закрыли. Сообщения встают
в очередь (queue_limit 4, самое старое выбрасывается с -1), а тот же текст, отправленный снова, пока он на экране, лишь
перезапускает таймер. Снекбар стоит внизу (или вверху — placement), над безопасной областью и клавиатурой, на слое 90 —
его видно поверх боковой панели. С tap_to_dismiss (включено) касание карточки без кнопок закрывает её; касание мимо
карточки всегда доходит до игры. show_text() и show_key() — однострочные формы.
GoNotice — строка в HUD
var notice := GoNotice.new()
hud_corner.add_child(notice)
notice.show_text("Game saved", GoTheme.SUCCESS)
notice.custom_minimum_size.x = notice.preferred_width(340)
Простая панель, которую вы размещаете сами — в GoHudAnchor, в углу, где угодно. Она не берёт ни ввод, ни фокус,
поэтому нажатие сквозь неё доходит до кнопки под ней, а новое сообщение заменяет старое. Задайте ей ширину:
preferred_width(limit) возвращает естественную ширину короткого текста и переносит длинный в пределах limit.
set_content() показывает вместо текста любой элемент управления.
GoPromptCard — вопрос, который может подождать
var spot := GoHudAnchor.new()
spot.spot = GoHudAnchor.Spot.TOP_CENTER
spot.reserve_space = false
spot.avoid_peers = true # встать под полосами здоровья, а не накрывать их
hud.add_child(spot)
var invite := GoPromptCard.new()
spot.add_child(invite)
invite.set_icon(GoIconSet.USERS, Color.TRANSPARENT, true)
invite.set_title("Party invite from Ann")
invite.set_subtitle("Dragon's Lair · 3 / 4 members")
invite.set_actions([{"text": "Accept", "action": accept, "primary": true},
{"text": "Decline", "action": invite.hide}])
invite.fit_width(340)
invite.show()
get_tree().create_timer(15.0).timeout.connect(invite.hide)
- Карточка стартует скрытой — вызовите
show(). Собственного таймера у неё нет; добавьте его, если у вопроса есть срок. - Её кнопки её не закрывают — решают действия. × (
set_closable(true)) испускаетclosed. fit_width(width)задаёт ширину и предсказывает высоту по шрифту, поэтому ничто, что раскладывается относительно неё, не прыгает кадром позже.- Её лицевая панель следует
hud_alpha— это часть HUD, а не окно.
GoBanner и всплывающие подсказки
GoBanner.make("You're offline.", [{"text": "Retry", "action": reconnect}], GoIconSet.WARNING) стоит в самой странице,
над содержимым, и никогда не исчезает по таймеру; кнопка закрывает его, если у неё нет "keep": true.
GoStyle.tooltip_node(text) — то, что нужно вернуть из _make_custom_tooltip(): плашку рисует движок
(TooltipPanel), а текст — gohud, с переносом после 260dp. Подсказки — для мыши; никогда не прячьте за ними то, что нужно игроку.
Туры и консоль

GoCoachMark
GoConsoleGoCoachMark — тур по настоящим элементам управления
var tour := GoCoachMark.new()
tour.keep_clear = [top_bar]
tour_layer.add_child(tour)
tour.finished.connect(func(completed: bool) -> void: save.tutorial_done = true)
tour.start([{"target": bag_button, "title": "Bag", "body": "Everything you pick up lands here."},
{"target": map_button, "title": "Map", "body": "Tap to open the full map."}])
- Кольцо вокруг настоящего элемента управления и карточка рядом. Нажатие на цель двигает тур дальше (её сигнал
pressedили"signal"в шаге); «Далее» и «Пропустить» — на карточке. Скрытая цель пропускается. - Карточка держится подальше от середины экрана (
avoid_center_band), от якорей HUD и отkeep_clear. - Пока открыт любой
GoSurface, тур прячется и ждёт. «Назад» или Escape завершают его сfinished(false). - Собственного слоя у него нет — добавляйте его последним или на слой выше HUD.
GoConsole — читы на время разработки
var console := GoConsole.new()
add_child(console)
console.register("give", "give <item> <count>", func(args: PackedStringArray) -> String:
return "gave %s x%s" % [args[0], args[1]] if args.size() >= 2 else "give <item> <count>")
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed(&"dev_console"): console.toggle()
Слой 200 — поверх всего, так что отлаживать можно и поверх диалога, — и консоль продолжает работать, пока дерево на паузе. Палитра подсказывает команды по мере набора; стрелки вверх и вниз листают историю; Escape её закрывает. Встроенной клавиши нет: назначьте свою.
debug_only (включено) блокирует её в релизной сборке — и open(), и run(). Читерские
команды в руках игрока кончают экономику игры в первый же день.
Слои, «Назад» и ввод
Три правила решают, чего может касаться игрок, пока что-то открыто: что лежит сверху, кому принадлежит «Назад» и кто получает касание.
Что лежит сверху
| Слой | Что там лежит | Чем задаётся |
|---|---|---|
| 5 | Ваш HUD (на ваш выбор) | — |
| 10 | GoSheet | CanvasLayer.layer |
| ~50 | Ваши собственные окна GoSurface, меню паузы | ваш CanvasLayer |
| 80 | GoDrawer | CanvasLayer.layer |
| 90 | GoSnackbar | layer_index |
| 95 | GoPopover | параметр "layer" |
| 96 | список GoCombobox | — |
| 100 | GoDialogs | layer_index |
| 200 | GoConsole | layer_index |
Порядок выбран намеренно: снекбар сообщает о только что случившемся, поэтому читается поверх боковой панели; карточка с привязкой относится к только что нажатому, поэтому лежит над ним; диалог задаёт вопрос и накрывает и то и другое; консоль накрывает всё, потому что её открывают, когда всё остальное пошло не так.
Кому принадлежит «Назад»
- Отвечает только самое верхнее окно. Среди открытых поверхностей окно на самом высоком слое (а при равенстве — последнее
в дереве) получает Escape, отмену на геймпаде (
ui_cancel), «Назад» на Android и касания подложки. Окна в стопке закрываются по одному, никогда не вместе. - «Назад» на Android обрабатывается кадром позже, поэтому одно нажатие никогда не закроет два окна.
- Пока открыто хоть что-то, что владеет «Назад», кнопка «Назад» на Android не закрывает приложение (
GoBackPolicyсчитает владельцев и восстанавливаетquit_on_go_backпосле последнего). Ваше собственное всплывающее окно, которое перехватывает «Назад», должно вызыватьGoBackPolicy.acquire()иrelease()— в том числе в_exit_tree. - Боковая панель, обучающий тур и форма уступают, пока открыта поверхность, — «Назад» сначала достаётся поверхности.
GoConfig.close_on_back(включено) — переключатель для поверхностей.
Кто получает касание
- Поверхность накрывает весь экран и останавливает ввод — прозрачная подложка тоже. Пока она открыта, ничто на нижних слоях не нажимается; слои над ней (снекбар) — нажимаются.
- Колесо мыши и жест щипка тоже останавливаются на окне: прокрутка за конец списка никогда не прокручивает игру.
- Игровой ввод, который идёт не через элементы управления, —
_unhandled_inputдля движения, перетаскивание камеры — должен вставать на паузу:if GoSurface.is_any_open(): return. Это покрывает диалоги, листы, карточки с привязкой и комбобокс; боковую панель спрашивайтеis_open()отдельно. - Снекбар, уведомление, карточка с вопросом и обучающая подсказка берут ввод только на своей карточке; во всех остальных местах его получает игра.
Фокус
Окно, открытое указателем, ничего не фокусирует — на касание никакое кольцо не мигает. Если окно открыто с клавиатуры или геймпада,
фокус получает кнопка закрытия (или initial_focus). Пока окно открыто, фокус остаётся внутри него, а при закрытии
возвращается к элементу, у которого был раньше. Диалоги каждый раз ставят фокус на OK.
Пауза в игре
Пауза дерева ставит на паузу и всплывающие окна — работать продолжает только консоль. Для меню паузы задайте его слою
process_mode = PROCESS_MODE_ALWAYS; тогда окно, его кнопки и «Назад» продолжают работать, пока игра стоит.
Одно и то же окно в каждом облике
Подтверждение из начала этой страницы, поверх той же игры, — поменялся только пресет. Код идентичен; остальное делают тема, скин и иконки.

default_dark
default_light
scifi_dark
scifi_light
medieval_dark
medieval_light
material_light Новое
material_dark Новое
kids_light Новое
kids_dark Новое
comic_light Новое
comic_dark Новое
arcade_light Новое
arcade_dark НовоеЧто меняет каждый облик
| Облик | Окно | Его шапка и кнопки |
|---|---|---|
| Обычный | Скруглённое (18dp), мягкая тень, непрозрачность 80% поверх игры | Простой заголовок и ×; основные кнопки поднимаются и проседают |
| Sci-fi | Срезанные углы с неоновой гранью и свечением, грань окрашена акцентом | Фокусные метки-скобки |
| Средневековый | Кованая рамка — железо и кожа либо пергамент — с заклёпками и угловыми орнаментами | Заголовки шрифтом Cinzel |
| Material 3 | Углы 28dp, отступы 24dp, полная непрозрачность | Заголовок 24sp шрифтом Roboto; кнопки-пилюли с плашкой 40dp внутри области касания 48dp |
| Детский | Карточка — желейная конфета с мягкой тенью, полная непрозрачность | Солнечная лента заголовка (ночью мятная), круглая кнопка закрытия-конфета, кнопки, которые сплющиваются при нажатии |
| Комикс | Жирный чернильный контур и жёсткая тень, полная непрозрачность | Жёлтая плашка заголовка, круглая кнопка закрытия, обведённая чернилами, кнопки, тень которых исчезает при нажатии |
| Аркада | Толстая панель — цветная рамка вокруг светлой середины, — полная непрозрачность | Золотая лента заголовка с белым заголовком в чернильном контуре, круглая красная клавиша закрытия, раскрашенные клавиши, которые подпрыгивают при нажатии |
Оформить всплывающее окно через скин
Скин решает то, до чего Theme не дотягивается. Ниже — хуки, которые вызывают всплывающие окна: переопределяйте их в собственном
подклассе GoSkin; по умолчанию они ничего не делают.
| Хук | Что оформляет |
|---|---|
title_plate_box() · title_plate_ink() · title_plate_min_size() | Плашку за заголовком окна. Заголовок центрируется на ней и уменьшается — вплоть до минимального размера (15), — чтобы остаться в одну строку. |
dress_close_button(button) | × окна — детский облик делает её круглой конфетой, комикс — кружком, обведённым чернилами, аркада — круглой красной клавишей. |
dress_title(label) | Подпись заголовка окна — аркада обводит её белый заголовок чернилами. |
press_feedback(control) | Срабатывает в момент, когда палец касается кнопки, созданной gohud, — детский облик её сплющивает, аркада заставляет подпрыгнуть. |
floating_box() · notice_box() · drawer_box() · banner_box() · overlay_box() | Лицевые панели карточек с вопросом и карточек тура, снекбаров и уведомлений, боковой панели, баннеров, консоли. |
coach_ring_box() · draw_coach_pointer() | Кольцо и указатель тура. |
Насколько прозрачно
Каждый вид всплывающих окон следует своей непрозрачности, поэтому карточка HUD может оставаться лёгкой, а диалог — плотным: окна,
листы, диалоги, карточки с привязкой и комбобокс следуют panel_alpha; боковая панель и карточка тура — card_alpha;
карточки с вопросом и консоль — hud_alpha; снекбары и уведомления — notice_alpha; меню — popup_alpha.
Material и детский облик делают их все непрозрачными; комикс тоже, кроме карточек с вопросом и консоли — у них 92%, и аркада тоже, кроме этих двух — у них 94%. Переопределите один вид через GoConfig.container_alpha_overrides,
все сразу — через container_alpha (доля — 0.95), а одно окно — через его alpha. Прозрачнеет
только лицевая панель; текст никогда не просвечивает. Непрозрачность подробно →
Настройки, которые определяют каждое всплывающее окно
Поля GoConfig (GoUi.config). Некоторые копируются при создании окна, поэтому задавайте их
до построения интерфейса — или вызовите GoUi.refresh() после изменения.
| Поле | По умолчанию | Что делает |
|---|---|---|
surface_max_width · surface_max_height | 480 · 700 | Наибольший размер окна. |
surface_height_ratio · surface_max_height_ratio | 0.68 · 0.72 | Обычная высота и потолок (он же предел перетаскивания). |
surface_fit_max_height_ratio | 0.94 | Насколько может вырасти карточка по центру, подогнанная под содержимое, прежде чем начнёт прокручиваться. |
surface_width_ratio_portrait · …_landscape | 0.94 · 0.72 | Ширина как доля места. |
dismiss_on_scrim | false | Касание подложки закрывает окна и диалоги. Копируется при создании окна. |
surface_fade_in · fade_seconds | false · 0.14 | Плавное появление окон и его скорость. |
close_on_back | true | Escape, ui_cancel и «Назад» на Android закрывают самое верхнее окно. |
close_button_visual | 36 | Видимый размер × — его область касания остаётся 48dp. |
suppress_pointer_focus_ring | true | Никакого кольца фокуса, когда окно открыто касанием или мышью. |
container_alpha · container_alpha_overrides | -1 · {} | Непрозрачность всех лицевых панелей или отдельно по видам (GoTheme.BOX_*). |
reduce_motion | false | Никаких проявлений, выездов, пульсаций и сплющиваний. |
min_touch_size | 48 | Высота кнопок у карточек с вопросом и обучающих подсказок. |
respect_safe_area | true | Держать окна подальше от выреза и полосы жестов. |
text_keys · text_overrides | — | Собственные слова набора: закрыть, назад, подтвердить, отмена, далее, готово, пропустить, поиск, пусто… |
sound_cues · вибрация | — | Что GoFeedback воспроизводит при открытии, закрытии, подтверждении, отмене и касании. |
Рецепты
Скопируйте, вставьте, поменяйте слова. Каждый вызов здесь сверен с исходным кодом.
Покупка с ценой в тексте
func buy(item_name: String, price: int) -> void:
if await dialogs.confirm("Buy {item}?", "It costs {price} gold.", "Buy", "", "",
{"item": item_name, "price": price}):
purchase(item_name)
Окно настроек с закреплённой кнопкой «Сохранить»
func open_settings() -> void:
var layer := CanvasLayer.new()
layer.layer = 50
add_child(layer)
var window := GoSurface.new()
window.set_title("Settings")
window.dismiss_on_scrim = true
window.close_requested.connect(layer.queue_free)
window.body.add_child(GoStyle.section("Sound", false))
window.body.add_child(GoStyle.toggle("Music", false))
window.body.add_child(GoStyle.slider(0.0, 1.0, 0.05))
var row := GoStyle.row()
row.add_child(GoStyle.button("Cancel", window.request_close))
row.add_child(GoStyle.button("Save", func() -> void:
save_settings()
layer.queue_free(), GoStyle.Tone.PRIMARY))
window.footer.add_child(row)
window.footer.visible = true
layer.add_child(window)
Форма, которая говорит, что не так
var field := GoField.make("Name", GoStyle.line_edit(), "3–12 letters")
window.body.add_child(field)
create_button.pressed.connect(func() -> void:
if name_is_taken():
field.set_error("That name is taken")
window.set_status_text("Name check failed", GoTheme.DANGER)
else:
window.clear_status()
create_character())
Дата в листе
sheet.open("Delivery date")
var picker := GoDatePicker.make({"year": 2026, "month": 10, "day": 14}, func(date: Dictionary) -> void:
delivery = date
sheet.close())
picker.min_date = GoDatePicker.today()
sheet.body.add_child(picker)
Меню паузы
func pause() -> void:
get_tree().paused = true
var layer := CanvasLayer.new()
layer.layer = 50
layer.process_mode = Node.PROCESS_MODE_ALWAYS # работает, пока игра на паузе
add_child(layer)
var menu := GoSurface.new()
menu.set_title("Paused")
menu.close_requested.connect(func() -> void:
get_tree().paused = false
layer.queue_free())
menu.body.add_child(GoStyle.button("Resume", menu.request_close, GoStyle.Tone.PRIMARY))
menu.body.add_child(GoStyle.button("Settings", open_settings))
menu.body.add_child(GoStyle.button("Quit to title", quit_to_title, GoStyle.Tone.DANGER))
layer.add_child(menu)
Игровой процесс, который ждёт всплывающие окна
func _unhandled_input(event: InputEvent) -> void:
if GoSurface.is_any_open() or drawer.is_open():
return
handle_gameplay(event)
Подводные камни
GoSurfaceникогда не освобождает себя сам. Подключитеclose_requested, иначе × ничего не делает.- Параметры, заданные после
add_child, —scrim_transparent,show_header,resizable,scroll_body, а также уGoDialogs—layer_indexиmax_width— не применяются. - Второй
confirm()сразу возвращаетfalse. Включитеqueue_when_busy, если два вопроса могут совпасть по времени. - Enter подтверждает. Фокус на OK — и в диалогах необратимых действий тоже.
- «Назад» на листе закрывает весь лист, а не подстраницу.
- Escape не закрывает боковую панель, а
is_any_open()её не видит. - Карточка с привязкой или список комбобокса блокируют игру, пока не закроются, даже с прозрачной подложкой, — а у карточки
с привязкой с
dismissable = falseи без заголовка на экране нет ничего, что её закроет. - Карточка с вопросом стартует скрытой и не имеет тайм-аута.
- Снекбар с
seconds: 0и без кнопки никогда не уходит. К тому же по умолчанию он ловит касания на своей карточке. - Долгое нажатие, о котором никто не знает, — функция, которой никто не пользуется. Покажите ещё и
⋯. - Дерево на паузе ставит на паузу и ваше всплывающее окно. Используйте
PROCESS_MODE_ALWAYSна его слое. - Уведомление без ширины сжимается до самой узкой — используйте
preferred_width(). - Никогда не спрашивайте в снекбаре и не сообщайте в диалоге. Вопрос исчезает, если его не заметить; новость не должна останавливать игру.