Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: SFML Pixel Perfect Collision only colliding when player facing right.  (Read 1127 times)

0 Members and 1 Guest are viewing this topic.

arzaan789

  • Newbie
  • *
  • Posts: 1
    • View Profile
    • Email
I'm learning SFML and making a basic 2D medieval game. As you can see in the image, the enemy collides with me when I am facing the right direction and hence it attacks me, but when I face the left direction it does not collide and hence walks straight. How do I fix this? I need it to collide even if I am facing the left direction. I have even outputted on the console if there is a collision, but it only outputs a collision if the player faces right as a collision is only happening in that case for some reason.

main.cpp
#include<iostream>

#include<SFML\Graphics.hpp>

#include "Collision.hpp"
#include "Enemy.h"
#include "Player.h"

using namespace std;
int main() {

    sf::RenderWindow window(sf::VideoMode(1280, 720), "My title", sf::Style::Titlebar | sf::Style::Close);


    sf::Texture backgroundTexture;
    sf::RectangleShape Background;
    backgroundTexture.loadFromFile("2_game_background.png");
    backgroundTexture.setSmooth(true);

    Background.setSize(sf::Vector2f(1280.0f, 720.0f));
    Background.setOrigin(sf::Vector2f(0.0f, 0.0f));
    Background.setPosition(0, 0);
    Background.setTexture(&backgroundTexture);




    sf::Texture playerTexture;
    Collision::CreateTextureAndBitmask(playerTexture, "knight.png");
    playerTexture.setSmooth(true);

    Player player(&playerTexture, sf::Vector2u(10, 3),0.06f,170.0f);

    sf::Texture reaper2Texture;
    Collision::CreateTextureAndBitmask(reaper2Texture, "reaper2.png");
    reaper2Texture.setSmooth(true);

    Enemy enemy(&reaper2Texture, sf::Vector2u(11, 3), 0.06f, 100.0f);


    float deltaTime = 0.0f;
    sf::Clock clock;




    while (window.isOpen()) {

        deltaTime = clock.restart().asSeconds();

        sf::Event evnt;

        while (window.pollEvent(evnt)) {

            switch (evnt.type) {

            case sf::Event::Closed:
                window.close();
                break;
            }
        }




        if (Collision::PixelPerfectTest(player.body, enemy.body)) {

            enemy.UpdateAttack(deltaTime);
            player.UpdateMove(deltaTime, true, enemy.faceRight);
            cout << "collision" << endl;


        }
        else {
            enemy.UpdateMove(deltaTime);

            player.UpdateMove(deltaTime, false, enemy.faceRight);
            cout << "no collision" << endl;

        }







        window.clear();




        window.draw(Background);
        player.Draw(window);
        enemy.Draw(window);
        window.display();

    }





}

 

Animation.h
#pragma once
#include<SFML/Graphics.hpp>
class Animation
{
public:
    Animation(sf::Texture* texture,sf::Vector2u imageCount,float switchTime);

    sf::IntRect uvRect;
    void Update(int row, float deltaTime, bool faceRight);

    float switchTime;
private:
    sf::Vector2u imageCount;
    sf::Vector2u currentImage;

    float totalTime;


};
 

Collision.hpp(from official SFML website)
#ifndef COLLISION_H
#define COLLISION_H

namespace Collision {
    //////
    /// Test for a collision between two sprites by comparing the alpha values of overlapping pixels
    /// Supports scaling and rotation
    /// AlphaLimit: The threshold at which a pixel becomes "solid". If AlphaLimit is 127, a pixel with
    /// alpha value 128 will cause a collision and a pixel with alpha value 126 will not.
    ///
    /// This functions creates bitmasks of the textures of the two sprites by
    /// downloading the textures from the graphics card to memory -> SLOW!
    /// You can avoid this by using the "CreateTextureAndBitmask" function
    //////
    bool PixelPerfectTest(const sf::Sprite& Object1, const sf::Sprite& Object2, sf::Uint8 AlphaLimit = 0);

    //////
    /// Replaces Texture::loadFromFile
    /// Load an imagefile into the given texture and create a bitmask for it
    /// This is much faster than creating the bitmask for a texture on the first run of "PixelPerfectTest"
    ///
    /// The function returns false if the file could not be opened for some reason
    //////
    bool CreateTextureAndBitmask(sf::Texture& LoadInto, const std::string& Filename);

    //////
    /// Test for collision using circle collision dection
    /// Radius is averaged from the dimensions of the sprite so
    /// roughly circular objects will be much more accurate
    //////
    bool CircleTest(const sf::Sprite& Object1, const sf::Sprite& Object2);

    //////
    /// Test for bounding box collision using the Separating Axis Theorem
    /// Supports scaling and rotation
    //////
    bool BoundingBoxTest(const sf::Sprite& Object1, const sf::Sprite& Object2);
}

#endif  /* COLLISION_H */
 
Enemy.h

#pragma once
#include<SFML/Graphics.hpp>
#include "Animation.h"


class Enemy
{
public:
    Enemy(sf::Texture* texture, sf::Vector2u imageCount, float switchTime, float speed);
    void UpdateMove(float deltaTime);
    void UpdateAttack(float deltaTime);
    void Draw(sf::RenderWindow& window);
    bool faceRight;
    sf::Sprite body;
private:


    Animation animation;
    unsigned int row;
    float speed;


};
 
Player.h
#pragma once
#include<SFML/Graphics.hpp>
#include "Animation.h"

class Player
{
public:
    Player(sf::Texture* texture, sf::Vector2u imageCount, float switchTime,float speed);
    void UpdateMove(float deltaTime,bool collide,bool enemyFaceRight);
    void Draw(sf::RenderWindow& window);
    sf::Sprite body;

private:


    Animation animation;
    unsigned int row;
    float speed;
    bool faceRight;

};
 

Animation.cpp
#include "Animation.h"
Animation::Animation(sf::Texture* texture, sf::Vector2u imageCount, float switchTime) {
    this->imageCount = imageCount;
    this->switchTime = switchTime;
    totalTime = 0.0f;
    currentImage.x = 0;

    uvRect.width = texture->getSize().x / (float)imageCount.x;
    uvRect.height = texture->getSize().y / (float)imageCount.y;


}


void Animation::Update(int row, float deltaTime,bool faceRight) {

    currentImage.y = row;
    totalTime += deltaTime;

    if (totalTime >= switchTime) {
        totalTime -= switchTime;
        currentImage.x++;

        if (currentImage.x >= imageCount.x) {
            currentImage.x = 0;
        }

    }


    uvRect.top = currentImage.y * uvRect.height;

    if (faceRight) {
        uvRect.left = currentImage.x * abs(uvRect.width);
        uvRect.width = abs(uvRect.width);
    }
    else {
        uvRect.left = (currentImage.x + 1) * abs(uvRect.width);
        uvRect.width = -abs(uvRect.width);
    }
}
 
Collision.cpp(also from official SFML website)
#include <SFML/Graphics.hpp>
#include <map>
#include "Collision.hpp"

namespace Collision
{
    class BitmaskManager
    {
    public:
        ~BitmaskManager() {
            std::map<const sf::Texture*, sf::Uint8*>::const_iterator end = Bitmasks.end();
            for (std::map<const sf::Texture*, sf::Uint8*>::const_iterator iter = Bitmasks.begin(); iter != end; iter++)
                delete[] iter->second;
        }

        sf::Uint8 GetPixel(const sf::Uint8* mask, const sf::Texture* tex, unsigned int x, unsigned int y) {
            if (x > tex->getSize().x || y > tex->getSize().y)
                return 0;

            return mask[x + y * tex->getSize().x];
        }

        sf::Uint8* GetMask(const sf::Texture* tex) {
            sf::Uint8* mask;
            std::map<const sf::Texture*, sf::Uint8*>::iterator pair = Bitmasks.find(tex);
            if (pair == Bitmasks.end())
            {
                sf::Image img = tex->copyToImage();
                mask = CreateMask(tex, img);
            }
            else
                mask = pair->second;

            return mask;
        }

        sf::Uint8* CreateMask(const sf::Texture* tex, const sf::Image& img) {
            sf::Uint8* mask = new sf::Uint8[tex->getSize().y * tex->getSize().x];

            for (unsigned int y = 0; y < tex->getSize().y; y++)
            {
                for (unsigned int x = 0; x < tex->getSize().x; x++)
                    mask[x + y * tex->getSize().x] = img.getPixel(x, y).a;
            }

            Bitmasks.insert(std::pair<const sf::Texture*, sf::Uint8*>(tex, mask));

            return mask;
        }
    private:
        std::map<const sf::Texture*, sf::Uint8*> Bitmasks;
    };

    BitmaskManager Bitmasks;

    bool PixelPerfectTest(const sf::Sprite& Object1, const sf::Sprite& Object2, sf::Uint8 AlphaLimit) {
        sf::FloatRect Intersection;
        if (Object1.getGlobalBounds().intersects(Object2.getGlobalBounds(), Intersection)) {
            sf::IntRect O1SubRect = Object1.getTextureRect();
            sf::IntRect O2SubRect = Object2.getTextureRect();

            sf::Uint8* mask1 = Bitmasks.GetMask(Object1.getTexture());
            sf::Uint8* mask2 = Bitmasks.GetMask(Object2.getTexture());

            // Loop through our pixels
            for (int i = Intersection.left; i < Intersection.left + Intersection.width; i++) {
                for (int j = Intersection.top; j < Intersection.top + Intersection.height; j++) {

                    sf::Vector2f o1v = Object1.getInverseTransform().transformPoint(i, j);
                    sf::Vector2f o2v = Object2.getInverseTransform().transformPoint(i, j);

                    // Make sure pixels fall within the sprite's subrect
                    if (o1v.x > 0 && o1v.y > 0 && o2v.x > 0 && o2v.y > 0 &&
                        o1v.x < O1SubRect.width && o1v.y < O1SubRect.height &&
                        o2v.x < O2SubRect.width && o2v.y < O2SubRect.height) {

                        if (Bitmasks.GetPixel(mask1, Object1.getTexture(), (int)(o1v.x) + O1SubRect.left, (int)(o1v.y) + O1SubRect.top) > AlphaLimit&&
                            Bitmasks.GetPixel(mask2, Object2.getTexture(), (int)(o2v.x) + O2SubRect.left, (int)(o2v.y) + O2SubRect.top) > AlphaLimit)
                            return true;

                    }
                }
            }
        }
        return false;
    }

    bool CreateTextureAndBitmask(sf::Texture& LoadInto, const std::string& Filename)
    {
        sf::Image img;
        if (!img.loadFromFile(Filename))
            return false;
        if (!LoadInto.loadFromImage(img))
            return false;

        Bitmasks.CreateMask(&LoadInto, img);
        return true;
    }

    sf::Vector2f GetSpriteCenter(const sf::Sprite& Object)
    {
        sf::FloatRect AABB = Object.getGlobalBounds();
        return sf::Vector2f(AABB.left + AABB.width / 2.f, AABB.top + AABB.height / 2.f);
    }

    sf::Vector2f GetSpriteSize(const sf::Sprite& Object)
    {
        sf::IntRect OriginalSize = Object.getTextureRect();
        sf::Vector2f Scale = Object.getScale();
        return sf::Vector2f(OriginalSize.width * Scale.x, OriginalSize.height * Scale.y);
    }

    bool CircleTest(const sf::Sprite& Object1, const sf::Sprite& Object2) {
        sf::Vector2f Obj1Size = GetSpriteSize(Object1);
        sf::Vector2f Obj2Size = GetSpriteSize(Object2);
        float Radius1 = (Obj1Size.x + Obj1Size.y) / 4;
        float Radius2 = (Obj2Size.x + Obj2Size.y) / 4;

        sf::Vector2f Distance = GetSpriteCenter(Object1) - GetSpriteCenter(Object2);

        return (Distance.x * Distance.x + Distance.y * Distance.y <= (Radius1 + Radius2) * (Radius1 + Radius2));
    }

    class OrientedBoundingBox // Used in the BoundingBoxTest
    {
    public:
        OrientedBoundingBox(const sf::Sprite& Object) // Calculate the four points of the OBB from a transformed (scaled, rotated...) sprite
        {
            sf::Transform trans = Object.getTransform();
            sf::IntRect local = Object.getTextureRect();
            Points[0] = trans.transformPoint(0.f, 0.f);
            Points[1] = trans.transformPoint(local.width, 0.f);
            Points[2] = trans.transformPoint(local.width, local.height);
            Points[3] = trans.transformPoint(0.f, local.height);
        }

        sf::Vector2f Points[4];

        void ProjectOntoAxis(const sf::Vector2f& Axis, float& Min, float& Max) // Project all four points of the OBB onto the given axis and return the dotproducts of the two outermost points
        {
            Min = (Points[0].x * Axis.x + Points[0].y * Axis.y);
            Max = Min;
            for (int j = 1; j < 4; j++)
            {
                float Projection = (Points[j].x * Axis.x + Points[j].y * Axis.y);

                if (Projection < Min)
                    Min = Projection;
                if (Projection > Max)
                    Max = Projection;
            }
        }
    };

    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 Separating Axis Theorem states that there can be no collision between the rectangles
            if (!((MinOBB2 <= MaxOBB1) && (MaxOBB2 >= MinOBB1)))
                return false;
        }
        return true;
    }
}
 
Enemy.cpp

#include "Enemy.h"
#include<SFML/Graphics.hpp>
#include<iostream>
using namespace std;

Enemy::Enemy(sf::Texture* texture, sf::Vector2u imageCount, float switchTime, float speed) :
    animation(texture, imageCount, switchTime) {

    this->speed = speed;
    row = 0;
    faceRight = true;

    body.setScale(sf::Vector2f(0.18f, 0.235f));
    body.setPosition(-20.0f, 479.0f);
    body.setTexture(*texture);





}

void Enemy::UpdateMove(float deltaTime) {

    sf::Vector2f movement(0.0f, 0.0f);

    //if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
    //  movement.x -= speed * deltaTime;
    //}
    //if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
    movement.x += speed * deltaTime;

    row = 2;
    if (movement.x > 0.0f) {
        faceRight = true;
    }
    else {
        faceRight = false;
    }


    animation.Update(row, deltaTime, faceRight);
    body.setTextureRect(animation.uvRect);

    body.move(movement);





}

void Enemy::UpdateAttack(float deltaTime)
{
    sf::Vector2f movement(0.0f, 0.0f);

    //if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
    //  movement.x -= speed * deltaTime;
    //}
    //if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
    //movement.x += speed * deltaTime;

    row = 1;
    //if (movement.x > 0.0f) {
    //  faceRight = true;
    //}
    //else {
    //  faceRight = false;
    //}


    animation.Update(row, deltaTime, faceRight);
    body.setTextureRect(animation.uvRect);

    //body.move(movement);
}

void Enemy::Draw(sf::RenderWindow& window) {

    window.draw(body);
}
 
Player.cpp
#include "Player.h"
#include<SFML/Graphics.hpp>
#include<iostream>
using namespace std;

Player::Player(sf::Texture* texture, sf::Vector2u imageCount, float switchTime, float speed) :
    animation(texture,imageCount,switchTime){

    this->speed = speed;
    row = 0;
    faceRight = true;

    body.setScale(sf::Vector2f(0.25f, 0.25f));
    body.setPosition(550.0f, 490.0f);
    body.setTexture(*texture);





}

void Player::UpdateMove(float deltaTime,bool collide, bool enemyFaceRight) {

    sf::Vector2f movement(0.0f, 0.0f);

    if (collide) {
        if (enemyFaceRight) {
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
                movement.x += speed * deltaTime;
            }
        }
        else {
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
                movement.x -= speed * deltaTime;
            }
        }

    }
    else {

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
            movement.x -= speed * deltaTime;
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
            movement.x += speed * deltaTime;
        }

    }



    if (movement.x == 0) {
        row = 1;

    }
    else {
        row = 2;
        if (movement.x > 0.0f) {
            faceRight = true;
        }
        else {
            faceRight = false;
        }
    }
    if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) {
        row = 0;
    }
    animation.Update(row, deltaTime, faceRight);
    body.setTextureRect(animation.uvRect);
    if (!(sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))) {
        body.move(movement);
    }




}



void Player::Draw(sf::RenderWindow& window) {

    window.draw(body);
}