Загрузка данных
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_image.h>
#include <cmath>
#include <vector>
#include <algorithm>
#include <cstring>
// Подключение специфичного заголовка Emscripten для веб-цикла
#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;
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Texture* screen_texture = nullptr;
Uint32* screen_buffer = nullptr;
Uint32* baseTexture = nullptr;
int baseTexW = 0, baseTexH = 0;
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.5f, decel = 0.15f, maxSpeed = 4.0f;
float rot_speed = 0.07f;
};
Player player;
int currentLevel = 1;
std::vector<Wall> staticWalls;
std::vector<Enemy> enemies;
Uint32* loadPixels(SDL_Surface* surf, int& w, int& h) {
if (!surf) {
// Fallback генерация шахматной текстуры, если файл не найден
w = 64; h = 64;
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) ? 0xFF00FF00 : 0xFF000000;
}
}
return pixels;
}
SDL_Surface* s = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_ARGB8888);
SDL_DestroySurface(surf);
w = s->w; h = s->h;
Uint32* pixels = new Uint32[w * h];
memcpy(pixels, s->pixels, w * h * sizeof(Uint32));
SDL_DestroySurface(s);
return pixels;
}
Uint32* fimozTexAlive = nullptr, *fimozTexDead = nullptr;
int fimozW = 0, fimozH = 0, fimozDW = 0, fimozDH = 0;
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(200,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(200, 200, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(450, 200, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(200, 400, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(450, 400, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(320, 300, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel10() {
staticWalls.clear(); enemies.clear();
staticWalls.emplace_back(20, 20, 620, 20, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(620, 20, 620, 580, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(620, 580, 20, 580, baseTexture, baseTexW, baseTexH);
staticWalls.emplace_back(20, 580, 20, 20, baseTexture, baseTexW, baseTexH);
enemies.emplace_back(300, 100, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
}
void loadLevel(int i) {
if (i == 1) loadLevel1();
else if (i == 2) loadLevel2();
else if (i == 3) loadLevel3();
else if (i == 4) loadLevel4();
else if (i == 5) loadLevel5();
else if (i == 6) loadLevel6();
else if (i == 7) loadLevel7();
else if (i == 8) loadLevel8();
else if (i == 9) loadLevel9();
else loadLevel10();
}
bool initSDL() {
if (!SDL_Init(SDL_INIT_VIDEO)) return false;
window = SDL_CreateWindow("Webasm Raycaster", WIDTH, HEIGHT, 0);
renderer = SDL_CreateRenderer(window, nullptr);
screen_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
screen_buffer = new Uint32[WIDTH * HEIGHT];
baseTexture = loadPixels(IMG_Load("bricks.png"), baseTexW, baseTexH);
fimozTexAlive = loadPixels(IMG_Load("fimoz.png"), fimozW, fimozH);
fimozTexDead = loadPixels(IMG_Load("fimozD.png"), fimozDW, fimozDH);
auto loadGun = [&](const char* file) {
SDL_Surface* s = IMG_Load(file);
if(!s) return (SDL_Texture*)nullptr;
SDL_Surface* c = SDL_ConvertSurface(s, SDL_PIXELFORMAT_ARGB8888);
SDL_DestroySurface(s);
SDL_Texture* t = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, c->w, c->h);
SDL_UpdateTexture(t, NULL, c->pixels, c->w * sizeof(Uint32));
SDL_DestroySurface(c);
return t;
};
gunTex1 = loadGun("gun1.png");
gunTex2 = loadGun("gun2.png");
return true;
}
bool shootEnemy(Player& p, const std::vector<Wall>& walls) {
float ray_angle = p.angle;
float dir_x = cosf(ray_angle), dir_y = sinf(ray_angle);
float wallDist = INFINITY;
for (auto& w : walls) {
float dx = w.x2 - w.x1, dy = w.y2 - w.y1;
float det = dir_x * dy - dir_y * dx;
if (fabs(det) < 1e-6f) continue;
float t = ((p.x - w.x1) * dy - (p.y - w.y1) * dx) / det;
float u = ((w.x1 - p.x) * dir_y - (w.y1 - p.y) * dir_x) / det;
if (t > 0 && u >= 0 && u <= 1) {
float ix = p.x + t * dir_x;
float iy = p.y + t * dir_y;
wallDist = std::min(wallDist, hypotf(ix - p.x, iy - p.y));
}
}
for (auto& enemy : enemies) {
if (enemy.hp <= 0) continue;
float dx = enemy.x - p.x;
float dy = enemy.y - p.y;
float nx = -dy, ny = dx;
float len = hypotf(nx, ny);
if (len < 1e-3f) len = 1.0f;
nx /= len; ny /= len;
float half = 20.0f;
Wall ew(enemy.x - nx * half, enemy.y - ny * half, enemy.x + nx * half, enemy.y + ny * half);
float det = dir_x * (ew.y2 - ew.y1) - dir_y * (ew.x2 - ew.x1);
if (fabs(det) < 1e-6f) continue;
float t = ((p.x - ew.x1) * (ew.y2 - ew.y1) - (p.y - ew.y1) * (ew.x2 - ew.x1)) / det;
float u = ((ew.x1 - p.x) * dir_y - (ew.y1 - p.y) * dir_x) / det;
if (t > 0 && u >= 0 && u <= 1) {
float ex = p.x + t * dir_x;
float ey = p.y + t * dir_y;
float dist = hypotf(ex - p.x, ey - p.y);
if (dist < wallDist) {
enemy.hp--;
return true;
}
}
}
return false;
}
void drawCrosshair(Uint32 color = 0xFF0000) {
int cx = WIDTH / 2, cy = HEIGHT / 2;
for (int dy = -5; dy <= 5; ++dy)
if (cy + dy >= 0 && cy + dy < HEIGHT)
screen_buffer[(cy + dy) * WIDTH + cx] = color;
for (int dx = -5; dx <= 5; ++dx)
if (cx + dx >= 0 && cx + dx < WIDTH)
screen_buffer[cy * WIDTH + cx + dx] = color;
}
void renderFrame(Player& p) {
float start = p.angle - FOV / 2;
float step = FOV / CASTED_RAYS;
// ОТРИСОВКА ПОЛА И ПОТОЛКА (Чтобы сцена не проваливалась в темноту)
std::fill_n(screen_buffer, WIDTH * (HEIGHT / 2), 0xFF444444); // Потолок
std::fill_n(screen_buffer + WIDTH * (HEIGHT / 2), WIDTH * (HEIGHT / 2), 0xFF222222); // Пол
std::vector<float> depthBuffer(CASTED_RAYS, INFINITY);
std::vector<Wall> all = staticWalls;
for (int r = 0; r < CASTED_RAYS; ++r) {
float ang = start + r * step;
float dx = cosf(ang), dy = sinf(ang);
float minD = INFINITY, bestU = 0;
Uint32* curTex = nullptr; int tW = 0, tH = 0;
for (auto& w : all) {
float ex = w.x2 - w.x1, ey = w.y2 - w.y1;
float det = dx * ey - dy * ex;
if (fabs(det) < 1e-6f) continue;
float t = ((p.x - w.x1) * ey - (p.y - w.y1) * ex) / det;
float u = ((w.x1 - p.x) * dy - (w.y1 - p.y) * dx) / det;
if (t > 0 && u >= 0 && u <= 1 && t < minD) {
minD = t; bestU = u;
curTex = w.tex; tW = w.texW; tH = w.texH;
}
}
if (minD < INFINITY && curTex) {
float dd = minD * cosf(p.angle - ang);
depthBuffer[r] = dd;
int h = int((HEIGHT * 2 * WALL_HEIGHT_SCALE) / (dd + 0.0001f));
int y0 = std::max(-h / 2 + HEIGHT / 2, 0);
int y1 = std::min(h / 2 + HEIGHT / 2, HEIGHT - 1);
int tx = std::clamp(int(bestU * tW), 0, tW - 1);
for (int y = y0; y < y1; ++y) {
int ty = (y - y0) * tH / h;
Uint32 col = curTex[ty * tW + tx];
if ((col >> 24) < 128) continue; // Пропуск прозрачных пикселей
for (int dxp = 0; dxp < SCALE; ++dxp) {
int sx = r * SCALE + dxp;
if (sx < WIDTH) screen_buffer[y * WIDTH + sx] = col;
}
}
}
}
all.clear();
for (auto& e : enemies) {
float dx = e.x - p.x, dy = e.y - p.y;
float nx = -dy, ny = dx;
float len = hypotf(nx, ny);
if(len < 1e-3f) len = 1.0f;
nx /= len; ny /= len;
float half = 20;
Uint32* tex = (e.hp > 0 ? e.aliveTex : e.deadTex);
int w = (e.hp > 0 ? e.aW : e.dW);
int h = (e.hp > 0 ? e.aH : e.dH);
all.emplace_back(e.x - nx * half, e.y - ny * half, e.x + nx * half, e.y + ny * half, tex, w, h);
}
for (int r = 0; r < CASTED_RAYS; ++r) {
float ang = start + r * step;
float dx = cosf(ang), dy = sinf(ang);
float minD = INFINITY, bestU = 0;
Uint32* curTex = nullptr; int tW = 0, tH = 0;
for (auto& w : all) {
float ex = w.x2 - w.x1, ey = w.y2 - w.y1;
float det = dx * ey - dy * ex;
if (fabs(det) < 1e-6f) continue;
float t = ((p.x - w.x1) * ey - (p.y - w.y1) * ex) / det;
float u = ((w.x1 - p.x) * dy - (w.y1 - p.y) * dx) / det;
if (t > 0 && u >= 0 && u <= 1 && t < minD) {
minD = t; bestU = u;
curTex = w.tex; tW = w.texW; tH = w.texH;
}
}
if (minD < INFINITY && curTex) {
float dd = minD * cosf(p.angle - ang);
if(dd > depthBuffer[r]) continue; // Проверка перекрытия стенами
int h = int((HEIGHT * 2 * WALL_HEIGHT_SCALE) / (dd + 0.0001f));
int y0 = std::max(-h / 2 + HEIGHT / 2, 0);
int y1 = std::min(h / 2 + HEIGHT / 2, HEIGHT - 1);
int tx = std::clamp(int(bestU * tW), 0, tW - 1);
for (int y = y0; y < y1; ++y) {
int ty = (y - y0) * tH / h;
Uint32 col = curTex[ty * tW + tx];
if ((col >> 24) < 128) continue;
for (int dxp = 0; dxp < SCALE; ++dxp) {
int sx = r * SCALE + dxp;
if (sx < WIDTH) screen_buffer[y * WIDTH + sx] = col;
}
}
}
}
drawCrosshair(0xFF2D9ED8);
}
// Главная функция одного игрового кадра (Main Loop Tick) для Emscripten
void main_loop_tick() {
SDL_Event e;
Uint32 frame_start = SDL_GetTicks();
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
#ifdef __EMSCRIPTEN__
emscripten_cancel_main_loop();
#endif
return;
}
if (e.type == SDL_EVENT_KEY_DOWN && e.key.scancode == SDL_SCANCODE_SPACE) {
gunFiring = true;
fireStartTime = frame_start;
shootEnemy(player, staticWalls);
}
}
const bool* keys = SDL_GetKeyboardState(NULL);
bool isAccel = false;
// УПРАВЛЕНИЕ: Стрелка ВВЕРХ — вперед, ВНИЗ — назад
if (keys[SDL_SCANCODE_DOWN]) {
player.vx += cosf(player.angle) * player.accel;
player.vy += sinf(player.angle) * player.accel;
isAccel = true;
}
if (keys[SDL_SCANCODE_UP]) {
player.vx -= cosf(player.angle) * player.accel;
player.vy -= sinf(player.angle) * player.accel;
isAccel = true;
}
float vlen = hypotf(player.vx, player.vy);
if (vlen > player.maxSpeed) {
player.vx *= player.maxSpeed / vlen;
player.vy *= player.maxSpeed / vlen;
}
if(!isAccel) {
player.vx *= (1.0f - player.decel);
player.vy *= (1.0f - player.decel);
if (hypotf(player.vx, player.vy) < 0.05f) { player.vx = 0; player.vy = 0; }
}
player.x += player.vx;
player.y += player.vy;
if (keys[SDL_SCANCODE_LEFT]) player.angle -= player.rot_speed;
if (keys[SDL_SCANCODE_RIGHT]) player.angle += player.rot_speed;
renderFrame(player);
SDL_UpdateTexture(screen_texture, NULL, screen_buffer, WIDTH * sizeof(Uint32));
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, screen_texture, NULL, NULL);
// Отрисовка интерфейса оружия поверх кадра
int gw = 200, gh = 150;
SDL_FRect dst = { (float)WIDTH / 2.0f - (float)gw / 2.0f, (float)HEIGHT - gh, (float)gw, (float)gh };
if (gunFiring && SDL_GetTicks() - fireStartTime < 200) {
if(gunTex2) SDL_RenderTexture(renderer, gunTex2, NULL, &dst);
} else {
gunFiring = false;
if(gunTex1) SDL_RenderTexture(renderer, gunTex1, NULL, &dst);
}
SDL_RenderPresent(renderer);
// Проверка триггера смены уровней
bool allDead = true;
for(auto& e : enemies) {
if(e.hp > 0) {
allDead = false;
break;
}
}
if(allDead && !enemies.empty()) {
currentLevel++;
if(currentLevel > 10) currentLevel = 1;
loadLevel(currentLevel);
player.x = 150.0f; player.y = 150.0f; player.angle = 0.0f; // Сброс позиции игрока
}
}
int main(int argc, char* argv[]) {
if (!initSDL()) return 1;
loadLevel(currentLevel);
#ifdef __EMSCRIPTEN__
// Передаем управление циклом браузеру
emscripten_set_main_loop(main_loop_tick, 0, 1);
#else
// Нативный цикл для десктопа (для отладки)
while (true) {
Uint32 start = SDL_GetTicks();
main_loop_tick();
Uint32 dt = SDL_GetTicks() - start;
if (dt < 1000 / FPS) SDL_Delay(1000 / FPS - dt);
}
#endif
// Очистка ресурсов при закрытии приложения
delete[] baseTexture;
delete[] screen_buffer;
for (auto& e : enemies) {delete[] e.aliveTex;delete[] e.deadTex;}if(screen_texture) SDL_DestroyTexture(screen_texture);if(renderer) SDL_DestroyRenderer(renderer);if(window) SDL_DestroyWindow(window);SDL_Quit();return 0;}