Десять вех одним состоянием: разделять их пофайлово нечем, потому что
src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений,
все проверки проходят.
ДВИЖОК (engine/, цель tile2d)
Каталог свой, и решение «это движок» принимается тем, куда положен файл.
Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра
и карты уехали в engine/config.h, потолок населения этажа стал своим
(MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала
центром карты вместо позиции отряда.
engine/projection.* единственное место, где движок знает про изометрию;
видов сразу два — абстракция с одной реализацией ничем
не проверена и обычно оказывается неправильной
engine/project.* манифест проекта: пути, вид, кривая глубины
engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла
engine/tilemap_draw движок научился показывать свою карту
РЕДАКТОР (editor/, цель tile2d_editor)
Открывает любой проект и ни одной игры по имени не знает.
--tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка
этажа, ASCII-карта, проверка набора. Правку делают файлами,
последствия читают командой (docs/19-agent.md)
терминал снизу: те же инструменты, git, сборка и запросы к claude/codex,
открытый проект уходит потомкам через TILE2D_PROJECT
ПРИМЕР (samples/walk, цель tile2d_walk)
Второй потребитель движка. Игра и редактор самостоятельность движка
проверить не могут: редактор мира не рисует, а игра — то место, куда общий
код и уезжает незаметно.
ИГРА (src/)
Экстракшен: вес и два порога переноски, конечный боезапас, расходники
применяются сами, общая сумка отряда, трата опыта на улучшения.
Каждое из правил проверяется прогоном, а не глазами, и проверки поймали:
вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче);
боец с клинком не тратил найденные патроны при запасном стволе в сумке;
массив виджетов экрана переполнился и молча съел хвост схрона; проверка
улучшений сама ломала забег, вызывая NewRun посреди спуска.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
4.7 KiB
C++
93 lines
4.7 KiB
C++
#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;
|
||
};
|