An Introduction to Drawing in Quorum
In this tutorial, we describe how to draw objects on the game screen in Quorum. This tutorial will cover primary topics: 1) drawing shapes, 2) drawing images, and 3) ImageSheets. The purpose of these topics is to give the reader a primer on how the drawing system works, so that he/she can draw shapes and images in the game correctly and efficiently.
Drawing ShapesWe start this tutorial with the templated Game Application from the New Project window, as described in the Getting Started tutorial. Our basic game code (with comments omitted) looks like this:
This template creates the architecture that tells Quorum to create a new game and begin execution of the Main Loop. Behind the scenes, this connects to a very powerful utility called OpenGL. OpenGL is an industry standard graphics library specifically designed for drawing graphics. While OpenGL is very powerful, it is not particularly easy to use. In order to understand its details one must have an understanding of a mathematical theory called Linear Algebra, which is an advanced topic that is typically taught at universities. The Quorum Game Engine handles all this complexity for us though so we just need to learn how to call the methods of the built in libraries to generate drawable images and shapes.To draw shapes to our empty game program, we need to add a use statement to our program to tell the compiler where to find the commands for a class in the standard library named Drawable. The code for the library reference is:
use Libraries.Game.Game class Main is Game action Main StartGame() end action CreateGame end action Update(number seconds) end end
This line of code tells Quorum that we want to use the Drawable class in our application. In other words, it tells the compiler that we want to be able to add items to the screen that will be drawable in the frame drawing portion of the Main Loop. Once we have access to the Drawable class, we need to create and name a drawable object (another word for this is "instantiate"). We can do this by adding another line of code just below the "class Main is Game" line, such as:
use Libraries.Game.Graphics.Drawable class Main is Game Drawable rectangle
While this code creates a drawable object, it does not draw anything on the screen or load an image to associate to the object. In order to do that, we need to insert code into the CreateGame action:
action CreateGame //This line of code draws a rectangle with a width and height of 50. //By default, the coordinates of this object are 0,0, which is the bottom left corner of our window. rectangle:LoadFilledRectangle(50, 50) //This line of code tells Quorum to add our drawable object to our game, //so that the game engine puts it in the list of items that it draws on each frame. //If we forget it, Quorum will have no way to know we want to draw this object. Add(rectangle) end
Once we have these lines of code in our program we can Run our game (F6) which will now look similar to this:There are many kinds of additional shapes that can be drawn by default. We can use these shapes in combination to create complex pictures. The full list of shapes is in the Drawable documentation. The list includes: 1) circles, 2) triangles, 3) rectangles, and 4) lines. In the case of of circles, triangles, and rectangles, the shapes can either be filled or not and a color can also be specified (if none, the default color is black).
Drawing ImagesIn addition to shapes, we can also display pre-made images on the screen. This is useful if we want to use external programs like Photoshop or Gimp (a free alternative) to make images ahead of time or if we want to use digital photos we've taken. Similarly, there is a large amount of free art on the Internet that can be used for commercial or non-commercial purposes (under the creative commons license, for example).
Quorum can load such images in two ways, by using: 1) a Drawable directly, or 2) an ImageSheet (a set of combined images). First let us discuss using a Drawable to load images.
To load an image we take the same approach we used before by creating a drawable object first:
Then we can load an image file stored on our hard drive by calling the Load method of the Drawable class. For this example, we will load the logo for the Quorum Hour of Code:
This image is licensed under Creative Commons, so please feel free to download and use it for your own purposes. To load this image, we first need to make a copy of it and place it in the directory of our project. It does not matter where in our project we place it, but for this example we made a new folder named "assets" in the main project directory:
To load the image into our "logo" drawable object, in our CreateGame action we simply call the Load method of the Drawable class with the path and filename of the file in our project and then call the Add action. (Notice that the path of the file is relative to the root of our main project directory.):
So the whole game code looks like this:
use Libraries.Game.Game use Libraries.Game.Graphics.Drawable class Main is Game Drawable logo action Main StartGame() end action CreateGame logo:Load("assets\hourofcode.png") Add(logo) end action Update(number seconds) end end
...and when we Run the program (F6) we get a game window that looks something like this:
Alternatively, we could use a file class to load the image file into the Drawable. In that case, our code would look like this:
use Libraries.Game.Game use Libraries.Game.Graphics.Drawable use Libraries.System.File class Main is Game Drawable logo action Main StartGame() end action CreateGame File file file:SetPath("assets\hourofcode.png") logo:Load(file) Add(logo) end action Update(number seconds) end end
ImageSheetsIn computer graphics, the computer can sometimes render (put on the screen) images faster by using what are often called Texture atlases. While we call these ImageSheets in Quorum, they are basically the same thing. Generally, many casual games can be written without using this technique, but it can speed up certain kinds of programs where there are a lot of objects to display. Essentially ImageSheets allow OpenGL to reduce the amount of loading that it has to do (which can be a very expensive process that must be done on each frame). In this tutorial, we will not get into the details of when to use, or not use ImageSheets, as this is a complicated discussion related to graphics hardware. We will say, however, for the types of 2D games Quorum's engine currently supports, using ImageSheets can often speed up a program. When in doubt, trying a program both ways can give clues, if efficiency is a concern. As you will see, the code for loading images is similar in any case.
To make an ImageSheet, let's first add a second image to our game application. This one is also creative commons and works for this example:
To make an ImageSheet, we need to open the properties for our application, which can be done by opening the context menu (right click on the project or navigate to the projects window (CTRL+1), select the project, hit the context menu button), then select "properties". From there, we navigate using the keyboard or mouse to the Games window on the left pane, which looks like this:
On the left pane of this window, there are two categories of options, Project Information and Games. If Games is selected, the right hand side contains information that tells Quorum to automatically generate ImageSheets. There are several options, which are listed below:
- Enable ImageSheet Support: If this option is disabled, Quorum will not store any settings related to ImageSheets.
- Rebuild on Compile: If this option is selected, Quorum will rebuild ImageSheets whenever a program is compiled. This is not necessary, but can be handy if the images are being changed regularly by artists.
- Build Path: This option lets us change where the ImageSheets will be generated.
- Image Sheets: This is a list we create of what ImageSheets should be generated by the system.
- Images: This is a list of images that will be put inside of a particular ImageSheet.
For our example, we will fill out our ImageSheet to include both the books image and the hour of code image. Our window will have a build path of "assets" (although it can be anything we want), with one ImageSheet, which we will call "MyImageSheet", and two files in that sheet. Our window appears like this:
Once the ImageSheet generator is enabled, Quorum will create them on each build (each time we hit Run) if that option was selected. When this is done, Quorum creates two files, MyImageSheet.png and MyImageSheet.atlas. First, MyImageSheet.png combines both images onto one (or several if they will not fit on one) image that looks like this:Besides the image, a second file is also generated, called an atlas file. This file is generated by the excellent tool LibGDX. Normally, we do not need to interact with this file directly, but the engine needs to know about it. In other words, barring a user has specialized needs for a particular application, we can ignore the contents of this file. The file is needed in order to let Quorum know where each sub-image is in the larger ImageSheet. For reference, here is what the file looks like in this example:
MyImageSheet.png size: 512,256 format: RGBA8888 filter: Nearest,Nearest repeat: none books rotate: false xy: 2, 32 size: 200, 130 orig: 200, 130 offset: 0, 0 index: -1 hourofcode rotate: false xy: 204, 2 size: 160, 160 orig: 160, 160 offset: 0, 0 index: -1
Once our ImageSheets are generated, we can then use them in a program, similarly to how we used Drawable's before. First, we add a use statement for ImageSheets:
Next, where we create our Drawables (books and bunny), we also create an ImageSheet from which we will extract the Drawables.
class Main is Game Drawable books Drawable bunny ImageSheet sheet
Then in the CreateGame action, we load the files from the ImageSheet into the Drawables and Add them to the game:
action CreateGame sheet:Load("assets/MyImageSheet.atlas") books = sheet:GetDrawable("books") bunny = sheet:GetDrawable("hourofcode") Add(books) Add(bunny) end
In this example, these images will draw over each other because we have not changed their position, but you can see how we can now load multiple Drawable objects onto the screen. We explain how to position objects in the next tutorial.
Here's what our final code looks like for loading two images from an ImageSheet and displaying them on the screen:
use Libraries.Game.Game use Libraries.Game.Graphics.Drawable use Libraries.Game.Graphics.ImageSheet class Main is Game //store a placeholder in memory for books and the bunny //by saying these are undefined, we are telling Quorum not to make the objects //the reason we do that is because the ImageSheet will make the objects for us Drawable books = undefined Drawable bunny = undefined //create an ImageSheet object we can use in our create game action. ImageSheet sheet action Main StartGame() end action CreateGame //this loads the atlas file into the ImageSheet. Importantly, we load //the .atlas, not the .png. The reason is because our atlas generator may //generate multiple images, which means the system should load them all. sheet:Load("assets/MyImageSheet.atlas") //load the two Drawables from the ImageSheet. books = sheet:GetDrawable("books") bunny = sheet:GetDrawable("hourofcode") //add the Drawables to the game. Add(books) Add(bunny) end action Update(number seconds) end end
Here's what this code will render:
In the next tutorial, we will discuss Animation in 2D, which describes how to use animation in 2D.