How to draw SFML shapes stored in a vector - c++

I'm making a copy of the game "Asteroids" using SFML.
To store all my asteroids I use a std::vector<sf::ConvexShape> vector, which stores all of the asteroid shapes. The problem is drawing the shapes from the vector. I looked at this post and saw that I could use an iterator to draw my shapes (I know he used sprites in that post but I presume it makes no difference if I were to use shapes.)
So I tried that:
for(std::vector<sf::ConvexShape>::iterator it=allShapes.begin();it!= allShapes.end(); ++it){
window.draw(*it);
}
And that throws an exception, terminating my program.
FYI: above, allShapes contains sf::ConvexShape shapes.
So the question is: How do I draw shapes that I store in a vector?
Full sauce:
using namespace std;
class asteroid{
public:
sf::Vector2f pos;
double angle;
void update(sf::Vector2f);
void create(std::vector<sf::ConvexShape>);
};
/* Will be implemented later
void asteroid::update(sf::Vector2f a){
pos += a;
};
*/
void asteroid::create(std::vector<sf::ConvexShape> a){
cout << "Creating..." << endl;
a.push_back(sf::ConvexShape()); //New asteroid SHAPE
std::vector<sf::ConvexShape>::iterator tempIt = a.end();
tempIt->setPointCount(4);
for(int i = 0; i < tempIt->getPointCount()+1; i++){ //Drawing asteroid
tempIt->setPoint(i, sf::Vector2f(i*100, i*100));
}
tempIt->setFillColor(sf::Color::White);
cout << "Done!" << endl;
};
int main()
{
// Init //
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
std::vector<sf::ConvexShape> allShapes; //List of all asteroid SHAPES
std::vector<asteroid> allAsteroids; //List of asteroid CLASS OBJECTS
allAsteroids.push_back(asteroid()); //New asteroid CLASS OBJECT
for(std::vector<asteroid>::iterator it = allAsteroids.begin(); it != allAsteroids.end(); ++it){ //Creating asteroids
it->create(allShapes);
}
// Loop //
while (window.isOpen())
{
// Event //
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
// Display //
window.clear();
for(std::vector<sf::ConvexShape>::iterator it = allShapes.begin(); it != allShapes.end(); ++it){
window.draw(*it);
}
window.display();
}
return 0;
}

You have a few problems here...
First:
std::vector<sf::ConvexShape>::iterator tempIt = a.end();
tempIt->setPointCount(4);
end() does NOT give you an iterator to the last element in the vector - it gives you an iterator to the "element" just past it, and is used for bounds checking (e.g. in a for loop). In other words it does not point to valid data, so the second line is invalid. There are many approaches you could use here; the following two are "most correct" though:
//with iterators using a.end() - 1
std::vector<sf::ConvexShape>::iterator tempIt = a.end() - 1;
tempIt->setPointCount(4);
//with back, accessing the element directly
a.back().setPointCount(4);
Secondly:
for(int i = 0; i < tempIt->getPointCount()+1; i++)
The asteroid has four points, so getPointCount()+1 = 5. This means that the loop executes for i = 0, 1, 2, 3, and 4 - one too many times.
Thirdly:
tempIt->setPoint(i, sf::Vector2f(i*100, i*100));
This line runs once for each i. Assuming you make the change above, this results in the points (0,0), (100, 100), (200, 200), and (300, 300). This is a line, not a convex shape. You will need to revise your formula. Setting the points directly is also a fine option, as there are only 4 to set.
You also may want to pass the vector by reference to avoid making any unnecessary copies:
void create(std::vector<sf::ConvexShape>& a){
//...
}

Related

SFML Vector of sprites and Sprite Collision Error

I am making a game with C++ and SFML, and I have a serious problem with it.
What I want to make is, when my player(a.k.a. human character) collides with one item, then only that item should be erased. For example, when player collides with 'chicken' item, then only 'chicken' item should be erased, not other items.
However, when I run my program, and when player collides with 'chicken' item, then other items are all erased. And I don't know why. I really need your help. In spite of my poor English, thanks for reading!
And here's my code :
#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];
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());
}
}
...
window.clear();
...
for (size_t i = 0; i < items.size(); i++)
{
window.draw(item[i]);
}
...
window.display();
}
return 0;
}
for (size_t i = 0; i < items.size(); i++)
{
// first problem: you are accessing item[i] instead of items[i]
if (humanArr[index].getGlobalBounds().intersects(item[i].getGlobalBounds()))
{
...
items.erase(items.begin()); // <---------- second problem is here
}
}
You are not erasing the item you are iterating over, you are instead erasing the first item. You are also accessing the wrong array. I recommend changing the name of item to avoid this in the future. Here is how you solve this:
// Pull human bounds out of the loop so that we dont' access them each
// iteration.
const humanBounds = humanArr[index];
for (auto iter = items.begin(); iter != items.end();)
{
if (humanBounds.intersects(iter->getGlobalBounds()))
{
iter = items.erase(iter);
}
else
{
++iter;
}
}
See std::vector::erase for more information.

Cannot create a sprite using a RenderTexture

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.

Most efficient way to Spawn and destroy objects for endless runner game? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I'm currently very new to SFML and i was wonder what the most efficient way to spawn and destroy objects in a vector based on the camera position ? Currently I'm instantiating a vector of wall "points" which will be linked together after debugging. Should i be Creating and destroying them based on the the cam position or moving the already existing ones to the correct position ?
I also wouldn't mind some feedback on my code for this.
Cave Chaos.cpp
int main()
{
//get classes from CaveChaos.h
CaveChaos::Wall objWall;
CaveChaos::Player objPlayer;
//set render window size and name
sf::RenderWindow window(sf::VideoMode(800, 600), "CaveChaos");
//set the view
sf::View view1(sf::Vector2f(0.f, 0.f), sf::Vector2f(window.getSize().x, window.getSize().y));
//create the player object as a triangle
sf::CircleShape Player(20, 3);
//initialise random
srand(time(0));
//sets the player position to a good position based on camera size
objPlayer.posx = window.getSize().x / 2;
objPlayer.posy = window.getSize().y / 1.1;
Player.setPosition(objPlayer.posx, objPlayer.posy);
//used to instantiate wall points
int locationsX[numofpoints];
//add random x values to locations
for (int i = 0; i < numofpoints; i++)
{
locationsX[i] = (rand() % 50) + 1;
}
while (window.isOpen())
{
sf::Event event;
//clear the window with black color
window.clear(sf::Color::Black);
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
//calls wallspawn in Walls.cpp which creates the wall points
WallSpawn(window, locationsX );
//calls playermove in Player.cpp which move the player
PlayerMove(window, Player);
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
//set the player as the cam follow point
view1.setCenter(Player.getPosition().x, Player.getPosition().y - objPlayer.posy / 3);
//set the view to the player
window.setView(view1);
window.display();
}
return 0;
}
Currently when i call WallSpawn() it loops through 10 objects and spawns them, would there be a way to spawn an infinite amount of objects instead ?
Walls.cpp
void WallSpawn(sf::RenderWindow& window, int locationsX[] )
{
//initiate random
srand(time(0));
//class objects
CaveChaos::Wall objWall;
CaveChaos::Player objPlayer;
//creates a vector of circleshapes
std::vector<sf::CircleShape> points;
//defines and circle of size 5
sf::CircleShape shape(5);
//loop through all of the points
for (int i = 0; i < numofpoints; i++)
{
//set the shape position to the random variable from CaveChaos.cpp
shape.setPosition(locationsX[i], objPlayer.posy + i * 55);
//set shape color to green
shape.setFillColor(sf::Color(100, 250, 50));
// copy shape to vector
points.push_back(shape);
}
// iterate through vector
for (std::vector<sf::CircleShape>::iterator it = points.begin(); it != points.end(); ++it)
{
//draw wall points on screen
window.draw(*it);
}
}
What would be the best way to move or destroy these objects after they have been spawned ?
I want this last point to either get destroy or moved to the front when i go forward:
and when i move forward something like this to happen:
If this method is completely wrong or i shouldn't be using this method at all please let me know.
thanks for the help.
Code Review
So first of all stackoverflow is not the right site for code review. If you seek for a complete review ask on codereview. However, here are some things I noticed:
Don't use rand or srand. If you want RNG use <random> (usually uniform_int_distribution or uniform_real_distribution).
Why are you using a native array for locationsX? std::vector seems much more reasonable.
Avoid frequent allocations inside performance critical loops (ex. you call WallSpawn each game loop iteration and WallSpawn itself creates a new vector sf::CircleShape each time which canbe avoided). More on that later.
Preallocate space for std::vector using vector.reserve() if you have an estimate of the final vector size.
Why do you do you have two event loops inside the window loop instead of just one?
Using auto instead of std::vector<sf::CircleShape>::iterator improves readability.
Design
Usually a game loop looks like this:
polling window events and handling user input,
updating game state (reacting to input, applying physics, character movement, ...), and
drawing the new game state.
Your WallSpawn function, for example, shows how it shouldn't be done. You're generating new game entities (wall points) and then you draw them straight away in the same function.
I have limited expirience with SFML but I know that there is a sf::Drawable base class which can be used for everything that needs to be drawn on the window. You don't have to use it but it's designed for easier OOP development. For example, you could create a type for your wall points or for the entire wall (depending on what you actually want to do with those points):
class WallPoint : public sf::Drawable
{
public:
WallPoint(int x, int y) :
position(x, y), circle(x, y, 5)
{
}
/*
* Getter and setter for position
*/
void draw(RenderTarget& target, RenderStates states) const override
{
target.draw(circle);
}
private:
sf::Point position;
sf::CircleShape circle;
};
And then keep a collection of those points in a variable outside of your game loop.
Entity Management
Large games with thousands or millions of game entities (vehicles, trees, rocks, walls, weapons, ammo, projectiles, crowds, NPCs, ...) have the problem that they have to be very efficient when it comes to managing them. However, in your case with a few simple circle shapes, efficient management is not really a concern. Don't optimize if you don't have to.
You gave too little information about the game behavior to make a profund recommendation, so here is some guessing.
How does the game progress affect your entities? Your case (a runner game) suggests that the environment is constantly moving along one axis (left <> right, or up <> down). If this is true, this means that your wall points always spawn and despawn at the edges of the current environment. In such a case std::deque is an ideal container to store your data.
Usually std::vector is a solid allrounder when it comes to container.
Always destroy your entities when you don't need them anymore. For example, if your wall points leave the view port (and you are sure they won't enter it again) remove them for the container.
Putting Things Together
Considering everything I wrote above, your game loop could now like this:
int main()
{
// Create window and init everything you need
std::deque<WallPoint> wallPoints;
// create some initial points
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
// event handling
}
// update game state
// add new wall points
// remove old points
// move the player
// draw the new game state
window.Clear();
for (const auto& point : wallPoints)
window.draw(point);
window.draw(player);
}
}

Printing a 2d array in sfml from a text file

Im trying to print out a 2d array of characters that is from a text file, into a renderwindow using the window.draw() function. However, everything seems to work perfectly fine, except that when printing its skipping exactly one character.
For instance if i had a line of 15 chars, it will only print 8,but when i print it on the terminal it prints it perfectly fine, I simply dont know why its behaving like that, tried several things like changing the text file itself, or changing the size. Nothing seems to work for me. any ideas or help is very much appreciated.
here is the part of the code:
void Level::printgrid(int level)
{
sf::RenderWindow window(sf::VideoMode(800, 600), "Game On");
sf::Font MyFont;
if (!MyFont.loadFromFile("tnr.ttf"))
{
// Error...
}
ifstream my_file("nn.txt"); //text that i will be reading from
char number;
int i = 0; //to help loop
int j = 0;
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
else
{
window.clear();
while (i<row) // ive tried 2 for loops that didnt work cause
{ // it kept re-entering those loops
while (j<col)
{
my_file.get(grid[i][j]); //getting the chars one by one
cout << grid[i][j]; // printing them on terminal
//so i can check its correct
//changing to text that is drawable to print in window
sf::Text text(grid[i][j], MyFont, 30);
text.setPosition(0 + (30 * i), 0 + (30 * j));
// drawing it in window
window.draw(text);
window.display();
j++;
}
my_file.get(number);
cout << endl;
i++;
j = 0;
}
}
}
}
}
There are a few issues with your code, but the most apparent problem is the fact that you're always drawing only one character, then presenting the result (which will flip buffers).
Due to this you're basically alternating characters between the two buffers (since SFML is double buffering).
In addition, you're continually reading from your file, recreating the characters/text elements, etc. which is not very effective and should fail once you reach the end of the file.
For some actual game or program, you should work on optimizing the drawing (drawing 10 characters at once as one sf::Text is faster than drawing 10 individual sf::Text objects). I'll be ignoring this for simplicity for now.
The following is just a quick (untested) example to show you the basic idea.
// First I'd use some container to store our `sf::Text` objects
// This will just create all the objects
std::vector<sf::Text> chars(rows * cols);
// Now let's read the characters and add to our vector
for (std::size_t y = 0; y < rows; ++y) {
for (std::size_t x = 0; x < cols; ++x) {
// Just an alias to simplify the following lines
sf::Text &text = chars[y * cols + x];
// Now just set up everything
text.setFont(my_font);
text.setFillColor(sf::Color::White);
text.setCharacterSize(30);
text.setPosition(30 * x, 30 * y);
text.setString(my_file.get());
}
}
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
// Just your usual event handling
}
window.clear();
// Iterate over all elements and draw them
for (const auto &text : chars)
window.draw(text);
window.display();
}
If you're trying to create some kind of graphical console, you should put all characters into some sf::VertexArray or potentially render them using a shader.

SFML 2.0 Looping a sprite to display more than once

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.