Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Can't point sf::Sprite to sf::Texture (white rectangle problem)  (Read 6590 times)

0 Members and 1 Guest are viewing this topic.

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Hi guys,

I’m having the seemingly age-old problem of an instance of sf::Sprite losing its texture. Now, before you shout at me, I have done my research. I’ve read the sf::Sprite documentation and the tutorial. I’ve spent the last 3 days (on and off, obviously) reading various forum posts – both here and elsewhere – about this and I know what’s causing it. The problem I’ve got is most of the solutions simply say “use a resource manager” or “pass a pointer to the texture”. Okay great, I get it… but none of them seems to say how to do either of those things or provide any examples (or at least none that work for me).

For background I decided it would be fun to try to implement the ‘Boids’ flocking algorithm using C++ and SFML. I was wrong – since hitting this problem it’s been anything but fun!! I created a Boid class and a GameEngine class (the latter was hacked together from a tutorial I found).

Here’s my Boid class:

// Class for objects that move with A-Life boid-type behavior

class Boid
{
public:
        // default constructor
        Boid();
        // overloaded constructor (creates a Boid of type NORMAL with the specified intial position)
        Boid(float StartX, float StartY);

        enum Type
        {
                NORMAL = 0,
                PREDATOR,
                PREY
        };

        bool IsPerching;

        sf::Vector2f GetVelocity() const;
        sf::Vector2f GetPosition() const;
        sf::Sprite GetSprite() const;

        void SetVelocity(sf::Vector2f NewVelocity);
        void SetPosition(sf::Vector2f NewPosition);
        void SetTexture(sf::Texture TextureImage);

        int FindDistanceFromNeighbour(Boid b);

        void Update(sf::Vector2f NewPosition);

private:
        float CurrentSpeed;
        float MAX_SPEED;

        sf::Vector2f Velocity;
        sf::Vector2f Position;
        sf::Sprite BoidSprite;
        //sf::Texture BoidTexture;
};
 

Boid::Boid()
{
        // the type of Boid
        Type BoidType = NORMAL;

        // the Boid's starting position
        Position.x = 0;
        Position.y = 0;

        // is the Boid perching?
        IsPerching = false;

        // How fast is the Boid travelling
        CurrentSpeed = .0f;

        // What is the Boid's maximum speed?
        MAX_SPEED = 1.f;

        // THIS HAS BEEN COMMENTED OUT AS I NO LONGER DO THIS (it didn’t work anyway)
        // associate a texture with the sprite
        //BoidTexture.loadFromFile("boid_small.png");
        //BoidSprite.setTexture(BoidTexture, true);
       
        // set the initial position of the sprite
        BoidSprite.setPosition(Position);
}

Boid::Boid(float StartX, float StartY)
{
        // the Boid's starting position
        Position.x = StartX;
        Position.y = StartY;

        // is the Boid perching?
        IsPerching = false;

        // How fast is the Boid travelling
        CurrentSpeed = .0f;

        // What is the Boid's maximum speed?
        MAX_SPEED = 1.f;

        // set the initial position of the sprite
        BoidSprite.setPosition(Position);
}

sf::Vector2f Boid::GetVelocity() const
{
        return Velocity;
}

sf::Vector2f Boid::GetPosition() const
{
        return Position;
}

sf::Sprite Boid::GetSprite() const
{
        return BoidSprite;
}

void Boid::SetPosition(sf::Vector2f NewPosition)
{
        Position.x = NewPosition.x;
        Position.y = NewPosition.y;

        return;
}

void Boid::SetTexture(sf::Texture TextureImage)
{
        BoidSprite.setTexture(TextureImage, true);
}

void Boid::SetVelocity(sf::Vector2f NewVelocity)
{
        Velocity.x = NewVelocity.x;
        Velocity.y = NewVelocity.y;

        return;
}

void Boid::Update(sf::Vector2f NewPosition)
{
        // move the sprite to its new position
        BoidSprite.setPosition(NewPosition);
}

int Boid::FindDistanceFromNeighbour(Boid b) // TODO write a function to calculate distance from neighbouring Boid(s)
{
        return 0;
}
 

And here’s the header file plus what I think are the relevant sections of my GameEngine class (I’m trying to keep the code as brief and relevant as possible but if you think more or less is needed let me know and I’ll edit the post as necessary).

class GameEngine
{
public:
        GameEngine();
        void Start();

private:
        // an SFML RenderWindow
        sf::RenderWindow Window;

        // declare a Sprite and Texture for he background
        sf::Sprite BackgroundSprite;
        sf::Texture BackgroundTexture;

        void Input();
        void Update(float DTimeAsSeconds);
        void Draw();
};


void Initialise_Flock();
void Initialise_Positions();
void Move_All_Boids_To_New_Positions();

// our 3 main rules
sf::Vector2f Rule1(Boid b);
sf::Vector2f Rule2(Boid b);
sf::Vector2f Rule3(Boid b);

// additional rules
sf::Vector2f Tend_Toward_Place(Boid b);
sf::Vector2f Bound_Position(Boid b);
void Limit_Velocity(Boid b);
 

int MAX_FLOCK_SIZE = 20;
Boid* Flock = new Boid[MAX_FLOCK_SIZE]; // TODO change to std::vector

// declare an instance of image_manager
image_manager ImageManager;
...
void GameEngine::Start()
{
        // TEMP This stuff goes here???
        Initialise_Flock();
        Initialise_Positions();

        // timing
        sf::Clock EngineClock;

        while (Window.isOpen())
        {
                // restart the clock and save the elapsed time in dt (delta time)
                sf::Time dt = EngineClock.restart();

                // make a fraction from the delta time
                float dtAsSeconds = dt.asSeconds();

                Input();
                Update(dtAsSeconds);
                Draw();
        }

        return;
}

...

void GameEngine::Update(float DTimeAsSeconds)
{
        Move_All_Boids_To_New_Positions();

        return;
}

void GameEngine::Draw()
{
        // rub out the last frame
        Window.clear(sf::Color::White);

        // draw the background
        Window.draw(BackgroundSprite);

        // draw our Boids
        for (int i = 0; i < MAX_FLOCK_SIZE; i++)
        {
                Window.draw(Flock[i].GetSprite());
        }

        // show everything we have just drawn
        Window.display();
}

...

void Move_All_Boids_To_New_Positions()
{
        // This is where our Boid Algorithm rules will be applied

        // TEMP for testing...
        sf::Vector2f tempPosUpdate;
        tempPosUpdate.x = 0.25;
        tempPosUpdate.y = 0.25;
        sf::Vector2f currentPos;

        for (int i = 0; i < MAX_FLOCK_SIZE; i++)
        {
                currentPos = Flock[i].GetPosition();
                currentPos = Add_Vec(currentPos, tempPosUpdate);
                Flock[i].SetPosition(currentPos);
                Flock[i].Update(currentPos);
        }
}
 

In Main I just instantiate GameEngine, which runs until either the window is closed or the escape key is pressed. All the ‘action’ happens inside GameEngine. This is probably the wrong way to do things but I’m new to OOP and it confuses the hell out of me. Still, white rectangle problem aside, it works so I’ll stick with it for now and re-write or re-factor as I learn.

I’m using this image manager to try to load the texture and keep it in scope. I’m instantiating image_manager globally in my GameEngine.cpp file so it should be available to everything inside there… right? (Otherwise how else am I supposed to keep the texture in scope?)

I’ve tried passing ImageManager.get_image( "boid_small.png") to the Boid’s SetTexture(sf::Texture TextureImage) in various different places – for example as I’m adding Boids to the Flock array in Initialise_Flock(), or just before Window.draw(Flock[].GetSprite()); in GameEngine::Draw().

Nothing works! Argh!!

I can’t add sf::Texture as a member variable of Boid and set the texture in the constructor because ImageManager doesn’t exist outside of GameEngine so the constructor doesn’t know what it is… this is why OOP really confuses me. I’m used to modular programming where I could just put ImageManager in a global module and make it available to anything that needed it anywhere in the program.

Please help me understand what I’m doing wrong. As I said above everything else works for now – the ‘Flock’ is initialised and all the boids move as per Move_All_Boids_To_New_Positions() – it’s just that they are all represented by white rectangles instead of having textures!

Thank you in advance!
« Last Edit: March 04, 2017, 01:43:19 pm by DoTheDonkeyKonga »

Turbine

  • Full Member
  • ***
  • Posts: 102
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #1 on: March 04, 2017, 03:08:50 pm »
I don't see you setting the texture anywhere, try that and then setting the sprite to use that texture.

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #2 on: March 04, 2017, 04:17:49 pm »
Hi Turbine

I don't see you setting the texture anywhere, try that and then setting the sprite to use that texture.

Sorry, it really isn't clear from the way I wrote my post - my bad! - but I did try setting the texture first. In several different places. My following paragraph explains:

Quote from: DoTheDonkeyKonga
I’ve tried passing ImageManager.get_image( "boid_small.png") to the Boid’s SetTexture(sf::Texture TextureImage) in various different places – for example as I’m adding Boids to the Flock array in Initialise_Flock(), or just before Window.draw(Flock[].GetSprite()); in GameEngine::Draw().

It doesn't appear in my code above as I've been moving it all over the place trying to make it work and must have forgotten to put it back in again before posting. Sorry. Again, my bad!

I also tried something along the lines of:

// outside the for loop...
sf::Texture BoidTexture = ImageManager.get_image( "boid_small.png");

//blah blah we're now inside the for loop...
Flock[i].SetTexture(BoidTexture)
 

But that didn't work either.

I suppose I have two (albeit intrinsically linked) issues really - one is not being able to associate a texture with a sprite, and the other is not knowing exactly where in my code I should be trying to do so.

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #3 on: March 04, 2017, 04:30:39 pm »
After the texture is loaded (i.e. sf::Texture has a texture), the sprite should be set (or reset) from the texture.
As long as the texture still exists - and doesn't change - the sprite doesn't need to be reset from the texture.

sf::Texture texture;
sf::Sprite sprite;
texture.loadFromFile("texture.png");
sprite.setTexture(texture);

Are you certain that the image manager is working as expected? Have you tried loading the texture directly (as in the example above)?


If the texture changes, "reset" the sprite from the texture:
sprite.setTexture(texture, true);
The same texture continues to be used but the added boolean parameter updates the size of the texture to be used (texture rectangle).
This part may not be related to your issue.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #4 on: March 04, 2017, 06:53:54 pm »
Hi Hapax. Thanks for the reply.

After the texture is loaded (i.e. sf::Texture has a texture), the sprite should be set (or reset) from the texture.
As long as the texture still exists - and doesn't change - the sprite doesn't need to be reset from the texture.

sf::Texture texture;
sf::Sprite sprite;
texture.loadFromFile("texture.png");
sprite.setTexture(texture);

Are you certain that the image manager is working as expected? Have you tried loading the texture directly (as in the example above)?

I've tried that. The only time it worked was during my initial tests when I was directly instantiating Boid (without putting it into an aray) just to test I could draw the sprite (complete with texture) to the screen successfully. It worked. As soon as I started using an array (Flock) to hold multiple instances of Boid it ceased to work. That's when I fist encountered the white rectangle problem. I know it's because adding them to the array (which will eventually be an std::vector once I'm done testing) destroys the associated Texture (or, rather, moves it around in memory so the sprite no longer points to it if I have that right?) and that's the problem I'm now trying to solve.

I've just tried making a new constructor which takes an sf::Texture as a parameter and assigns it to the Boid's member sprite like so...

Boid::Boid(sf::Texture TextureImage)
{
        // the type of Boid
        Type BoidType = NORMAL;

        // the Boid's starting position
        Position.x = 0;
        Position.y = 0;

        // is the Boid perching?
        IsPerching = false;

        // How fast is the Boid travelling
        CurrentSpeed = .0f;

        // What is the Boid's maximum speed?
        MAX_SPEED = 1.f;

        // associate a texture with the sprite
        BoidSprite.setTexture(TextureImage, true);

        // set the initial position of the sprite
        BoidSprite.setPosition(Position);
}
 

And then changed Initialise_Flock() like so...

void Initialise_Flock()
{
        sf::Texture BoidTexture;
        BoidTexture.loadFromImage(ImageManager.get_image("boid_small.png"));
       
        Boid boid(BoidTexture);

        for (int i = 0; i < MAX_FLOCK_SIZE; i++)
        {
                Flock[i] = boid;
        }

        return;
}
 

But that hasn't worked either. Everything still compiles fine and the sprites come into being a move correctly - just with no associated textures :(

Perhaps the image manager isn't working as I expect. I looked into the thor resource manager but that seems so complicated and so much overkill for simply keeping a sprite with it's associated texture.

So back to the drawing board I guess...

Edit: I'm aware of this...
If the texture changes, "reset" the sprite from the texture:
sprite.setTexture(texture, true);
The same texture continues to be used but the added boolean parameter updates the size of the texture to be used (texture rectangle).
This part may not be related to your issue.
...but I don't think that's the problem here. The texture isn't changing. It should be the same texture for each Boid and that texture should remain for the whole time the Boid is in existence - wherever it moves to on screen. If that makes sense?
« Last Edit: March 04, 2017, 06:58:43 pm by DoTheDonkeyKonga »

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #5 on: March 04, 2017, 07:12:37 pm »
Since an array (c-style array) is static, there is no moving around of its items since there can be no adding or removing. All of them are always in existence.
If if they were to be moved (as may be the case with using an std::vector), it's only the pointers that move, not their contents.
In fact, even if the sprites themselves were moved around, they would still point to the same texture as the texture as long as the texture is not moved.

Is it possible that you drawing more sprites than you realise (the rest of the array that you aren't using)? The ones you haven't set the texture for may be drawn over the ones that are working.



UPDATE:

I've just tried making a new constructor which takes an sf::Texture as a parameter and assigns it to the Boid's member sprite
This won't work:
Boid::Boid(sf::Texture TextureImage)
{
        // ...
        BoidSprite.setTexture(TextureImage, true);
        // ...
}
 
The parameter is passed by value so is copied locally and that is set to the sprite and then the local texture is destroyed (this is the essence of the explanation of the White Square Problem). Pass that parameter instead by reference:
Boid::Boid(sf::Texture& TextureImage)
and make sure the original texture that is passed continues to exist (currently, it is destroyed at the end of Initialise_Flock but needs to continue to exist as long as the sprites use it.


I suppose the easiest way to fix the resource problem is to store them in main() (or in an object that is stored in main) and then pass them to the functions and classes that need to use it.
« Last Edit: March 04, 2017, 07:21:19 pm by Hapax »
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #6 on: March 04, 2017, 08:55:41 pm »
The parameter is passed by value so is copied locally and that is set to the sprite and then the local texture is destroyed (this is the essence of the explanation of the White Square Problem). Pass that parameter instead by reference:
Boid::Boid(sf::Texture& TextureImage)
and make sure the original texture that is passed continues to exist (currently, it is destroyed at the end of Initialise_Flock but needs to continue to exist as long as the sprites use it.

That hasn't worked either. I thought by storing the texture in the image manager (which is instantiated at the top of GameEngine.cpp - outside of Initialise_Flock) it continues to exist for as long as the image manager does (which is the life of the program). Thus when I associate any sprite with that texture it'll always point to that same texture. I take it I'm very wrong on this then?

Quote
Is it possible that you drawing more sprites than you realise (the rest of the array that you aren't using)? The ones you haven't set the texture for may be drawn over the ones that are working.

I don't think so. I've set the array size to 20 (just an arbitrary number for testing purposes). I loop through the array adding a new Boid object to each element (that's Initialise_Flock). Then I draw them to the screen (GameEngine::Draw), then loop through the array again updating their positions (GameEngine::Update), then draw them to the screen again (GameEngine::Draw) and so on ad infinitum. This is working in that 20 objects are being drawn to the screen and then moving around on each run through the loop, just as they're supposed to - it's just that they have no associated textures.

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #7 on: March 04, 2017, 10:55:02 pm »
Does your Image Manager store sf::Textures or images with which you create sf::Textures?
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #8 on: March 05, 2017, 12:44:39 am »
It stores images and I'm using sf::Texture's loadFromImage method.

Yesterday I actually converted image_manager to use textures instead of images though and that didn't work either. Same result.

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #9 on: March 05, 2017, 12:50:50 am »
Textures can be loaded from images (the images being sf::Images, of course).

The thing, though, is that the texture that is created from those image is the actual texture used by the sprite:
The sf::Texture must exist as long as the sprite uses it.
That is, create the sf::Texture in main() and keep it alive. Then, pass it (by reference) to all the functions that need it and see if you still have the problem.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #10 on: March 05, 2017, 10:29:09 am »
Ah, I see. That makes perfect sense now.

And it works!!!! A thousand thank yous Hapax! Seriously.

I'm still a little confused about something though - doesn't this make using a resource manager unnecessary? I thought the point of a resource manager was to (in this case at least) create a single instance of a resource - an image in my case - that stays in the same place in memory for the life of the program that the textures of each sprite that need it can then point to.

Doesn't creating the texture in main() without use of a resource manager then mean that each sprite is setting a copy of the texture - meaning that now I have 20 copies of the same texture in memory? Please bear in mind as well that 20 is an arbitrary cap I implemented for the sake of testing - a flock of Boids could potentially contain hundreds of objects!

Or am I very confused again?

Sorry for all the questions - it's just that this solution seems to contradict a lot of the other solutions proposed in other threads, at least to my mind.

Anyway, thanks again.

Edit: Sorry, I'm being stupid. Ignore me. I forgot I'm passing the texture by reference so it's not creating multiple copies - all the sprites are using the same one. Still, my question about the resource manager still stands - what benefit(s) does using one bring to the table, and is it better to do so for a project this small or continue to just create the textures in main() and keep them there (there will likely be more than just this one texture by the time I've finished the project - but I can't imagine there'll be too many)?
« Last Edit: March 05, 2017, 11:57:08 am by DoTheDonkeyKonga »

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #11 on: March 05, 2017, 12:22:33 pm »
You are very welcome and I'm glad you managed to get it working! :)

A resource manager is useful and you can store them inside one. The important thing to remember is that the actual sf::Texture should be considered a resource. The resource manager should store sf::Textures and they can then last for the entire time.

As you rightly realised, sf::Sprites just hold pointers to the textures that you pass them. This is why the sf::Texture has to continue to exist; the sprite points to it and if the texture is destroyed, it has nothing to point to.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

DoTheDonkeyKonga

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Can't point sf::Sprite to sf::Texture (white rectangle problem)
« Reply #12 on: March 05, 2017, 01:59:05 pm »
I understand. That makes complete sense now.

Thank you once again Hapax. I've learned a lot from this thread.