Пауза: игровой мир, интерфейс и таймеры
ProcessMode, физика, сигналы и CreateTimer; рабочие кнопки паузы и продолжения.
Запустить паузу с работающим интерфейсом
Добавьте ProcessCounter2D.cs и PauseDemo2D.cs в C#-проект 1.0.0. Прикрепите PauseDemo2D к Entity2D отдельного уровня, задайте viewport 640×420, соберите и нажмите F6. Pausable останавливается после кнопки «Пауза», Always и статус продолжают обновляться. «Продолжить» возвращает игровое время.
ProcessCounter2D.cs
using Reckfell;
public partial class ProcessCounter2D : Entity2D
{
public double Elapsed { get; private set; }
public int ProcessCalls { get; private set; }
public int PhysicsCalls { get; private set; }
public override void _Process(double delta) { Elapsed += delta; ProcessCalls++; QueueRedraw(); }
public override void _PhysicsProcess(double delta) => PhysicsCalls++;
public override void _Draw()
{
DrawCircle(Vector2.Zero, 36, new Color(.08f, .09f, .11f));
DrawCircle(Vector2.FromAngle((float)Elapsed * 2) * 27, 8, new Color(.25f, .45f, 1));
DrawString(ThemeDB.FallbackFont, new Vector2(-40, 64), Name.ToString(), fontSize: 17);
}
}PauseDemo2D.cs
using Reckfell;
public partial class PauseDemo2D : Entity2D
{
public ProcessCounter2D GameClock { get; private set; } = null!;
public ProcessCounter2D UiClock { get; private set; } = null!;
public Label Status { get; private set; } = null!;
private bool _previousPause;
public override void _EnterTree()
{
ProcessMode = ProcessModeEnum.Always;
_previousPause = GetTree().Paused;
}
public override void _Ready()
{
bool ru = TranslationServer.GetLocale().StartsWith("ru");
AddChild(new Label { Position = new Vector2(28, 24), Text = ru ? "Пауза: игровое время и интерфейс" : "Pause: game time and interface" });
GameClock = new ProcessCounter2D { Name = "Pausable", Position = new Vector2(180, 225), ProcessMode = ProcessModeEnum.Pausable };
UiClock = new ProcessCounter2D { Name = "Always", Position = new Vector2(440, 225), ProcessMode = ProcessModeEnum.Always };
AddChild(GameClock);
AddChild(UiClock);
Status = new Label { Position = new Vector2(28, 340) };
AddChild(Status);
var pause = new Button { Position = new Vector2(28, 82), Text = ru ? "Пауза" : "Pause" };
var resume = new Button { Position = new Vector2(160, 82), Text = ru ? "Продолжить" : "Resume" };
pause.Pressed += () => GetTree().Paused = true;
resume.Pressed += () => GetTree().Paused = false;
AddChild(pause);
AddChild(resume);
}
public override void _Process(double delta)
{
if (Status is not null) Status.Text = "Paused: " + GetTree().Paused + " | Pausable: " + GameClock.ProcessCalls + " | Always: " + UiClock.ProcessCalls;
}
public override void _ExitTree() => GetTree().Paused = _previousPause;
}Демонстрация владеет глобальной паузой и при выходе восстанавливает исходное состояние. В игре назначьте одного ответственного контроллера; несколько независимых компонентов, меняющих один Paused, могут снять чужую паузу.
Выбрать ProcessMode
| Режим Entity | Поведение |
|---|---|
| Inherit | Наследует режим по иерархии Parent |
| Pausable | Обрабатывается, когда EntityTree.Paused = false |
| WhenPaused | Обрабатывается, когда Paused = true |
| Always | Обрабатывается при обоих состояниях |
| Disabled | Обычная обработка отключена; дети с собственным явным режимом могут отличаться |
Наследование режима следует Parent, а не сериализационному Owner. IsProcessing показывает включённый callback, но не обещает, что объект сейчас проходит паузу и условия активного дерева. Для этого есть CanProcess. SetProcess(false) и ProcessMode решают связанные, но разные задачи.
Контроллер кнопки продолжения должен обрабатываться при паузе: оставленный Pausable интерфейс может потерять ввод, которым вы собирались его оживить. Сигналы и прямые методы, включая CallGroup, не перестают выполняться только из-за Paused. Сам факт, что обработчик перестал получать _Process, не делает его недоступным для других вызовов.
Физика и ожидания
EntityTree.Paused останавливает 2D/3D physics servers в обычном режиме. Always может продолжать _PhysicsProcess callback, но сам по себе не включает остановленную симуляцию. Стенд проверяет одновременно растущий счётчик Always и остановленное RigidBody2D. Не путайте выполнение кода физического шага с интеграцией тела.
| Ожидание | Поведение при паузе |
|---|---|
| CreateTimer(seconds) | По умолчанию processAlways = true: таймер продолжает идти |
| CreateTimer(seconds, processAlways: false) | Останавливается до продолжения игры |
| Entity Timer | Учитывает свой ProcessMode и отдельные свойства Timer |
| Подписка/await на сигнал | Может завершиться, если источник испустит сигнал во время паузы |
Пауза не равна Engine.TimeScale = 0 и не равна удалению объекта. Для замедления, игнорирования time scale и физического шага таймера нужны отдельные настройки. Проверены обе политики EntityTreeTimer, Timer с Pausable/Always, сигналы и возобновление; системный сон приложения и все платформенные события этим примером не покрыты.