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.
Related
I am making a game with C++ and SFML, and I am having a problem during rendering various items with array. When I try to draw an array of sprites, the debugging is alright, and there are no warning and errors, but my game program runs only 20 seconds, then it stops running, and says, 'Unhandled exception at 0x7C50CF2E(sfml-graphics-d-2.dll), ECOVID-19 SFML.exe): 0xC0000005: Access violation reading location 0xCCCCCCD0.' I did everything I can, but I don't know why this exception occurs.
Here are my codes that I suspect the causation of the error.
Thank you for reading in spite of my poor English.
#include <SFML/Graphics.hpp>
...
using namespace std;
using namespace sf;
...
int main () {
...
//item Sprites
Texture bombTex;
bombTex.loadFromFile("images/bomb.png");
Sprite bomb;
...
Texture bomb2Tex;
bomb2Tex.loadFromFile("images/bomb_2.png");
Sprite bomb_2;
...
Texture cakeTex;
cakeTex.loadFromFile("images/cake.png");
Sprite cake;
...
Texture coffeeTex;
coffeeTex.loadFromFile("images/coffee.png");
Sprite coffee;
...
Texture chickenTex;
chickenTex.loadFromFile("images/chicken.png");
Sprite chicken;
...
Texture pizzaTex;
pizzaTex.loadFromFile("images/pizza.png");
Sprite pizza;
//item array (I made an item array to display & render various items in the game screen.)
Sprite item[10]; //Although I change the array size to 4 or 5, the same exception occurs.
item[0] = bomb;
item[1] = coffee;
item[2] = bomb_2;
item[3] = chicken;
item[4] = pizza;
std::vector<Sprite> items;
items.push_back(Sprite(item[4]));
...
while (window.isOpen())
{ ...
...
for (size_t i = 0; i < items.size(); i++)
{
if (humanArr[index].getGlobalBounds().intersects(item[i].getGlobalBounds()))
//humanArr[index] is a player Sprite.
{
...
items.erase(items.begin() + i);
}
}
...
window.clear();
...
for (size_t i = 0; i < items.size(); i++)
{
window.draw(item[i]); // <- the exception error occurs here.
}
...
window.display();
}
return 0;
}
What may be happening is that when you copy the Sprite item[10]; to the std::vector<Sprite> items; the Sprite class is making a shallow copy. It means that if the Sprite class is allocating any memory with new operator and storing it in a member pointer, then the shallow copy will only copy the address to which the pointer is pointing. When you do call items.erase(items.begin() + i); the destructor of the Sprite will be called, and in the destructor it may be calling delete in that pointer to some resourse.
When you call window.draw(item[i]); the library will try to use that resource and will find an invalid address.
What I suggest is that you don't use the Sprite item[10]; and only the std::vector<Sprite> items; , like this:
std::vector<Sprite> items;
items.push_back(bomb);
items.push_back(coffee);
...
window.draw(items[i]);
You don't need to use an intermediate array, just the std::vector<Sprite>;
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;
}
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 have a dynamic body with many polygon shapes for my game character. In order to turn back the game character I flip vertices using this code:
void Box2dManager::flipFixtures(bool horizzontally, b2Body* physBody)
{
b2Fixture* fix = physBody->GetFixtureList();
while(fix)
{
b2Shape* shape = fix->GetShape();
if(shape->GetType()== b2Shape::e_polygon)
{
// flipping x or y coordinates
b2PolygonShape* ps = (b2PolygonShape*)shape;
for(int i=0; i < ps->GetVertexCount(); i++)
horizzontally ? ps->m_vertices[i].x *= -1 : ps->m_vertices[i].y *= -1;
// revert the vertices (no need after Box2D 2.3.0 as polygon creation computes the convex hull)
b2Vec2* reVert = new b2Vec2[ps->GetVertexCount()];
int j = ps->GetVertexCount() -1;
for(int i=0; i<ps->GetVertexCount();i++)
reVert[i] = ps->m_vertices[j--];
ps->Set(&reVert[0], ps->GetVertexCount());
}
fix = fix->GetNext();
}
}
I also have static edge shapes as walls. And it happens, that when I flip the character, sometimes vertices of the same polygon appear to be in different sides of the same static edge shape. As a result my character sticks to the wall (it is being trapped in the static edge shape). How I should handle this situation?
I have asked a similar question in the past but I still can't get my head around this. I am doing an invaders game based on SFML 2.0. So far I have one sprite sheet which runs through using my clock. This part works just fine:
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <iostream>
#include <string>
int spriteWalkSpeed = 10;
int DownspriteWalkSpeed = 5;
int up=-spriteWalkSpeed, down=DownspriteWalkSpeed, left=-spriteWalkSpeed, right=spriteWalkSpeed;
int xVelocity =0, yVelocity=0;
const int SC_WIDTH=800;
const int SC_HEIGHT= 600;
const float REFRESH_RATE =0.01f; //how often we draw the frame in seconds
const double delay=0.1;
const int SPRITEROWS=1; //number of ROWS OF SPRITES
const int SPRITECOLS=2;//number of COLS OF SPRITES
std::string gameOver = "Game Over";
int main()
{
// Create the main window
sf::RenderWindow App (sf::VideoMode(SC_WIDTH, SC_HEIGHT, 32), "Space Invaders!",sf::Style::Close );
// Create a clock for measuring time elapsed
sf::Clock Clock;
//background texture
sf::Texture backGround;
backGround.loadFromFile("images/background.jpg");
sf::Sprite back;
back.setTexture(backGround);
//load the invaders images
sf::Texture invaderTexture;
invaderTexture.loadFromFile("images/invaders.png");
sf::Sprite invadersSprite(invaderTexture);
std::vector<sf::Sprite> invaderSprites(10, sf::Sprite(invaderTexture));
int invadersWidth=invaderTexture.getSize().x;
int invadersHeight=invaderTexture.getSize().y;
int spaceWidth=invadersWidth/SPRITECOLS;
int spaceheight=invadersHeight/SPRITEROWS;
//Sprites
sf::IntRect area(0,0,spaceWidth,spaceheight);
invadersSprite.setTextureRect(area);
invadersSprite.setPosition(30, NULL);
App.setKeyRepeatEnabled(false);
//Collision detection
// Start game loop
while (App.isOpen())
{
// Process events
sf::Event Event;
while (App.pollEvent(Event))
{
// Close window : exit
if (Event.type == sf::Event::Closed)
App.close();
}
// Create an array of 10 sprites (cannot initialise them with textures here)
for (int i = 0; i < 10; i++)
{
invaderSprites[i].setPosition(30,0);
if(Clock.getElapsedTime().asSeconds()>REFRESH_RATE)
{
//carry out updating tasks
static float spriteTimer=0.0; //keep track of sprite time
spriteTimer+=Clock.getElapsedTime().asSeconds();
static int count=0; //keep track of where the sub rect is
if(spriteTimer>delay)
{
invaderSprites[i].setTextureRect(area);
++count;
invaderSprites[i].move(xVelocity, yVelocity);
if(count==SPRITECOLS) //WE HAVE MOVED OFF THE RIGHT OF THE IMAGE
{
area.left=0; //reset texture rect at left
count=0; //reset count
}
else
{
area.left+=spaceWidth; //move texture rect right
}
spriteTimer=0; //we have made one move in the sprite tile - start timing for the next move
}
Clock.restart();
}
App.draw(back);
App.draw(invaderSprites[i]);
// Finally, display the rendered frame on screen
App.display();
}
}
return EXIT_SUCCESS;
}
The issue I am having is that the sprite only shows once, not 10 times (as the for loop states)
std::vector<sf::Sprite> invaderSprites(10, sf::Sprite(invaderTexture));
// Loop over the elements of the vector of sprites
for (int i = 0; i < invaderSprites.size(); i++)
{
invaderSprites[i].setPosition(30, NULL);
}
// Create an array of 10 sprites (cannot initialise them with textures here)
sf::Sprite invaderSprites[10]; // Loop over each sprite, setting their textures
for (int i = 0; i < 10; i++)
{
invaderSprites[i].setTexture(invaderTexture);
}
I am pretty sure it has something to do with the app drawing invadersSprite whereas the loop is setup for invaderSprites. Even just a little insight into what is going wrong would be such a big help.
I am pretty sure it has something to do with the app drawing
invadersSprite whereas the loop is setup for invaderSprites.
Yes, that certainly has something to do with it. You need to call App.draw(...) for each sprite that you want to draw. You're not calling it for any of the sprites in your vector. For that, you would want a loop:
for (int i=0; i<invaderSprites.size(); ++i)
App.draw(invaderSprites[i]);
There are other problems though. For example, why are you declaring an array of sprites called invaderSprites, when you already have a vector of sprites with that same name? The latter hides the former once it is declared.
Another thing is that you are setting all the sprites to the same position, so even if if you do manage to draw them all, they will all be in the same spot, and as such they will not appear as separate objects.