Tutorial 9: Z-ordering

In this tutorial you will learn how to use the built-in z-ordering.

Before starting this tutorial be sure to setup an empty project with the Setup Tutorial.

 

what are we going to get?

at the end of this tutorial we will have this code:

Ness::ScenePtr scene = renderer.create_scene();

Ness::ZNodePtr node = scene->create_znode();

That's it. :)

Now it's time to explore more node types!  Next we'll have the tilemap node, which helps you to create tilemaps!
 
continue to next tutorial -->

#include <NessEngine.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    Ness::init();

    Ness::Renderer renderer("new project", Ness::Sizei(800,600));

 

    // create the scene and znode

    Ness::ScenePtr scene = renderer.create_scene();

    Ness::ZNodePtr node = scene->create_znode();

 

    // set sprite defaults

    Ness::Sprite::Defaults.anchor = Ness::Point(0.5f, 1.0f);

    Ness::Sprite::Defaults.alpha_channels = true;

 

    // create the player

    Ness::SpritePtr player = node->create_sprite("ness-engine/resources/gfx/wizard.png");

    player->set_source_from_sprite_sheet(Ness::Pointi(0, 0), Ness::Sizei(4, 4), true);

    player->set_scale(2.0f);

    player->set_position(Ness::Point(100, 100));

    const float PlayerSpeed = 150.0f;

 

    // create trees

    for (unsigned int i = 0; i < 25; i++)

    {

        Ness::SpritePtr tree = node->create_sprite("ness-engine/resources/gfx/tree.png");

        tree->set_position(Ness::Pointi(rand() % renderer.get_screen_size().x, rand() % renderer.get_screen_size().y));

        tree->set_zindex(tree->get_position().y);

    }

 

    // event handlers

    Ness::Utils::EventsPoller EventsPoller;

    Ness::Utils::ApplicationEvents app;

    EventsPoller.add_handler(app);

    Ness::Utils::Keyboard keyboard;

    EventsPoller.add_handler(keyboard);

 

    // main loop

    while( !app.got_quit() )

    {

        EventsPoller.poll_events();

 

        // move player up

        if (keyboard.key_state(SDLK_UP) && player->get_position().y > 0)

        {

            player->set_position(player->get_position() - Ness::Point(0, renderer.time_factor() * PlayerSpeed));

        }

        // move player down

        else if (keyboard.key_state(SDLK_DOWN) && player->get_position().y < renderer.get_screen_size().y)

        {

            player->set_position(player->get_position() + Ness::Point(0, renderer.time_factor() * PlayerSpeed));

        }

        // move player left

        else if (keyboard.key_state(SDLK_LEFT) && player->get_position().x > 0)

        {

            player->set_position(player->get_position() - Ness::Point(renderer.time_factor() * PlayerSpeed, 0));

        }

        // move player down

        else if (keyboard.key_state(SDLK_RIGHT) && player->get_position().x < renderer.get_screen_size().x)

        {

            player->set_position(player->get_position() + Ness::Point(renderer.time_factor() * PlayerSpeed, 0));

        }

 

        // update player zindex

        player->set_zindex(player->get_position().y);

 

        // render

        renderer.start_frame();

        scene->render();

        renderer.end_frame();

    }

}

Ness::Sprite::Defaults.anchor = Ness::Point(0.5f, 1.0f);

Ness::Sprite::Defaults.alpha_channels = true;

Ness::SpritePtr player = node->create_sprite("ness-engine/resources/gfx/wizard.png");

player->set_source_from_sprite_sheet(Ness::Pointi(0, 0), Ness::Sizei(4, 4), true);

player->set_scale(2.0f);

player->set_position(Ness::Point(100, 100));

const float PlayerSpeed = 150.0f;

    for (unsigned int i = 0; i < 25; i++)

    {

        Ness::SpritePtr tree = node->create_sprite("ness-engine/resources/gfx/tree.png");

        tree->set_position(Ness::Pointi(rand() % renderer.get_screen_size().x, rand() % renderer.get_screen_size().y));

        tree->set_zindex(tree->get_position().y);

    }

and let's create some trees:

    // event handlers

    Ness::Utils::EventsPoller EventsPoller;

    Ness::Utils::ApplicationEvents app;

    EventsPoller.add_handler(app);

    Ness::Utils::Keyboard keyboard;

    EventsPoller.add_handler(keyboard);

 

    // main loop

    while( !app.got_quit() )

    {

        EventsPoller.poll_events();

 

        // move player up

        if (keyboard.key_state(SDLK_UP) && player->get_position().y > 0)

        {

            player->set_position(player->get_position() - Ness::Point(0, renderer.time_factor() * PlayerSpeed));

        }

        // move player down

        else if (keyboard.key_state(SDLK_DOWN) && player->get_position().y < renderer.get_screen_size().y)

        {

            player->set_position(player->get_position() + Ness::Point(0, renderer.time_factor() * PlayerSpeed));

        }

        // move player left

        else if (keyboard.key_state(SDLK_LEFT) && player->get_position().x > 0)

        {

            player->set_position(player->get_position() - Ness::Point(renderer.time_factor() * PlayerSpeed, 0));

        }

        // move player down

        else if (keyboard.key_state(SDLK_RIGHT) && player->get_position().x < renderer.get_screen_size().x)

        {

            player->set_position(player->get_position() + Ness::Point(renderer.time_factor() * PlayerSpeed, 0));

        }

 

        // update player zindex

        player->set_zindex(player->get_position().y);

 

        // render

        renderer.start_frame();

        scene->render();

        renderer.end_frame();

    }

znode->set_break_groups(boolVal);

which will produce the following window:

when pressing the arrow keys the character will move around. when standing in front of a tree the character will hide it, when moving behind a tree the tree will hide the character. this z-ordering happens in realtime. note: to keep it short there are no animations here, but you can add animations from this tutorial if you want as a practice.

 

Step 1: creating the znode

If you read the tutorial about nodes, you should know already that a node is a group of entities / nodes that share common transformations. however, nodes are not used just for grouping and transforming. another important aspect about nodes is that they can also determine which objects are rendered, how, and in which order.

 

This property enables us to create special nodes to achieve specal effects. ness-engine comes with several built-in nodes, and one of them is the znode - a node that reorders the entities based on their z-index in realtime.

 

Let's create the scene and the znode:

very simple. now everything we put under the znode will be rendered by it's z-index.

 

Step 2: creating the objects

First, we'll set some defaults to the sprite entities:

first we set the anchor to (0.5f, 1.0f), which is the bottom-center of the sprite, so they will have the effect of "standing up" entities. 

next, we set them to have alpha_channels by default so we don't have to set their blend mode for transparent background.

 

now let's create the player sprite:

note that for the trees we set the zindex to be their y position (set_zindex()). remember that we set default anchor to be bottom center? so be setting the z-index to be y position we set the z-value to be at the roots of the tree:

running the code (assuming you have main loop that renders) will show you that the trees are ordered currectly, but the player character is always at background (because we didn't set it's z-index yet).

 

Step 3: player controls

Now let's create the main loop and the player controls:

this is something we did in previous tutorials so I won't go over it, but notice how we set the player zindex to be it's y position as well.

 

run the code and you will be able to move the player around and see how it gets z-ordering currectly.

 

Z-index inheritance

Z-index is just another type of transformation, and as such, it is inheritable.

 

breaking groups

When it comes to son-nodes, the z-node has two ways to handle them:

  1. treat the son node as a group sharing the same z-index.
  2. break the son node and take out all the entities, ordering them independently.

 

to explain the differences better, examples are needed:

let's say you have a node representing your player character. it contains one sprite for body and one sprite for clothing, which may change:

in this case you probably want the z-node to keep groups, because there is no reason to seperate the armor and the body when ordering the sprites right?

 

but now let's consider another example - you have a node representing a room with objects in it. you want to be able to create transformations to effect the entire room, but you also want the player to be able to enter it and hide / be hidden by the entities in it. so in this case we will want to break the son nodes and order all the entities seperately.

 

remember however the z-index is inherited, so if you break groups you can choose: either set the parent node base z-index and all his son entities will have relevant z-index, or keep the parent node with z-index of 0 and have all entities with absolute z-index.

 

to switch between the two modes use the following function:

by default, the znode will not break groups.

 
Z-node efficiency

The built-in znode is designed to be generic, and thus not very efficient. basically it will take all the objects that are currently visible, put them in a list, and sort that list based on z-index. it's good enough for most games but if performance is an issue for you I suggest you write your own znode with optimizations tailored to your game type.

Help ness-engine grow!