Reza Ghobadinic

Node Callbacks

In the previous post called Node Creation, we created simple examples of drawing shapes on the screen using the draw() method of the Node class. In this post we move to callback feature for these nodes to have some kind of change or progress in our nodes in time!

These callbacks methods, are used to do something periodically in time. For example, you can have the update() method call on every frame to change the position or rotation of your nodes to have them move around the scene.

 

The default update() method

There is a callback method in the node class that if you activate, it will run on every frame and as an input, will carry a delta variable that keeps the time it took from the last time it was called.

This callback method is called update() and you can schedule it using the scheduleUpdate() method.

Lets continue with the Simple Example from the Node Creation post that was drawing a square in the middle of the screen and have it move and rotate on every frame.

In the header file MyDrawNode.hpp, add the update method:

virtual void update(float delta);

The delta variable holds the time difference from the last time that update was called. You can use this to adjust the speed of your characters according to rate per second in any machine, regardless of the CPU speed.

This is how the header should look now:

#ifndef MyDrawNode_hpp
#define MyDrawNode_hpp

#include <stdio.h>
#include "cocos2d.h"

using namespace cocos2d;

class MyDrawNode:public Node
{
    bool isDrawn;
public:
    static Node* createNode();
    
    virtual bool init();
    virtual void update(float delta);
    virtual void draw(Renderer* renderer, const Mat4& transform, uint32_t flags);
    
    CREATE_FUNC(MyDrawNode);
};

#endif /* MyDrawNode_hpp */

Now we can define the method in MyDrawNode.cpp as below:

void MyDrawNode::update(float delta)
{
    auto position = getPosition();  // Get the current position of self
    position.x += (50 * delta);     // Increase the current position by 50 units per second
    setPosition(position);          // Set the position to the newly increased value
    auto rotation = getRotation();  // Get the current rotation of self
    rotation += 1;                  // Increase the current rotation
    setRotation(rotation);          // Set the rotation to the newly increased value
}

We get the current position and add 50 units per second to the x component to have the box move horizontally on the screen.

We also rotate it 1 unit per frame (not per second here). No matter how fast the program runs here, rotation will happen 1 unit per frame so it may be different on different machines and systems. Usually we don’t want that and that is why we should consider using the delta variable.

If the game runs at 1 frame per second, delta will be 1.0

If the game runs at 2 frame per second, delta will be 0.5

If the game runs at 4 frames per second, delta will be 0.25 … you get the idea!

Now we just have to activate the update method in the init() method by adding:

scheduleUpdate();

So the init() will be:

bool MyDrawNode::init()
{
    if(!Node::init())
        return false;
    
    isDrawn = false;
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    float cx = origin.x + visibleSize.width * 0.5;
    float cy = origin.y + visibleSize.height * 0.5;
    
    setPosition(cx, cy);
    
    scheduleUpdate(); // Activate the update()
    
    return true;
}

Now build and run the project to see your box moving to the right and rotating clock wise.

 

Other Callback Related Methods

There are a few methods that let you control the callback functions. Remember, the update() method is the standard callback but it is not the only method you can schedule. You can schedule any method of your class to be a callback.

The update() method is a standard callback but that is not the only method you can schedule.

virtual void update (float delta)

To schedule (start) a selector (callback):

// Schedule the default update()
void scheduleUpdate (void)void scheduleUpdateWithPriority (int priority);
void scheduleUpdateWithPriority (int priority);

// Schedule custom methods
void schedule (SEL_SCHEDULE selector);
void schedule (SEL_SCHEDULE selector, float interval);
void schedule (SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay);

// Schedule C++11 Lambda
void schedule (const std::function< void(float)> &callback, const std::string &key);
void schedule (const std::function< void(float)> &callback, float interval, const std::string &key);
void schedule (const std::function< void(float)> &callback, float interval, unsigned int repeat, float delay, const std::string &key);

// Schedule custom method for one time
void scheduleOnce (SEL_SCHEDULE selector, float delay);

// Schedule C++11 Lambda for one time
void scheduleOnce (const std::function< void(float)> &callback, float delay, const std::string &key);

To unschedule (stop) a selector (callback):

// Unschedule the default update()
void unscheduleUpdate (void);

// Unschedule a custom method
void unschedule (SEL_SCHEDULE selector);

// Unschedule a C++11 lambda
void unschedule (const std::string &key);

// Unschedule all callbacks
void unscheduleAllCallbacks ();

To pause/resume:

virtual void pause (void);
virtual void resume (void);
void resumeSchedulersAndActions ();
void pauseSchedulersAndActions ();

To check the scheduled selectors:

// Check to see if a method is scheduled
bool isScheduled (SEL_SCHEDULE selector) const;

// Check to see if a Lambda is scheduled
bool isScheduled (const std::string &key) const;

As you see, there are methods to schedule your own selectors and even methods to schedule C++11 lambda functions as callbacks. You can specify interval and repeat and also schedule once only.

Most of these methods are self explanatory, but I will try some of them here to make sure I can use them later when I need them.

 

Schedule Custom Methods

You can define any method for the class and schedule it as a callback with delay, interval and repeat parameters.

Lets define a new method called myUpdate() inside the MyDrawNode class:

void myUpdate(float delta);

Define the function similar to the default update() that we did earlier:

void MyDrawNode::myUpdate(float delta)
{
    auto position = getPosition();  // Get the current position of self
    position.x += (50 * delta);     // Increase the current position by 50 units per second
    setPosition(position);          // Set the position to the newly increased value
    auto rotation = getRotation();  // Get the current rotation of self
    rotation += 1;                  // Increase the current rotation
    setRotation(rotation);          // Set the rotation to the newly increased value
}

Now we can schedule it using one of the following methods:

// Schedule myUpdate
schedule(SEL_SCHEDULE(&MyDrawNode::myUpdate));

// Schedule myUpdate with interval (0.1 second)
schedule(SEL_SCHEDULE(&MyDrawNode::myUpdate), 0.1);

// Schedule myUpdate with interval (0.1 second), repeat, delay (1 second)
schedule(SEL_SCHEDULE(&MyDrawNode::myUpdate), 0.1, 5, 1);

// Schedule Once with delay (1 second)
scheduleOnce(SEL_SCHEDULE(&MyDrawNode::myUpdate), 1);

// Schedule with priority of 0
scheduleUpdateWithPriority(0);

It is very simple!

Note that in the case of scheduleUpdateWithPriority(int priority), scheduled methods with lower priority number will be called before the ones that have higher priority.

 

Schedule Lambda Functions

You can also use lambda functions (C++11 and later standards) for scheduling.

If you need a refresher on lambdas, have a look at C++11 FAQ page or cppreference.com. Also, it might be worth to read about std::function and std::bind as they may help you understand lambda usage a bit better.

Here is the same functionality of our previous default update() again in lambda format (inside init()):

    schedule([this](float delta) {
        auto position = getPosition();  // Get the current position of self
        position.x += (50 * delta);     // Increase the current position
        setPosition(position);          // Set the position to the newly increased value
        auto rotation = getRotation();  // Get the current rotation of self
        rotation += 1;                  // Increase the current rotation
        setRotation(rotation);          // Set the rotation to the newly increased value
    }, "myLambda");

You can make it prettier if you use a std::function variable:

    std::function<void(float)> myLambda = [this](float delta) {
        auto position = getPosition();  // Get the current position of self
        position.x += (50 * delta);     // Increase the current position
        setPosition(position);          // Set the position to the newly increased value
        auto rotation = getRotation();  // Get the current rotation of self
        rotation += 1;                  // Increase the current rotation
        setRotation(rotation);          // Set the rotation to the newly increased value
    };
    
    schedule(myLambda, "myLambda");

Node that in the lambda, we capture [this] so that we can access its member variables and define a float as delta, the way that it is expected by Cocos2d-x.

Here is the complete init() method after adding the lambda:

bool MyDrawNode::init()
{
    if(!Node::init())
        return false;
    
    isDrawn = false;
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    float cx = origin.x + visibleSize.width * 0.5;
    float cy = origin.y + visibleSize.height * 0.5;
    
    setPosition(cx, cy);
    
    std::function<void(float)> myLambda = [this](float delta) {
        auto position = getPosition();  // Get the current position of self
        position.x += (50 * delta);     // Increase the current position
        setPosition(position);          // Set the position to the newly increased value
        auto rotation = getRotation();  // Get the current rotation of self
        rotation += 1;                  // Increase the current rotation
        setRotation(rotation);          // Set the rotation to the newly increased value
    };
    
    schedule(myLambda, "myLambda");

    return true;
}

The key parameter of the schedule() method is used to unschedule the lambda if you need to:

unschedule("myLambda");

I think this should be a good start for working with schedulers and periodic functions in Cocos2d-x.

Download Source

Exit mobile version