158 lines
6.5 KiB
C++
158 lines
6.5 KiB
C++
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Tile2D walk — самое маленькое приложение, которое имеет смысл.
|
|||
|
|
//
|
|||
|
|
// Ни игры, ни редактора: открыть проект, собрать этаж, походить по нему
|
|||
|
|
// камерой. Существует ради одного вопроса — сколько кода нужно, чтобы что-то
|
|||
|
|
// показать на этом движке. Ответ виден целиком на одном экране, и если он
|
|||
|
|
// однажды перестанет туда влезать, это будет значить, что движок оброс тем,
|
|||
|
|
// что должно было остаться в игре.
|
|||
|
|
//
|
|||
|
|
// Проверяет заодно то, что нельзя проверить ни игрой, ни редактором: движок
|
|||
|
|
// собирается и работает БЕЗ них. Игра могла бы незаметно унести к себе что-то
|
|||
|
|
// общее — тайлсет, проекцию, цикл, — и никто бы не заметил, пока не появился
|
|||
|
|
// второй потребитель. Вот он.
|
|||
|
|
//
|
|||
|
|
// tile2d_walk [--project <имя>] [--depth N] [--seed N] [--windowed] [--shot N]
|
|||
|
|
// WASD / стрелки — идти, R — пересобрать этаж, Esc — выход
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
#include <raylib.h>
|
|||
|
|
|
|||
|
|
#include <cstdio>
|
|||
|
|
#include <cstring>
|
|||
|
|
|
|||
|
|
#include "engine/app.h"
|
|||
|
|
#include "engine/catalog.h"
|
|||
|
|
#include "engine/level_gen.h"
|
|||
|
|
#include "engine/project.h"
|
|||
|
|
#include "engine/room.h"
|
|||
|
|
#include "engine/tilemap.h"
|
|||
|
|
#include "engine/tilemap_draw.h"
|
|||
|
|
#include "engine/tileset.h"
|
|||
|
|
#include "engine/view2d.h"
|
|||
|
|
|
|||
|
|
int main(int argc, char** argv)
|
|||
|
|
{
|
|||
|
|
const char* projectName = nullptr;
|
|||
|
|
int depth = 1;
|
|||
|
|
uint32_t seed = 1;
|
|||
|
|
bool windowed = false;
|
|||
|
|
int shotFrame = -1; // --shot N: снять кадр и выйти (картинки в документацию)
|
|||
|
|
|
|||
|
|
for (int i = 1; i < argc; ++i)
|
|||
|
|
{
|
|||
|
|
if (std::strcmp(argv[i], "--project") == 0 && i + 1 < argc) projectName = argv[++i];
|
|||
|
|
else if (std::strcmp(argv[i], "--depth") == 0 && i + 1 < argc) depth = std::atoi(argv[++i]);
|
|||
|
|
else if (std::strcmp(argv[i], "--seed") == 0 && i + 1 < argc)
|
|||
|
|
seed = uint32_t(std::atoi(argv[++i]));
|
|||
|
|
else if (std::strcmp(argv[i], "--windowed") == 0) windowed = true;
|
|||
|
|
else if (std::strcmp(argv[i], "--shot") == 0 && i + 1 < argc)
|
|||
|
|
shotFrame = std::atoi(argv[++i]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- проект: пути, вид отображения, кривая глубины -------------------------
|
|||
|
|
Project project;
|
|||
|
|
std::string why;
|
|||
|
|
const std::string dir = FindProjectDir(projectName ? projectName : "");
|
|||
|
|
if (dir.empty() || !project.Load(dir, &why))
|
|||
|
|
{
|
|||
|
|
std::printf("проект не открыт: %s\n", dir.empty() ? "не выбран" : why.c_str());
|
|||
|
|
for (const std::string& n : AvailableProjects()) std::printf(" доступен: %s\n", n.c_str());
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
project.Apply(); // ставит проекцию и кривую на движок
|
|||
|
|
|
|||
|
|
EntityCatalog catalog;
|
|||
|
|
RoomLibrary rooms;
|
|||
|
|
if (!catalog.Load(project.catalogPath, &why) || !rooms.Load(project.roomsDir, &why))
|
|||
|
|
{
|
|||
|
|
std::printf("содержимое не читается: %s\n", why.c_str());
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- этаж -----------------------------------------------------------------
|
|||
|
|
static Tilemap map;
|
|||
|
|
static GeneratedLevel level;
|
|||
|
|
auto build = [&]() {
|
|||
|
|
if (GenerateLevel(rooms, catalog, DescribeFloor(depth), seed, map, level, &why)) return true;
|
|||
|
|
std::printf("этаж не собрался: %s\n", why.c_str());
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
if (!build()) return 1;
|
|||
|
|
|
|||
|
|
std::printf("%s: этаж %d (%s), сид %u, комнат %d, тварей %d\n", project.name.c_str(),
|
|||
|
|
level.floor.depth, ThemeName(level.floor.theme), unsigned(seed), level.roomsPlaced,
|
|||
|
|
level.monstersPlaced);
|
|||
|
|
|
|||
|
|
// --- окно и цикл ----------------------------------------------------------
|
|||
|
|
App app;
|
|||
|
|
AppConfig cfg;
|
|||
|
|
cfg.title = "Tile2D walk";
|
|||
|
|
cfg.borderless = !windowed;
|
|||
|
|
if (!app.Init(cfg)) return 1;
|
|||
|
|
|
|||
|
|
TileSet tiles;
|
|||
|
|
tiles.Generate();
|
|||
|
|
|
|||
|
|
v2d::Camera cam;
|
|||
|
|
cam.SnapTo(level.squadStart);
|
|||
|
|
Vec2 pos = level.squadStart;
|
|||
|
|
|
|||
|
|
while (app.NextFrame())
|
|||
|
|
{
|
|||
|
|
// Ввод в ЭКРАННЫХ осях переводится в мировые проекцией — тем же
|
|||
|
|
// способом, что и в игре. Без этого WASD в изометрии ведёт вбок.
|
|||
|
|
Vec2 move{};
|
|||
|
|
if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) move.x -= 1.0f;
|
|||
|
|
if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) move.x += 1.0f;
|
|||
|
|
if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP)) move.y -= 1.0f;
|
|||
|
|
if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN)) move.y += 1.0f;
|
|||
|
|
|
|||
|
|
const int steps = app.Advance(false);
|
|||
|
|
for (int i = 0; i < steps; ++i)
|
|||
|
|
{
|
|||
|
|
if (LengthSq(move) > 0.0f)
|
|||
|
|
{
|
|||
|
|
const Vec2 step = v2d::ScreenDirToWorld(move);
|
|||
|
|
pos = pos + step * (6.0f * FIXED_DT);
|
|||
|
|
map.ResolveCircle(pos, 0.3f); // сквозь стены не ходим
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (IsKeyPressed(KEY_R))
|
|||
|
|
{
|
|||
|
|
++seed;
|
|||
|
|
if (build()) { pos = level.squadStart; cam.SnapTo(pos); }
|
|||
|
|
}
|
|||
|
|
if (IsKeyPressed(KEY_ESCAPE)) app.quit = true;
|
|||
|
|
|
|||
|
|
cam.Follow(pos, app.frameDt, 9.0f);
|
|||
|
|
|
|||
|
|
int ox = 0, oy = 0;
|
|||
|
|
cam.ScreenOffset(ox, oy);
|
|||
|
|
|
|||
|
|
app.fb.Clear(RGBA(8, 8, 10));
|
|||
|
|
DrawTilemap(app.fb, map, tiles, ox, oy);
|
|||
|
|
|
|||
|
|
// Где стоим, где спуск. Кружком, а не спрайтом: спрайты — дело игры,
|
|||
|
|
// движок про то, как выглядит отряд, не знает ничего.
|
|||
|
|
auto dot = [&](Vec2 w, uint32_t c) {
|
|||
|
|
const Vec2 s = v2d::WorldToScreen(w);
|
|||
|
|
app.fb.CircleFilled(int(s.x) + ox, int(s.y) + oy, 3, c);
|
|||
|
|
};
|
|||
|
|
dot(level.exitPos, RGBA(60, 200, 90));
|
|||
|
|
dot(pos, RGBA(240, 230, 200));
|
|||
|
|
|
|||
|
|
app.Present();
|
|||
|
|
app.Finish();
|
|||
|
|
|
|||
|
|
if (shotFrame >= 0 && --shotFrame <= 0)
|
|||
|
|
{
|
|||
|
|
app.Screenshot("walk.png");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
app.Shutdown();
|
|||
|
|
return 0;
|
|||
|
|
}
|