Интерфейс: счёт, сообщение и кнопка
Создайте CanvasLayer поверх уровня, настройте прямоугольники элементов и подключите кнопку один раз.
Префаб интерфейса
Создайте корень CanvasLayer с именем Hud и сохраните Prefabs/Hud.rprefab:
Hud (CanvasLayer, DodgeHud.cs)
└── Root (Control)
├── ScoreLabel (Label)
├── MessageLabel (Label)
├── StartButton (Button)
└── InstructionsLabel (Label)У Root выберите Full Rect, Mouse Filter = Ignore. Для поля 640 × 360 задайте прямоугольники через смещения относительно верхнего левого угла (Offsets):
| Элемент | Left, Top, Right, Bottom | Дополнительно |
|---|---|---|
ScoreLabel | 24, 16, 240, 52 | Размер шрифта 24 |
MessageLabel | 60, 120, 580, 172 | Выравнивание по центру, шрифт 24 |
StartButton | 220, 200, 420, 248 | Кнопка шириной 200 и высотой 48 |
InstructionsLabel | 60, 320, 580, 352 | Выравнивание по центру |
У всех четырёх дочерних Control оставьте нулевые якоря, чтобы таблица задавала прямоугольники от верхнего левого угла. У трёх Label задайте Mouse Filter = Ignore; у кнопки оставьте Stop. У Hud оставьте Layer = 1: CanvasLayer помещает интерфейс отдельным слоем над 2D-объектами. Эти координаты рассчитаны на фиксированное окно; для меняющегося размера нужен другой layout с контейнерами и якорями.
Код
Прикрепите к корню Scripts/DodgeHud.cs. Тексты выбираются по TranslationServer.GetLocale(): ru даёт русский, остальные локали — английский. Обе версии используют одни и те же пути и сигналы.
Полный DodgeHud.cs
using Reckfell;
public partial class DodgeHud : CanvasLayer
{
[Signal] public delegate void StartRequestedEventHandler();
private Label _score = null!;
private Label _message = null!;
private Label _instructions = null!;
private Button _button = null!;
public override void _EnterTree()
{
_score = GetEntity<Label>("Root/ScoreLabel");
_message = GetEntity<Label>("Root/MessageLabel");
_instructions = GetEntity<Label>("Root/InstructionsLabel");
_button = GetEntity<Button>("Root/StartButton");
_button.Pressed += OnPressed;
_instructions.Text = L("Движение: WASD или стрелки", "Move: WASD or arrow keys");
}
public override void _ExitTree()
{
if (ReckfellObject.IsInstanceValid(_button))
_button.Pressed -= OnPressed;
}
public void SetScore(int score) => _score.Text = L("Счёт: ", "Score: ") + score;
public void ShowReady() => ShowMessage(L("Избегайте белых кругов", "Avoid the white circles"), L("Начать", "Start"));
public void ShowGameOver() => ShowMessage(L("Игра окончена", "Game over"), L("Ещё раз", "Restart"));
public void ShowPlaying()
{
_message.Hide();
_button.Hide();
}
public void ShowSetupError()
{
ShowMessage(L("Проверьте Prefabs/Enemy.rprefab", "Check Prefabs/Enemy.rprefab"), "");
_button.Disabled = true;
}
private void ShowMessage(string message, string buttonText)
{
_message.Text = message;
_message.Show();
_button.Text = buttonText;
_button.Show();
}
private void OnPressed() => EmitSignal(SignalName.StartRequested);
private static string L(string ru, string en) => TranslationServer.GetLocale().StartsWith("ru") ? ru : en;
}SetScore() меняет только подпись. ShowPlaying() скрывает сообщение и кнопку; ShowGameOver() возвращает их, сохраняя счёт. Кнопка испускает StartRequested, а не меняет уровень самостоятельно: раундом управляет DodgeGame.
Если кнопка видна, но не нажимается, проверьте перекрывающие Control и Mouse Filter: декоративные элементы не должны забирать щелчок. Ignore у Root не требует выключать обработку у дочерней кнопки.
Подключение
Скрипт сам соединяет Button.Pressed с именованным методом. Второе соединение в редакторе не нужно. При выходе из дерева обработчик снимается, поэтому повторное добавление интерфейса не удваивает запрос начала. После восстановления UI отображайте текущее состояние игры явно: сигнал не хранит историю.
Добавьте экземпляр Hud.rprefab в уровень как Hud. Соберите все скрипты и запустите Arena.rscene: до начала видна кнопка, во время раунда — счёт, после попадания — сообщение и повторный запуск. Если кнопки не видно, проверьте путь Root/StartButton, размеры, видимость и запуск нужного документа.