# 15. Анимации

Table of Contents

15. Анимации

Цель

Сделать интерфейс и игровые события визуально плавными: игрок видит откуда берутся новые шары, как линия «взрывается», как появляется итог партии и как откликается HUD без усложнения логики матчинга.

Описание выполненной работы

Почему анимации завязаны на await, а не только на таймеры

В проекте уже были твины перемещения шара по пути и сжатие клетки при удалении линии — это минимальная анимация из прошлых шагов. Шаг 15 расширяет поведение там, где важно дождаться конца эффекта, прежде чем разрешить ввод или следующий этап хода. В Godot 4 любая функция, внутри которой используется await на Tween или другой асинхронный источник, становится корутиной: её нужно вызывать из контекста, который тоже может ждать (например _ready игрового поля или завершение хода ИИ).

Мы сознательно не блокируем сохранение состояния и игровую логику дольше необходимого: вход оверлея результата запускает Tween через call_deferred отдельным методом, чтобы не зависеть от порядка кадров CanvasLayer после visible = true.

Выкладка трёх шаров: возвращаемые индексы и общий параллельный твин

Функция _place_kinds_on_board теперь возвращает Array[int] — список клеток, куда реально попали новые виды (не больше числа свободных мест и размера партии в три шара). После _sync_cells(), который восстанавливает визуал и сбрасывает масштаб после прошлых анимаций, вызывается _animate_placement_pop(indices):

  • для каждой клетки из списка CellView.scale ставится в небольшое значение (0.14);
  • создаётся Tween с set_parallel(true), для всех затронутых клеток параллельно крутится tween_property(..., "scale", Vector2.ONE, PLACEMENT_POP_SEC) с TRANS_BACK и EASE_OUT;
  • await tw.finished гарантирует, что _busy на стороне человека или ИИ не снимется до окончания «пружинки».

Так мы не анимируем все клетки при загрузке сохранения с середины партии там, где выкладка не делается через эту ветку — только реальные моменты новой тройки.

Снятие линии: антиципация и ускоренное схлопывание

Раньше одна фаза уменьшала scale и modulate.a синхронно. Теперь список затронутых CellView собирается в массив, затем:

  1. Подготовительный параллельный твин на ANTICIPATE секунд увеличивает масштаб примерно до 1.12, чтобы линия «набрала объём» перед исчезновением (TRANS_QUAD, EASE_OUT).
  2. Второй параллельный твин длительностью MATCH_POP_SEC - ANTICIPATE с TRANS_EXPO и EASE_IN одновременно ведёт scale к почти нулевому значению и modulate.a к нулю для всех клеток списка.

Константа MATCH_POP_SEC слегка увеличена, чтобы второй этап не был слишком коротким при вычете антиципации. После анимации по-прежнему вызывается clear_cells и _sync_cells(), reset_visual_for_sync в CellView возвращает нормальный modulate для дальнейшей подсветки выбора.

Оверлей результата: отдельный метод и call_deferred

Чтобы карточка знала центр масштабирования, после показа слоя нужен один кадр валидного size. Метод _present_result_overlay_entrance:

  • сохраняет целевую альфу затемнения из текущего цвета ColorRect;
  • обнуляет альфу затемнения, уменьшает карточку и её modulate.a;
  • включает CanvasLayer и через call_deferred("_result_overlay_run_entrance_tween", ...) стартует параллельный твин: подъём альфы затемнения, появление карточки и scale до Vector2.ONE с TRANS_BACK.

Тот же метод вызывается и при _offer_game_over, и при загрузке сохранённой завершённой партии после _fill_result_overlay_content, так что поведение едино.

HUD: счёт, баннер, превью

  • _pulse_score_label кратко завышает modulate подписи счёта и возвращает к белому твином; вызывается сразу после начисления очков человеку и ИИ (после _refresh_scores_ui), не затрагивая остальные обновления текста.
  • _set_turn_banner после смены строки делает короткую подсветку _turn_label.modulate — дешёвая альтернатива масштабу с pivot_offset.
  • PlanetPreview.apply_planet убивает предыдущий твин при быстром обновлении очереди, ставит scale около 0.72 и анимирует к ONE с TRANS_BACK; в NOTIFICATION_RESIZED выставляется pivot_offset, чтобы подпрыгивание было от центра иконки.

Титульный экран

В title_screen.gd у VBox с кнопками на старте modulate.a = 0, затем твин проявления до 1.0 с небольшой задержкой. Слои настроек и справки не трогаются — они по-прежнему открываются поверх без лишней задержки.

Согласование await по цепочке хода

Поскольку _start_human_turn теперь ждёт анимацию выкладки, вызовы из _ready обёрнуты в await _start_human_turn(). _after_ai_moves_finished вызывает await _start_human_turn(). В _start_ai_turn после выкладки тоже await _animate_placement_pop, затем последовательность ИИ и await _after_ai_moves_finished(). Загрузка снимка для незавершённой партии на ходе ИИ по-прежнему проходит через await _ai_play_sequence() и await _after_ai_moves_finished() — эта цепочка сохранена с учётом новых ожиданий.

Как дальше развивать шаг без переписывания логики

  • Вынести длительности в GameSettings или ресурс tuning/tweens.tres, если понадобятся пресеты «быстро / красиво».
  • Добавить лёгкий параллакс или смещение ghost-шара по дуге — сейчас путь строго по клеткам, логика уже отделена в _animate_move_then_apply.
  • Для слабых устройств можно пропускать антиципацию линии или сокращать PLACEMENT_POP_SEC, проверяя Performance.get_monitor.

После изменений имеет смысл прогнать партию до конца и с открытием сохранения «игра окончена», убедиться что кнопка «Меню» остаётся отзывчивой, а твины не накладываются на повторном быстром клике — при необходимости у CellView или оверлея можно вызывать create_tween().kill() на защищённый ссылочный твин, аналогично превью планет.

Ссылка на проект: Third Planet

Ссылка на игру: Google Play Rustore

Next: 16. Тени
Аватар автора

Спасибо, что прочитали статью. Посмотрите другие материалы в архиве, там много практических разборов по разным технологиям.


godot Series

# 16. Тени

godot 16 / 19
4 min read

Тени через StyleBoxFlat у ячеек, HUD, слота баннера и карточки результата; стили кнопок на титуле и поле; лёгкий контактный затемнённый слой на шарах-планетах.

Read