Controlling Multiple Sprites with Mouse Event Listener

I got few Sprites and mouse event listener. I need to know for each of them when player clicks over it. So far my code looks like this:

Vector<Sprite*> blocks;
for (auto i = 0; i < 5; i++)
{
	auto sprite = Sprite::create("block" + std::to_string(i + 1) +".png");
	blocks.pushBack(sprite);
	blocks.at(i)->setPosition(100, 100 * i + 100);
	this->addChild(blocks.at(i), i + 1);
}

auto mouseListener = EventListenerMouse::create();
for (auto i = 0; i < 5; i++)
{
	_eventDispatcher->addEventListenerWithSceneGraphPriority(i == 0 ? mouseListener : mouseListener->clone(), blocks.at(4-i));
}

mouseListener->onMouseDown = [&blocks](Event* event) {
	EventMouse* mouseEvent = dynamic_cast<EventMouse*>(event);
	
	auto target = dynamic_cast<Sprite*>(mouseEvent->getCurrentTarget());
	// more code here
};

Is this way of cloning event listener a good idea? Is there something better you would do? Must I switch to Touch Event Listener for what I’m trying to achieve? Please, any help/idea/suggestion is good.

So with this approach I ended up grabbing the last Sprite in the Vector no matter where I click, but my point was to capture any of the 5 Sprites in the Vector, depending on whether I clicked over it or not.

Why not do everything inside the first loop? Like this:

Vector<Sprite*> blocks;
for (auto i = 0; i < 5; i++)
{
    auto sprite = Sprite::create("block" + std::to_string(i + 1) +".png");
    blocks.pushBack(sprite);
    sprite->setPosition(100, 100 * i + 100);
    this->addChild(sprite); //Each new sprite is automatically added to the top of the nodes stack, therefore specifying an index of i+1 is not needed.
    auto mouseListener = EventListenerMouse::create();
    mouseListener->onMouseDown = [sprite](Event* event) 
    {
        if (sprite->getBoundingBox().containsPoint(event->getLocation())) 
        {
            //This considers the sprite as a simple square. For a more precise collision detection you need to implement your own method.
            sprite->setPosition(sprite->getPosition() + Point(10,  0));
        }
    };
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(mouseListener, sprite);
}

It’s shorter, uses less cpu and it is easier to read :slight_smile: .
PS: I don’t understand whether you want block1.png to be the sprite with the highest priority or the lowest. In my snippet it is the one with the lowest priority. If you want it to be the one with the highest you just need to reverse the loop, like:

for (auto i = 4; i >= 0; i--) {}

EDIT 1

I have further improved the code inside the for loop.

EDIT 2

Fixed events being added to the layer’s event listener and not to the director’s event listener.

1 Like

I understand your point, but I was actually thinking to move just the single sprite I click over, not all five sprites at once

I have not tested the code, but it still should do what you are trying to achieve, because when you create each EventListenerMouse you specify which sprite it listens to, and when it detects something it passes you a pointer to the given sprite. Have you tried it and it moves all sprites at once? If yes, how are you performing the sprite’s movement? Maybe there is a bug in there that is causing the problem.

Could be my mistake, I’m trying to move it like this:

		mouseListener->onMouseDown = [sprite](Event* event)
	{
		if (sprite == event->getCurrentTarget())
			sprite->setPosition(sprite->getPosition() + Point(10, 0));
	};

and they all move

Ok. All the sprites move because mouse listeners (and touch listeners) check for clicks on the whole screen, but are not aware about whether the click was on the sprite or not. Whenever you click on the screen all mouse listeners will call onMouseDown, and you have to check whether the click was on the sprite before performing the action. The condition in your if is always true, therefore the movement will always happen whenever the screen (not the specific sprite) is clicked. To fix this you can replace it with the following:

if (sprite->getBoundingBox().containsPoint(event->getLocation())) 
{
    //This considers the sprite as a simple square. For a more precise collision detection you need to implement your own method.
    sprite->setPosition(sprite->getPosition() + Point(10,  0));
}

PS: is your project restricted to computers or are you planning to publish it for mobile phones? If the case is the latter then you need to use touch listeners, for (as far as I know) mouse listeners work only on computers, while touch listeners work on both.

EDIT

Check out the code snippet I posted before, I have corrected a few more things and it should work as expected now.

1 Like

Yes, my project is restricted to computers.

This works perfectly, with a small change. I couldn’t find a method event->getLocation() so I’ve used EventListenerTouchOneByOne instead of EventListenerMouse and swapped event->getLocation() for touch->getLocation() and it turned out to work the way I want.

cei thank you for your help. topic closed :smile: