Hey guys,
I've been on a roll with SFML, creating a simple scrolling shooter, but so far the only way I've been able to keep references to graphics available long enough to be used is by storing them in fixed-size arrays or class properties for every instance. Initially I thought it was possible to use vectors, maps, etc. but quickly found out that the way those classes work internally is to make copies, so anything I create is destroyed the second it goes out of scope. Sounds don't play, and graphics get the white square bug, if I use anything out of the STL.
But then again, I'm thinking that might not actually be the case. I've only been doing C++ for about 6 months, and it's the first language I've learned where memory management is an issue. So odds are that there's some other way I just haven't learned yet. Take for example these 2 versions of the same class:
// This is the version that works, using a class property for every instance
#ifndef SOUND_PLAYER_H
#define SOUND_PLAYER_H
#include <map>
#include <SFML/Audio.hpp>
class SoundPlayer
{
public:
static SoundPlayer * Get();
void PlayerLaser();
private:
static SoundPlayer * Instance;
SoundPlayer();
sf::SoundBuffer player_laser, enemy1_explode;
sf::Sound sound;
};
SoundPlayer * SoundPlayer::Instance = NULL;
SoundPlayer * SoundPlayer::Get()
{
if (Instance == NULL)
Instance = new SoundPlayer();
return Instance;
}
SoundPlayer::SoundPlayer()
{
if (!player_laser.loadFromFile("bin\\debug\\sounds\\player_laser.wav"))
throw "Error loading the player laser sound";
if (!enemy1_explode.loadFromFile("bin\\debug\\sounds\\enemy1_explode.wav"))
throw "Error loading the enemy 1 explosion sound";
}
void SoundPlayer::PlayerLaser()
{
sound.setBuffer(player_laser);
sound.play();
}
#endif
// And this is the one that doesn't work, using maps
// I've tried maps of objects, and maps of pointers (which is what's shown here)
// and both don't seem to have what it takes to work in SFML - but they've been around for decades so it's obviously just me being a noob :)
#ifndef SOUND_PLAYER_H
#define SOUND_PLAYER_H
#include <map>
#include <SFML/Audio.hpp>
class SoundPlayer
{
public:
static SoundPlayer * Get();
void PlayerLaser();
private:
static SoundPlayer * Instance;
SoundPlayer();
std::map<std::string, sf::SoundBuffer*> buffers;
std::map<std::string, sf::Sound*> sounds;
};
SoundPlayer * SoundPlayer::Instance = NULL;
SoundPlayer * SoundPlayer::Get()
{
if (Instance == NULL)
Instance = new SoundPlayer();
return Instance;
}
SoundPlayer::SoundPlayer()
{
sf::SoundBuffer b1;
if (!b1.loadFromFile("bin\\debug\\sounds\\player_laser.wav"))
throw "Error loading the player laser sound";
buffers.insert(std::make_pair("player_laser", &b1));
sf::Sound s1;
s1.setBuffer(b1);
sounds.insert(std::make_pair("player_laser", &s1));
}
void SoundPlayer::PlayerLaser()
{
sounds["player_laser"]->play();
}
#endif
Both compile fine, and both "look" like they should work, but because of however these STL classes work "under the hood" they just don't seem to work well with SFML, making the idea little more than wishful thinking. From what I've read (including another question on this forum) the way they work is to make copies of the objects, and I haven't been able to figure out a way around that obstacle yet. I've tried vectors/maps of objects, and also pointers (shown above), and none of it seems to work... so it's gotta be me just being a noob (lol).
But it's no biggy if it actually can't be done. Fixed arrays and class properties per object are working reliably, and I've got a pretty cool little game going using them as my go-to for everything SFML-related. Heck, even the NES could only have up to 64 objects on the screen at once, so my player object only being able to use up to 5 laser instances or there only being up to 16 enemies is not an issue. But there's a reason the STL was created, problems with the fixed-size way of doing things that it was meant to solve, and I bet there's a better way to use it that I just haven't figured out yet.
So how do you guys handle textures, sprites, sound buffers, sounds, and other stuff like that? I'm sure I'm not the first guy who's ever had this question (and since the white square issue is brought up in the documentation it must be super-common) but so far I haven't managed to fish up any answers on Google, so I figured I'd ask the pros. :)
Thanks in advance. :)
Yes, you can. Usually using a vector is enough for most cases, but you could use lists or maps as well if needed, just make sure that you know the differences between them, as each have both advantages and disadvantages. If you are just starting out, I advice just using vectors, as they are pretty straightforward.
It's true that the elements are destroyed when the container goes out of scope, but that's the case for any variable, so I don't really see the problem. You just need to make sure to declare your vector/list/map in the appropriate scope, like in your main, for example, and the elements shouldn't be destroyed until your game is closed.
The white square problem happens when your sprite is using a texture that no longers exists, so it's not a problem about the STL containers per se, but more about scope/lifetime. Take the following example:
int main() {
sf::Sprite sprite;
int number = 1;
if (number == 1) {
sf::Texture texture;
texture.loadFromFile("sprite1.png");
sprite.setTexture(texture);
}
}
The code above will produce the white square problem because the texture is declared inside the "if" statement, so when the code inside the brackets ends, the texture will no longer exists, and you'll get a white square when you draw your sprite.
If you do the following instead:
int main() {
sf::Texture texture;
sf::Sprite sprite;
int number = 1;
if (number == 1) {
texture.loadFromFile("sprite1.png");
sprite.setTexture(texture);
}
}
Then it's not a problem, the texture won't get destroyed when the "if" statement ends due to being declared outside of it. The same applies if you are using a vector of textures, or a class, or anything, really.
As to why your particular code doesn't work, I can't really say anything because I'm not familiar with maps, and besides, you only provided us the code of your class, but not the code where you actually use it. However, I can say that you seem to be overcomplicating yourself by using an static instance. I'm no expert at C++, but that looks like bad design, and it seems like it would bring tons of trouble. Maybe your problem is related to that.