Using Qt to Develop for Embedded Linux
In January of 2001, I began an internship at Trolltech's embedded development office in Brisbane, Australia. My goal was twofold: to learn C++ and to learn how to develop embedded applications. I worked at Trolltech for a period of five weeks, which was long enough for me to achieve both goals (although truly mastering C++ takes a lifetime, of course). It should be noted that before starting at Trolltech, my prior programming experience consisted of one year of Java study at university, and I had never used C++ or Qt. At the end of five weeks, I had developed two complete 2-D games, both of which are now included in the Qt Palmtop Environment (QPE).
For those who don't know, Qt/Embedded is the embedded version of Trolltech's Qt, a cross-platform C++ GUI application framework that supports Windows, UNIX, Mac and embedded Linux. Qt and Qt/Embedded are available to developers under an elegant dual-license program. For programmers developing free software, Qt is available at no charge (under GPL and QPL; Qt/Embedded is available only under the GPL). For programmers developing commercial applications, the framework is available under a commercial license. Both commercial and open-source versions are available directly from Trolltech at http://www.trolltech.com/.
I believe anyone with basic programming skills can learn to use Qt and quickly develop useful, professional-looking applications. I found the API quite intuitive, which greatly shortened the learning time. While C++ can be overwhelming in terms of language textures, Qt uses a modest subset, so you don't have to be a C++ expert to use it.
This article explains techniques used to develop a simple game. Before attempting this, however, you should be familiar with four areas: the Qt tutorial, basic programming, object-oriented programming and Qt reference documentation.
The first few lessons of the Qt tutorial will take you through developing a simple application, such as a ``hello world'' program. They cover several specific skills, including the creation of source and header files, and how to compile and run a Qt program.
Qt is an application framework for C++, but I learned both the framework and basic programming at the same time. If you don't know C++, I would recommend a good textbook to use as a reference, especially to learn about pointers.
I had studied Java at university and found object-oriented programming techniques to be an excellent foundation for learning Qt. Qt is designed to make object-oriented programming easier, so familiarity with multiple classes, objects and other component programming techniques is helpful.
To use Qt you will need to ensure you have installed the necessary software and are familiar with the Qt reference documentation. A C++ compiler is necessary and a debugger is very useful, both of which are included in all Linux distributions. A text editor, such as vi or Emacs, is also required for creating source files. Most importantly, you will need to install Qt for X11 and for embedded Linux.
The tools I used to create my Qt/Embedded applications come with most Linux distributions, including GCC and GDB. I also used TMake, a simple tool from Trolltech to create and maintain makefiles; the Qt documentation (available at http://doc.trolltech.com/); and a Qt textbook, Kalle Dalheimer's Programming with Qt. Features in the Qt documentation that you should look at include an explanation of how Qt makes it very easy to create simple applications. Its API contains several classes that are designed to make common tasks much easier and faster.
In particular, for creating a 2-D game, you should look at three main features to get started: QMainWindow, which provides a typical application window complete with toolbars or menus and status bars; QCanvas, a 2-D graphic area on which QCanvasItems (graphical objects) can be placed; and QCanvasView, which provides a view of the canvas. Looking at the Qt documentation is a good starting point because it will give you an idea of the rich functionality that Qt offers. Also, many things you will want to do have already been done.
This next portion of this article highlights Qt features that make it easy to create a simple game, using example code from a Snake game I have written (see Figure 1). The principles explained here can be applied to create many other 2-D games.
MainWindow
The first thing you will need is a main class. This should be located in a file called main.cpp. It will contain only a main method that creates an instance of your program. The main method from Snake appears below.
int main(int argc, char **argv) { QPEApplication app(argc,argv); SnakeGame* m = new SnakeGame; // creates an instance of Snake m->resize(m->sizeHint()); qApp->setMainWidget(m); m->show(); return app.exec(); }
If you have already written Qt programs, you will find some of this code familiar. It is a standard way to start a program.
You now need a class to set up the program. Its constructor will create an instance of the program, as used in the main method that appears above. It will inherit from QMainWindow and will create menus or toolbars and possibly a status bar. It also needs to create a QCanvas and a QCanvasView. All of this is shown in Listing 1.
Listing 1. Creating a QCanvas and QCanvasView
This class will also contain some other methods to be used in your program. In Snake, this class contains methods to display a title screen, start a new game, update the score, change levels, clear the screen, end the game and handle keyboard interaction (key events). These methods will be specific to your program, so I have not included the code here.
Now that you have a QCanvas, you can start placing QCanvasItems on it. There are many types of QCanvasItems, and to create a graphical item, we will use QCanvasSprite. See the Qt documentation for information on the other types of QCanvasItems. In the Snake game, the target mouse is a QCanvasSprite created from an image. The mouse is defined in its own class and inherits from QCanvasSprite. The following code shows the constructor for the mouse in a class called Target:
Target::Target(QCanvas* canvas) : QCanvasSprite(0, canvas) //inherits from QCanvasSprite { mouse = new QCanvasPixmapArray setSequence(mouse); newTarget(); }
The first thing this code does is load the image into a pixmap array called ``mouse''. If you had several images and wanted to animate your sprite, there is a readPixmaps() function to load all them into the array. You can then use the setSequence() function in QCanvasSprite to tell your sprite to use the images just loaded into the array. This constructor also calls a function newTarget(). This simply generates random integer x and y values at which the new target can be placed. It then calls the move (int x, int y) function in QCanvasSprite to set its position and show() to make it visible. The wall is also created in the same way.
Now you can make your program interactive. The Qt class keyEvent collects information about key events. The event handlers keyPressEvent and keyReleaseEvent receive this information. To use these, you will need to implement them as a function in your program. A typical implementation of keyPressEvent would be to use a switch statement to call functions when the user presses certain keys.
In the game, the snake changes direction when one of the arrow keys is pressed. This was implemented with a switch statement that calls a function move(), with the direction pressed as its argument. Here is the implementation of the keyPressEvent() function. It is quite simple because it calls a function in the Snake class that remembers the last key pressed and calls another function, moveSnake(), to determine which direction the snake should move in. To enact this, add these lines to the interface.cpp file:
void SnakeGame::keyPressEvent(QKeyEvent* event) { int newkey = event->key(); snake->go(newkey) }
To the snake.cpp file, add:
void Snake::moveSnake() { switch (last) { case Key_Up: move(up); break; case Key_Left: move(left); break; case Key_Right: move(right); break; case Key_Down: move(down); break; } detectCrash(); }If the user presses the Up key, the program calls move(up). The move function executes, and the snake moves in the direction of the key pressed: in this case, up. The code in the move() function will be specific to the particular game created.
The actual movement of the snake is quite complex because the snake's body is a collection of little images. To move, the end image is put at the front, and the head and tail pieces are moved. In other situations, such as pressing a key to shoot a bullet, the setVelocity() function in Qt could be used to set a constant speed at which the bullet will move.
At this stage you have a QMainWindow containing a QCanvasView with a canvas that contains items, some of which may be moving. The next step is to make your program more interesting by detecting collisions between the items. You can get information about collisions between QCanvasItems using collidesWith() and collisions(). collidesWith() is used to test if one item will collide with another particular item, and collisions() returns a list of all items with which a specific item will collide. In the Snake game, collisions() is used because the snake can collide with itself, the wall or the target. Listing 2 shows how collisions were handled in the game.
Listing 2. Handling Collisions
The list of QCanvasItems that the head of the snake collides with are assigned to a QCanvasItemList called ``l''. The false argument to collisions() specifies testing for collisions in inexact mode. This will return items that are near the head, but it works much faster than exact mode. If the item is a useful candidate, it is tested with collidesWith().
The ``for'' loop is used to iterate over the list l, assigning each item to a variable called ``item''. Each QCanvasItem on the canvas can have a specified rtti() value, an integer that allows you to identify the items. The target's rtti() value is 1,500, and this information was used in the if statement to test whether the item in the list was the target. If it was the target, the function returns; otherwise it moves on to the next if statement to test whether the item is the wall. Note that this code segment is for illustrative purposes only. In the actual Snake game, there are other if statements in the for loop to handle other situations. The ``do something'' is replaced with code.
Another important consideration when developing any program is the design. Qt offers assistance with component programming--otherwise known as object-oriented programming--through its signals and slots mechanism. This is a great feature that saves a lot of time and effort, as it allows easy communication among the different objects in your program. It allows an object to emit a signal() that activates a slot() in another object. Slots can be normal member functions, and parameters can be also be passed into them.
I found this feature particularly useful when developing Snake. An example of the use of signals and slots occurs when the game ends. The snake object emits a dead() signal when it collides with a wall. Then the snake's body, or the edges of the screen, connects to a gameOver() slot in the interface class to end the game and begin a new one. The code for this is in snake.cpp:
//check if snake hit itself for (uint i = 3; i < snakelist.count(); i++) { if (head->collidesWith(snakelist.at(i)) ) { emit dead(); // signal to end game autoMoveTimer->stop(); return; } }
And the code from file interface.cpp in the newGame() function is:
// connect the signal to the slot connect(snake, SIGNAL(dead()), this, SLOT(gameOver()) );
If the test of an application framework is the ability to quickly build professional, powerful and bug-free code, then Qt/Embedded passes with flying colors. As an intern with fairly limited programming experience, I was able to create a 2-D computer game good enough to be included in the suite of applications for the Qt Palmtop Environment. And I did it in under five weeks of actual development time. The tagline for Qt/Embedded is ``Small Enough for the Job''. I think that's pretty accurate. It was powerful, with a rich set of functionalities that made programming very easy, and it took up an amazingly small amount of main memory and Flash. If the other tools I come across in my career are even half this good, I'll be one happy developer.
Natalie Watson (nwatson04@optusnet.com.au) is currently a student at Griffith University, where she is working toward a Bachelor degree in Information Technology. Her programming experience includes Java and C++.
email: nwatson04@optusnet.com.au