Hello. Thanks for clicking on this!
Anyways, I'm working on the framework for an in-game GUI, and I decided to start with tileable images that drained from the top down. I did this with rendertextures, but found that they were too slow to be updated several times a second.
So I made a class that will hold a dynamic amount of sprites that I can easily move, rotate, scale, etc. To do this, I have a class that stores textures and sprites in separate vectors. I'm going to make functions that can set the position/rect/rotation/scale of individual sprites, as well as transform all the sprites as a group. I don't get any errors in my code, but the sprites very infrequently draw to the screen. And if they do, only the most recent one is displayed (the earlier ones will either not display at all, or show up as a tan box).
My code (trimmed down so as to not bog you down with the stuff unrealated to this problem) is as follows:
Main.cpp
#include <iostream>
#include <utility>
#include <vector>
#include <SFML/Graphics.hpp>
#include "Sprite_Holder.h"
int main() {
sf::RenderWindow window(sf::VideoMode(640,480),"UI Test");
Sprite_Holder heartContainer;
heartContainer.add_sprite("heart_back");
heartContainer.add_sprite("heart");
heartContainer.add_sprite("heart_shine");
while (window.isOpen()) {
sf::Event testEvent;
while (window.pollEvent(testEvent)) {
if(testEvent.type == sf::Event::Closed) {
window.close();
}
}
window.clear(sf::Color(255,255,255));
for(unsigned int i=0; i<heartContainer.sprites.size(); i++) {
window.draw(heartContainer.sprites[i]);
}
window.display();
}
return 1;
}
Sprite_Holder.h
#ifndef _SPRITE_HOLDER_LB_H
#define _SPRITE_HOLDER_LB_H
#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Sprite_Holder
{
public:
int add_sprite(string filename, int index=-1, string extension="png", string directory="img");
void move_sprite(int x, int y);
void rotate_sprite(float angle);
void scale_sprite(float x, float y);
void set_sprite_rect(int spriteIndex, int left, int top, int width, int height);
void set_sprite_pos(int spriteIndex, float x, float y);
void set_sprite_rotate(int spriteIndex, float angle);
void set_sprite_scale(int spriteIndex, float angle);
vector<sf::Sprite> sprites;
vector<sf::Texture> textures;
};
#endif
Sprite_Holder.cpp
#include "Sprite_Holder.h"
int Sprite_Holder::add_sprite(string filename, int index, string extension, string directory)
{
sf::Sprite newSprite;
sf::Texture newTexture;
newTexture.loadFromFile(directory + "/" + filename + "." + extension);
if(sprites.size() <= index || index == -1) {
sprites.push_back(newSprite);
textures.push_back(newTexture);
sprites.back().setTexture(textures.back());
return (sprites.size() - 1);
} else {
sprites.insert(sprites.begin()+index-1, newSprite);
textures.insert(textures.begin()+index-1, newTexture);
sprites[index].setTexture(textures[index]);
return index;
}
return -1;
}
I'm using SFML 2.0 on visual c++ 2008. Thanks for reading this. Any help you could offer would be GREAT. I've been working on this for the past couple days (mostly just figuring out rendertexture and some algorythems for only drawing the proper percentage of the heart, but still) and I am completely stuck. If this just won't work, is there another solution for drawing things to the screen composed of a dynamic amount of images (I want to allow blue hearts to appear over the red ones if the player's max health goes past a certain point) that can be executed quickly?
It doesn't use a local texture. It stores the texture in a vector, then assigns it to the sprite (said sprite already being stored in a vector). So as far as I know, I'm keeping a copy of the texture in memory that I can refer to.
(that part is as follows. sprites and vectors are class variables declared in the header)
int Sprite_Holder::add_sprite(string filename, int index, string extension, string directory)
{
sf::Sprite newSprite;
sf::Texture newTexture;
newTexture.loadFromFile(directory + "/" + filename + "." + extension);
if(sprites.size() <= index || index == -1) {
sprites.push_back(newSprite);
textures.push_back(newTexture);
sprites.back().setTexture(textures.back());
return (sprites.size() - 1);
} else {
sprites.insert(sprites.begin()+index-1, newSprite);
textures.insert(textures.begin()+index-1, newTexture);
sprites[index].setTexture(textures[index]);
return index;
}
return -1;
}
Am I doing it right? Or am I not properly keeping hold of the texture reference?
I see what the problem is. It's the two vectors your using to store the sprites and textures in your Sprite_Holder class. Your basically overwriting the last sprite/texture you made when calling Sprite_Holder::add_sprite. This is because your not storing them in vectors of pointers, therefore no new memory can be created! What you need to do is this
//make these vectors of pointers instead
vector<sf::Sprite*> sprites;
vector<sf::Texture*> textures;
Now when creating a new sprite or texture use the new operator. Like this
sprites.push_back(new sf::Sprite);
textures.push_back(new sf::Texture);
Then go ahead and set the last sprites texture to the last texture
sprites.back()->setTexture(*textures.back());
You should be able to easily implement this into your code and modify it to your needs. Btw make sure to delete that newly allocated memory when you don't need it anymore. Make sure when doing this to delete the sprite FIRST, THEN the texture or you'll have a null reference error when that sprite trys to access its texture. If you want to be safe and not have to worry about the mess of raw pointers, I would suggest using c++11's std::unique_ptr class to store those objects in. PM if you want help implementing this, I've done it several times and it was a pain at first!
@xDarkShadowKnightx, I've had bad luck trying to work with textures as pointers though (as sprites can't accept a pointer to a texture in their setTexture method usually), so I'm not sure if I'll be able to get that to work.
Why not just dereference the pointer first? Like:
Sprite.setTexture(*texturePointer);
Also, if you use std::unique_ptr<sf::Texture> and std::unique_ptr<sf::Sprite> then you won't have to write the destructor and won't need to worry about memory leaks. :)