0
votes

I am pretty new to SFML, any help I can get with this issue would be greatly appreciated. In game.cpp I am trying to perform a collision test on line 261 between my background sprite and my player sprite(currently does not exist) using the collision.cpp PixelPerfectTest() method. But the way I'm loading in my sprite sheet using the Animation class which is essentially a std::vector of texture rectangles (sf::IntRect) and a sf::Texture reference. So you have to provide a spritesheet and push back your texture reactangles. Then you create a AnimatedSprite object and provide it with an Animation. The AnimatedSprite class inherits from both sf::Drawable and sf::Transfomable, so it behaves much like a regular sprite. With out ever creating a Sprite object. I need to be able to load the current frame that is being used and load into a sprite obj then use that sprite for my collision test.

Thanks,

game.cpp

#include "iostream"
#include <SFML/Graphics.hpp>
//#include "Player.h"
#include "Animation.h"
#include "AnimatedSprite.h"
//#include "TextureHolder.h"
#include "Collision.h"

using namespace sf;
using namespace std;
using namespace Collision;

int main()
{
    //******************************************************
    //game screen & resolution & View

   

    sf::Vector2i screenDimensions(1920, 1080);
    sf::RenderWindow window(sf::VideoMode(screenDimensions.x, screenDimensions.y), "Lost In the Rocks!");
    window.setFramerateLimit(60);


    //******************************************************
    //game states

    //the game will always be on one of the four states
    enum class State
    {
        PAUSED, LEVELING_UP, GAME_OVER, PLAYING
    };

    //starts game with GAME_OVER State
    State state = State::PLAYING;

    //******************************************************
    //clock & Time

    //clock object to store time
    Clock frameClock;
    //time object to store how long the PLAYING state have been active
    Time gameTimeTotal;

    //******************************************************
    //Starting background Objects, Variables & Images

    //loads cave1 texture into gpu one time
    Texture start1Layer1;
    start1Layer1.loadFromFile("graphics/Start1Layer1.png");
    Texture start1Layer2;
    //start1Layer2.loadFromFile("graphics/Start1Layer2.png");
    Texture start1Layer3;
    //start1Layer3.loadFromFile("graphics/Start1Layer3.png");
    //loads image into collision method and creates bitmask
    CreateTextureAndBitmask(start1Layer2, "graphics/Start1Layer2.png");
    CreateTextureAndBitmask(start1Layer3, "graphics/Start1Layer3.png");
  
    //creates sprite obj to store each texure
    Sprite spriteStart1L1;
    Sprite spriteStart1L2;
    Sprite spriteStart1L3;
    //loads each image into each sprite obj
    spriteStart1L1.setTexture(start1Layer1);
    spriteStart1L2.setTexture(start1Layer2);
    spriteStart1L3.setTexture(start1Layer3);
    //sets starting point for each sprite
    spriteStart1L1.setPosition(25, 25);
    spriteStart1L2.setPosition(25, 25);
    spriteStart1L3.setPosition(25, 25);


    //******************************************************
    //Player Objects, Variables & Images

    Texture tplayer;
    CreateTextureAndBitmask(tplayer, "graphics/Player1.png");
    
    //walking up frames
    Animation walkingAnimationUp;
    walkingAnimationUp.setSpriteSheet(tplayer);
    walkingAnimationUp.addFrame(sf::IntRect(0, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(63, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(126, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(189, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(252, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(315, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(378, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(441, 518, 64, 63));
    walkingAnimationUp.addFrame(sf::IntRect(504, 518, 64, 63));
    //walking down frames
    Animation walkingAnimationDown;
    walkingAnimationDown.setSpriteSheet(tplayer);
    walkingAnimationDown.addFrame(sf::IntRect(0, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(63, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(126, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(189, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(252, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(315, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(378, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(441, 646, 64, 63));
    walkingAnimationDown.addFrame(sf::IntRect(504, 646, 64, 63));
    //walking left frames
    Animation walkingAnimationLeft;
    walkingAnimationLeft.setSpriteSheet(tplayer);
    walkingAnimationLeft.addFrame(sf::IntRect(0, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(63, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(126, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(189, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(252, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(315, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(378, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(441, 582, 64, 63));
    walkingAnimationLeft.addFrame(sf::IntRect(504, 582, 64, 63));
    //walking right frames
    Animation walkingAnimationRight;
    walkingAnimationRight.setSpriteSheet(tplayer);
    walkingAnimationRight.addFrame(sf::IntRect(0, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(63, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(126, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(189, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(252, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(315, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(378, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(441, 710, 64, 63));
    walkingAnimationRight.addFrame(sf::IntRect(504, 710, 64, 63));
    //attacking lefet with sword frames 
    Animation AttackingAnimationRight;
    AttackingAnimationRight.setSpriteSheet(tplayer);
    AttackingAnimationRight.addFrame(sf::IntRect(0, 1926, 192, 186));
    AttackingAnimationRight.addFrame(sf::IntRect(192, 1926, 192, 186));
    AttackingAnimationRight.addFrame(sf::IntRect(384, 1926, 192, 186));
    AttackingAnimationRight.addFrame(sf::IntRect(576, 1926, 192, 186));
    AttackingAnimationRight.addFrame(sf::IntRect(768, 1926, 192, 186));
    AttackingAnimationRight.addFrame(sf::IntRect(960, 1926, 192, 186));
    

    Animation* currentAnimation = &walkingAnimationUp;

    // set up AnimatedSprite
    AnimatedSprite animatedSprite(sf::seconds(0.2), true, false);
    animatedSprite.setPosition(sf::Vector2f(screenDimensions / 2));

    float speed = 100.f;
    bool noKeyWasPressed = true;

    int CurrentImage = 0;

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    //Game Loop

    while (window.isOpen())
    {
        //*********************************************************************************************
        //player Input

        //event object
        Event event;

        while (window.pollEvent(event))
        {
            if (event.type == Event::KeyPressed)
            {
                if (event.key.code == Keyboard::Escape)
                {
                    window.close();
                }
            }
        }

        Vector2f movement(0.f, 0.f);

        sf::Time frameTime = frameClock.restart();

        if (state == State::PLAYING)
        {
            //if statments for pressing and releasing (WASD) Keys
            if (Keyboard::isKeyPressed(Keyboard::W))
            {

                currentAnimation = &walkingAnimationUp;
                movement.y -= speed;
                noKeyWasPressed = false;
                CurrentImage = 1;
                
            }
            
            if (Keyboard::isKeyPressed(Keyboard::S))
            {
                currentAnimation = &walkingAnimationDown;
                movement.y += speed;
                noKeyWasPressed = false;
                CurrentImage = 2;
            }
            
            if (Keyboard::isKeyPressed(Keyboard::D))
            {
                currentAnimation = &walkingAnimationRight;
                movement.x += speed;
                noKeyWasPressed = false;
                CurrentImage = 3;
                
               
            }
            if (Keyboard::isKeyPressed(Keyboard::Num1) && CurrentImage == 3)
            {
                currentAnimation = &AttackingAnimationRight;
                movement.x += 0.1f;
                noKeyWasPressed = false;
            }
           
            if (Keyboard::isKeyPressed(Keyboard::A))
            {
                currentAnimation = &walkingAnimationLeft;
                movement.x -= speed;
                noKeyWasPressed = false;
                CurrentImage = 4;
               
            }

            animatedSprite.play(*currentAnimation);
            animatedSprite.move(movement * frameTime.asSeconds());

            // if no key was pressed stop the animation
            if (noKeyWasPressed)
            {
                animatedSprite.stop();
            }
            noKeyWasPressed = true;
            
        }//end if State playing:: Player Input


        //*********************************************************************************************
        //updating the game/frame


       //collision tests

        if (state == State::PLAYING)
            {

            if (PixelPerfectTest(/*CurrentSpriteImageGoesHere*/, spriteStart1L2))
            {
                std::cout << "collision";
            }
            

            // update AnimatedSprite
            animatedSprite.update(frameTime);


                

            }

        //*********************************************************************************************
        //DRAW the Game

        if (state == State::PLAYING)
        {
            //clears the previus frame
            window.clear();
            //updates player with time for movement and collision

            //window.setView(mainView);

            //draws everything anew.
            window.draw(spriteStart1L1);
            window.draw(spriteStart1L2);
            window.draw(spriteStart1L3);

            //draws the player
            window.draw(animatedSprite);

        }
        window.display();
    }
    return 0;
}

AnimatedSprite.cpp

#include "AnimatedSprite.h"

AnimatedSprite::AnimatedSprite(sf::Time frameTime, bool paused, bool looped) :
    m_animation(NULL), m_frameTime(frameTime), m_currentFrame(0), m_isPaused(paused), m_isLooped(looped), m_texture(NULL)
{

}

void AnimatedSprite::setAnimation(const Animation& animation)
{
    m_animation = &animation;
    m_texture = m_animation->getSpriteSheet();
    m_currentFrame = 0;
    setFrame(m_currentFrame);
}

void AnimatedSprite::setFrameTime(sf::Time time)
{
    m_frameTime = time;
}

void AnimatedSprite::play()
{
    m_isPaused = false;
}

void AnimatedSprite::play(const Animation& animation)
{
    if (getAnimation() != &animation)
        setAnimation(animation);
    play();
}

void AnimatedSprite::pause()
{
    m_isPaused = true;
}

void AnimatedSprite::stop()
{
    m_isPaused = true;
    m_currentFrame = 0;
    setFrame(m_currentFrame);
}

void AnimatedSprite::setLooped(bool looped)
{
    m_isLooped = looped;
}

void AnimatedSprite::setColor(const sf::Color& color)
{
    // Update the vertices' color
    m_vertices[0].color = color;
    m_vertices[1].color = color;
    m_vertices[2].color = color;
    m_vertices[3].color = color;
}

const Animation* AnimatedSprite::getAnimation() const
{
    return m_animation;
}

sf::FloatRect AnimatedSprite::getLocalBounds() const
{
    sf::IntRect rect = m_animation->getFrame(m_currentFrame);

    float width = static_cast<float>(std::abs(rect.width));
    float height = static_cast<float>(std::abs(rect.height));

    return sf::FloatRect(0.f, 0.f, width, height);
}

sf::FloatRect AnimatedSprite::getGlobalBounds() const
{
    return getTransform().transformRect(getLocalBounds());
}

bool AnimatedSprite::isLooped() const
{
    return m_isLooped;
}

bool AnimatedSprite::isPlaying() const
{
    return !m_isPaused;
}

sf::Time AnimatedSprite::getFrameTime() const
{
    return m_frameTime;
}

void AnimatedSprite::setFrame(std::size_t newFrame, bool resetTime)
{
    if (m_animation)
    {
        //calculate new vertex positions and texture coordiantes
        sf::IntRect rect = m_animation->getFrame(newFrame);

        m_vertices[0].position = sf::Vector2f(0.f, 0.f);
        m_vertices[1].position = sf::Vector2f(0.f, static_cast<float>(rect.height));
        m_vertices[2].position = sf::Vector2f(static_cast<float>(rect.width), static_cast<float>(rect.height));
        m_vertices[3].position = sf::Vector2f(static_cast<float>(rect.width), 0.f);

        float left = static_cast<float>(rect.left) + 0.0001f;
        float right = left + static_cast<float>(rect.width);
        float top = static_cast<float>(rect.top);
        float bottom = top + static_cast<float>(rect.height);

        m_vertices[0].texCoords = sf::Vector2f(left, top);
        m_vertices[1].texCoords = sf::Vector2f(left, bottom);
        m_vertices[2].texCoords = sf::Vector2f(right, bottom);
        m_vertices[3].texCoords = sf::Vector2f(right, top);
    }

    if (resetTime)
        m_currentTime = sf::Time::Zero;
}

void AnimatedSprite::update(sf::Time deltaTime)
{
    // if not paused and we have a valid animation
    if (!m_isPaused && m_animation)
    {
        // add delta time
        m_currentTime += deltaTime;

        // if current time is bigger then the frame time advance one frame
        if (m_currentTime >= m_frameTime)
        {
            // reset time, but keep the remainder
            m_currentTime = sf::microseconds(m_currentTime.asMicroseconds() % m_frameTime.asMicroseconds());

            // get next Frame index
            if (m_currentFrame + 1 < m_animation->getSize())
                m_currentFrame++;
            else
            {
                // animation has ended
                if (m_isLooped)
                {
                    m_isPaused = true;
                }
                else
                {
                    m_currentFrame = 0; // reset to start
                }



            }

            // set the current frame, not reseting the time
            setFrame(m_currentFrame, false);
        }
    }
}

void AnimatedSprite::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    if (m_animation && m_texture)
    {
        states.transform *= getTransform();
        states.texture = m_texture;
        target.draw(m_vertices, 4, sf::Quads, states);
    }
}

Animation.cpp

#include "Animation.h"

Animation::Animation() : m_texture(NULL)
{

}

void Animation::addFrame(sf::IntRect rect)
{
    m_frames.push_back(rect);
}

void Animation::setSpriteSheet(const sf::Texture& texture)
{
    m_texture = &texture;
}

const sf::Texture* Animation::getSpriteSheet() const
{
    return m_texture;
}

std::size_t Animation::getSize() const
{
    return m_frames.size();
}

const sf::IntRect& Animation::getFrame(std::size_t n) const
{
    return m_frames[n];
}

Collision.cpp

#include <SFML/Graphics.hpp>
#include <map>
#include "Collision.h"

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;
        }
}
1

1 Answers

1
votes

You can add a function to your AnimatedSprite class which creates and returns a sprite object. Something like

sf::Sprite getCurrentSprite()
{
    sf::Sprite temp;
    temp.setTexture(<insert your texture>);
    temp.setTextureRect(<the current frame>);
    return temp;
}

A more efficient approach to this will be using the sf::Sprite::setTextureRect() function to change/loop through the frames, instead of storing them into a std::vector. That way you only need to have a sf::Sprite object which your collision detection function uses.

I presume that you didn't create that collision detector since it use sf::Sprite and you are using an AnimatedSprite class which you created (which seems incompatible with your CD). So alternatively you can easily modify the collision detection algo to work with your animated sprite class.