Resolution independent 2D graphics engine
03 Event System
picture of event system application

This tutorial shows how keyboard input can be read in Quad-Ren and how custom animators and event receivers can be created. The result is a space ship which can be controlled using the arrow keys.

As usual, include the Quad-Ren header file and math.h for cos and sin. #include <quad-ren/quad-ren.h>
#include <math.h>

The scene will consist of one scene node for the ship, attached to this will be a event receiver and a animator. The event receiver will listen for key state changes and the animator will react to these changes and animate the location and rotation of the scene node as well as loop the ships animation.

In order to move the key states array between the event receiver and animator a scene graph variable is used, these are similar to class member variables, but are lexically scoped within the scene graph, not a class hierarchy.

Firstly we create a new class for the event receiver, deriving it from qr::event_receiver. class ship_receiver : public qr::event_receiver
{
public:

Event receivers must override the following 3 methods, on_attach, on_event, and on_detach. The on_attach and on_detach methods are called when the event receiver is attached or detached from a scene node. Here we use the on_attach method to create a new scene graph variable for storing the key states array.     void on_attach(qr::scene_node *node)
    {
        node->create_var(qr::keyarr(qr::QR_KEYSYM_COUNT), "keys down");
    }

The on_event method is called whenever an event occurs, here we listen for key state changes and update the key states array.     void on_event(qr::scene_node *node, qr::event *n_event)
    {

First we get the current key states from the scene graph variable.         qr::keyarr keys_down = node->get_var<qr::keyarr>("keys down");

Next we check the event type, if it is equal to ether qr::EV_KEYDOWN or qr::EV_KEYUP then the event is down cast to the appropriate type and the keys is extracted. Setting that element of the key states array to true for a key down event and false for a key up event.

We also listen for escape key events, and raise a quit event if one occurs to allow the application to be quit by hitting the escape key.         switch (n_event->get_type())
        {
        case(qr::EV_KEYDOWN):
            {
            qr::keydown_event *d_event = (qr::keydown_event*) n_event;

            if(d_event->get_keysym() == qr::KEY_ESCAPE)
                node->raise_event(new qr::quit_event());

            keys_down[d_event->get_keysym()] = true;
            break;
            }
        case(qr::EV_KEYUP):
            {
            qr::keyup_event *u_event = (qr::keyup_event*) n_event;
            keys_down[u_event->get_keysym()] = false;
            break;
            }
        }

To finish off the on_event method, we now save any changes made to the key states array back to the scene graph variable.         node->set_var("keys down", keys_down);
    }

Here the on_detach method is used to delete the key states scene graph variable when the event receiver is detached from a scene node.     void on_detach(qr::scene_node *node)
    {
        node->delete_var("keys down");
    }
};

Now we create the animator class, this is done in a similar way to the event receiver above, except the class is derived from qr::animator. class ship_animator : public qr::animator
{

Like event receivers, animators also have on_attach and on_detach methods which are called when attaching or detaching from a scene node. Here we use the on_attach method to create a scene graph variable for storing the current animation frame.     void on_attach(qr::scene_node *node)
    {
        node->create_var(float(1), "current frame");
    }

The on_animate method is called whenever the scene nodes using the animator are rendered. Here it is used to animate the location of the ship scene node and loop the animation.     void on_animate(qr::scene_node *node, float time_delta)
    {

To animate the scene node first we get the key states graph variable set by the event receiver object above from the scene node.         qr::keyarr keys_down = node->get_var<qr::keyarr>("keys down");

Next we get the location and rotation from the ship scene node, the rotation is also converted into radians as required by the C++ cos and sin functions.         qr::vector2d_f pos = node -> get_location();
        float rot          = (node -> get_rotation() + 90) / (180 / 3.142);

Now we calculate a vector pointing in the forward direction of the ship.         qr::vector2d_f forw(
            (cos(rot) * 0.003) * time_delta,
            (sin(rot) * 0.003) * time_delta);

Next we check to see if there are any keys presses currently and calculate the ships new location and rotation.         if(keys_down[qr::KEY_RIGHT])
            rot -= 0.002 * time_delta;

        if(keys_down[qr::KEY_LEFT])
            rot += 0.002 * time_delta;

        if(keys_down[qr::KEY_UP])
            pos = qr::vector2d_f(pos.X - forw.X, pos.Y - forw.Y);

        if(keys_down[qr::KEY_DOWN])
            pos = qr::vector2d_f(pos.X + forw.X, pos.Y + forw.Y);

After we have calculated the nodes new potion the ships rotation and location need to be updated.         node -> set_rotation((rot * (180 / 3.142)) - 90);
        node -> set_location(pos);

Now that ship movement has bean implemented, that is left to do in the animator is to loop through the ships animation. Firstly we need to get the total number of frames form the ships sprite.         qr::sprite *ob_sprite = node->get_sprite();
        int tot_frames = ob_sprite->get_total_frames();

Then we get the current frame and increment it by the frame rate times the time delta divided by 1000 to loop through the sprites frames at 25 frames per second.         float current_frame = node->get_var<float>("current frame");

        current_frame += 25 * (time_delta / 1000);

Here we loop the sprites frame back to 1 if it goes past the end of the animation sequence.         if(current_frame > tot_frames + 2)
            current_frame = 1;

Finally for the on_animate method, we update the nodes current frame and the current frame counter.         node->set_frame(current_frame);
        node->set_var("current frame", current_frame);
    }

Like the event receiver, the on_detach method is used to delete the current frame graph variable.     void on_detach(qr::scene_node *node)
    {
        node->delete_var("current frame");
    }
};

With the class implementation out of the way, all that is left to do is set everything up. First the renderer is set up, like in the previous tutorials. int main()
{
    qr::renderer *renderer_ob = new qr::renderer(qr::vector2d_i(16, 9),
        10.0 ,false ,qr::vector2d_i(0, 0));

    qr::scene_manager *scene_man = renderer_ob ->
        get_scene_manager();

    renderer_ob -> set_window_title("Event System - Quad-Ren");

Next we load a space image for the background from a PNG image and attach it to the background quad.     qr::sprite *background = new qr::sprite(scene_man, 1);

    background -> load_png_frame(
        "../media/star_background.png", 1);

    background -> convert_data();
    renderer_ob -> set_bg_sprite(background);

Now we load the sprite for the space ship from a QRDD file.     qr::sprite *ship_sprite;
    ship_sprite = qr::qrdd_file_handler::load_sprite
        (scene_man, "../media/pod.QRDD");

Next we create a quad for the space ship and attach the sprite to it. We also rotate it by 180 degrees to make it point Upwards.     qr::scene_node *ship = new qr::quad(scene_man);
    ship -> set_sprite(ship_sprite);
    ship -> set_rotation(180);

Here we create an instance of our event receiver and animator then attach them to the space ship quad.     qr::event_receiver *ship_revr = new ship_receiver(

scene_man );
    qr::animator       *ship_anim = new ship_animator(

scene_man );
    ship -> set_event_receiver(ship_revr);
    ship -> set_animator(ship_anim);

Finally we run the applications main loop     renderer_ob -> main();

Drop the renderer and delete the animator and event receiver when the application exits.     renderer_ob -> drop();

    delete ship_revr;
    delete ship_anim;
}