-
Hi there, I couldn't find anything on the whole web about this topic, so I would like to ask the pros here (SFML 2.1 with VS 2010): How to select sprites individually (from a list) by clicking on them? I know that I need a for loop for this, that cycles through the whole list and that "newSprite" is not correct, because its only the sprite that was created as last. Any hints appreciated. But what to take instead of this. Every tutorial is only about a single sprite, but not multiple during runtime created sprites.
if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Middle)
{
for(EnemyIt = EnemyList.begin();EnemyIt != EnemyList.end();EnemyIt++)
if (newSprite.getGlobalBounds().contains(mMainWindow.mapPixelToCoords(sf::Mouse::getPosition(mMainWindow))))
{
//EnemyList.reverse(); For later use.
std::cout << "Unit selected" << std::endl;
}
}
-
Obviously, "newSprite" should be replaced with something coming from "EnemyIt". But since we don't know what's inside the Enemy class, it's hard to help you more ;)
-
ok, here is the code:
#include <SFML/Graphics.hpp>
#include <iostream>
#include <list>
int main()
{
sf::RectangleShape rectangle;
rectangle.setSize(sf::Vector2f(1000, 600));
rectangle.setFillColor(sf::Color(100,80,100,100));
rectangle.setPosition(0, 0);
sf::Sprite newSprite;
sf::FloatRect rightBound = rectangle.getGlobalBounds();
sf::RenderWindow mMainWindow(sf::VideoMode(1000, 600), "Map");
mMainWindow.setFramerateLimit(60);
sf::Texture unittexture;
unittexture.loadFromFile("warrior.png");
std::list<sf::Sprite> EnemyList;
std::list<sf::Sprite>::iterator EnemyIt;
while (mMainWindow.isOpen())
{
sf::Event event;
while (mMainWindow.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
mMainWindow.close();
}
if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Middle)
{
for(EnemyIt = EnemyList.begin();EnemyIt != EnemyList.end();EnemyIt++)
if (newSprite.getGlobalBounds().contains(mMainWindow.mapPixelToCoords(sf::Mouse::getPosition(mMainWindow))))
{
//EnemyList.reverse();
std::cout << "Unit selected" << std::endl;
}
}
if(event.type == sf::Event::MouseButtonPressed)
if(event.mouseButton.button == sf::Mouse::Right)
{
sf::Vector2f mousecoords(mMainWindow.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y)));
EnemyList.remove_if([=](sf::Sprite unitsprite){return unitsprite.getGlobalBounds().contains(mousecoords); });
}
if(event.type == sf::Event::MouseButtonPressed)
if(event.mouseButton.button == sf::Mouse::Left)
if (rectangle.getGlobalBounds().contains(mMainWindow.mapPixelToCoords(sf::Mouse::getPosition(mMainWindow))))
{
newSprite.setTexture(unittexture);
newSprite.setPosition(sf::Mouse::getPosition(mMainWindow).x,sf::Mouse::getPosition(mMainWindow).y);
EnemyList.push_back(newSprite);
}
}
for(EnemyIt = EnemyList.begin();EnemyIt != EnemyList.end();EnemyIt++)
{
mMainWindow.draw(*EnemyIt);
}
mMainWindow.display();
mMainWindow.clear(sf::Color(0, 200, 0, 255));
}
return 0;
}
-
So it's just EnemyIt->getGlobalBounds().contains(...), since EnemyIt is an iterator that points directly to a sf::Sprite. I'm surprised you got your drawing loop working but couldn't figure out this one, it's exactly the same thing.
And you don't have to call sf::Mouse::getPosition(mMainWindow), since you're processing a MouseButtonPressed event you can simply use event.mouseButton.x and event.mouseButton.y.
-
Ok, everything worked out just well, thank you!
In this context I have another question: Is it possible to select only one (the topmost) unit at a time, if there are multiple units at the same position?
-
Of course it is - if you write the code to manage that.
If you are simply drawing stuff in a container from first to last, then it follows that any stuff later in the container will be on top of earlier stuff, so to find the one "on top" would be a simple matter of searching the container in reverse order and the first one to match the coordinates will be the top one.
More complicated scheemes can easily be conceived if you need something more complicated.
Edit: for the simple solution, look into rbegin(), rend().
-
The most brain dead way would be to sort your enemy container by front to back, then iterate from the beginning to the end and when you get a hit break the loop.
-
Ok, I' ve got it :-)
Code looks like this now:
std::list<sf::Sprite>::reverse_iterator EnemyRevIt;
if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Middle)
{
for(EnemyRevIt=EnemyList.rbegin(); EnemyRevIt!=EnemyList.rend(); ++EnemyRevIt) // go forward in reverse order (from end to begin) through list
if (EnemyRevIt->getGlobalBounds().contains(mMainWindow.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y))))
{
EnemyList.rbegin();
EnemyRevIt->move(50,50); //do some stuff on the sprite: only the topmost unit should be affected!
std::cout << "unit selected" << std::endl; //print, whenever a unit was selected
break;
}
}
-
FYI you don't need to have a separate iterator variable.
for (auto& enemy = EnemyList.rbegin(); enemy != EnemyList.rend(); ++enemy)
{
.....
}
And you don't need this in the middle of your loop.
EnemyList.rbegin();
-
Ok, it worked, thx.