0
\$\begingroup\$

I'm making an RPG game where players and enemies can collide with walls. Right now it pushes the player off the wall when he tries to 'go through' it. However, I detect which way he should be pushed based on his current direction. This means that enemies can still push him through walls. Also if the player moves diagonally, detection doesn't work properly. This is a problem... My game

I'm using SFML and these Collison-Detection methods: https://github.com/LaurentGomila/SFML/wiki/Source:-Simple-Collision-Detection-for-SFML-2

Specifically I'm using this method:

bool BoundingBoxTest(const sf::Sprite& Object1, const sf::Sprite& Object2) 
{
    OrientedBoundingBox OBB1 (Object1);
    OrientedBoundingBox OBB2 (Object2);

    // Create the four distinct axes that are perpendicular to the edges of the two rectangles
    sf::Vector2f Axes[4] = {
        sf::Vector2f (OBB1.Points[1].x-OBB1.Points[0].x,
        OBB1.Points[1].y-OBB1.Points[0].y),
        sf::Vector2f (OBB1.Points[1].x-OBB1.Points[2].x,
        OBB1.Points[1].y-OBB1.Points[2].y),
        sf::Vector2f (OBB2.Points[0].x-OBB2.Points[3].x,
        OBB2.Points[0].y-OBB2.Points[3].y),
        sf::Vector2f (OBB2.Points[0].x-OBB2.Points[1].x,
        OBB2.Points[0].y-OBB2.Points[1].y)
    };

    for (int i = 0; i<4; i++) // For each axis...
    {
        float MinOBB1, MaxOBB1, MinOBB2, MaxOBB2;

        // ... project the points of both OBBs onto the axis ...
        OBB1.ProjectOntoAxis(Axes[i], MinOBB1, MaxOBB1);
        OBB2.ProjectOntoAxis(Axes[i], MinOBB2, MaxOBB2);

        // ... and check whether the outermost projected points of both OBBs overlap.
        // If this is not the case, the Seperating Axis Theorem states that there can be no collision between the rectangles
        if (!((MinOBB2<=MaxOBB1)&&(MaxOBB2>=MinOBB1)))
        {
            return false;
        }
    }
    return true;
}

Note that the method only checks if the sprites collide, not how or where they collide. I don't know how exactly the collision detection works, I've tried printing out the OBB values but they're nonsensical to me.

Should I somehow modify the function, or is there another way of doing collision direction detection? I'm stuck here.

UPDATE

I managed to actually check for which direction the player was colliding with the wall. I have a really odd problem now: It only works properly on the last wall I place.

  • I have 4 booleans (canWalkUp, canWalkDown, canWalkLeft, canWalkRight) to check for if the player is holding down both left and right, or up and down - so they don't negate each other. This prevents wonky movement.
  • I also have 4 booleans to check if the player is currently colliding with a wall.

The code which handles player-boundary collision is as follows:

void EventHandler::collisionPlayerBoundary(std::shared_ptr<Entity> player,     std::shared_ptr<Entity> boundary)
{
std::vector<sf::Vector2f> distances = Collision::BoundingBoxDistance(player->sprite, boundary->sprite);
float pushSpeed = 75;
if(distances.at(0).y < 20) //push left!
{
    std::cout << "pushing left" << std::endl;
    player->pushX = -pushSpeed;
    player->canWalkRight = false;
    player->collidingRight = true;
}
if(distances.at(1).y < 20) //push down!
{
    std::cout << "pushing down" << std::endl;
    player->pushY = pushSpeed;
    player->canWalkUp = false;
    player->collidingUp = true;
}
if(distances.at(0).x < 20) //push right!
{
    std::cout << "pushing right" << std::endl;
    player->pushX = pushSpeed;
    player->canWalkLeft = false;
    player->collidingLeft = true;
}
if(distances.at(2).x < 20) //push up!
{
    std::cout << "pushing up" << std::endl;
    player->pushY = -pushSpeed;
    player->canWalkDown = false;
    player->collidingDown = true;
}
}
\$\endgroup\$
  • \$\begingroup\$ Remember that there are actually two things going on: collision detection and collision resolution. To resolve the collision you have to do extra work and figure out how far the objects intersected. This could be a simple vector which can be multiplied by the position of the collided object, resulting in directional movement. \$\endgroup\$ – Iggy Dec 29 '14 at 15:29
  • \$\begingroup\$ Is distances.at(2).x a typo? There only seem to be two Vector2f objects stored in the std::vector so I assume you are trying to access index 1 and not 2 for the vertical movement (as with your code for pushing down). \$\endgroup\$ – ChrisC Dec 29 '14 at 19:46
  • \$\begingroup\$ It's not really a typo, the vector actually has 4 elements, but you can choose to use the two of them. I can't explain it, as I don't fully understand it. However, I see that I can just use distances.at(1).x instead. Thanks. \$\endgroup\$ – Mathias Siig Nørregaard Dec 31 '14 at 12:09
0
\$\begingroup\$

First you need to add all forces (movements) applied to the player, in this case both the intentional movement and the enemy pushing (collisions with enemies first).

If both push against one another equally you're left with (0, 0) and you're done.

Otherwise, iteratively do collision detection (environment and enemies) using the potentially new position (current_position + movement), and reduce movement until there is no collision.

As long as movement isn't larger than the smallest AABB (incl. the character) discrete collision checks should be fine (as opposed to full raycast).

You can add a check if player_still_collides_post_push then hurt_player

Optionally you can check/reduce both X and Y movements separately to allow the player to slide along walls when pushed away. This works for square maps, maps with diagonals gets more complicated.

You can make enemy collisions "soft" allowing the player to intersect with enemies and not walls so the player can still squeeze out of a pinch while not going through walls.

\$\endgroup\$
0
\$\begingroup\$

There are two ways to this that I know of. The first is to separate the velocity of object into independent axis. For example, if you are at (0,0) moving towards (5,5), you would first move the object to (5,0) and then from (5,0) to (5,5). If there is an obstacle at (2,0) Then starting from (0,0) you would move object to (2,0) and then to (2,5).

Of course, the issue with this implementation is that the actual path taken by object and the path used for collision are different and thus sometimes your object will phase through objects and other times stopped by an "invisible" object. This can be alleviated (not completely) by ensuring the velocities are small. This might be enough for your needs.

The second method would be a sweep which actually returns the point of intersection and the normals of intersection. This method is more involved and here is an excellent tutorial: http://www.gamedev.net/page/resources/_/technical/game-programming/swept-aabb-collision-detection-and-response-r3084.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.