Update project configuration files and improve save menu functionality. Added editorconfig settings for various file types, enhanced .gitattributes for text and binary file handling, expanded .gitignore for build and local settings, and modified save_menu.gd to maintain focus on UI elements after refresh.
This commit is contained in:
@@ -2,3 +2,23 @@ root = true
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# GDScript требует табов для отступов.
|
||||||
|
[*.{gd,gdshader}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# Сцены и ресурсы Godot редактирует сам — не трогаем пробелы.
|
||||||
|
[*.{tscn,tres,godot,import,uid}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.{md,yml,yaml,json}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|||||||
+31
-1
@@ -1,2 +1,32 @@
|
|||||||
# Normalize EOL for all files that Git considers text files.
|
# Нормализуем переводы строк во всех текстовых файлах.
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Текстовые форматы Godot — держим явно, чтобы Git не гадал.
|
||||||
|
*.gd text eol=lf
|
||||||
|
*.gdshader text eol=lf
|
||||||
|
*.tscn text eol=lf
|
||||||
|
*.tres text eol=lf
|
||||||
|
*.godot text eol=lf
|
||||||
|
*.cfg text eol=lf
|
||||||
|
*.import text eol=lf
|
||||||
|
*.uid text eol=lf
|
||||||
|
*.svg text eol=lf
|
||||||
|
|
||||||
|
# Бинарные ассеты: не нормализовать и не пытаться сливать построчно.
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.webp binary
|
||||||
|
*.ogg binary
|
||||||
|
*.wav binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.ttf binary
|
||||||
|
*.otf binary
|
||||||
|
*.res binary
|
||||||
|
*.scn binary
|
||||||
|
*.ctex binary
|
||||||
|
*.glb binary
|
||||||
|
*.blend binary
|
||||||
|
|
||||||
|
# Плагины — не наши исходники, не учитывать в статистике языков GitHub.
|
||||||
|
addons/** linguist-vendored
|
||||||
|
|||||||
+36
-1
@@ -1,3 +1,38 @@
|
|||||||
# Godot 4+ specific ignores
|
# --- Godot 4+ ---------------------------------------------------------------
|
||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
|
|
||||||
|
# Сборки
|
||||||
|
/build/
|
||||||
|
/builds/
|
||||||
|
/export/
|
||||||
|
|
||||||
|
# Может содержать пароли от keystore и пути конкретной машины.
|
||||||
|
# Если понадобится общий пресет экспорта — храните его отдельно, без секретов.
|
||||||
|
export_presets.cfg
|
||||||
|
|
||||||
|
# Скомпилированные переводы: собираются из исходных .csv/.po
|
||||||
|
*.translation
|
||||||
|
|
||||||
|
# --- .NET / C# --------------------------------------------------------------
|
||||||
|
.mono/
|
||||||
|
data_*/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
*.csproj.user
|
||||||
|
*.sln.DotSettings.user
|
||||||
|
|
||||||
|
# --- Локальные настройки инструментов ---------------------------------------
|
||||||
|
.claude/settings.local.json
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# --- Мусор ОС ---------------------------------------------------------------
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# --- Прочее -----------------------------------------------------------------
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
Заметки для агентов, работающих с этим репозиторием.
|
||||||
|
|
||||||
|
## Что это за проект
|
||||||
|
|
||||||
|
Двумерная игра с видом сбоку на Godot 4.7. Референс — Fallout Shelter: разрез убежища,
|
||||||
|
комнаты в сетке, жители. **Игра полностью 2D.** Если встречаете 3D-узлы — это остатки
|
||||||
|
заглушки, а не направление проекта.
|
||||||
|
|
||||||
|
## Работа через 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`: пресет ставит левый
|
||||||
|
верхний угол карточки в центр экрана.
|
||||||
|
- Контейнеры не раскладывают скрытые узлы — после `visible = true` нужен
|
||||||
|
`await get_tree().process_frame`, прежде чем читать `size` (например, для `pivot_offset`).
|
||||||
|
- Наведение мышью делается фокусом (`mouse_entered` → `grab_focus`), чтобы у мыши и клавиатуры
|
||||||
|
была одна подсветка.
|
||||||
|
|
||||||
|
## Автозагрузки
|
||||||
|
|
||||||
|
`GameSettings` (`GameSettingsService`) и `SaveManager` (`SaveManagerService`).
|
||||||
|
У обеих есть `class_name`: константы, перечисления и типы берём через него, экземпляр —
|
||||||
|
через `get_node("/root/...")`. Имя автозагрузки редактор не резолвит без перезапуска проекта.
|
||||||
|
|
||||||
|
Контракт сохранения игровой сцены описан в [README.md](README.md).
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
Изменения в UI считаются сделанными только после запуска и скриншота. Логи смотреть в обоих
|
||||||
|
источниках: `logs_read(source="game")` и `logs_read(source="editor")` — ошибки загрузки скриптов
|
||||||
|
попадают только во второй.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# G-WORLD
|
||||||
|
|
||||||
|
Двумерная игра с видом сбоку на Godot 4.7. Референс по подаче и структуре — **Fallout Shelter**:
|
||||||
|
разрез убежища, комнаты в сетке, жители, которых расселяют по этажам, и мир, реагирующий на
|
||||||
|
решения игрока.
|
||||||
|
|
||||||
|
> Статус: ранняя альфа. Готов весь каркас меню; игровой слой пока заглушка.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что уже работает
|
||||||
|
|
||||||
|
| Экран | Файл | Что умеет |
|
||||||
|
|---|---|---|
|
||||||
|
| Главное меню | `scenes/ui/main_menu.tscn` | анимированный фон на шейдере, появление по стаггеру, единая подсветка для мыши и клавиатуры |
|
||||||
|
| Настройки | `scenes/ui/settings_menu.tscn` | видео, звук, переназначение клавиш; сохраняется в `user://settings.cfg` |
|
||||||
|
| Пауза | `scenes/ui/pause_menu.tscn` | останавливает дерево, переиспользует экраны настроек и сохранений |
|
||||||
|
| Сохранения | `scenes/ui/save_menu.tscn` | 6 слотов с миниатюрами, датой и наигранным временем, запись/чтение/удаление |
|
||||||
|
|
||||||
|
Экраны настроек и сохранений — отдельные сцены, вложенные инстансами и в главное меню, и в паузу.
|
||||||
|
Дублирования нет: правка в одном месте видна везде.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- **Godot 4.7** (Forward+, .NET-сборка не обязательна — код на GDScript)
|
||||||
|
- Windows / Linux / macOS
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
Откройте папку проекта в Godot и нажмите F5 — главная сцена уже прописана
|
||||||
|
(`res://scenes/ui/main_menu.tscn`).
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
scenes/
|
||||||
|
ui/ main_menu, settings_menu, pause_menu, save_menu
|
||||||
|
game/ game.tscn — заглушка вместо игрового слоя
|
||||||
|
scripts/
|
||||||
|
autoload/ game_settings.gd, save_manager.gd
|
||||||
|
ui/ по скрипту на экран
|
||||||
|
game/ game_stub.gd
|
||||||
|
shaders/ menu_background.gdshader + материал
|
||||||
|
themes/ main_menu.theme.tres — единая тема всех экранов
|
||||||
|
default_bus_layout.tres — шины микшера: Master → Music, SFX
|
||||||
|
```
|
||||||
|
|
||||||
|
## Соглашения
|
||||||
|
|
||||||
|
**Тема.** Все экраны наследуют `themes/main_menu.theme.tres`. Цвета не прописываются в узлах:
|
||||||
|
акцент `#2ee6c5`, второй акцент `#7b6bf5`, текст `#dbe7f0`, приглушённый `#8fa5ba`.
|
||||||
|
Локальные `theme_override_*` — только там, где элемент должен отличаться намеренно.
|
||||||
|
|
||||||
|
**Язык.** Интерфейс и комментарии — русские, имена узлов, файлов и методов — английские.
|
||||||
|
|
||||||
|
**Автозагрузки.**
|
||||||
|
|
||||||
|
- `GameSettings` (`GameSettingsService`) — настройки: чтение, применение к движку, `user://settings.cfg`
|
||||||
|
- `SaveManager` (`SaveManagerService`) — слоты сохранений в `user://saves/`
|
||||||
|
|
||||||
|
У обеих есть `class_name`: константы и типы берутся через него, экземпляр — через
|
||||||
|
`get_node("/root/GameSettings")`. Так редактор резолвит имена без перезапуска проекта.
|
||||||
|
|
||||||
|
**Контракт игровой сцены.** Чтобы сцена умела сохраняться:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group(SaveManagerService.PERSIST_GROUP)
|
||||||
|
SaveManager.begin_session(scene_file_path)
|
||||||
|
|
||||||
|
func save_data() -> Dictionary:
|
||||||
|
return {"...": ...}
|
||||||
|
|
||||||
|
func load_data(data: Dictionary) -> void:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
`SaveManager` обходит группу `persistent`, собирает словари по путям узлов и пишет JSON.
|
||||||
|
Миниатюра слота — кадр, снятый в момент вызова паузы, до показа оверлея.
|
||||||
|
|
||||||
|
**Действия ввода** (`project.godot` → Input Map): `move_left`, `move_right`, `move_forward`,
|
||||||
|
`move_back`, `jump`, `sprint`, `interact`, `pause`. Переназначаются в настройках.
|
||||||
|
|
||||||
|
**Слияния.** `.tscn` и `.tres` — текстовые, но построчный авто-мердж по ним даёт мусор.
|
||||||
|
Конфликтующие сцены лучше разрешать, открывая обе версии в редакторе, а не в diff-вьювере.
|
||||||
|
|
||||||
|
## Что дальше
|
||||||
|
|
||||||
|
- [ ] Перевести игровой слой на 2D: сетка комнат в разрезе, вид сбоку
|
||||||
|
- [ ] Жители: найм, характеристики, распределение по комнатам
|
||||||
|
- [ ] Ресурсы и производство, циклы времени
|
||||||
|
- [ ] Заменить `scenes/game/game.tscn` на настоящую сцену
|
||||||
|
- [ ] Звук: подключить шины Music и SFX
|
||||||
|
- [ ] Лицензия не выбрана
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
Не определена. Плагин `addons/godot_ai` поставляется со своей лицензией — см.
|
||||||
|
`addons/godot_ai/LICENSE`.
|
||||||
+27
-5
@@ -72,11 +72,33 @@ func close() -> void:
|
|||||||
|
|
||||||
# --- Список слотов ----------------------------------------------------------
|
# --- Список слотов ----------------------------------------------------------
|
||||||
|
|
||||||
func _refresh() -> void:
|
## Перестраивает список. focus_row/focus_delete возвращают фокус на ту же
|
||||||
|
## кнопку, что была нажата, — иначе после перерисовки он теряется.
|
||||||
|
func _refresh(focus_row: int = -1, focus_delete: bool = false) -> void:
|
||||||
for child in _slots_box.get_children():
|
for child in _slots_box.get_children():
|
||||||
|
_slots_box.remove_child(child)
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
||||||
|
var rows: Array[Node] = []
|
||||||
for info: Dictionary in _saves.list_slots():
|
for info: Dictionary in _saves.list_slots():
|
||||||
_slots_box.add_child(_make_row(info))
|
var row := _make_row(info)
|
||||||
|
_slots_box.add_child(row)
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
if focus_row >= 0 and focus_row < rows.size():
|
||||||
|
_focus_row_button(rows[focus_row], focus_delete)
|
||||||
|
|
||||||
|
|
||||||
|
func _focus_row_button(row: Node, delete: bool) -> void:
|
||||||
|
var buttons: Array[Button] = []
|
||||||
|
for child: Node in row.get_child(0).get_children():
|
||||||
|
if child is Button:
|
||||||
|
buttons.append(child as Button)
|
||||||
|
if buttons.is_empty():
|
||||||
|
return
|
||||||
|
var target := buttons[1] if delete and buttons.size() > 1 else buttons[0]
|
||||||
|
if not target.disabled:
|
||||||
|
target.grab_focus()
|
||||||
|
|
||||||
|
|
||||||
func _make_row(info: Dictionary) -> PanelContainer:
|
func _make_row(info: Dictionary) -> PanelContainer:
|
||||||
@@ -196,7 +218,7 @@ func _on_action_pressed(index: int) -> void:
|
|||||||
_status.text = "Сохранено в слот %d." % (index + 1)
|
_status.text = "Сохранено в слот %d." % (index + 1)
|
||||||
else:
|
else:
|
||||||
_status.text = "Не удалось сохранить в слот %d." % (index + 1)
|
_status.text = "Не удалось сохранить в слот %d." % (index + 1)
|
||||||
_refresh()
|
_refresh(index)
|
||||||
|
|
||||||
|
|
||||||
func _on_delete_pressed(index: int) -> void:
|
func _on_delete_pressed(index: int) -> void:
|
||||||
@@ -207,13 +229,13 @@ func _on_delete_pressed(index: int) -> void:
|
|||||||
_pending = {}
|
_pending = {}
|
||||||
_saves.delete_slot(index)
|
_saves.delete_slot(index)
|
||||||
_status.text = "Слот %d удалён." % (index + 1)
|
_status.text = "Слот %d удалён." % (index + 1)
|
||||||
_refresh()
|
_refresh(index)
|
||||||
|
|
||||||
|
|
||||||
func _set_pending(action: String, index: int, message: String) -> void:
|
func _set_pending(action: String, index: int, message: String) -> void:
|
||||||
_pending = {"action": action, "index": index}
|
_pending = {"action": action, "index": index}
|
||||||
_status.text = message
|
_status.text = message
|
||||||
_refresh()
|
_refresh(index, action == "delete")
|
||||||
|
|
||||||
|
|
||||||
func _is_pending(action: String, index: int) -> bool:
|
func _is_pending(action: String, index: int) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user