GUI - 1 - Базовий інтерфейс.
Передумови
Скачайте даний проєкт.
GUI - Вступ
Загальна інформація про структуру GUI.
Структура:
Кожен екран це окрема сцена, а саме:
- MainMenu (потрібно вибрати як сцену за замовчуванням ('Set as main
scene')) - LevelMenu
- PauseMenu
А також клас GameManager:
Цей клас керує станом паузи гри, дозволяючи перемикати її за допомогою клавіші ESC та динамічно створювати або видаляти UI меню паузи.
GameManager відповідає за глобальний контроль над станом GetTree().Paused. У методі _Ready він завантажує PackedScene меню паузи та встановлює власний ProcessMode у значення Always, що є критичним для того, щоб скрипт міг обробляти натискання клавіш, коли весь інший ігровий світ зупинено. При натисканні 'ui_cancel' викликається TogglePause, який змінює стан паузи та відповідно або додає _pauseMenuInstance до кореня сцени (Root), або видаляє його через QueueFree. Також клас надає метод Unpause для зовнішнього керування станом гри.
Сцена - MainMenu
Огляд структури та функціоналу MainMenu.
Дерево:
- MainMenu (CanvasLayer)
- ColorRect
- MarginContainer
- HBoxContainer
- VBoxContainer
- LabelTitle
- ButtonLevels %
- ButtonExit %
Пояснення:
Клас MainMenu імплементує головне меню гри, наслідуючи CanvasLayer для коректного відображення UI. Під час ініціалізації в методі _Ready, він зв'язує кнопки з відповідними функціями-обробниками через подію Pressed. Для оптимізації переходу між екранами, сцена вибору рівнів завантажується заздалегідь як PackedScene за допомогою GD.Load. Взаємодія з SceneTree через ChangeSceneToPacked завантажує LevelMenu коли це потрібно, а функція Quit закриває гру.
ВАЖЛИВО:
Встановіть MainMenu як дефолтну (за замовчуванням сцену) (клік правою кнопкою миші на сцену у файловому провіднику.).
Код:
using Godot;
using System;
public partial class MainMenu : CanvasLayer
{
// Посилання на кнопки головного меню
private Button ButtonLevels { get; set; }
private Button ButtonExit { get; set; }
// Збережена сцена меню вибору рівнів
private PackedScene LevelMenuInstance { get; set; }
// Метод викликається один раз при ініціалізації нода в дереві сцени
public override void _Ready()
{
// Отримання посилань на кнопки за допомогою унікальних імен (%)
ButtonLevels = GetNode<Button>("%ButtonLevels");
ButtonExit = GetNode<Button>("%ButtonExit");
// Встановлення тексту для кнопок
ButtonLevels.Text = "ButtonLevels";
ButtonExit.Text = "ButtonExit";
// Підписка на подію Pressed для кожної кнопки
ButtonLevels.Pressed += ButtonLevelsClicked;
ButtonExit.Pressed += ButtonExitClicked;
// Завантаження PackedScene для меню рівнів з ресурсів проекту
LevelMenuInstance = GD.Load<PackedScene>("res://Scenes/Menus/LevelMenu/LevelMenu.tscn");
}
// Обробник натискання кнопки "Рівні" — перемикає сцену на меню вибору рівнів
private void ButtonLevelsClicked()
{
GetTree().ChangeSceneToPacked(LevelMenuInstance);
}
// Обробник натискання кнопки "Вихід" — повністю закриває гру
private void ButtonExitClicked()
{
GetTree().Quit();
}
}Сцена - LevelMenu
Огляд структури та функціоналу LevelMenu.
Дерево:
- LevelMenu (CanvasLayer)
- ColorRect
- MarginContainer
- HBoxContainer
- LevelListVBoxContainer %
- LabelTitle
Пояснення:
Клас LevelMenu наслідує CanvasLayer, що дозволяє інтерфейсу відображатися поверх ігрових об'єктів. Під час ініціалізації в методі _Ready він завантажує шляхи до файлів рівнів у список Levels та автоматично генерує кнопки в VBoxContainer. Кожна кнопка викликає LevelButtonClicked з аргументом (рівнем) який потрібно загрузити.
Код:
using Godot;
using System.Collections.Generic;
// Клас меню вибору рівнів, що наслідується від CanvasLayer для відображення UI поверх ігрового світу
public partial class LevelMenu : CanvasLayer
{
// Посилання на контейнер VBoxContainer, у якому будуть розміщені кнопки рівнів
private VBoxContainer LevelListVBoxContainer { get; set; }
// Список об'єктів PackedScene, що містять завантажені сцени рівнів
private List<PackedScene> Levels = new();
public override void _Ready()
{
// Отримуємо посилання на VBoxContainer з використанням unique name (%)
LevelListVBoxContainer = GetNode<VBoxContainer>("%LevelListVBoxContainer");
// Завантажуємо сцени рівнів за їхніми шляхами та додаємо до списку Levels
Levels.Add((PackedScene)GD.Load("res://Scenes/Levels/Level1.tscn"));
Levels.Add((PackedScene)GD.Load("res://Scenes/Levels/Level2.tscn"));
// Ітеруємо по списку рівнів для динамічного створення UI-елементів
foreach (var level in Levels)
{
// Створюємо новий екземпляр кнопки
Button levelButton = new();
// Встановлюємо текст кнопки (наприклад, "Level 1", "Level 2")
levelButton.Text = "Level " + (Levels.IndexOf(level) + 1);
// Підписуємось на подію Pressed, передаючи обрану сцену в метод LevelButtonClicked через лямбда-вираз
levelButton.Pressed += () => LevelButtonClicked(level);
// Додаємо створену кнопку як дочірній елемент до контейнера
LevelListVBoxContainer.AddChild(levelButton);
// Виводимо шлях до ресурсу рівня в консоль для відладки
GD.Print(level.ResourcePath);
}
}
// Метод, що обробляє натискання кнопки вибору рівня
public void LevelButtonClicked(PackedScene levelToLoad)
{
// Змінює поточну активну сцену гри на обрану PackedScene
GetTree().ChangeSceneToPacked(levelToLoad);
}
}Сцена - PauseMenu
Огляд структури та функціоналу PauseMenu.
Дерево:
- PauseMenu (CanvasLayer)
- ColorRect
- MarginContainer
- HBoxContainer
- VBoxContainer
- LabelTitle
- ButtonToMenu %
- ButtonResume %
Пояснення:
PauseMenu працює як overlay-інтерфейс, що успадковує CanvasLayer для відображення поверх гри. ProcessModeEnum.Always, дозволяє меню обробляти введення та логіку, навіть коли вся гра перебуває в стані Paused. Клас взаємодіє з кнопками через Unique Names (%) та підписується на їхні події Pressed для виконання дій OnResume або OnMainMenu. Також реалізована обробка введення в _Input, що дозволяє закрити меню за допомогою клавіші ESC (ui_cancel). При виході з меню або переході в головне меню використовується QueueFree для очищення пам'яті від екземпляра меню.
Код:
using Godot;
public partial class PauseMenu : CanvasLayer
{
public override void _Ready()
{
// КРИТИЧНО: Встановлюємо ProcessMode на Always, щоб UI продовжував працювати,
// навіть коли GetTree().Paused = true
ProcessMode = ProcessModeEnum.Always;
// Отримуємо посилання на кнопки за допомогою Unique Names (%)
var resumeBtn = GetNode<Button>("%ButtonResume");
var menuBtn = GetNode<Button>("%ButtonToMenu");
// Підписуємося на подію Pressed для кожної кнопки
resumeBtn.Pressed += OnResume;
menuBtn.Pressed += OnMainMenu;
}
public override void _Input(InputEvent @event)
{
// Перевіряємо, чи натиснута клавіша, що відповідає дії "ui_cancel" (зазвичай ESC)
if (@event.IsActionPressed("ui_cancel"))
{
OnResume();
// Позначаємо подію як оброблену, щоб вона не передавалася далі в інші ноди
GetViewport().SetInputAsHandled();
}
}
// Метод для повернення в гру
private void OnResume()
{
// Знімаємо стан паузи з ігрового дерева
GetTree().Paused = false;
// Видаляємо екземпляр меню з пам'яті та сцени
QueueFree(); // Remove overlay
}
// Метод для виходу в головне меню
private void OnMainMenu()
{
// Знімаємо стан паузи, щоб головне меню працювало коректно
GetTree().Paused = false;
// Змінюємо поточну сцену на сцену головного меню
GetTree().ChangeSceneToFile("res://Scenes/Menus/MainMenu/MainMenu.tscn");
// Видаляємо меню паузи
QueueFree();
}
}Клас-Singleton - GameManager
Огляд структури та функціоналу GameManager.
Singleton (Одинак) — це патерн проєктування, який гарантує, що клас має лише один екземпляр, і надає до нього глобальну точку доступу.
Пояснення:
GameManager відповідає за реалізацію механіки паузи. При ініціалізації він завантажує PackedScene меню паузи та встановлює ProcessModeEnum.Always, що потрібно для того, щоб об'єкт міг реагувати на натискання клавіші ESC навіть тоді, коли вся інша гра у стані паузи. При виклику TogglePause, клас змінює стан GetTree().Paused та відповідно додає екземпляр меню в Root або видаляє його через QueueFree(). Цей механізм тісно взаємодіє з класом (сценою) PauseMenu, який надає користувачеві інтерфейс для повернення в гру або виходу в MainMenu.
ВАЖЛИВО:
Додайте GameManager у Globals.
Код:
using Godot;
public partial class GameManager : Node
{
// Зберігає посилання на файл сцени меню паузи
private PackedScene _pauseMenuScene;
// Зберігає посилання на створений екземпляр меню паузи в дереві сцени
private Node _pauseMenuInstance;
public override void _Ready()
{
// Завантажуємо PackedScene меню паузи з визначеного шляху
_pauseMenuScene = GD.Load<PackedScene>("res://Scenes/Menus/PauseMenu/PauseMenu.tscn");
// КРИТИЧНО: Встановлюємо ProcessMode в Always, щоб цей Node продовжував
// обробляти ввід та працювати, навіть коли GetTree().Paused = true
ProcessMode = ProcessModeEnum.Always;
}
public override void _UnhandledInput(InputEvent @event)
{
// Перевіряємо, чи було натиснуто дію "ui_cancel" (за замовчуванням це клавіша ESC)
if (@event.IsActionPressed("ui_cancel"))
{
TogglePause();
// Позначаємо подію як оброблену, щоб вона не передавалася далі іншим об'єктам
GetViewport().SetInputAsHandled();
}
}
private void TogglePause()
{
// Визначаємо новий стан паузи (інвертуємо поточний стан GetTree().Paused)
bool willBePaused = !GetTree().Paused;
// Встановлюємо стан паузи для всього дерева сцени
GetTree().Paused = willBePaused;
if (willBePaused)
{
// Якщо гра стає на паузу: створюємо екземпляр меню паузи та додаємо його в корінь сцени
_pauseMenuInstance = _pauseMenuScene.Instantiate<Node>();
GetTree().Root.AddChild(_pauseMenuInstance);
}
else
{
// Якщо гра виходить з паузи: перевіряємо існування меню та видаляємо його з пам'яті
if (_pauseMenuInstance != null && IsInstanceValid(_pauseMenuInstance))
{
_pauseMenuInstance.QueueFree();
_pauseMenuInstance = null;
}
}
}
// Публічний метод для примусового виходу з режиму паузи (наприклад, при натисканні кнопки "Resume")
public void Unpause()
{
if (GetTree().Paused)
TogglePause();
}
}Відео
Відео настройки проєкту/сцен.
GameManager:
MainMenu:
LevelMenu:
PauseMenu:
build info
- compiler
- 2.0.5.1 · 4054EF9BF6C4510A
- built
- 2026-11-08 14:31:25 UTC
WebUniverse