Вы находитесь на странице: 1из 10

Flashy games with QML

Riccardo Iaconelli - riccardo@kde.org December 27, 2010


Abstract There is a big market to be exploited which consists of relatively simple but highly entertaining games. The web is full of popular examples, mostly written in Flash, of these things. With QtQuick and Meego, there is the possibility of allowing a much nicer user experience, with extremely fast graphics and tighter integration with the workspace. Additionally, a great way to get started in designing rich applications is to create some simple yet attractive games that can give you immediate satisfaction. The QML language part of the QtQuick framework allows you to do this very easily, also providing all the must-have features, like smooth animations and stunning eects. This article will teach you how to build your rst application with QML to unleash the power of the new Qt API, leaving some exercise to the user. It will guide you to build a simple memory game, add animations, images, and everything you need. The article will also get you started with developing with Qt Creator, talking briey of future possibilities of easy deployment to Meego clients.

Introduction

In this article, we will introduce some features of the QML language trough a compelling real world use case: creation of simple games targeted to mobile devices. The game we will write is a very simple one: Memory. The player sees a grid of cards on the screen, all turned on the back. Also, there are two copies of each card. The user ips two cards: if they are identical the duplicate is found and they are kept turned, otherwise both are turned back again, the user must memorize their position to be able to nd future pairs of cards. The target of the game is to nd all the possible pair in the least possible time and move number. As we will see, it is possible to write the complete game logic and visualization in a combination of the QML language, for the Declarative part and JavaScript, for the Imperative bits, moving the need to use C++ code only for more complex and particular use cases. The declarative programming paradigm is a powerful concept which allows you to dene how something should look in a ceratin state, without having to write any line of code which takes care of how to show the transition that happens in between. Since the code doesnt contain any painting logic itself, it is also possible for the graphic card to optimize in the best way possible the actual painting. This already 1

results in much snappier UIs that immediately adapt to any platform, without requiring the work of any graphic ninja, or even knowledge of how accelerated painting systems work.

1.1

Long live JavaScript

One of the big strenght of QML is that it is mostly interpretated JavaScript. JavaScript is a very powerful yet very simple language that is used throughout the web to create rich user experiences. It is a language commonly known by designers, and its the de-facto standard for the most disparate uses. QML uses a very fast interpreter, QtScript, which is directly built into Qt. Almost any expression. While javascript generally oers a good enough solution for rapid development, sometimes it is not the right thing to use if you have exceptionally strict requirements on computing speed (e.g. you need to do lots of computing intensive operations) or if you need to use data from external libraries which do not bind to QML. For this reason it is possible, and simple, to bind some C++ code to the QML you write. Actually, this is exactly how the ocial QtQuick bindings are done! This ensures the maximum exibility while still allowing to share the work between designers (who can directly provide working prototypes) and developers (which can just bind the working code that designers did feeding it with real data). JavaScript is the glue to all this: a modern, powerful, fast and intuitive language which seamlessly run on any platform, and which enables you to write awesome UIs thanks to QtQuick. Almost any expression that you can write in QML is actually JavaScript, which is evaluated runtime. This means that you can rewrite any value with some code generated at runtime.

1.2

Some tools that well be using

There are some tools that you might nd useful for your development. The rst one is qmlviewer. You can use it to preview QML les, or to run QML applications. It comes with Qt 4.7 and contains several useful features for development, like the ability to run the application with slower animations, or to debug values. It is very easy to use it - you just need to open a QML le with it, either with the Open le dialog or as a command line argument. For example, for the game that we are going to develop, you can simply run: $ qmlviewer PairsCanavas.qml Another fantastic tool if you are going to develop any Qt application is the Qt Creator IDE. Qt Creator is a tool which makes it very convenient to write any kind of Qt-based application, with easy access to the documentation and several targets to dene. With Qt Creator you can easily specify multiple targets to build your applications for, it has an embedded debugger optimized for Qt and several other things which truly make it a developers best friend.

While this tutorial is platform and editor agnostic, I truly reccomend following it with Qt creator - it is an IDE that, talking out of experience, really makes your life easier. Using Qt Creator is very easy, but for any need remember to check out its whitepaper (http://developer.qt.nokia.com/wiki/QtCreatorWhitepaper).

Getting started with the code

Before we start, a tarball with the code and all the images is available at http://les.ruphy.org/pairsgame.tar.bz2. You can download it, use it to play and try to add additional features. Instead of including all the code, in this tutorial well mostly concentrate on commenting the interesting parts of it.

2.1

Preparing a canavas

So, lets start with some real code: import QtQuick 1.0 Image { id: pairsCanvas; width: 800; height: 480; source: "background.jpg" property Item currentCard; property int matchesCountdown; } What are we doing here? Lets start from the beginning: import QtQuick 1.0 This is the line needed to tell QML to load all the convenient Qt objects and bindings. This is the piece of code where the magic happens, and that makes almost everything of what will follow achievable. If we were to use QML without it, it would really be quite boring. Now, every QML le needs a root object, which in this case will be a nice background image, to make the game nicer. If you dont want or need a custom background, you could start with the more generic Item, instead of the Image that we are using here, without any issues. What happens next? We give it an id (note that ids are unique for the whole QML le, and can be referenced).

2.2

Model and visualization

Our game has a board of cards: we have to keep track the content and disposition of each card and their state: what cards have already matched, what pair we are checking now and update the game state accordingly. 3

This maps pretty well to a classic paradigm in computing: the model-view system. We split the game in two separate layers: the logic and the data storage (where with data we intend the state of the game, the cards disposition and so on) will be separated and distinct from the visualization of said data: this will be what the user actually sees of the game. This paradigm will make easier to modify the game rules without aecting the look and the artwork and in the same way will be easy to completely rewamp the visual appearance without touching the game logic code. Since one of the goals of QML is easing the separation of tasks between the artist and the coder, it provides some built in facilities to exploit the model/view paradigm. QML provides access to both simple models created directly in QML/JavaScript (that we will use in this example) and C++/Qt based models used in more complex applications. In the same way, QML provides classic list views and grid views for large data sets, and the concept of the Repeater (that we will see in detail) for smaller ones. 2.2.1 Model: adding some logic

In this example, we use a very simple model, that permits the insertion and removal of the data completely in JavaScript. It is ListModel. To instantiate one from QML it will be sucient to write: ListModel { id: elements Component.onCompleted: { GameLogic.init() } } A ListModel instance when created will be empty, so we will populate it by calling a JavaScript function as soon the ListModel component is nished loading: this is done with Component.onCompleted. GameLogic is a JavaScript module that resides in its own le and is imported with the directive: import "gamelogic.js" as GameLogic Lets see what the init() function does: function init() { gameTimer.running = true gameTimer.time = 0 winMessage.y = -winMessage.height matchesCountdown = rows*columns / 2; var pairs = new Array("bug", "bug", "clock", "clock", "kde", "kde", "konqueror", "konqueror", "magicwand", "magicwand", "plasma", "plasma") elements.clear(); 4

var remaining = maxVal for (var i=0; i < maxVal; ++i) { var randVal = Math.round((Math.random()*remaining-1)) var element = pairs.splice(randVal, 1) --remaining elements.append({"card": String(element)}) elementsGrid[Math.floor(i/columns)][columns%i] = element } } This function initializes the model that represents the card deck, together with other bookeeping values. We have two widgets, of which well talk about later, that represent a timer, that tells the use how much time elapsed. when the game starts (or re-starts) the timer started and the count will be set to zero. The second extra widget used here is a message that tells the user he won. When the game starts, that message will have to be moved out of the way, thats why its position is changed. Now, lets see the actual play logic and model population. The variable matchesCountDown is initialized as the number of pairs there are (updating this will be easy to notice we have won, when the count will reach zero). An array with the values of all possible cards gets populated, in this case for simplicity hardcoded here. The model will be lled with the contents of the array, but shued in a random order to make the game enjoyable. 2.2.2 The view

Now we have a model of the game, but users cant play with it, so we need a view. QML has two paradigms to show the elements of a model: the traditional views, like ListView and GridView, where only the needed visual elements to ll a viewport are instantiated and the Repeater, that will instantiate all the elements of a model disposed in one of the layout systems oered by QML, such as Row, Column or Grid, whose behaviour is pretty autoexplicative from their names. In this case, since we want to represent the whole board game well use a Repeater, while in other case a traditional view is needed, be careful to not use a repeater to represent a mailbox with 50000 emails in it or the memory usage would skyrocket! A view (or in this case repeater) will instantiate the items as the kind of components dened as delegate: Grid { id: mainGrid anchors.fill: parent rows: GameLogic.rows columns: GameLogic.columns spacing: 5 5

Repeater { id: repeater model: elements delegate: Card { cardPicture: card+"-card.png" } } } In the above example a Grid will position the elements produced by a Repeater: they will come from the model identied by the id elements and be of the type Card. Card has a property called cardPicture that is simply the path of the image it will load to represent the card. the le name is generated from the (in the QML jargon) attached property card, that comes from the model (remember that we used the instruction elements.append("card": String(element)) to pupulate the model). Well see in the subsequent paragraphs how the Card delegate is implemented. 2.2.3 Adding the images

One of the most used components of a view is the Image component, in our example we use images to show to the user both a couple of buttons and all the game cards pictures. To display an image we just need to dene an image object and to set its source property: Image { source: "application-exit.png" } This image displays a close icon but we still need the code to allow users to interact with it, so we have to dene a MouseArea inside the item, where the user can click on. In this case we will see how it works for the close button. The MouseArea must cover all the close button image and it also has to call Qt.quit() when the user clicks on it. Doing this is really straightforward and we just need to set anchor.fill and onClicked properties. Image { source: "application-exit.png" MouseArea { anchors.fill: parent; onClicked: Qt.quit(); } } While the onClicked: Qt.quit(); line is pretty much self explanatory, the following one may not be obvious at rst sight to someone who isnt really familiar with the anchors concept. anchors.fill: parent;

This line simply sets the size of the MouseArea to be as big as the size of the parent object, which in this case is the button itself. The anchors idea is to make layouting in QML easy. We will not talking of it in this tutorial since you will be able to read about it thoroughly in Qts online documentation. The card can be implemented in a similar way, we just need to change source according to card picture. The card will use an Image element to load the card picture, specied by the cardPicture property, when the user clicks on a card the following will happen: if the card is not visible if we are not checking any card, flip this one and set it as the card we are checking else compare it with the one we are checing if they match, set both as matched, disable them and decrement the variable matchesCountDown in the GameLogic JavaScript object else flip them again since the match is failed In actual code, it can be expressed in this way: MouseArea { id: mouseArea; anchors.fill: parent; onClicked: { if (!pairsCanvas.currentCard){ pairsCanvas.currentCard = card; card.state = "checking" } else if (pairsCanvas.currentCard != card) { if (pairsCanvas.currentCard.cardPicture == card.cardPicture){ card.state = pairsCanvas.currentCard.state = "matched" pairsCanvas.matchesCountdown--; } else { card.state = pairsCanvas.currentCard.state = "normal" checkAnimation.running = true } pairsCanvas.currentCard = null; } } } At the clicked signal of the cards MouseArea, we can execute a bit of JavaScript code, that does the noperations described in the previous pseudocode bit. The card were checking is saved in the property currentCard of the global object pairsCanvas. The property state of the cards deserve an explanation a little more in deep, of which well talk about next paragraphs.

2.3
2.3.1

Make it shine!
States

At the begging all the cards pictures are hidden, then when a card is chosen the picture is displayed to the user and nally if the user is able to match 2 cards they get removed from the game canvas. This description can be summarized with only 3 states: normal (the default state), checking (the picture is shown to the user) and matched (the card has been matched with another card with the same picture). The QML implementation requires to dene a states array, then in the array we can dene all the states we need. states: [ State { name: "normal"; }, State { name: "checking"; }, State { name: "matched"; } ] Optionally we can let QML to manage state transitions when a condition on a property is satised (in this example this technique is not used). State { name: "checking"; when showPicture == true [...] } When a card is in the normal state the picture must be hidden: State { name: "normal"; PropertyChanges { target: card; showPicture: false } [...] } The current state is reected by the property state of the cards. The state machine makes also really easy to write animations that will occur during the transitions between two states.

2.3.2

Animations

In our physical world a moving object is something that change its position in function of the time; elements on the screen behaves in a similar way: a property is changed according to the time, but thaks to QML we dont have to write all the code, we can just use a NumberAnimation. We only need to specify the target of our animation, the property which has to be changed from a start value to a stop value during a certain amount of time. In this example when a card is clicked it gets iped and then the hidden picture is shown to the user. This animation is obtained with a card rotation: NumberAnimation { target: rotation property: "angle" duration: 300 /* duration is expressed in milliseconds */ from: 0 to: 180 } We cant animate a ipable card using just one rotation but we need a sequence of simple animations. To create a sequence of animations we need to dene a SequentialAnimation, inside of the SequentialAnimation we dene sequentally our simple animations: SequentialAnimation { id: checkAnimation NumberAnimation { target: rotation As soon as our rst rotation ends we need to pause our SequentialAnimation for 300 milliseconds using PauseAnimation: PauseAnimation { duration: 300 } Now we can easily rotate back our element using another NumberAnimation similar to the rst one, then we just need to close the block. Doing so we completed our card animation. Usually animations like ip are triggered when something happen or when the state of an element is changed, for example when a card is shown to the user. We can bind our ip animation to a state change or we can invoke the animation with one line of javascript code. checkAnimation.running = true As soon as the running property is changed the animation starts. The same animation can be triggered during a state transition: transitions: [ Transition { from: "normal"; to: "checking" 9

SequentialAnimation { [...] } }, Transition { [...] ]

Deploying to mobile devices

If you have used Qt Creator, deploying to mobile devices becomes a breeze. If your setup is correct, you can already deploy your Qt based application to a mobile device in a second. To obtain more information you can take a look at http://tinyurl.com/2ebu5px

Further resources
Qt Quick homepage http://qt.nokia.com/products/qt-quick/ Qt Quick reference documentation, examples, and others http://doc.qt.nokia.com/latest/qtquick.html Other QML examples http://doc.qt.nokia.com/latest/qdeclarativeexamples.html Qt Creator homepage http://qt.nokia.com/products/developer-tools/

4.1

Credits

For the immense help in writing this tutorial: Davide Bettio - bettio@kde.org Marco Martin - notmart@gmail.com

10

Вам также может понравиться