Hi dear community,
I have added collision response for a dynamic entity colliding with a static one (at least that's what I spontaneously chose to call them). By static I mean it doesn't move and by dynamic I mean that it can move.
If I run into a static object and let go of the controls, my entity is positioned nicely at the outer bounds of the static once. (for reference see attachment)
However, if I, after colliding with the static object, continue to move towards the static object (which causes the dynamic object to slide btw), the dynamic one is further in than the outside bounds of the static object (for reference see attachment) Once I stop moving, it immediately jumps back to the correct position.
I think this is a bug (in my game of course!), and it rather annoys me. I mean, it still fulfills its purpose as preventing the player from running through a brick wall, but I believe it isn't correct.
I can't figure out what I have to change in my moving/collision response mechanics to solve this problem. I would be very thankful if someone could help me out and give me some hints.
I checked out this thread:
http://en.sfml-dev.org/forums/index.php?topic=13766.0 and I really loved the little example program, but I couldn't really use it to fix my problem.
Another question I have, however it is even less related to SFML, is whether my method of collision response is appropriate. I have read quite many blog posts, tutorials and StackOverflow posts, yet frankly I only understood half of them at best, and from what I picked up, my method was once described (except that I don't use a collision normal (yep, don't exactly know what that is either) to determine the axis, but some ugly if-statements). Please consider this a low priority question, if it is not linked to my problem.
Here is the response code (added comments for hopefully clarification):
// pair is a pair of colliding Entities
else if (matches_categories(pair, Category::DynamicBody, Category::StaticBody)) {
// dynamic as in movable (e.g. a car), static as in non-movable (e.g. a wall)
auto& dynamicEnt = static_cast<Entity&>(*pair.first);
auto& staticEnt = static_cast<Entity&>(*pair.second);
auto dynamicBounds = dynamicEnt.getBoundingRect();
auto staticBounds = staticEnt.getBoundingRect();
// since the origins are centered, I need additional offset
// so setPosition() puts it to the outside of the static Entity
const float offset = dynamicBounds.width / 2.f;
sf::Vector2f position = dynamicEnt.getPosition();
// in some situations the dynamic entity would jump around (to the corners mostly), adding
// this tolerance value fixed the problem
float tol = 5;
if (bottom(dynamicBounds) > staticBounds.top + tol && dynamicBounds.top < bottom(staticBounds) - tol) {
// the collision if happening along the X-Achsis
// dynamicEnt.setVelocity(0, dynamicEnt.getVelocity().y);
if (position.x > staticEnt.getPosition().x)
// collision happened to the right of the static entity
position.x = right(staticBounds) + offset;
else
// collision happened to the left of the static entity
position.x = staticBounds.left - offset;
}
else if (right(dynamicBounds) > staticBounds.left && dynamicBounds.left < right(staticBounds)) {
// the collision is happending along the Y-Achsis
// dynamicEnt.setVelocity(dynamicEnt.getVelocity().x, 0);
if (position.y > staticEnt.getPosition().y)
// collision happened at rhe bottom of the static entity
position.y = bottom(staticBounds) + offset;
else
// collision happened at the top of the static entity
position.y = staticBounds.top - offset;
}
dynamicEnt.setPosition(position);
My update of the world is something like this (broken down):
update
set player velocity (handle input)
handle collisions
update entities (e.g. move player by player velocity * dt)
set player velocity to (0, 0)
My input handling for movement is done like this (the actual direction is determined by rotation):
if direction Forward
entity.setVelocity(0, -mech.get_max_speed());
else if direction Backward
entity.setVelocity(0, mech.get_max_speed());
};
I have created a minimal example. I know the positioning isn't perfect from each side, but it illustrates what I have described quite nicely:
#include <SFML/Graphics.hpp>
float right(const sf::FloatRect& rect) {
return rect.left + rect.width;
}
float bottom(const sf::FloatRect& rect) {
return rect.top + rect.height;
}
void check_handle_collision(sf::RectangleShape& dynamic,
const sf::RectangleShape& stat)
{
auto dynamicBounds = dynamic.getGlobalBounds();
auto staticBounds = stat.getGlobalBounds();
if(dynamicBounds.intersects(staticBounds)) {
const float offset = dynamicBounds.width / 2.f;
sf::Vector2f position = dynamic.getPosition();
float tol = 2;
if (bottom(dynamicBounds) > staticBounds.top + tol && dynamicBounds.top < bottom(staticBounds) - tol) {
if (position.x > stat.getPosition().x)
position.x = right(staticBounds) + offset;
else
position.x = staticBounds.left - offset;
}
else if (right(dynamicBounds) > staticBounds.left && dynamicBounds.left < right(staticBounds)) {
if (position.y > stat.getPosition().y)
position.y = bottom(staticBounds) + offset;
else
position.y = staticBounds.top - offset;
}
dynamic.setPosition(position);
}
}
int main() {
sf::RenderWindow window(sf::VideoMode(640, 480), "Minimal example");
sf::RectangleShape dynamic(sf::Vector2f(64, 64));
dynamic.setFillColor(sf::Color::Transparent);
dynamic.setOutlineColor(sf::Color::Green);
dynamic.setOutlineThickness(1);
dynamic.setOrigin(dynamic.getGlobalBounds().width / 2.f,
dynamic.getGlobalBounds().height / 2.f);
dynamic.setPosition(100, 100);
sf::RectangleShape stat = dynamic; // short for static
stat.setOutlineColor(sf::Color::Blue);
stat.setPosition(sf::Vector2f(window.getSize()) / 2.f);
sf::Clock clock;
sf::Time timeSinceLastUpdate = sf::Time::Zero;
const sf::Time TimePerFrame = sf::seconds(1.f / 60.f);
const float speed = 200.f;
sf::Vector2f vel(0, 0);
while (window.isOpen()) {
sf::Time dt = clock.restart();
timeSinceLastUpdate += dt;
while (timeSinceLastUpdate > TimePerFrame) {
timeSinceLastUpdate -= TimePerFrame;
sf::Vector2f vel(0, 0);
sf::Event event;
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed)
window.close();
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
vel.y += -speed;
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
vel.y += speed;
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
vel.x += -speed;
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
vel.x += speed;
check_handle_collision(dynamic, stat);
dynamic.move(vel * TimePerFrame.asSeconds());
}
window.clear();
window.draw(stat);
window.draw(dynamic);
window.display();
}
}
Sorry for the long post, I just really wanted to describe things precisely, and provide sufficient material.