# 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 собирается в массив, затем:
- Подготовительный параллельный твин на
ANTICIPATEсекунд увеличивает масштаб примерно до1.12, чтобы линия «набрала объём» перед исчезновением (TRANS_QUAD,EASE_OUT). - Второй параллельный твин длительностью
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