This game idea and its image and audio resources are from the excellent Corona tutorial on Ray Wenderlich's site http://www.raywenderlich.com/22064/how-to-make-a-simple-game-with-corona.
This tutorial assumes you already had a look at Getting Started with Felgo and Qt Creator. They provide you with the very basics which may not be fully explained in this tutorial. Nevertheless, this tutorial aims at beginners, so let's give it a shot.
Images and sounds for this game are available for you to download right here: Download Resources
Create a new Empty Felgo Project with the name BalloonPop. Now extract the resource files that you just downloaded into the qml
directory of your project. Since you are already here, also create a
new folder entities
(which we will need later) inside the qml
directory.
On to a little brainstorming. What major components do we need to create this game?
main.qml
which will then hold our Scene for the game.
We cut down the Code in the main.qml
to the minimum, like this:
import Felgo 4.0 import QtQuick 2.0 GameWindow { Scene {} }
Keep in mind that larger games will definitely have more than just the game scene (e.g. menu, settings,...). So it's common to define the different scenes in separate files, making the code more readable. Go ahead and add a
BalloonScene.qml
to your qml
directory.
Now we will put the Scene element here, and our GameWindow will hold our custom scene.
BalloonScene.qml
import Felgo 4.0
import QtQuick 2.0
Scene {
}
main.qml
import Felgo 4.0 import QtQuick 2.0 GameWindow { // our scene that contains the game BalloonScene {} }
Our main.qml
is finished now since this is our only scene.
Let's take a look at our BalloonScene.qml
. We add some basic features that we definitely need.
//... Scene { // number of balloons currently on the scene property int balloons: 0 // maximum number of balloons property int balloonsMax : 100 // duration of the game property int time : 20 // flag if game is running property bool gameRunning: false // position the scene on the top edge sceneAlignmentY: "top" // used to create balloons at runtime EntityManager { id: entityManager entityContainer: balloonScene } // make the balloons float up PhysicsWorld {z: 1; gravity.y: -1} // add a background image Image {source:"../assets/img/clouds.png"; anchors.fill:gameWindowAnchorItem} // the pop sound used by balloon entities GameSoundEffect {source:"../assets/snd/balloonPop.wav"} // add background music BackgroundMusic {source:"../assets/snd/music.mp3"} }
Now we can set the maximum number of balloons that we want to have on the scene, as well as the time we give the player to pop them. Furthermore we added a counter balloons
that will hold the current number of
balloons alive and a flag to indicate if the game is running or not.
The EntityManager is used to create balloons at runtime. The PhysicsWorld with a very low gravity of -1
in y direction makes the
balloons float up gently.
The background Image and BackgroundMusic improve the gaming experience, I have no doubt about that! Have a look at
anchors.fill:gameWindowAnchorItem
in the background Image. This is needed to avoid black borders on different mobile devices with different screen
sizes. Also the sceneAlignmentY: "top"
is there for a similar reason, we ensure that the ceiling of our scene is at the top of the screen.
The GameSoundEffect object loads the pop sound for the balloons. This one will have to be available to the balloons themselves so we need to add a little fix here.
//... Scene { id: balloonScene // provide the pop sound public for the balloons property alias popSound: popSound //... // the pop sound used by balloon entities GameSoundEffect {id:popSound; source:"../assets/snd/balloonPop.wav"} //... }
Adding an id to the Scene gives us the possibility to interact with it from outside. The pop sound can now be accessed via balloonScene.popSound
thanks to the property alias that
points at our GameSoundEffect object.
Run the project and enjoy the beautiful sky, prettified by the delightful music, isn't that already enough? Not for us!
We'll proceed with some walls, everyone likes walls.
Following the lessons we learned in Getting Started with Felgo and Qt Creator, we will create a reusable wall entity. Add an entities
folder as subfolder of
qml
if you haven't done that already. Now add a new file Wall.qml
to the entities
folder.
Insert the following code to Wall.qml
import QtQuick 2.0 import Felgo 4.0 // for accessing the Body.Static type EntityBase { entityType: "wall" // default width and height width: 1 height: 1 // only collider since we want the wall to be invisible BoxCollider { anchors.fill: parent bodyType: Body.Static // the body shouldn't move } }
So what happened here? We want the walls to be invisible, so we don't add anything like a Rectangle or similar. We just want them to block our balloons, so we
add a BoxCollider that fills the whole wall. Also, the wall shouldn't be affected by gravity, that's why we tell it to be Body.Static
.
Now add some walls to the BalloonScene.qml
, just before the last closing }
. Additionally we need to import our entities
folder to be able to use our Wall.qml
//... import "entities" Scene { //... // left wall Wall {height:parent.height+50; anchors.right:parent.left} // right wall Wall {height:parent.height+50; anchors.left:parent.right} // ceiling Wall {width:parent.width; anchors.bottom:parent.top} }
We want the walls to be outside of the scene, so we use the anchors. E.g. we anchor the furthest right side of the wall to the furthest left side of the scene, meaning the body of the wall is left outside of the screen. The reason I put +50 to the height, which causes the wall to go 50 pixels below the scene, is that we don't lose any balloons to the sides while creating them. You will understand this security measure in a few moments.
Ok looks fine, not that there is anything new to see if we run the project, but anyway, time for balloons!
Add a Balloon.qml
file to the entities
folder.
Insert the following code to Balloon.qml
import QtQuick 2.0 import Felgo 4.0 EntityBase { entityType: "balloon" width: sprite.width height: sprite.height MultiResolutionImage { id: sprite source: "../../assets/img/balloon.png" } CircleCollider { radius: sprite.width/2 // restitution is bounciness, balloons are quite bouncy fixture.restitution: 0.5 } MouseArea { anchors.fill: sprite } // gives the balloon a random position when created Component.onCompleted: { x = utils.generateRandomValueBetween(0,balloonScene.width-sprite.width) y = balloonScene.height } }
There are some more things to say about this piece of code. In our case, we want the size of the balloon to result from the size of the MultiResolutionImage we load, so it's super easy to replace that one. The MultiResolutionImage uses the correct image size depending on the display resolution. To learn more about multi-resolution support check out How to create mobile games for different screen sizes and resolutions.
A CircleCollider matches the shape of a balloon best and way better than a BoxCollider. Add some fixture.restitution
to make the
balloon bouncy.
The MouseArea fills the whole balloon image and is used to make them pop when the balloon is touched.
And the last little bit of code, the Component.onCompleted
section will define the position of the balloon when it is created. We want the balloons to spawn at the very bottom of your scene, and thanks to the
id:balloonScene
that we gave the Scene, we can get its width and height for this calculation.
At the end of BalloonScene.qml
, just before the last closing }
, we add this piece of code
//... // create balloons with short intervals in between Timer { interval: 20 // milliseconds running: true // start running from the beginning, when the scene is loaded repeat: true onTriggered: { // after every 20ms we create a new balloon entityManager.createEntityFromUrl(Qt.resolvedUrl("entities/Balloon.qml")); balloons++ // if the maximum number of balloons is reached, we stop the timer and therefore the balloon creation and start the game if(balloons===balloonsMax) { running = false gameRunning = true } } } //...
Finally we got some action in here. Watch the balloons getting a little crazy when you run the project. So crazy that we made the side walls a little longer than maybe needed, just in case...
What did we do? We used a Timer with an interval of 20ms. So every 20ms it will trigger. And on each of these triggers we tell our EntityManager to create a new entity from our Balloon.qml
file, in other words a new balloon. If our maximum number of balloons is reached, we stop the timer and start the game.
When running the code you might have noticed the Felgo Splash Screen, which is only visible for developers that use our free plan. Our game scene is already created and starts running in the background while the splash is still visible. This allows the splash to also act as a basic loading screen for the first visible scene - after splash is finished the scene is immediately shown.
Unfortunately there's one issue caused by this behavior: We already start the game and create balloons while the splash is still visible. This happens because the Timer that creates the balloons starts running automatically after it is created. So let's add some code to delay our balloon creation until the splash screen is finished:
Let us change the Timer in BalloonScene.qml
to not run automatically:
Scene { // ... // starts the game function start() { spawnBaloons.start() } // create balloons with short intervals in between Timer { id: spawnBaloons // after the game initialization is complete we start the timer manually using the id interval: 20 // milliseconds repeat: true onTriggered: { // after every 20ms we create a new balloon entityManager.createEntityFromUrl(Qt.resolvedUrl("entities/Balloon.qml")); balloons++ // if the maximum number of balloons is reached, we stop the timer and therefore the balloon creation and start the game if(balloons===balloonsMax) { running = false gameRunning = true } } } }
For the Timer, we removed the running: true
setting and added an id. We can then use the new start()
function of our scene to start the Timer anytime we want. To start it right after the splash is
gone we can use the onSplashScreenFinished signal in our main.qml
.
GameWindow { id: gameWindow // start the game when is splash finished onSplashScreenFinished: balloonScene.start() // our scene that contains the game BalloonScene { id: balloonScene } }
Start the game again, you will see the balloons popping up after the splash is gone just as we wanted them to.
Note: For initialization code that can safely be run while the splash is still showing you can also use the Component.onCompleted signal instead. In addition, if you removed the splash screen by purchasing a Felgo license, the two signals are identical. We recommend to only use onSplashScreenFinished for code that requires to be run right after the splash. Otherwise the regular Component.onCompleted signal is the better choice.
Now we have everything correctly set up, so what's next? You don't want to hurt no balloon, do you? Me neither, but the players may want to, so let's make them pop-able.
This is done by adding a simple piece of code to our Balloon.qml
, more precisely the MouseArea
MouseArea { anchors.fill: sprite onPressed: { // if you touch a balloon and the game is running, it will pop if(balloonScene.gameRunning) { balloonScene.balloons-- balloonScene.popSound.play() removeEntity() } } }
We listen on the MouseArea::pressed signal of the MouseArea. If the player touches the balloon we count down the current number of balloons, play the pop sound and remove the entity. Of course we only allow this to happen if the game is running.
Not too challenging when you got infinite time. We take care of that now.
Add this at the end of BalloonScene.qml
, just before the last closing }
// game timer, default interval is 1 second Timer { id: gameTimer running: gameRunning // time only counts down if game is running repeat: true onTriggered: { time-- // if time is over, or each balloon is popped, we stop the game and give the player some feedback about his success if(time === 0 || balloons === 0) { gameRunning = false } } }
The timer is only running if the game is running, meaning it starts after all balloons are loaded and the game started. After each second (we don't necessarily need to define that interval because 1 second is the default interval) the timer triggers. On every trigger we decrease the remaining time. Then we check if the time is already over or the player managed to pop all balloons, in both cases we stop the game.
The only thing missing now is a little heads-up display.
We want to keep it as simple as possible, one row at the bottom will do it. We want to display the remaining time, and a little info field.
Add this to BalloonScene.qml
, e.g. above our timers
//... // HUD Row { anchors.bottom: parent.bottom z: 2 // make sure the HUD is always on top // info text area Text {id:infoText; width:200; height:40; text:"Loading balloons..."} // display remaining time Text {id:timeText; height:40; text:"Time: "+balloonScene.time} } //...
Now we got a Row with 2 Texts. The 2nd one displays the text "Time: " followed by the remaining time of the game.
The 1st one should give the player some information. We start with the text "Loading balloons...". After all balloons are loaded we want to give the player feedback that he can start popping them now.
Add this to your BalloonScene.qml
, more precisely the first Timer, used to create the balloons
//... // if the maximum number of balloons is reached, we stop the timer and therefore the balloon creation and start the game if(balloons===balloonsMax) { running = false gameRunning = true infoText.text = "Hurry!" } //...
This changes the text
property of our infoText Item to "Hurry!", telling the player that the game is running.
The last thing we want to add are some motivating words to the player at the end of the game.
Add this to your BalloonScene.qml
, more precisely the second Timer, used for the game time
//... // if time is over, or each balloon is popped, we stop the game and give the player some feedback about his success if(time === 0 || balloons === 0) { gameRunning = false if(balloons === 0) infoText.text = "Perfect, take a cookie!" else if(balloons < balloonsMax/2) infoText.text = "Well, that was decent..." else infoText.text = "Not your day huh..." } //...
Depending on the number of balloons the player popped, he gets a feedback about his success as soon as the time ran out or he popped all balloons.
So that's it for now, enjoy the gaming experience!
Visit Felgo Games Examples and Demos to gain more information about game creation with Felgo and to see the source code of existing apps in the app stores.