Интерфейс и сигналы
Счётчик на кнопке, подписки и отписки, повторный вход в дерево и клавиатурный фокус.
Сигнал — сообщение объекта о событии, например о нажатии кнопки. В C# вы подписываете метод на соответствующее событие. Не нужно каждый кадр спрашивать кнопку, нажата ли она.
1. Создайте интерфейс
Создайте отдельный документ с корнем Control, назовите его Counter и сохраните как Counter.rscene. Элементы Control предназначены для интерфейса: у них есть размеры, привязки и правила размещения.
Counter (Control)
├── CountLabel (Label)
└── AddButton (Button)Выделите корень и выберите размещение Full Rect в инструментах Layout. Для детей оставьте привязки к верхнему левому углу. В инспекторе Layout задайте:
| Объект | Position | Size | Text |
|---|---|---|---|
CountLabel | (32, 32) | (200, 40) | 0 |
AddButton | (32, 96) | (200, 48) | Добавить +1 |
Если инспектор показывает границы Offsets, эквивалент для кнопки: Left 32, Top 96, Right 232, Bottom 144. Камера и свет для этого документа не нужны.
2. Добавьте Counter.cs
Прикрепите скрипт Scripts/Counter.cs к корню Counter, а не к кнопке. Полный код:
using Reckfell;
public partial class Counter : Control
{
private int _count;
private Label _label = null!;
private Button _button = null!;
public override void _EnterTree()
{
_label = GetEntity<Label>("CountLabel");
_button = GetEntity<Button>("AddButton");
_button.Pressed += AddOne;
_label.Text = _count.ToString();
}
public override void _ExitTree()
{
if (ReckfellObject.IsInstanceValid(_button))
_button.Pressed -= AddOne;
}
private void AddOne()
{
_count += 1;
_label.Text = _count.ToString();
}
}Сохраните, соберите C# и запустите текущий документ через F6. Нажмите кнопку несколько раз: появятся 1, 2, 3 и так далее.
Как это связано
_count хранит целое число и начинается с нуля. _label и _button хранят ссылки на дочерние объекты. При входе в дерево _EnterTree() находит уже созданных детей и подключает обработчик. Для чтения данных, которые дети вычисляют в собственном _Ready(), используйте _Ready() родителя: в _EnterTree() дети ещё могут быть не готовы.
Pressed += AddOne означает «когда кнопка сообщит о нажатии, вызови метод AddOne». Здесь AddOne записывается без круглых скобок: мы передаём обработчик события, а не вызываем его сразу.
_count += 1 увеличивает счётчик. ToString() превращает число в текст для свойства Label.Text. private означает, что поле или метод предназначены для внутренней работы этого класса. Запись null! позволяет инициализировать ссылку позже; она не создаёт Label и не исправляет ошибку в имени CountLabel.
Почему подписка имеет пару
Pressed -= AddOne отключает тот же метод, который был подключён через +=. Он вызывается в _ExitTree(): удалённый из уровня интерфейс перестаёт получать события. Проверка IsInstanceValid() учитывает, что кнопку могли удалить отдельно.
Если вернуть тот же Counter через AddChild(), _EnterTree() подключит обработчик снова, а _count сохранит значение. Это отличается от удаления через QueueFree(): новый экземпляр начнёт с нуля. Пара _EnterTree() / _ExitTree() работает и при повторном добавлении, когда _Ready() сам не повторяется.
Не подключайте Pressed одновременно этим кодом и через редактор: одно нажатие может увеличить счётчик дважды. Не создавайте подписку каждый кадр в _Process(). Для возможности отписаться используйте именованный метод; новая лямбда при -= не равна ранее подключённой лямбде.
Проверка мышью и клавиатурой
- После запуска нажмите кнопку три раза: подпись должна показать
3. - Переведите фокус на кнопку через Tab и нажмите действие
ui_accept— по умолчанию Enter или Space. Счётчик должен увеличиться на один. - Если проект изменяет встроенные действия ввода, проверьте назначение
ui_acceptв Input Map. Для кнопки нужен разрешённый режим фокуса.
Событие Pressed подходит для одного действия по активации. Для переключателя используйте Toggle Mode и событие Toggled: состояние не стоит восстанавливать по количеству нажатий.
| Симптом | Проверка |
|---|---|
Ошибка GetEntity при старте | Скрипт прикреплён к Counter; дети называются ровно CountLabel и AddButton и имеют нужные типы |
| Одно нажатие добавляет два | Сигнал подключён ещё и в редакторе либо другой код повторяет подписку |
| Кнопка видна, но не нажимается | Проверьте Disabled, перекрывающий Control и его Mouse Filter |
| Tab не выделяет кнопку | Проверьте режим фокуса, видимость и действие ui_focus_next |
| После удаления кнопки возникла ошибка доступа | Не сохраняйте непроверенную native-ссылку для позднего вызова; отключайте подписки по времени жизни |
Отдельный экран или HUD
Этот документ — отдельный экран. В игре с перемещающейся камерой интерфейс поверх мира обычно помещают в CanvasLayer, а внутрь — дерево Control. Так камера мира не уносит счётчик за экран.
Для адаптивного размещения используйте контейнеры VBoxContainer, HBoxContainer и привязки, а не масштабирование текста через Scale.
Счётчик сбрасывается при новом запуске: в примере нет сохранения на диск. Справочник: Control, Button, Label.