Rendering tutorial

In this tutorial we are going to look at how rendering works. We will load a bitmap image and draw it on a sprite. We are also going to touch upon canvas instancing which is the sharing of one canvas across several sprites.

Initialization

A couple of steps are required to initialize the graphics and start rendering on the screen. The "create" function opens a new system window and selects a display mode. This function can be found in the global display object. Its parameters include a title for our application, width, height and color depth of the target resolution as well as a boolean flag indicating whether it should run in a window or fullscreen. The "create" function will fail if your video card doesn't support the supplied display mode. Some older video cards for example, can only run hardware accelerated graphics in fullscreen mode.

Next, we create a layer object. As you'll find out later, layers are container objects that can store sprites and other layers. The ability to nest layers allows you to build a tree-like hierarchy called a "scene". In order to render our scene layer we add it as a child node to the default viewport. The viewport is a clipped rectangle in which geometry is rendered. In this case, its dimensions equal that of the application window.

display:create ( "My Window", 800, 600, 32, true )

-- creates a scene layer
scene = Layer ( )

-- renders the layer within the default viewport
display.viewport:add_child ( scene )

Sprites and canvases

The visual objects in the scene are called sprites. Each sprite has a position property (so that you can move it around in the scene) as well as its own canvas. The canvas object is where all of your geometry is generated and stored. Let's look at an example that loads a texture from file and renders it on the screen.

display:create ( "My Window", 800, 600, 32, true )

scene = Layer ( )
display.viewport:add_child ( scene )

-- loads image resource
my_texture = Image ( )
my_texture:load ( "Tutorials/image.bmp" )

-- draw the image on the sprite's canvas
my_sprite = Sprite ( )
my_sprite.canvas:set_source_image ( my_texture )
my_sprite.canvas:paint ( )

scene:add_child ( my_sprite )
Download:  sprite.lua
image.bmp

First, an image object is created and loaded from a file. The "load" function accepts a texture file location relative to the engine's executable. For optimal compatibility, your images should be square and their width and height should be a power of 2. It's also possible to load images with a color key, but we won't get into that for now.

Next, we create an instance of sprite. Since no coordinates were supplied to its constructor, the new sprite will be positioned at the origin (0,0) of its parent layer. In this example, the origin of the scene happens to be the center of the window. We then call the "set_source_image" function passing the previously loaded image as a parameter. Our image won't be drawn until an explicit call to "paint". Since no coordinates were given to "set_source_image", our image will be drawn in the center of the canvas. Notice that "set_source_image" and "paint" are not functions of the sprite. They are found in the sprite's assoicated canvas.

Lastly, we insert our sprite in the scene layer via its "add_child" method. We can add any number of sprites to the scene layer. In this example, the scene layer is essentially the root or parent node of all the geometry that is rendered on the screen.

Canvas instancing

It is possible to share one canvas across any number of sprites. This is called instancing and in certain cases can improve your game's performance and lower memory consumption. Here is an example of how it works:

display:create ( "My Window", 800, 600, 32, true )

scene = Layer ( )
display.viewport:add_child ( scene )

a = Sprite ( -100, 0 )
b = Sprite ( 100, 0 )

-- draws a rectangle on the first sprite's canvas
a.canvas:rectangle ( 20, 50 )
a.canvas:fill ( )

-- instancing
b.canvas = a.canvas

scene:add_child ( a )
scene:add_child ( b )
Download: instancing.lua

We start by creating two sprites at different locations. Then, a filled rectangle shape is drawn on one of the sprites. All we have to do next is assign one sprite's canvas to the other and voilą! From this point onward, whenever you edit the first sprite's canvas the second one will be affected too. Although the two sprites are sharing one canvas, you can still alter each sprite's individual transformation properties. Changing the position, rotation or scale of the sprites will not affect their shared canvas in any way.