c++ SFML. Проблема с коллизией круга и квадратов
Проблема заключается в методе intersects. Суть в том, что все прекрасно работает ровно до того момента, пока круг не начинает идти в стену и влево/наверх. Круг перестает скользить вдоль стены на грани квадрата и следующего квадрата, будто бы уперся в невидимую стену. Я попробовал изменить проход по объектам(если раньше он начинал с первого объекта, то теперь с последнего) и направления блокировки инвертировались, то есть проблема как минимум частично заключается в этом. Как можно разрешить кругу скользить вдоль стен вне зависимости направления?
Код:
#include "math.h"
#include "SFML/Graphics.hpp"
sf::RenderWindow window;
sf::Event event;
sf::View camera;
const int amountOfTile = 32;
sf::RectangleShape tiles[amountOfTile][amountOfTile];
sf::CircleShape player;
void intersects(sf::CircleShape &player, sf::RectangleShape &rect) {
bool leftIsBlocked = 0;
bool rightIsBlocked = 0;
bool topIsBlocked = 0;
bool bottomIsBlocked = 0;
float testX = player.getPosition().x;
float testY = player.getPosition().y;
//проверка на то, какие грани пересекает круг
//изначально здесь были позиции точек квадрата, а не отдаление от центра. Однако ситуация в обоих случаях одинакова!
if (player.getPosition().x < rect.getPosition().x - 16) { testX = rect.getPosition().x - 16; leftIsBlocked = 1; } //левая грань
else if (player.getPosition().x > rect.getPosition().x + 16) { testX = rect.getPosition().x + 16; rightIsBlocked = 1; } //правая
if (player.getPosition().y < rect.getPosition().y - 16) { testY = rect.getPosition().y - 16; topIsBlocked = 1; } //верхняя
else if (player.getPosition().y > rect.getPosition().y + 16) { testY = rect.getPosition().y + 16; bottomIsBlocked = 1; } //нижняя
float distX = abs(player.getPosition().x - testX);
float distY = abs(player.getPosition().y - testY);
float distance = sqrt((distX * distX) + (distY * distY));
//если дистанция меньше радиуса круга, то пришло время для коллизии
if (distance <= 16) {
//если какое либо булево значение активно, высчитывается расстояние до точки на грани квадрата и уже оно вычитывается/прибавляется к позиции игрока
//предполагаю, что ошибка где то здесь, однако что бы я здесь не менял - не помогало
if (leftIsBlocked) player.setPosition(player.getPosition().x - abs(16 - distance), player.getPosition().y);
else if (rightIsBlocked) player.setPosition(player.getPosition().x + abs(16 - distance), player.getPosition().y);
if (topIsBlocked) player.setPosition(player.getPosition().x, player.getPosition().y - abs(16 - distance));
else if (bottomIsBlocked) player.setPosition(player.getPosition().x, player.getPosition().y + abs(16 - distance));
}
}
int main() {
srand(time(0));
window.create(sf::VideoMode(1000, 1000), "sfml");
window.setFramerateLimit(60);
//расстановка квадратов
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
tiles[i][j].setSize(sf::Vector2f(32, 32));
tiles[i][j].setOrigin(sf::Vector2f(16, 16));
tiles[i][j].setFillColor(sf::Color::Red);
tiles[i][j].setPosition(sf::Vector2f((i + 0) * 32, (j + 0) * 32));
tiles[i][j].setOutlineThickness(1);
tiles[i][j].setOutlineColor(sf::Color::Blue);
}
}
//случайные стены-квадраты
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
if (rand() % 100 > 90) {
tiles[i][j].setFillColor(sf::Color::Green);
}
}
}
//настройка персонажа
player.setRadius(16);
player.setOrigin(sf::Vector2f(16, 16));
player.setFillColor(sf::Color::White);
player.setPosition(sf::Vector2f(256, 128));
while (window.isOpen()) {
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
//resize window
if (event.type == sf::Event::Resized) {
sf::Vector2f cameraCenter = camera.getCenter();
camera.setSize(event.size.width, event.size.height);
camera.setCenter(cameraCenter);
}
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) player.move(0, -2.0f);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) player.move(0, 2.0f);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) player.move(-2.0f, 0);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) player.move(2.0f, 0);
//проверка всех возможных коллизий(в будущем проверяться будут ближайшие)
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
if (tiles[i][j].getFillColor() == sf::Color::Green) {
intersects(player, tiles[i][j]);
}
}
}
camera.setCenter(player.getPosition());
window.setView(camera);
window.clear();
//в будущем я засуну тайлы в вертексные массивы
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
window.draw(tiles[i][j]);
}
}
window.draw(player);
window.display();
}
return 0;
}```
Ответы (1 шт):
Короче проблема оказалась довольно неочевидной(как минимум для меня). Коллизия за раз проверяет один объект. То есть если объект пересекает два объекта, происходит коллизия сначала с одним объектом, а только потом со вторым - при этом результаты столкновения сразу переносятся в первый объект и если он более не пересекает второй объект - то все, коллизия произошла только с одним объектом. Это неправильно. Решением(одно из, скорее всего) является то, что нельзя двигать объект ровно до того момента, пока он не проверит себя с другими - то есть понадобится вектор сдвига, который будет применяться к основному объекту после всех проверок.
Исправленный код:
#include <iostream>
#include "math.h"
#include "SFML/Graphics.hpp"
sf::RenderWindow window;
sf::Event event;
sf::View camera;
const int amountOfTile = 32;
sf::RectangleShape tiles[amountOfTile][amountOfTile];
sf::CircleShape player;
void intersects(sf::CircleShape &player, sf::RectangleShape &rect, sf::Vector2f &vec) {
bool leftIsBlocked = 0;
bool rightIsBlocked = 0;
bool topIsBlocked = 0;
bool bottomIsBlocked = 0;
float testX = player.getPosition().x;
float testY = player.getPosition().y;
if (player.getPosition().x < rect.getPosition().x - 16) { testX = rect.getPosition().x - 16; leftIsBlocked = 1; } //левая грань
else if (player.getPosition().x > rect.getPosition().x + 16) { testX = rect.getPosition().x + 16; rightIsBlocked = 1; } //правая
if (player.getPosition().y < rect.getPosition().y - 16) { testY = rect.getPosition().y - 16; topIsBlocked = 1; } //верхняя
else if (player.getPosition().y > rect.getPosition().y + 16) { testY = rect.getPosition().y + 16; bottomIsBlocked = 1; } //нижняя
float distX = abs(player.getPosition().x - testX);
float distY = abs(player.getPosition().y - testY);
float distance = sqrt((distX * distX) + (distY * distY));
if (distance <= 16) { //теперь здесь двигается НЕ ИГРОК, а вектор сдвига, поэтому для остальных объектов игрок не сдвинулся. И да, это работает не совсем правильно на краю объектов, но я уже не знаю, как это решить. Но для моих нужд этого должно хватить
if (leftIsBlocked) vec.x -= abs(16 - distance);
else if (rightIsBlocked) vec.x += abs(16 - distance);
if (topIsBlocked) vec.y -= abs(16 - distance);
else if (bottomIsBlocked) vec.y += abs(16 - distance);
}
}
int main() {
srand(time(0));
window.create(sf::VideoMode(1000, 1000), "sfml");
window.setFramerateLimit(60);
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
tiles[i][j].setSize(sf::Vector2f(32, 32));
tiles[i][j].setOrigin(sf::Vector2f(16, 16));
tiles[i][j].setFillColor(sf::Color::Red);
tiles[i][j].setPosition(sf::Vector2f((i + 0) * 32, (j + 0) * 32));
tiles[i][j].setOutlineThickness(1);
tiles[i][j].setOutlineColor(sf::Color::Blue);
}
}
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
if (rand() % 100 > 90) {
tiles[i][j].setFillColor(sf::Color::Green);
}
}
}
player.setRadius(16);
player.setOrigin(sf::Vector2f(16, 16));
player.setFillColor(sf::Color::White);
player.setPosition(sf::Vector2f(256, 128));
while (window.isOpen()) {
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
//resize window
if (event.type == sf::Event::Resized) {
sf::Vector2f cameraCenter = camera.getCenter();
camera.setSize(event.size.width, event.size.height);
camera.setCenter(cameraCenter);
}
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) player.move(0, -2.0f);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) player.move(0, 2.0f);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) player.move(-2.0f, 0);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) player.move(2.0f, 0);
sf::Vector2f vec; //вектор сдвига
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
if (tiles[i][j].getFillColor() == sf::Color::Green) {
intersects(player, tiles[i][j], vec); //теперь здесь передается vec
}
}
}
player.setPosition(player.getPosition().x + vec.x, player.getPosition().y + vec.y); //применение вектора сдвига
camera.setCenter(player.getPosition());
window.setView(camera);
window.clear();
for (int i = 0; i < amountOfTile; ++i) {
for (int j = 0; j < amountOfTile; ++j) {
window.draw(tiles[i][j]);
}
}
window.draw(player);
window.display();
}
return 0;
}