使用 AnimatedSprite 进行 SFML 碰撞测试

SFML Collision tests with AnimatedSprite

我是 SFML 的新手,如果我能就此问题获得任何帮助,我将不胜感激。在 game.cpp 中,我尝试使用 collision.cpp PixelPerfectTest() 方法在我的背景精灵和我的玩家精灵(当前不存在)之间执行第 261 行的碰撞测试。但是我使用动画 class 在我的精灵 sheet 中加载的方式本质上是一个 std::vector 纹理矩形 (sf::IntRect) 和一个 sf::Texture 参考。所以你必须提供一个 spritesheet 并推回你的纹理 reactangles。然后创建一个 AnimatedSprite 对象并为其提供动画。 AnimatedSprite class 继承自 sf::Drawable 和 sf::Transfomable,所以它的行为很像一个普通的精灵。无需创建 Sprite 对象。我需要能够加载正在使用的当前帧并加载到 sprite obj 中,然后使用该 sprite 进行碰撞测试。

谢谢,

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

您可以向 AnimatedSprite 添加一个函数 class 来创建和 returns 一个精灵对象。像

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

一种更有效的方法是使用 sf::Sprite::setTextureRect() 函数 change/loop 通过帧,而不是将它们存储到 std::vector 中。这样你只需要有一个你的碰撞检测函数使用的 sf::Sprite 对象。

我假设您没有创建那个碰撞检测器,因为它使用 sf::Sprite 并且您正在使用您创建的 AnimatedSprite class(它似乎与您的 CD 不兼容)。因此,您也可以轻松修改碰撞检测算法以与动画精灵一起使用 class.