1
\$\begingroup\$

From the official SFML tutorials, The White Box Problem:-

"When you set the texture of a sprite, all it does internally is store a pointer to the texture instance. Therefore, if the texture is destroyed or moves elsewhere in memory, the sprite ends up with an invalid texture pointer." Thus you will see a sprite without a texture.

I have a class called World. In this class I made a 2d integer array called level and a vector of type Block called blocks. Now I wanted to store the 'Block' objects inside the vector whenever level[ i ][ j ] = 1.

header file of the 'World' class:-

#ifndef WORLD_H
#define WORLD_H
#include <vector>
#include "Block.h"
#include "Grass.h"
#include <SFML/Graphics.hpp>

using namespace std;

class World
{
    public:
        World();
        void draw(sf::RenderWindow *window);
        vector<Block> blocks;

    private:
        int level[12][16];
        int wd;
        int hi;
};

#endif // WORLD_H

cpp file of the 'World' class :-

#include "World.h"
#include "Grass.h"
#include "Block.h"
#include <iostream>
#include <SFML/Graphics.hpp>

using namespace std;

World::World() : level{
        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1},
        {1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1},
        {1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1},
        {1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
    }
{
    wd = 16;
    hi = 12;
    int count = 1;
    //make a 'Block' object and pass it in the vector when level[ i ][ j ] = 1.
    for(int i = 0; i<hi; i++)
    {
        for(int j = 0; j<wd; j++)
        {
            if(level[i][j] == 1)
            {
                Block block(j*50, i*50);
                blocks.push_back(block);
            }
        }
    }
}
void World::draw(sf::RenderWindow *window)
{
    for(unsigned int i = 0; i<blocks.size(); i++)
    {
        blocks[i].draw(window);
    }
}

The 'Block' class has two members - sf::Texture blockT and sf::Sprite block. It also has a draw(RenderWindow *window) method. This is how the 'Block' class is made :-

header file for block class

#ifndef BLOCK_H
#define BLOCK_H
#include <iostream>
#include <SFML/Graphics.hpp>

using namespace std;

class Block
{
    public:
        Block(float x, float y);
        void draw(sf::RenderWindow *window);

    private:
        sf::Texture blockT;
        sf::Sprite block;
};

#endif // BLOCK_H

cpp file for 'Block' class

#include "Block.h"
#include <iostream>
#include <SFML/Graphics.hpp>

using namespace std;

Block::Block(float px, float py)
{
    if(!(blockT.loadFromFile("textures/block.png")))
    {
        cout<<"Could not load block texture."<<endl;
    }
    block.setTexture(blockT);
    block.setPosition(sf::Vector2f(px, py));
    cout<<px<<endl;
    cout<<py<<endl;
}

void Block::draw(sf::RenderWindow *window)
{
    window->draw(block);
}

When I run the program, in place of blocks, only white box is shown. I don't understand how the texture is getting destroyed. This is what the output looks like :-

messed up textures

As you can see, the white places are sprites each of size 50*50 without any texture.

Edit: The Main Function :-

int main()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "Untitled game");
    window.setVerticalSyncEnabled(true);
    Player player;
    World world;

    //variables

    while(window.isOpen())
    {
        //event handling

        //updating

        //drawing
        window.clear(sf::Color::Black);
        world.draw(&window);
        window.display();
    }

    return 0;
}
\$\endgroup\$
2
\$\begingroup\$

Your problem is most likely the fact that you create copies of your blocks when adding them to your vector.

That way once the original copy/block is destroyed, the texture won't be valid anymore (since there's a shallow copy happening, so even the new copies still point to the old texture, despite having their own copy).

As a potential fix, change your vector to std::vector<Block*> and only store pointers in there (don't forget to free the memory later on) or use smart pointers straight away.

\$\endgroup\$
1
\$\begingroup\$

You appear to be correctly storing a texture and then setting that texture to a sprite, so I don't think the White Square problem they're referring to in the documentation applies to you in this situation as you don't have the texture being created in a local function scope etc. std::vector<T>::push_back() creates a copy of the argument and stores it in the vector so that shouldn't be a problem here I believe.

You didn't post your main function so I'm unsure, but it would appear that you don't have a clear() or display() function wrapped around your primary SFML draw() function. http://www.sfml-dev.org/tutorials/2.2/graphics-draw.php

Assuming the texture is not actually white, in order to do some quick elimination testing you could pick a section of the loaded texture i.e. :

sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));

or you could just test with a colour i.e. :

sprite.setColor(sf::Color(0, 255, 0)); // green

Additionally, I've noticed that you're loading a new texture for each block. This kind of defeats the object of using a texture. With a texture you want to load it once into video memory and then point all of your sprites to that once instance to reduce draw calls and memory requirements etc (aka batching your draws). In 2D games using the concept of one large texture atlas is very common because it's so efficient. You could have a variety of block textures all tightly packed into one 'blockTypes' texture and then just use 'sprite.setTextureRect' to refer to different sections of that texture as mentioned above.

I'd just throw a few texture atlases into a map so that you have a single texture dictionary to refer to in all of your block objects.

\$\endgroup\$
1
\$\begingroup\$

A quick test, just grab the logo image from the SFML site and paste it into the working directory of your project (or just somewhere simple and explicitly state that in code i.e. "C:\test\img.png")

#include <vector>
#include <iostream>

#include <SFML/Graphics.hpp>

int main()
{

    sf::Texture texture;
    if (!texture.loadFromFile("sfml-logo-big.png")) {
        // error out here
        std::cout << "Error loading texture!" << std::endl;
    }

    std::vector<sf::Sprite> sprites = std::vector < sf::Sprite > {
        sf::Sprite(texture,), sf::Sprite(texture), sf::Sprite(texture)
    };

    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML works!");

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        for (int x = 0; x < sprites.size(); x++) {
            window.draw(sprites[x]);
        }
        window.display();
    }

    return 0;
}
\$\endgroup\$
1
\$\begingroup\$

When I asked this question I didn't knew much about sprites and textures, but then I learned quite a lot from the internet and sfml tutorials. Actually what I was doing in the program was not correct. I was storing the texture in a local scope which was getting destroyed at the end of the scope. Textures should be stored at a global scope.

In fact, one should use vertex array and map texture coordinates to its vertices, instead of making thousands of sprites, as it is faster and more convenient for the graphics card. The lesser the number of calls to the 'draw' function, the better is the performance of the game. So a million sprite objects means a million calls to the 'draw()' function of your application which reduces performance.

Tutorials about the use of vertex arrays in sfml can be found in sfml's website, in the tutorials section. http://www.sfml-dev.org/

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

The simplest solution is to keep textures in World class, like this:

class World
{
    public:
        World();
        void draw(sf::RenderWindow *window);
        vector<Block> blocks;

    private:
        int level[12][16];
        int wd;
        int hi;
        sf::Texture Texture;
};

And definition:

World::World()
{
    if (!(Texture.loadFromFile("textures/block.png", sf::IntRect(0, 0, 50, 50))))
    {
        cout << "Could not load block texture." << endl;
    }

    wd = 16;
    hi = 12;
    int count = 1;
    //make a 'Block' object and pass it in the vector when level[ i ][ j ] = 1.
    for (int i = 0; i<hi; i++)
    {
        for (int j = 0; j<wd; j++)
        {
            if (level[i][j] == 1)
            {
                Block block(j * 50, i * 50, &Texture);
                blocks.push_back(block);
            }
        }
    }
}

You also need to change Block constructor in Block.h:

Block(float x, float y, sf::Texture* Tex);

In Block.cpp:

Block::Block(float px, float py, sf::Texture* Tex)
{
    block.setTexture(*Tex);
    block.setPosition(sf::Vector2f(px, py));
    cout << px << endl;
    cout << py << endl;
}

In professional games, resources like textures, audio, and so on, are kept in separate container (or class), to prevent resource being destroyed or moved elsewhere in memory and it saves memory. Actually, the answer to problem ends here, however you can also make you Block class inherit form sf::Drawable:

class Block : public sf::Drawable

You will have to change void draw(sf::RenderWindow *window); to virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;, so whole Block class will look like this:

class Block : public sf::Drawable
{
public:
    Block(float x, float y, sf::Texture* Tex);
    Block();

private:
    sf::Sprite block;
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
}

You also need to add default constructor.

In Block.cpp:

void Block::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    states.texture = block.getTexture();
    target.draw(block, states);
}

It is much nicer way to draw an object by making it drawable from SFML's point of view.

You must also change implementation of void draw(sf::RenderWindow *window); in World class, because you don't know what are the states:

void World::draw(sf::RenderWindow *window)
{
    for (unsigned int i = 0; i<blocks.size(); i++)
    {
        window->draw(blocks[i]);
    }
}

It's possible because Block inherits from sf::Drawable, so window can call `Block::draw(...) internally.

For future reading: http://www.sfml-dev.org/tutorials/2.3/graphics-vertex-array.php#creating-an-sfml-like-entity

\$\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.