SFML texture displaying as a white box

与世无争的帅哥 提交于 2020-08-25 03:53:24

问题


I have a texture and sprite in a base class that is being extended by another class, however when drawn, the sprite displays as a white box. I know this is something to do with the sprite losing it's link to the texture object, but I'm kind of new to C++, so I'm not really sure how it happened.

Here is the code (I've removed some of the irrelevant parts to cut down the size):

Pickup.h:

#ifndef PICKUPS_PICKUP_H
#define PICKUPS_PICKUP_H

#include <SFML\Graphics.hpp>
#include "..\Player.h"

namespace Pickups
{
    class Pickup
    {
    private:
        sf::Vector2f position;
        sf::Texture texture;
        sf::Sprite sprite;
    public:
        Pickup();

        bool drop(float dt);
        void draw(sf::RenderWindow* window);

        void setTexture(sf::String name);

        void setPos(sf::Vector2f position);
        sf::Vector2f getPos();

        void isColliding(Player* player);

        virtual void onCollect(Player* player) = 0;
    };
}

#endif

pickup.cpp:

#include "Pickup.h"

namespace Pickups
{
    Pickup::Pickup()
    {
    }

    void Pickup::draw(sf::RenderWindow* window)
    {
        window->draw(sprite);
    }

    void Pickup::setTexture(sf::String name)
    {
        if (!texture.loadFromFile("images/pickups/" + name + ".png"))
            std::cout << "Error loading image: images/pickups/" + name.toAnsiString() + ".png" << std::endl;
        else
            sprite.setTexture(texture);
    }
}

Health.h:

#ifndef PICKUPS_HEALTH_H
#define PICKUPS_HEALTH_H

#include "Pickup.h"

namespace Pickups
{
    class Health : public Pickup
    {
    private:
        int worth;
    public:
        Health(sf::Vector2f position, int worth);
        void onCollect(Player* player);
    };
}

#endif

health.cpp:

#include "Health.h"

namespace Pickups
{
    Health::Health(sf::Vector2f position, int worth)
    {
        setTexture("health");
        setPos(position);
        this->worth = worth;
    }

    void Health::onCollect(Player* player)
    {
        player->addLives(worth);
    }
}

(I don't know if this is part of the problem, but I might as well post it too) I store the pickups in a vector like so:

std::vector<Pickups::Health> pickups;

回答1:


A std::vector copies or moves the inserted elements, so as long as you have the default copy constructor or as long as you do not change this dirty a texture per element-style, (the elements just need to have one common texture object to actually point to, so you waste much much memory) the pointer that the sf::Sprite object holds to the texture gets invalid. To see why we need to think whats happens on insertion:

You setup a nice Pickupish object and add it to the vector which calls the copy-constructor. Lets say your nice object that you wanted to add is object A and the now added/copied object is B. Both have a sprite S and a texture T. Both textures are valid, but the problem is this: A's S points to A's T, so after copying it to B B's S points also to A's T! As I assume A is just temporary so it gets destructed, together with its texture, and there you have it, a nice white box.

You can solve this in some other dirty ways like making your own copy-constructor in Pickup like this:

Pickup::Pickup(const Pickup& other)
: position(other.position), texture(other.texture), sprite(other.sprite)
{ sprite.setTexture(texture); }

or by storing std::unique_ptr<Pickups::Health>'s and not just Pickups::Health's.

However a much better way you should use is some kind of Resourcemanager, which just stores all relevant textures, ideally one, a big tileset, because loading once but big is faster than loading multiple but small textures. You can write your own very simple manager or use some other e.g. the one from the great Thor library. To set a specific tile as texture for a Sprite just call sf::Sprite::setTextureRect.


I want to mention some additional improvements to your design. Let Pickup derive from sf::Drawable and define its pure virtual draw function, which you can make private. Thus your from Pickup deriving object doesn't need to know from any sf::RenderTarget and you can just do target.draw(myPickupObject).

There is no need to store the position, just let Pickup derive from sf::Transformable, too. You don't have to implement any functions, the only thing you need to do is applying the matrix to the sf::RenderStates object thats passed to draw.

Overall your draw function might look like this:

void Pickup::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    //'applying' transformation-matrix
    states.transform *= getTransform();

    target.draw(sprite, states);
}

So your Pickup has now only sf::Sprite as member and overall your header just needs to include SFML/Graphics/Sprite.hpp.




回答2:


For avoid this type of problem I always declare my Texture as a pointer and deleting it in the destructor of the class. Like this your Texture will always exist whenever your object is not destroyed.

And it's always good to verify the loading of the image :

    if (!texture.loadFromFile("images/pickups/health.png")){
      //some error code
    }

But it's not the problem here.



来源:https://stackoverflow.com/questions/27951870/sfml-texture-displaying-as-a-white-box

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!