152 lines
12 KiB
Markdown
152 lines
12 KiB
Markdown
# 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_entered` → `grab_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()`, в релиз не попадает):
|
||
|
||
```bash
|
||
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](README.md).
|
||
|
||
## Проверка
|
||
|
||
Изменения в UI считаются сделанными только после запуска и скриншота. Логи смотреть в обоих
|
||
источниках: `logs_read(source="game")` и `logs_read(source="editor")` — ошибки загрузки скриптов
|
||
попадают только во второй.
|