Загрузка данных
// Полный код с уровнями
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_image.h>
#include <cmath>
#include <vector>
#include <algorithm>
#include <cstring>
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.4f, decel = 0.5f, maxSpeed = 5.0f;
float rot_speed = 0.12f;
};
std::vector<Wall> staticWalls;
std::vector<Enemy> enemies;
Uint32* loadPixels(SDL_Surface* surf, int& w, int& h) {
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(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);
}
// --- Новые уровни (с 5 по 10) ---
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();
// Две изолированные квадратные комнаты (арены)
// Комната 1
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);
// Комната 2
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);
// Настоящая армия "Фимозов" (6 штук)
enemies.emplace_back(300, 100, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(400, 150, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(500, 250, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(300, 500, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(200, 400, fimozTexAlive, fimozW, fimozH, fimozTexDead, fimozDW, fimozDH);
enemies.emplace_back(500, 450, 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 if (i == 10) loadLevel10();
else {
// Если прошли все 10 уровней — возвращаемся на первый уровень
loadLevel1();
}
}
bool initSDL() {
if (!SDL_Init(SDL_INIT_VIDEO)) return false;
window = SDL_CreateWindow("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];
SDL_Surface* surf = IMG_Load("bricks.png");
baseTexture = loadPixels(surf, baseTexW, baseTexH);
SDL_Surface* aS = IMG_Load("fimoz.png");
fimozTexAlive = loadPixels(aS, fimozW, fimozH);
SDL_Surface* dS = IMG_Load("fimozD.png");
fimozTexDead = loadPixels(dS, fimozDW, fimozDH);
auto loadGun = [&](const char* file) {
SDL_Surface* s = IMG_Load(file);
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);
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;
memset(screen_buffer, 0, WIDTH * HEIGHT * sizeof(Uint32));
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];
Uint8 a = col >> 24;
if (a < 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);
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];
Uint8 a = col >> 24;
if (a < 128) continue;
for (int dxp = 0; dxp < SCALE; ++dxp) {
int sx = r * SCALE + dxp;
if (sx < WIDTH) screen_buffer[y * WIDTH + sx] = col;
}
}
}
}
// Crosshair
drawCrosshair(0xFF2D9ED8);
}
int main(int argc, char* argv[]) {
if (!initSDL()) return 1;
Player player;
int currentLevel = 1;
loadLevel(currentLevel);
SDL_Event e;
while (true) {
Uint32 frame_start = SDL_GetTicks();
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) goto end;
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 - player.decel);
player.vy *= (1 - player.decel);
}
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, 255, 255, 255, 255);
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, screen_texture, NULL, NULL);
// Отрисовка оружия
int gw = 200, gh = 150;
SDL_FRect dst = { WIDTH / 2 - gw / 2, HEIGHT - gh, gw, gh };
if (gunFiring && SDL_GetTicks() - fireStartTime < 200)
SDL_RenderTexture(renderer, gunTex2, NULL, &dst);
else {
gunFiring = false;
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)
{
currentLevel++;
loadLevel(currentLevel);
}
Uint32 dt = SDL_GetTicks() - frame_start;
if (dt < 1000 / FPS) SDL_Delay(1000 / FPS - dt);
}
end:
delete[] baseTexture;
delete[] screen_buffer;
for (auto& e : enemies) {
delete[] e.aliveTex;
delete[] e.deadTex;
}
SDL_DestroyTexture(screen_texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
SDL_Quit();
return 0;
}