Skip to content

Instantly share code, notes, and snippets.

@dumganhar
Last active December 25, 2015 13:49
Show Gist options
  • Save dumganhar/6986147 to your computer and use it in GitHub Desktop.
Save dumganhar/6986147 to your computer and use it in GitHub Desktop.
When to Add EventListener? After 'create' or after 'Node::onEnter'?

Registering Touch Event Listener for a Sprite

auto sprite = Sprite::create("file.png");
layer1->addChild(sprite);

auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = some_functions;
listener->onTouchMoved = some_functions;
listener->onTouchEnded = some_functions;
listener->onTouchCancelled = some_functions;

// This event listener registeration is before the `sprite` is added into scene graph, that means before `onEnter`.
sprite->addEventListenerWithSceneGraphPriority(listener, sprite);
// If in some places, we remove sprite from its parent(layer1), and add it into another layer (layer2)
sprite->retain();
sprite->removeFromParent();
layer2->addChild(sprite);  // Here we re-add the sprite to layer2
sprite->release();

Problem

In Node::onExit, we remove the event listener from EventDispatcher.

void Node::onExit()
{
    ...
    
    removeAllEventListeners();
}

Since the touch event registeration is right after the sprite was created rather than sprite's onEnter, sprite will never get response of touch if without registering touch listener again.

Possible Solutions

Solution 1: Clone the listener after it's created and when sprite is added to a new layer, we invoke adding touch listener again.

auto listener = EventListenerTouchOneByOne::create();
...
// Clone the listener
auto cloneListener = listener->clone();
// If in some places, we remove sprite from its parent(layer1), and add it into another layer (layer2)
sprite->retain();
sprite->removeFromParent();
layer2->addChild(sprite);  // Here we re-add the sprite to layer2
sprite->addEventListenerWithSceneGraphPriority(cloneListener, sprite);
sprite->release();

Solution 2: Adding hook std::function, check and invoke it in Node::onEnter

void Node::onEnter()
{
   ...
   if (this->onEnterHook)
   {
       this->onEnterHook();
   }
}
auto sprite = Sprite::create("file.png");
layer1->addChild(sprite);

// Hook onEnter
sprite->onEnterHook = [this](){
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = some_functions;
    listener->onTouchMoved = some_functions;
    listener->onTouchEnded = some_functions;
    listener->onTouchCancelled = some_functions;
    
    // This event listener registeration is before the `sprite` is added into scene graph, that means before `onEnter`.
    sprite->addEventListenerWithSceneGraphPriority(listener, sprite);
};

Benefit of Solution 2

  • No need to save the listener pointer and add the listener by clone. When the second time sprite is added to scene graph, the onEnterHook will be called. And a new event listener will be added to EventDispatcher again.

  • No need to subclass node and override onEnter method. Just assign Node::onEnterHook.

Example:

void runHelloWorldScene()
{
    auto scene = Scene::create();
    auto layer = Layer::create();
    scene->addChild(layer);
    
    layer->onEnterHook = [&](){
        // do_something_to_init_the_layer
        auto sp = Sprite::create();
        layer->addChild(sp);
    };
    
    Director::getInstance()->replaceScene(scene);
}

What're your opinions?

@ricardoquesada
Copy link

It should be handled like update or actions.

onExit pauses the updates, actions. It doesn't remove it.
onEnter resumes the updates, actions. It does't create new ones.

It should be the same for events.

eg:

void Node::onExit()
{
    this->pause();

    _running = false;

    arrayMakeObjectsPerformSelector(_children, onExit, Node*);    
}

void Node::onEnter()
{
    _isTransitionFinished = false;

    arrayMakeObjectsPerformSelector(_children, onEnter, Node*);

    this->resume();

    _running = true;
}


void Node::pause()
{
    _scheduler->pauseTarget(this);
    _actionManager->pauseTarget(this);
    _eventDispatcher->pauseTarget(this);
}

void Node::resume()
{
    _scheduler->resumeTarget(this);
    _actionManager->resumeTarget(this);
    _eventDispatcher->resumeTarget(this);
}

@dumganhar
Copy link
Author

Make sense, thanks, Riq.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment