#pragma once // ----------------------------------------------------------------------------- // Приложение Tile2D: окно, кадр и ход времени. // // До этого окно, безрамочный режим, фреймбуфер, блиттер и fixed timestep жили // двумя почти одинаковыми копиями — в игре и в редакторе. Третьему приложению // пришлось бы написать третью, и она бы уже отличалась: где-то забытый // `SetExitKey`, где-то другой потолок шагов, где-то апскейл не целым числом. // // Здесь НЕ инверсия управления: цикл остаётся у приложения, а App отвечает на // вопросы «пора ли закрываться», «сколько шагов симуляции прошло» и «куда лёг // кадр в окне». Колбэки сделали бы порядок «шаг — рендер — интерфейс» // невидимым, а он у каждого приложения свой и в нём вся суть. // // Типичный цикл целиком: // // App app; // app.Init({"Моя игра"}); // while (app.NextFrame()) // { // for (int i = 0, n = app.Advance(false); i < n; ++i) game.Step(FIXED_DT); // Render(game, app.fb, app.Alpha()); // app.Present(); // кадр на экран // DrawUi(app.fit); // поверх кадра, примитивами raylib // app.Finish(); // } // app.Shutdown(); // ----------------------------------------------------------------------------- #include "engine/config.h" #include "engine/framebuffer.h" struct AppConfig { const char* title = "Tile2D"; // Безрамочное на весь монитор — обычный режим. Окно нужно для отладки и // для съёмки картинок: на полном экране их не сравнить между собой. bool borderless = true; bool vsync = true; // Разрешение кадра. По умолчанию — размер из engine/config.h. int frameW = INTERNAL_W; int frameH = INTERNAL_H; }; struct App { Framebuffer fb; ScreenBlitter screen; // --- состояние текущего кадра --------------------------------------------- int winW = 0; int winH = 0; // Куда лёг кадр в окне: на безрамочном экране он центрирован и вокруг него // чёрные поля. Интерфейс и оверлей обязаны считать координаты ЧЕРЕЗ это. ScreenRect fit; float frameDt = 0.0f; // Приложение может попроситься наружу само (пункт меню «выход», Esc в // редакторе). Отдельный флаг, а не возврат из NextFrame: решение о выходе // принимается в середине кадра, а закрыться надо в его конце. bool quit = false; bool Init(const AppConfig& cfg); void Shutdown(); // Начало кадра: закрытие окна, F11, размеры, ScreenFitRect, dt. // false — пора выходить. bool NextFrame(); // Сколько раз выполнить шаг симуляции в этом кадре. Шаг постоянный // (FIXED_DT), лишнее время копится до следующего кадра, потолок // MAX_STEPS_PER_FRAME спасает от спирали смерти. // // halted — пауза: время НЕ копится. Иначе после меню симуляция догоняла бы // пропущенные секунды одним рывком. int Advance(bool halted); // Доля незакрытого шага, [0..1). По ней интерполируется картинка: рендер // идёт чаще симуляции, и без неё движение дёргалось бы на 60+ Гц. float Alpha() const; void Present(); // BeginDrawing + кадр на экран; дальше можно рисовать UI void Finish(); // EndDrawing // Снять кадр в файл. Здесь, а не в приложении, потому что снимать надо // ПОСЛЕ Finish, и об этом легко забыть. void Screenshot(const char* file) const; private: double accumulator = 0.0; };