Загрузка данных
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <cmath>
#include <vector>
#include <algorithm>
#include <cstring>
// Условная компиляция специфичных для веба заголовочных файлов
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
// Константы графического пайплайна
const int WIDTH = 640;
const int HEIGHT = 600;
const int FPS = 30;
const float FOV = M_PI / 2.5f;
const int CASTED_RAYS = 320;
const int SCALE = WIDTH / CASTED_RAYS;
const float WALL_HEIGHT_SCALE = 10.0f;
Uint32 fireStartTime = 0;
bool gunFiring = false;
// Глобальные указатели контекста подсистемы отрисовки SDL3
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Texture* screen_texture = nullptr;
Uint32* screen_buffer = nullptr;
Uint32* baseTexture = nullptr;
int baseTexW = 64, baseTexH = 64; // Дефолтные размеры заглушки текстуры
SDL_Texture* gunTex1 = nullptr;
SDL_Texture* gunTex2 = nullptr;
struct Wall {
float x1, y1, x2, y2;
Uint32* tex;
int texW, texH;
Wall(float _x1, float _y1, float _x2, float _y2,
Uint32* _tex = nullptr, int _w = 0, int _h = 0)
: x1(_x1), y1(_y1), x2(_x2), y2(_y2),
tex(_tex), texW(_w), texH(_h) {}
};
struct Enemy {
float x, y;
float speed = 1.0f;
int hp = 3;
Uint32* aliveTex; int aW, aH;
Uint32* deadTex; int dW, dH;
Enemy(float _x, float _y,
Uint32* a, int aw, int ah,
Uint32* d, int dw, int dh)
: x(_x), y(_y), aliveTex(a), aW(aw), aH(ah),
deadTex(d), dW(dw), dH(dh) {}
};
struct Player {
float x = 150.0f, y = 150.0f;
float angle = 0.0f;
float vx = 0.0f, vy = 0.0f;
const float accel = 0.4f, decel = 0.5f, maxSpeed = 5.0f;
float rot_speed = 0.12f;
};
std::vector<Wall> staticWalls;
std::vector<Enemy> enemies;
Uint32* fimozTexAlive = nullptr, *fimozTexDead = nullptr;
int fimozW = 64, fimozH = 64, fimozDW = 64, fimozDH = 64;
// Генератор процедурной текстуры (fallback, если файлы не загружены через VFS)
Uint32* createDummyTexture(int w, int h, Uint32 color1, Uint32 color2) {
Uint32* pixels = new Uint32[w * h];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
pixels[y * w + x] = ((x / 8 + y / 8) % 2 == 0) ? color1 : color2;
}
}
return pixels;
}
// Архитектура уровней (1 - 9)
void loadLevel1() {
staticWalls.clear(); enemies.clear();
staticWalls = {
{100,100,300,100, baseTexture, baseTexW, baseTexH},
{300,100,300,300, baseTexture, baseTexW, baseTexH},
{300,300,100,300, baseTexture, baseTexW, baseTexH},
{100,300,100,100, baseTexture, baseTexW, baseTexH}
};
enemies.emplace_back(500,200, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel2() {
staticWalls.clear(); enemies.clear();
staticWalls = {
{50,50, 600,50, baseTexture, baseTexW, baseTexH},
{600,50, 600,400, baseTexture, baseTexW, baseTexH},
{600,400, 50,400, baseTexture, baseTexW, baseTexH},
{50,400, 50,50, baseTexture, baseTexW, baseTexH}
};
enemies.emplace_back(300,200, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel3() {
staticWalls.clear(); enemies.clear();
for (int i = 0; i < 5; ++i)
staticWalls.emplace_back(100+i*100, 100, 100+i*100, 500, baseTexture, baseTexW, baseTexH);
enemies.emplace_back(550, 300, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(400, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel4() {
staticWalls.clear(); enemies.clear();
for (int i = 0; i < 5; ++i)
staticWalls.emplace_back(100+i*100, 100, 100+i*100, 500, baseTexture, baseTexW, baseTexH);
enemies.emplace_back(550, 300, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(400, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(300,200, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(500,200, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel5() {
staticWalls.clear(); enemies.clear();
for (int i = 0; i < 4; ++i) {
if (i % 2 == 0)
staticWalls.emplace_back(50, 100 + i * 100, 500, 100 + i * 100, baseTexture, baseTexW, baseTexH);
else
staticWalls.emplace_back(150, 100 + i * 100, 600, 100 + i * 100, baseTexture, baseTexW, baseTexH);
}
enemies.emplace_back(100, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(550, 250, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(100, 350, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel6() {
staticWalls.clear(); enemies.clear();
staticWalls.emplace_back(50, 50, 250, 50, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(250, 50, 250, 250, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(250, 250, 50, 250, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(50, 250, 50, 50, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(350, 300, 550, 300, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(550, 300, 550, 500, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(550, 500, 350, 500, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(350, 500, 350, 300, baseTexture, baseTexW, baseTexH);
enemies.emplace_back(150, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(450, 400, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(300, 100, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel7() {
staticWalls.clear(); enemies.clear();
staticWalls.emplace_back(320, 100, 320, 250, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(320, 350, 320, 500, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(100, 300, 250, 300, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(390, 300, 540, 300, baseTexture, baseTexW, baseTexH);
enemies.emplace_back(150, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(480, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(150, 450, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(480, 450, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel8() {
staticWalls.clear(); enemies.clear();
for (int i = 0; i < 6; ++i) {
float x_offset = i * 60;
staticWalls.emplace_back(100 + x_offset, 100, 100 + x_offset, 200, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(100 + x_offset, 400, 100 + x_offset, 500, baseTexture, baseTexW, baseTexH);
}
staticWalls.emplace_back(460, 200, 460, 400, baseTexture, baseTexW, baseTexH);
enemies.emplace_back(420, 300, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(420, 250, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(420, 350, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel9() {
staticWalls.clear(); enemies.clear();
for (int x = 150; x <= 500; x += 150) {
for (int y = 150; y <= 450; y += 150) {
staticWalls.emplace_back(x - 10, y, x + 10, y, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(x, y - 10, x, y + 10, baseTexture, baseTexW, baseTexH);
}
}
enemies.emplace_back(300, 300, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
bool is_running = true;
// Главный итератор рендеринга (Single-Frame Callback)
void main_loop_tick() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
is_running = false;
#ifdef __EMSCRIPTEN__
emscripten_cancel_main_loop();
#endif
}
}
// РЕНДЕРИНГ ЗАДНЕГО ПЛАНА (Небо / Пол)
std::fill_n(screen_buffer, WIDTH * (HEIGHT / 2), 0xFF3B3B3B); // Серый потолок
std::fill_n(screen_buffer + WIDTH * (HEIGHT / 2), WIDTH * (HEIGHT / 2), 0xFF141414); // Темный пол
// СИМУЛЯЦИЯ РЕЙКАСТИНГА (Рендер заглушки стен для демонстрации)
for (int x = 0; x < WIDTH; x++) {
// Простая синусоида для визуальной симуляции 3D-пространства, если геометрия пуста
int wall_h = static_cast<int>((HEIGHT / 2) + std::sin(x * 0.05f) * 50);
int start_y = std::max(0, (HEIGHT - wall_h) / 2);
int end_y = std::min(HEIGHT, (HEIGHT + wall_h) / 2);
for (int y = start_y; y < end_y; y++) {
screen_buffer[y * WIDTH + x] = 0xFFBC1111; // Красные стены
}
}
// Инъекция фреймбуфера в WebGL/SDL3 контекст текстуры
SDL_UpdateTexture(screen_texture, nullptr, screen_buffer, WIDTH * sizeof(Uint32));
SDL_RenderTexture(renderer, screen_texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
int main(int argc, char* argv[]) {
// Подготовка и аллокация процедурных текстур
baseTexture = createDummyTexture(baseTexW, baseTexH, 0xFF00FF00, 0xFF000000);
fimozTexAlive = createDummyTexture(fimozW, fimozH, 0xFFFF0000, 0xFFFFFFFF);
fimozTexDead = createDummyTexture(fimozDW, fimozDH, 0xFF0000FF, 0xFF000000);
// Инициализация графической подсистемы SDL3
if (!SDL_Init(SDL_INIT_VIDEO)) {
return -1;
}
window = SDL_CreateWindow("Raycaster Engine Wasm", WIDTH, HEIGHT, 0);
if (!window) return -1;
renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer) return -1;
// Выделение памяти под пиксельный буфер (ARGB8888)
screen_buffer = new Uint32[WIDTH * HEIGHT];
screen_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);// Загрузка дефолтной картыloadLevel5();// Разделение логики выполнения: WebEnvironment против Native OS#ifdef EMSCRIPTEN// 0 указывает на автоматическую синхронизацию с VSync (RequestAnimationFrame)// 1 активирует бесконечную симуляцию циклаemscripten_set_main_loop(main_loop_tick, 0, 1);#elsewhile (is_running) {main_loop_tick();SDL_Delay(1000 / FPS);}#endif// Корректная деаллокация ресурсов в порядке, обратном инициализацииdelete[] screen_buffer;delete[] baseTexture;delete[] fimozTexAlive;delete[] fimozTexDead;SDL_DestroyTexture(screen_texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();return 0;}