I'm experiencing the white square problem in SFML. I'm working on a game that uses a tiled map. A Game class will load the tile set as a sf::Texture from a file and then a getTileById function in Game will "Cut" out the appropriate tile from the tileset texture. Eg.
sf::Sprite GameScreen::getSpriteByPos(int pos, sf::Texture texture, int tileSize) {
sf::IntRect subRect;
subRect.left = (pos-1)*tileSize;
subRect.top = (pos-1)*tileSize;
subRect.width = tileSize;
subRect.height = tileSize;
sf::Sprite tileSprite(texture, subRect);
return tileSprite;
}
The sprite will then be passed into a Tile object, which will then set its sprite_ attribute to it. Eg.
void Tile::setSprite(sf::Sprite sprite) {
sprite_ = sprite;
}
The Game object will load all tiles in this way and store them all in a vector. To draw them, it will loop through the vector and call the Tile::draw(sf::RenderWindow&) method on each one of them (Passing in the RenderWindow to be drawn to). This method takes an sf::RenderWindow and the tile simply calls the sf::RenderWindow::draw(sf::Sprite) on the RenderWindow with its sprite_ attribute. Eg.
void Tile::draw(sf::RenderWindow &window){
window.draw(sprite_);
return;
}
I'd like to do drawing this way because the Tile::draw method is inherited from a Drawable class, so that all objects that are drawable can inherit from Drawable and implement their drawing method in a way that suits them, something that will be necessary in my project. The sprite is being drawn as a white square though, which strikes me as strange as the tileSet_ attribute has not been destroyed, it is an attribute of the Game class and still exists.
Can anybody tell me what is going on here? Any help would be appreciated.
You are passing your texture "by value". That means you get a copy of your texture inside this function:
sf::Sprite GameScreen::getSpriteByPos(int pos, sf::Texture texture, int tileSize) {
sf::IntRect subRect;
subRect.left = (pos-1)*tileSize;
subRect.top = (pos-1)*tileSize;
subRect.width = tileSize;
subRect.height = tileSize;
sf::Sprite tileSprite(texture, subRect);
return tileSprite;
}
But that copy is destroyed when the function ends.
You don't need that copy, so don't make a copy:
sf::Sprite GameScreen::getSpriteByPos(int pos, const sf::Texture& texture, int tileSize) {
sf::IntRect subRect;
subRect.left = (pos-1)*tileSize;
subRect.top = (pos-1)*tileSize;
subRect.width = tileSize;
subRect.height = tileSize;
sf::Sprite tileSprite(texture, subRect);
return tileSprite;
}
Related
I'm trying to build a gameengine in SDL2 with cpp. I have a class called 'entity' which has some data for movement and also some pointers to a surface and a texture. A function "render" is called inmass to render each sprite based on the g_entities vector.
class entity {
...
SDL_Surface* image;
SDL_Texture* texture;
entity(const char* filename, SDL_Renderer * renderer, float size) {
image = IMG_Load(filename);
width = image->w * size;
height = image->h * size;
texture = SDL_CreateTextureFromSurface(renderer, image);
g_entities.push_back(this);
}
~entity() {
SDL_DestroyTexture(texture);
SDL_FreeSurface(image);
//TODO remove from g_entities
}
void render(SDL_Renderer * renderer) {
SDL_Rect dstrect = { x, y, width, height };
SDL_RenderCopy(renderer, texture, NULL, &dstrect);
}
...
}
So the program makes a new texture and surface for each sprite. Is this okay? Is there a faster way?
If so, I'd like to clean that up before it becomes a bigger mess.
I made a testlevel with 96 sprites that each take up 2% of the screen with tons of overdraw and ft is 15ms (~65fps)at a resolution of1600x900
Yes but actually, no. If the same sprite will be used many times without modification, its most efficient for those objects to have pointers to the same SDL_Texture. Additionally, the image can be freed after the texture is generated. Furthermore, loading these in the constructor may be a bad idea since objects made on-the-fly will require disk-reading.
I set up a system where entities are given another variable on construction, and if it is positive, the entity will check and see if any other entity used the same file for it's sprite, and if so, just use that same reference.
That means that objects like bullets that are spawned and destroyed can be handled efficiently by spawning a single bullet in the level.
https://www.reddit.com/r/sdl/comments/lo24vt/is_it_okay_to_have_a_sdl_surface_and_sdl_texture/
I'm making a game, and I have created a class to store the map. I have a function to create a single sprite on which is drawn all the visible map. I've used a RenderTexture and then I create and return a sprite created with it. However, the sprite is completely white.
Here is the code for the map generation and the sprite draw
void Map::procedural_generate()
{
cout << "Starting the map generation" << endl;
int generation_max = m_size.x * m_size.y,
current_generation = 0;
vector<Decor> decor_vector;
m_decor_vector.clear();
const vector<Decor> const_vector
{
Decor(GRASS)
};
for (int i = 0; i < m_size.x; i++)
{
decor_vector.clear();
for (int j = 0; j < m_size.y; j++)
{
decor_vector.push_back(const_vector[GRASS]);
decor_vector[j].set_position(Vector2f(i * 32, j * 32));
current_generation++;
cout << "Generation : " << current_generation << '/' << generation_max << '\r';
}
m_decor_vector.push_back(decor_vector);
decor_vector.clear();
}
cout << "Map generation has ended" << endl;
}
Sprite Map::sprite()
{
RenderTexture map;
if (!map.create(WINDOW_WIDTH, WINDOW_HEIGTH))
cout << "Map : Unable to create the RenderTexture" << endl;
map.clear(Color::Green);
for (int i = 0; i < m_size.x; i++)
for (int j = 0; j < m_size.y; j++)
map.draw(m_decor_vector[i][j].sprite());
map.display();
Sprite sprite(map.getTexture());
return sprite;
}
The problem seems to come from the map.draw part, as, if I use map.clear(Color::Red) before the double loop, the sprite stays white, but if I use sprite.setColor(Color::Red), it works. The fact is that the decor sprites are not covering the whole texture (the texture is 1920x1080, and the sprites 320x320), so I can't understand what's happening.
This is not the decor sprite loading, if I use map.draw(Decor(GRASS)) the sprite is displayed correctly.
I've tried to use pointer for const_vector and for the returned sprite, whithout success.
Popular mistake when using SFML.
Sprite Map::sprite()
{
RenderTexture map;
// ...
Sprite sprite(map.getTexture());
return sprite;
}
map is local. When function ends, map is destroyed. Sprite takes texture of map as shallow copy, it is only pointer to texture, according to official tutorial/documentation:
The white square problem
You successfully loaded a texture, constructed a sprite correctly, and... all you see on your screen now
is a white square. What happened?
This is a common mistake. 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.
So, sprite returned by copy stores dangling pointer. It is just undefined behaviour.
Related (my) answer, posted 1 day ago
Solution: you have to wrap sprites with textures in some kind of deep-copy-able class.
You cannot rely on shallow defaulted generated copy operations.
Such a class could look like:
class TexturedSprite {
public:
sf::Sprite sprite;
sf::Texture texture;
TexturedSprite() {
init(); // load texture
}
void init() {
// load texture
texture.loadFromFile("texture1.png");
sprite.setTexture(texture);
sprite.setPosition(0,0);
}
TexturedSprite(const TexturedSprite& theOther) {
texture = theOther.texture; // deep copy
sprite.setTexture(texture);
}
TexturedSprite& operator=(const TexturedSprite& theOther) {
if (this == &theOther)
return *this;
texture = theOther.texture; // deep copy
sprite.setTexture(texture);
return *this;
}
};
then the code:
TexturedSprite fooMain;
{
TexturedSprite foo; // local
fooMain = foo;
} // foo is destroyed, but = did deep copy of texture
is safe.
I'm having trouble implementing this function.
//Engine.cpp
void Game::createPlayer(sf::Sprite &player)
{ ///Can't get this to work
sf::Texture player_texture;
if (!player_texture.loadFromFile("player.png"))
{
//Error Loading
}
player.setTexture(player_texture);
}
I want it to replace the "creates player in void Game::run but I realize that Player_texture is local to Createplayer and that it won't exist when the function returns.
void Game::run()
{
sf::RenderWindow window(sf::VideoMode(SCREEN_X, SCREEN_Y), "Shogun Master");
srand((unsigned int)time(NULL));
//Creates Player [Makes into function]
sf::Texture player_texture;
player_texture.loadFromFile("sprites/player.png");
sf::Sprite player(player_texture);
//Creates Enemy [Make into function]
sf::Texture enemy_texture;
enemy_texture.loadFromFile("sprites/enemy.png");
sf::Sprite enemy[MAX_ENEMIES];
for (int x = 0; x < MAX_ENEMIES; x++)
{
enemy[x].setTexture(enemy_texture);
enemy[x].setPosition(rand_int(100, SCREEN_X - 100), rand_int(100, SCREEN_Y - 100)); //Spawning Point
}
//Sets Positions
player.setPosition(500, 300);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
check_closeWindows(event, window); //Closes Game if Executed
player_movement(event); //Moves Character
attack(event); //Character's attacks
}
border(player); //Border so player does not go off screen
for (int x = 0; x < total_enemies; x++)
border(enemy[x]);
movementUpdate(player, enemy); //Player & Enemy Movement Updates
collision(player, enemy[0]);
window.clear();
window.draw(player); //Draws Player
for (int x = 0; x < total_enemies; x++)
window.draw(enemy[x]); //Draws Enemy
window.display();
}
}
so how would I implement this, so that my sprite doesn't return a white box because it went out of scope.
//Engine.h
void Game::createPlayer(sf::Sprite &player);
The documentation (http://www.sfml-dev.org/documentation/2.0/classsf_1_1Sprite.php#a3729c88d88ac38c19317c18e87242560) for the setTexture() method you're calling says:
The texture argument refers to a texture that must exist as long as the sprite uses it. Indeed, the sprite doesn't store its own copy of the texture, but rather keeps a pointer to the one that you passed to this function. If the source texture is destroyed and the sprite tries to use it, the behaviour is undefined.
One way to deal with this would be to make your own struct or class which contains both the Sprite and its texture:
struct SpriteWithTexture
{
sf::Texture texture;
sf::Sprite sprite;
SpriteWithTexture()
{
sprite.setTexture(texture);
}
SpriteWithTexture(const SpriteWithTexture& that)
: texture(that.texture)
{
sprite.setTexture(texture);
}
SpriteWithTexture& operator=(const SpriteWithTexture& that)
{
texture = that.texture;
sprite.setTexture(texture);
return *this;
}
};
Then you can return this from your function:
SpriteWithTexture Game::createPlayer()
{
SpriteWithTexture player;
if (!player.texture.loadFromFile("player.png"))
{
//Error Loading
}
return player;
}
Now the texture will always live as long as the sprite.
Note however that when you construct your "enemies" you use a single texture for all of them. To enable sharing one texture between many sprites, we can enhance the above:
struct SpriteWithTexture
{
std::shared_ptr<sf::Texture> texture;
sf::Sprite sprite;
SpriteWithTexture(const std::shared_ptr<sf::Texture>& texture_)
: texture(texture_)
{
sprite.setTexture(*texture);
}
};
Now you can use it this way:
std::shared_ptr<sf::Texture> player_texture(new sf::Texture);
player_texture->loadFromFile("sprites/player.png");
SpriteWithTexture player(player_texture);
std::shared_ptr<sf::Texture> enemy_texture(new sf::Texture);
enemy_texture->loadFromFile("sprites/enemy.png");
std::vector<SpriteWithTexture> enemies;
for (int x = 0; x < MAX_ENEMIES; x++)
{
enemies.emplace_back(enemy_texture); // construct enemy Sprite
enemies.back().sprite.setPosition(rand_int(100, SCREEN_X - 100), rand_int(100, SCREEN_Y - 100)); //Spawning Point
}
Now all the enemies in the vector share a single texture. Maybe this matters for efficiency.
I'm having a strange problem where one sprite is loading however another isn't
Here is my main.cpp
window.draw(universe.getPlayer()->draw()); //Draw Player
std::list<AbstractBlock*>::const_iterator i;
std::list<AbstractBlock*>* values = universe.getLoadedBlocks();
for (i = values->begin(); i != values->end(); ++i){
window.draw((*i)->draw()); //Draw Blocks
}
window.display();
Here you can see the player drawing and all the blocks in the universe drawing. However, only the player draws and the blocks don't draw at all. I have made sure that the loop is actually working. However because draw() returns void I can't actually see if its working or not.
Here is the DirtBlock.cpp (I'm inheriting from AbstractBlock)
DirtBlock::DirtBlock(int x, int y, float rotation, b2World *world){
bodyDef.position.Set(x, y);
bodyDef.linearDamping = .03f;
bodyDef.type = b2_dynamicBody;
fixDef.density = .1f;
b2PolygonShape shape;
shape.SetAsBox(16, 16);
fixDef.shape = &shape;
body = world->CreateBody(&bodyDef);
body->CreateFixture(&fixDef);
texture.loadFromFile("Dirt.bmp");
sprite.setTexture(texture);
sprite.setOrigin(16, 16);
}
sf::Sprite DirtBlock::draw(){
sprite.setPosition(body->GetPosition().x, body->GetPosition().y);
return sprite;
}
Not everything is included, only the stuff that is involved with the drawing.
My Player class is very similar:
Player::Player(b2World *world){
texture.loadFromFile("player.bmp");
bodyDef.position.Set(10, 10);
bodyDef.type = b2_dynamicBody;
fixDef.density = .1f;
b2PolygonShape shape;
shape.SetAsBox(16, 16);
fixDef.shape = &shape;
body = world->CreateBody(&bodyDef);
body->CreateFixture(&fixDef);
body->SetLinearDamping(.03f);
sprite.setTexture(texture);
sprite.setOrigin(16, 16);
force = 10.f;
}
sf::Sprite Player::draw(){
sprite.setPosition(body->GetPosition().x, body->GetPosition().y);
sprite.setRotation(body->GetAngle() * (180 / b2_pi));
return sprite;
}
Since they are so similar why is one drawing and the other not? I have a feeling it might be because of my inheritance. I'm typically a Java programmer and I'm not 100% sure I did the inheritance correctly in C++. Should it be like this? (My DirtBlock.h)
class DirtBlock: public AbstractBlock
{
public:
DirtBlock();
DirtBlock(int x, int y, float rotation, b2World *world);
~DirtBlock();
virtual sf::Sprite draw();
virtual void destroy(b2World *world);
private:
sf::Sprite sprite;
};
I actually fixed it myself. It turned out to be a stupid mistake on my part but I might as well write and answer so if anyone else makes this mistake they may find this and it may fix their problem.
I didn't make the function draw in the AbstractBlock class virtual. Because of this, when I was calling draw for the DirtBlock, it was looking for the draw method in AbstractBlock that didn't have a virtual flag.
I'm a openFrameworks newbie. I am learning basic 2d drawing which is all great so far. I have drawn a circle using:
ofSetColor(0x333333);
ofFill;
ofCircle(100,650,50);
My question is how do I give the circle a variable name so that I can manipulate in the mousepressed method? I tried adding a name before the ofCircle
theball.ofSetColor(0x333333);
theball.ofFill;
theball.ofCircle(100,650,50);
but get I 'theball' was not declared in this scope error.
As razong pointed out that's not how OF works. OF (to the best of my knowledge) provides a handy wrapper to a lot of OpenGL stuff. So you should use OF calls to effect the current drawing context (as opposed to thinking of a canvas with sprite objects or whatever). I usually integrate that kind of thing into my objects. So lets say you have a class like this...
class TheBall {
protected:
ofColor col;
ofPoint pos;
public:
// Pass a color and position when we create ball
TheBall(ofColor ballColor, ofPoint ballPosition) {
col = ballColor;
pos = ballPosition;
}
// Destructor
~TheBall();
// Make our ball move across the screen a little when we call update
void update() {
pos.x++;
pos.y++;
}
// Draw stuff
void draw(float alpha) {
ofEnableAlphaBlending(); // We activate the OpenGL blending with the OF call
ofFill(); //
ofSetColor(col, alpha); // Set color to the balls color field
ofCircle(pos.x, pos.y, 5); // Draw command
ofDisableAlphaBlending(); // Disable the blending again
}
};
Ok cool, I hope that makes sense. Now with this structure you can do something like the following
testApp::setup() {
ofColor color;
ofPoint pos;
color.set(255, 0, 255); // A bright gross purple
pos.x, pos.y = 50;
aBall = new TheBall(color, pos);
}
testApp::update() {
aBall->update()
}
testApp::draw() {
float alpha = sin(ofGetElapsedTime())*255; // This will be a fun flashing effect
aBall->draw(alpha)
}
Happy programming.
Happy designing.
You can't do it that way. ofCircle is a global drawing method and draws just a circle.
You can declare a variable (or better three int for rgb - since you can't use ofColor as an argument for ofSetColor) that store the color for the circle and modify it in the mousepressed method.
Inside the draw method use your variables for ofSetColor before rendering the circle.