На хабре было много статей как создать такой движок, но их проблема была в том что они давали только отрывки кода, не объясняя полностью весь процесс, который бы мог бы полезен новичкам или полным нулям. Поэтому я решил импровизировать, но во многом мне помогла эта статья
Принцип работы
Итак, в чём же принцип работы такого рода движков? Да всё просто: от игрока бросается виртуальные лучи, которые ударясь об стену рисуя прямоугольники на экране. Каждому лучу соотвествует одна полоска проецированной стены на экране.
Но у моей первоначальной версии не хватало одного: поддержки углов, да и область видимости было только 3 квадрата.
void rendering(RectangleShape rects[], int columns[], int tex_nums[]){ if (!texture.loadFromFile("brick_wall.jpg")) { // error... std::cout<<"brick_wall.jpg not found!"<<std::endl; } if (!tex_soldier.loadFromFile("soldier.png")) { // error... std::cout<<"soldier.png not found!"<<std::endl; } for(int i = 0; i < 3; i++){ //rects[i].setFillColor(sf::Color::Green); float h = 480.f; float c {h / columns[i]+1}; // c = 5.2 if(tex_nums[i]==1){ rects[i].setTexture(&tex_soldier); } else{ rects[i].setTexture(&texture); } rects[i].setSize(sf::Vector2f(210,c)); rects[i].setPosition(sf::Vector2f(i*210, 240-c/2)); } } void raycasting(int x, int y, Enemy enemy) { static int tmp[3] = {0, 0, 0}; static int tex_nums[3] = {0,0,0}; // Обнуление временного массива перед новым вычислением std::fill(tmp, tmp + 3, 0); std::fill(tex_nums, tex_nums + 3, 0); for (int i = y; i >= 0; i--) { // Проверяем на границы if (x - 1 >= 0 && map[i][x - 1] == 0) tmp[0]++; if (map[i][x] == 0) tmp[1]++; if (x + 1 < 5 && map[i][x + 1] == 0) tmp[2]++; if (map[i][x] == 2 && enemy.getHP()>0){ tex_nums[1] = 1; is_visible = true;} } if(tmp[0] == 0) tmp[0] = 1; if(tmp[2] == 0) tmp[2] = 1; std::cout << x << " " << y << std::endl; std::cout << tmp[0] << " " << tmp[1] << " " << tmp[2] << std::endl; rendering(rectangles, tmp, tex_nums); }

Поэтому я решил переписать, с JS на свой любимый C++, код о котором говорится в начале этой статьи.
#include <SFML/Graphics.hpp> #include <cmath> #include <vector> struct Player { float x = 1; float y = 1; float angle = 1; float fov = M_PI / 4; } player; std::vector<std::vector<int>> map = { {1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 1, 0, 1, 0, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 1, 1, 0, 1, 0, 0, 1}, {1, 0, 1, 0, 0, 1, 0, 0, 1}, {1, 0, 0, 1, 0, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 1, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1} }; int getMapCell(int y, int x) { if (y < 0 || y >= (int)map.size() || x < 0 || x >= (int)map[0].size()) { return 1; } return map[y][x]; } float castRay(float rayAngle) { float x = player.x; float y = player.y; float dx = cos(rayAngle); float dy = sin(rayAngle); int i = 0; while (getMapCell(static_cast<int>(y), static_cast<int>(x)) == 0) { x += dx * 0.1; y += dy * 0.1; i++; if (i > 400) break; } const float distance = sqrt(pow((x - player.x), 2) + pow((y - player.y), 2)); const float wallHeight = 300 / distance; return wallHeight; } std::vector<sf::RectangleShape> wallSlices; void drawWallSlice(int i, float wallHeight, float sliceWidth) { // Создаем новый прямоугольник для каждого среза стены sf::RectangleShape slice(sf::Vector2f(sliceWidth, wallHeight)); slice.setFillColor(sf::Color(180, 0, 180)); // Позиционируем срез по центру экрана по вертикали float yPosition = 300 - wallHeight / 2; slice.setPosition(i * sliceWidth, yPosition); wallSlices.push_back(slice); } void raycast() { // Очищаем предыдущие срезы wallSlices.clear(); const int rays = 200; const int screenWidth = 800; const float sliceWidth = screenWidth / rays; const float angleStep = player.fov / rays; for (int i = 0; i < rays; i++) { const float rayAngle = player.angle - (player.fov / 2) + i * angleStep; float wallHeight = castRay(rayAngle); drawWallSlice(i, wallHeight, sliceWidth); } } int main() { sf::RenderWindow window(sf::VideoMode(800, 600), "Raycaster"); // Настройка игрока для лучшей видимости player.x = 1.5; player.y = 1.5; player.angle = 0; while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); // Добавляем управление для тестирования if (event.type == sf::Event::KeyPressed) { if (event.key.code == sf::Keyboard::Left) { player.angle -= 0.1; } if (event.key.code == sf::Keyboard::Right) { player.angle += 0.1; } } } // Выполняем рейкастинг raycast(); // Очищаем экран window.clear(sf::Color::Black); // Отрисовываем все срезы стен for (const auto& slice : wallSlices) { window.draw(slice); } window.display(); } return 0; }
P.S: Чтобы использовать текстуры стены используйте этот код:
slice.setTexture(&wallTexture); int texX = static_cast<int>((i * sliceWidth)) % wallTexture.getSize().x; slice.setTextureRect(sf::IntRect(texX, 0, 1, wallTexture.getSize().y));
вместо этого:
slice.setFillColor(sf::Color(180, 0, 180));

Заодно, за это время, у меня появилась вот такая оригинальная идея для игры,которая использует мемы и аниме в виде спарйтов.
Исходиники первой версии (Я уже не сижу на гитхабе, поэтому не удивлятесь лишней папке magic)
