SFML community forums

Help => Graphics => Topic started by: Bogdan on July 17, 2014, 04:12:54 pm

Title: Selecting sprites by click.
Post by: Bogdan on July 17, 2014, 04:12:54 pm
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;
                                        }
                                }
Title: Re: Selecting sprites by click.
Post by: Laurent on July 17, 2014, 04:50:40 pm
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 ;)
Title: Re: Selecting sprites by click.
Post by: Bogdan on July 17, 2014, 04:53:19 pm
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;
}
Title: Re: Selecting sprites by click.
Post by: Laurent on July 17, 2014, 05:07:29 pm
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.
Title: Re: Selecting sprites by click.
Post by: Bogdan on July 17, 2014, 06:20:42 pm
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?
Title: Re: Selecting sprites by click.
Post by: Jesper Juhl on July 17, 2014, 06:32:13 pm
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().
Title: Re: Selecting sprites by click.
Post by: zsbzsb on July 17, 2014, 06:32:26 pm
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.
Title: Re: Selecting sprites by click.
Post by: Bogdan on July 18, 2014, 03:52:26 am
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;
                                        }
                                }
Title: Re: Selecting sprites by click.
Post by: zsbzsb on July 18, 2014, 04:25:10 am
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();
Title: Re: Selecting sprites by click.
Post by: Bogdan on July 18, 2014, 03:18:41 pm
Ok, it worked, thx.