Files
g-world/CLAUDE.md
T

12 KiB
Raw Blame History

CLAUDE.md

Заметки для агентов, работающих с этим репозиторием.

Что это за проект

Двумерная игра с видом сбоку на Godot 4.7. Референс — Fallout Shelter: разрез убежища, комнаты в сетке, жители. Игра полностью 2D. Новые игровые сцены — на Node2D/Control; 3D-узлам в проекте места нет. Рендерер при этом Forward+ — так решил владелец проекта, не переключайте его без явной просьбы.

Работа через MCP, а не через файлы

В проекте включён плагин addons/godot_ai, редактор Godot обычно запущен и подключён. Сцены и ресурсы правьте инструментами MCP (scene_manage, ui_manage, node_create, theme_manage, resource_manage), а не редактированием .tscn руками — иначе открытый редактор перезапишет изменения при сохранении.

Скрипты — через script_create / script_patch: они возвращают диагностику парсера сразу.

Проверять результат — project_run + editor_screenshot(source="game"). Ввод в запущенную игру шлётся через game_manage(op="input_key"); input_action не двигает фокус Control-ов, для навигации по UI нужны настоящие события клавиш.

Известные особенности

  • node_set_property не принимает целочисленный 0 — используйте batch_execute с командой set_property.
  • Правка скрипта, который зарегистрирован автозагрузкой, даёт в ответе GDScript reload failed with error code 43. Это неудачная горячая перезагрузка в редакторе, а не ошибка разбора: проверяйте logs_read(source="editor"), игра при этом запускается чисто.
  • project_manage(op="settings_set") отказывается менять application/run/main_scene.
  • Большие ui_manage(op="build_layout") иногда не доезжают целиком — стройте дерево несколькими вызовами поменьше.
  • Запуск из редактора отдаёт встроенное окно: Embedded window can't be resized в логе — норма.

Конвенции кода

  • Отступы — табы (см. .editorconfig), статическая типизация везде, где возможно.
  • Комментарии и весь UI-текст — по-русски; имена узлов, файлов, методов, сигналов — по-английски.
  • Комментарий объясняет почему, а не пересказывает код.
  • Приватные члены — с подчёркиванием: _card, _focus_first_button().
  • Документирующие комментарии ## — на классах, сигналах и неочевидных методах.

Архитектура UI

  • Единая тема themes/main_menu.theme.tres на корне каждого экрана. Цвета в узлах не хардкодим.
  • Экран = сцена со своим скриптом, наружу торчат open() / close() и сигнал closed.
  • Оверлеи центрируются CenterContainer, а не пресетом center: пресет ставит левый верхний угол карточки в центр экрана. Та же ловушка у bottom_wide — он прижимает к нижнему краю верх панели, и она уезжает за экран; для нижней панели используйте MarginContainer во весь экран + HBoxContainer с size_flags_vertical = SIZE_SHRINK_END у содержимого.
  • Контейнеры не раскладывают скрытые узлы — после visible = true нужен await get_tree().process_frame, прежде чем читать size (например, для pivot_offset).
  • Наведение мышью делается фокусом (mouse_enteredgrab_focus), чтобы у мыши и клавиатуры была одна подсветка.

Игровой слой

Файлы в scripts/game/ разделены по принципу «данные и правила отдельно, отрисовка отдельно».

Модель и правила — без узлов и без _draw():

  • vault_grid.gd — сетка и комнаты. Сюда идут типы комнат, размещение, ресурсы. Вертикальное смещение бункера под землю — константа DEPTH, её учитывают и cell_rect(), и cell_at(), поэтому картинка и попадание мышью не разъезжаются.
  • game_clock.gd — игровые дата и время, скорость, своя пауза. Такт наружу — сигнал minute_passed; темп задаётся MINUTES_PER_SECOND.
  • weather_system.gd — тип погоды, матрица переходов и плавная смена параметров. Такт берёт у часов, поэтому стоит вместе с паузой. Форма осадков решается температурой, а не месяцем: у нуля дождь становится снегом.
  • climate.gd — сезонная норма температуры и ветра (средняя полоса Европы). Перечисление погоды сюда не тянем — вышла бы циклическая ссылка.
  • sky_cycle.gd — палитра неба по часу суток, положение солнца и луны. Чистые вычисления.

Отрисовка — только _draw(), читают модель и ничего не решают:

  • sky_view.gd — градиент неба, звёзды, светила.
  • surface_view.gd — поверхность: горы, холмы, деревья, облака, осадки.
  • vault_view.gd — разрез: порода, корпус, шахта, комнаты, будка входа.

vault_scene.gd связывает всё это с камерой и HUD и держит контракт сохранения.

Новый тип комнаты добавляется одной записью в VaultGrid.KINDS: кнопка в панели строительства и подсчёт ресурсов подхватятся сами. Новый тип погоды — записью в WeatherSystem.KINDS плюс строкой в TRANSITIONS.

Текстуры фона

Ловушка с плиткой. draw_texture_rect(tex, rect, tile = true) повторяет текстуру в её натуральную величину и по обеим осям. Если растянуть прямоугольник ради масштаба, текстура просто уложится в него несколько раз, и на стыке копий пойдёт горизонтальная полоса через весь экран. Плюс при texture_repeat = ENABLED фильтрация на краю подмешивает противоположный край текстуры — ещё одна тонкая линия.

Поэтому полосы пейзажа кладутся плитками вручную. При этом важен ещё один момент: крайние столбцы пикселей у этих текстур полупрозрачные от сглаживания, и встык они складываются дважды, давая вертикальную линию. Отсюда EDGE_INSET — плитки рисуются через draw_texture_rect_region() с отступом в пиксель с каждой стороны, плюс пиксель нахлёста. Повтор на узле выключен.

background/ — набор Kenney Background Elements. Два стиля, и они не взаимозаменяемы: PNG/Flat/* — бледные, почти белые силуэты, их место на дальних планах, где цвет всё равно задаётся тонировкой; PNG/* — цветные спрайты для ближнего плана. Перепутать легко: деревья из Flat на переднем плане выглядят выцветшими.

Консоль разработчика

Открывается тильдой или F1 в любой сцене, работает и на паузе. Сама об игре ничего не знает: команды регистрируют сцены через DevConsole.register(...), и они умирают вместе со сценой — консоль выбрасывает команду, когда её Callable протухает.

Команды игровой сцены живут в vault_scene.gd, в разделе «Команды консоли»: stats, time, date, speed, weather, temp, wind, build, wipe. У погоды есть латинские ключи (snow, rain, …) — набирать русские названия с латинской раскладки посреди отладки неудобно.

Файловый мост — как выполнять команды из MCP

Набрать команду через game_manage(op="input_key") нельзя: внедрённые события доносят до LineEdit код клавиши, но не символ. Enter при этом проходит — то есть дело не в фокусе. Поэтому в консоли есть мост через файлы в user:// (только OS.is_debug_build(), в релиз не попадает):

DIR="$APPDATA/Godot/app_userdata/GWorld"
printf 'weather storm\ntemp -3\nstats\n' > "$DIR/console_in.txt"
cat "$DIR/console_log.txt"

Консоль опрашивает console_in.txt четыре раза в секунду, выполняет строки и удаляет файл. Весь вывод дублируется в console_log.txt, который перезаписывается на каждый запуск игры. Работает и с закрытой консолью, и на паузе.

Автозагрузки

GameSettings (GameSettingsService), SaveManager (SaveManagerService) и DevConsole (сцена scenes/ui/dev_console.tscn). У обеих есть class_name: константы, перечисления и типы берём через него, экземпляр — через get_node("/root/..."). Имя автозагрузки редактор не резолвит без перезапуска проекта.

Контракт сохранения игровой сцены описан в README.md.

Проверка

Изменения в UI считаются сделанными только после запуска и скриншота. Логи смотреть в обоих источниках: logs_read(source="game") и logs_read(source="editor") — ошибки загрузки скриптов попадают только во второй.