1

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 a sprite without a texture will be seen.

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.

anonymous
  • 448
  • 1
  • 6
  • 25
  • You should be more careful about using namespaces, particularly in header files! :) See http://stackoverflow.com/a/5849668/781978 – twsaef May 12 '15 at 03:54

2 Answers2

1

You should customise copy-construction of blocks so it updates the texture pointer, something like:

Block::Block(const Block& other)
  : blockT(other.blockT), block(other.block)
{
    block.setTexture(blockT);
}

That will be used when push_back() in the vector forces a resize, and the newly allocated, larger buffer's elements are copy-constructed from the old elements before the latter are "destructed" and deallocated.

It would be a good idea to support the same kind of update for assignment, i.e. operator=(const Block& rhs).

Regarding your "Thus a sprite without a texture will be seen." - it's much more likely that the behaviour is undefined since you're effectively following a pointer to released memory that could get corrupted with new content at any time, and happens to manifest as a lack of texture currently in your testing, but might crash and burn at some other optimisation level, after some minor code changes, on another compiler or OS etc..

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
0

Your solution of having a Block class which stores its own instance of an sf::Texture is going to cause you to have duplicate copies of textures hanging around in memory. It's also going to require you to learn and follow the rule of three when you're dealing with your Block objects as per Tony D's answer.

The simpler solution is to have a separate std::map of filenames to sf::Textures into which you can load the textures that you require once, and retrieve everywhere you need them.

// Have one of these, maybe as a member of your World class?
std::map<std::string,sf::Texture> textures;

// load your textures into it ...

Then in your Block class ...

class Block
{
public:
    // constructor now takes a reference to a texture map ...
    Block(float x, float y,std::map<std::string,sf::Texture>& textures);

And in the implementation you can retrieve the texture you want by its filename, and assign it t the sprite with setTexture.

Community
  • 1
  • 1
twsaef
  • 2,082
  • 1
  • 15
  • 27