SFML community forums

Help => Graphics => Topic started by: Ivan on August 09, 2013, 04:54:25 pm

Title: How to fix white square problem when used vectors?
Post by: Ivan on August 09, 2013, 04:54:25 pm
Hi, first of all I know the "White square problem" described in the sprite tutorial. I understand it.

In my case, I create a Player class with a sprite and texture members. In main code, when  a user press M key, create a new instance of Player and add it into a vector. I known that the elements of a vector move in memory when the vector change and then lost the reference of the sprite texture.

Now, my question is how I can fix it? I tried with add setTexture every time I draw, but I don't know if its a good solution.

I attach an screenshoot about I'm saying.

(http://en.sfml-dev.org/forums/index.php?action=dlattach;topic=12583.0;attach=841;image)

I wrote a minimal example. This is the code, can download here (http://sdrv.ms/18gIjix).
I use SFML-2.1 with Code:Blocks (Windows 8 Pro, 32bits)

Player.h
//Player class

#ifndef PLAYER_H
#define PLAYER_H

#include <SFML/Graphics.hpp>
#include <iostream>

class Player
{
    public:
        Player(sf::Vector2i position, sf::Color colorSpr,  sf::String filenameSpr);
        ~Player();
        void Draw(sf::RenderWindow &Window);

        sf::Sprite sprPlayer;
        sf::Texture texturePlayer;
        int sprWidth, sprHeight;

    protected:
    private:
};

#endif
 

Player.cpp
#include "Player.h"

//constructor
Player::Player(sf::Vector2i position, sf::Color colorSpr,  sf::String filenameSpr)
{
    if(!texturePlayer.loadFromFile(filenameSpr, sf::IntRect(0, 0, 32, 32)))
        std::cout << "Error, can't load SpriteSheet " << std::endl;

    sprPlayer.setTexture(texturePlayer);
    sprPlayer.setColor(colorSpr);
    sprWidth = sprPlayer.getTextureRect().width;
    sprHeight = sprPlayer.getTextureRect().height;
    sprPlayer.setPosition(position.x * sprWidth, position.y * sprHeight);
}

//destructor
Player::~Player()
{
}

//draw player
void Player::Draw(sf::RenderWindow &Window)
{
    //sprPlayer.setTexture(texturePlayer); //<- with this it works
    Window.draw(sprPlayer);
}
 

Main.cpp

#include <SFML/Graphics.hpp>
#include <vector>
#include "Player.h"

const int maxPlayers = 10;

//colors for players
sf::Color pColors[maxPlayers] = {sf::Color(255, 0, 0, 255), sf::Color(0, 255, 0, 255),
                                 sf::Color(100, 100, 0, 255), sf::Color(0, 0, 255, 255),
                                 sf::Color(80, 200, 120, 255), sf::Color(100, 100, 155, 255),
                                 sf::Color(123, 160, 90, 255), sf::Color(200, 0, 255, 255),
                                 sf::Color(254, 195, 172, 255), sf::Color(255, 255, 255, 255)
                                   };
sf::String spriteSheets[maxPlayers] = {"punky2.png", "punky.png", "punky.png", "punky.png", "punky.png",
                                       "punky.png", "punky.png", "punky.png", "punky.png", "punky.png"
                                      };
int main()
{
    sf::Vector2i playerPosition(0, 0);
    int numRivals = 0;

    std::vector <Player> rivals;
    rivals.reserve(maxPlayers);

    sf::RenderWindow Window(sf::VideoMode(640, 480, 32), "Test");
    Window.setKeyRepeatEnabled(false);

    //create player
    Player hero(playerPosition, pColors[0], spriteSheets[0]);

    while (Window.isOpen())
    {
        sf::Event event;
        while (Window.pollEvent(event))
        {
            switch (event.type)
            {
            case sf::Event::Closed:
                {
                    Window.close();
                    break;
                }
            case sf::Event::KeyPressed:
                if (event.key.code == sf::Keyboard::Escape)
                {
                    Window.close();
                }
                else if (event.key.code == sf::Keyboard::Right)
                {
                    hero.sprPlayer.move(3, 0);
                }
                else if (event.key.code == sf::Keyboard::Left)
                {
                    hero.sprPlayer.move(-3, 0);
                }
                else if (event.key.code == sf::Keyboard::Down)
                {
                    hero.sprPlayer.move(0, 3);
                }
                else if (event.key.code == sf::Keyboard::Up)
                {
                    hero.sprPlayer.move(0, -3);
                }
                else if (event.key.code == sf::Keyboard::M)
                {
                    if (numRivals < maxPlayers - 1)
                    {
                        numRivals++;
                        Player rival(sf::Vector2i(3 + numRivals, 0), pColors[numRivals], spriteSheets[numRivals]);
                        rivals.push_back(rival);
                    }
                }
                break;
           }
        }

        //draw stuff
        hero.Draw(Window);
        if (numRivals > 0)
        {
            for (int k = 0; k < rivals.size(); k++)
            {
                rivals[k].Draw(Window);
            }
        }

        Window.display();
        Window.clear(sf::Color(255, 255, 0, 255));
    }
    return 0;
}
 

Title: Re: How to fix white square problem when used vectors?
Post by: binary1248 on August 09, 2013, 05:34:14 pm
Either do as you said and reset the texture at every draw (which isn't so expensive because this doesn't involve additional OpenGL calls), or... the much simpler method is to just use an STL container that doesn't move data around when it is modified, such as std::list instead of std::vector.
Title: Re: How to fix white square problem when used vectors?
Post by: Ivan on August 09, 2013, 05:58:21 pm
Thanks for reply binary1248. I tried with std::list but still doesn't work. Don't show the texture. I'm confused  ???

std::list <Player> rivals;

....

Player rival(sf::Vector2i(3 + numRivals, 0), pColors[numRivals], spriteSheets[numRivals]);
rivals.push_back(rival);
....

std::list<Player>::iterator e;
for (e = rivals.begin(); e != rivals.end(); ++e)
{
      (*e).Draw(Window);
}
 
Title: Re: How to fix white square problem when used vectors?
Post by: The Hatchet on August 09, 2013, 08:51:34 pm
Another way to get around this problem would be to have an asset manager hold all your textures(so they don't get moved around in memory) then when the player is created have them set their textures to the ones in the texture manager. 

You'd probably want to do this anyways once your project starts getting bigger and bigger, that way you can load all your textures right at the start and don't have to worry when new entities get added or deleted.

You could also try setting your vectors reserve size big enough to hold all your entities.  I think vector elements get moved in memory when the vector has to resize itself, at which a new vector is created(new memory location), all the contents are copied over with the new element added and the old is destroyed.  If you have a vector reserved big enough already I don't think it moves around in memory.
Title: Re: How to fix white square problem when used vectors?
Post by: binary1248 on August 09, 2013, 09:50:36 pm
You need to set the texture of the sprite after you insert into the std::list. If you set before and insert, the memory location of the data you will reference in the future will change since the std::list makes a copy of whatever you insert into it.
Title: Re: How to fix white square problem when used vectors?
Post by: Ivan on August 10, 2013, 10:03:22 pm
Another way to get around this problem would be to have an asset manager hold all your textures(so they don't get moved around in memory) then when the player is created have them set their textures to the ones in the texture manager. 

You'd probably want to do this anyways once your project starts getting bigger and bigger, that way you can load all your textures right at the start and don't have to worry when new entities get added or deleted.

Please can you show me and example of doing this, I don't know how make it. Thank you for your answers.

You could also try setting your vectors reserve size big enough to hold all your entities.  I think vector elements get moved in memory when the vector has to resize itself, at which a new vector is created(new memory location), all the contents are copied over with the new element added and the old is destroyed.  If you have a vector reserved big enough already I don't think it moves around in memory.

I'm doing this but doesn't work.
  std::vector <Player> rivals;
   rivals.reserve(maxPlayers);
 

You need to set the texture of the sprite after you insert into the std::list. If you set before and insert, the memory location of the data you will reference in the future will change since the std::list makes a copy of whatever you insert into it.

The problem is I load the texture in the constructor, I can't set after insert to vector. May be the solution is create an empty constructor and define a setPlayer() function??
Thanks for your answer.
Title: Re: How to fix white square problem when used vectors?
Post by: Ixrec on August 10, 2013, 10:11:40 pm
Another option might be having the vector hold (smart) pointers to players instead of the player objects themselves.

Regarding the asset manager, I *think* (correct me if I'm wrong) that would be something like a std::map from some kind of key to a smart pointer for a texture.  So each Player would simply hang on to a key, and when they needed the texture they'd put that key into the map.
Title: Re: How to fix white square problem when used vectors?
Post by: binary1248 on August 10, 2013, 10:30:17 pm
The problem is I load the texture in the constructor, I can't set after insert to vector. May be the solution is create an empty constructor and define a setPlayer() function??
Since your member variables are public, you can just do rivals.back().setTexture( rivals.back().texturePlayer );. You should make them private though and provide accessor methods.
Title: Re: How to fix white square problem when used vectors?
Post by: Ivan on August 10, 2013, 11:12:23 pm
Yes, it should be private, I made public only in this test for simplify work.

With rivals.back().sprPlayer.setTexture(rivals.back().texturePlayer); it Works. But still don't like having to setTexture() two times. I'll keep thinking about how to solve. Thank you binary1248.
Title: Re: How to fix white square problem when used vectors?
Post by: Lee R on August 11, 2013, 12:30:55 am
You need to define both a copy constructor and copy assignment operator for the 'Player' class which sets the sprite's texture to that instance's internal copy (e.g. 'sprPlayer.setTexture(texturePlayer);').