На хабре было много статей как создать такой движок, но их проблема была в том что они давали только отрывки кода, не объясняя полностью весь процесс, который бы мог бы полезен новичкам или полным нулям. Поэтому я решил импровизировать, но во многом мне помогла эта статья
Принцип работы
Итак, в чём же принцип работы такого рода движков? Да всё просто: от игрока бросается виртуальные лучи, которые ударясь об стену рисуя прямоугольники на экране. Каждому лучу соотвествует одна полоска проецированной стены на экране.
Но у моей первоначальной версии не хватало одного: поддержки углов, да и область видимости было только 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)
stungnthumz
Были написаны не Вами.