Doc: Add documentation for the Maroon in Trouble example
Change-Id: I8b272894ef6374aeb6ec09275d7ce96d16a6be0f Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
This commit is contained in:
parent
5c392490d7
commit
0314043655
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -29,14 +29,861 @@
|
|||
\title Qt Quick Demo - Maroon in Trouble
|
||||
\ingroup qtquickdemos
|
||||
\example demos/maroon
|
||||
\brief A cute game designed for touchscreens.
|
||||
\image qtquick-demo-maroon-med-1.png
|
||||
\brief A Qt Quick game for touch devices that uses SpriteSequence,
|
||||
ParticleSystem, Emitter, and Wander types to animate objects and the SoundEffect type to
|
||||
play sound effects.
|
||||
|
||||
\image qtquick-demo-maroon-med-2.png
|
||||
|
||||
\e{Maroon in Trouble} demonstrates various QML and \l{Qt Quick} features
|
||||
such as displaying custom components and playing sound effects.
|
||||
\e{Maroon in Trouble} demonstrates QML features that are useful when
|
||||
developing games:
|
||||
|
||||
\list
|
||||
\li Using custom QML types to create different screens for
|
||||
different stages of the game.
|
||||
\li Using the \l Item and \l Image types to construct a game background.
|
||||
\li Using the SequentialAnimation, NumberAnimation, ParticleSystem,
|
||||
\l Emitter, and \l Wander types to animate background objects.
|
||||
\li Using the \l Timer and \l Repeater types to display a countdown
|
||||
sequence before starting the game.
|
||||
\li Using a custom QML type with custom properties to construct a game
|
||||
board.
|
||||
\li Using the SpriteSequence and \l Sprite types to add animated objects
|
||||
to the game board.
|
||||
\li Using a custom QML type that uses the \l Image type with some custom
|
||||
properties to add a menu where the players can buy objects.
|
||||
\li Using custom properties with private functions to keep track of game
|
||||
statistics and a custom QML type to display them to the players.
|
||||
\li Using the \l State type with JavaScript functions to manage game
|
||||
states.
|
||||
\li Using the \l SoundEffect type to play individual sound effects
|
||||
depending on the object type and the action applied to the object.
|
||||
\li Using signal handlers to specify keyboard shortcuts for some game
|
||||
actions.
|
||||
\li Using resource files to package game resources for deployment and
|
||||
delivery.
|
||||
\endlist
|
||||
|
||||
\include examples-run.qdocinc
|
||||
|
||||
\section1 Adding Screens
|
||||
|
||||
In the Maroon in Trouble app, we use the following custom types that
|
||||
are each defined in a separate .qml file to create the game screens:
|
||||
|
||||
\list
|
||||
\li NewGameScreen.qml
|
||||
\li GameCanvas.qml
|
||||
\li GameOverScreen.qml
|
||||
\endlist
|
||||
|
||||
To use the custom types, we add an import statement to the main QML file,
|
||||
maroon.qml that imports the folder called \c content where the types are
|
||||
located:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto content
|
||||
\printuntil "
|
||||
|
||||
We use the screen types at different stages of the game. The NewGameScreen
|
||||
type is used to create the screen that appears when the players start the
|
||||
app. In NewGameScreen.qml, we use an \l{Image} type to create a New Game
|
||||
button that the players can press to start a new game.
|
||||
|
||||
\image qtquick-demo-maroon-med-1.png
|
||||
|
||||
Tapping the button initiates a countdown timer that triggers the creation
|
||||
of the game canvas by using the GameCanvas type. Another \l{Timer} type
|
||||
spawns mobs of fish inside bubbles that the players must free before they
|
||||
reach the surface. The players can tap on the screen to open a menu where
|
||||
they can buy different types of weapons (melee, ranged, and bombs) to burst
|
||||
the bubbles.
|
||||
|
||||
\image qtquick-demo-maroon-med-2.png
|
||||
|
||||
When the game finishes, a screen created by using the GameOverScreen type
|
||||
appears. On this screen, the players can see their score and start a new
|
||||
game.
|
||||
|
||||
\image qtquick-demo-maroon-med-3.jpg
|
||||
|
||||
The screens are all created on the same background and use some of the same
|
||||
images and animations.
|
||||
|
||||
\section1 Constructing the Background
|
||||
|
||||
In the maroon.qml file, we use an \l{Item} type with the id \c root and a
|
||||
fixed width and height to create a main window for the game:
|
||||
|
||||
\skipto Item
|
||||
\printuntil passedSplash
|
||||
|
||||
We declare two custom properties for the root item, \c gameState and
|
||||
\c passedSplash that we will use later to manage game states.
|
||||
|
||||
We use an \l{Image} item to display the game background image:
|
||||
|
||||
\printuntil anchors.bottom
|
||||
|
||||
We want to be able to load the background image only once at app startup
|
||||
and still use different scenes for the game screens. Therefore,
|
||||
background.png is three times the length of the root item and displays a
|
||||
scene that stretches from the bottom of sea to the sky above the horizon.
|
||||
|
||||
We use the \c anchors.bottom property to anchor the background image to the
|
||||
bottom of the \l{Column} layout that we use to position the screens:
|
||||
|
||||
\skipto Column
|
||||
\printuntil GameOverScreen
|
||||
|
||||
We set a negative value for the \c y property to set the first scene at the
|
||||
bottom of the sea. We calculate the position by subtracting the height of
|
||||
a screen from the \c height property.
|
||||
|
||||
Within the column layout, we use an \l{Item} type to add objects to the
|
||||
background. Within the item, we use \l{Row} layout objects to position
|
||||
\l{Image} objects that display waves on the game canvas and the game over
|
||||
screen:
|
||||
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\dots
|
||||
\skipto Row
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
The second row of waves is positioned on the y axis with a slight offset to
|
||||
the first row. We also use the \c opacity property to make the waves appear
|
||||
lighter in color than the first two waves, which gives the background some
|
||||
depth.
|
||||
|
||||
We use \l{Image} objects to also display sunlight on the new game screen and
|
||||
on the game canvas:
|
||||
|
||||
\skipto Image
|
||||
\printuntil anchors
|
||||
\dots
|
||||
\skipto Image
|
||||
\printuntil anchors
|
||||
|
||||
We set the \c opacity property of the images to \c 0.02 and \c 0.04 to give
|
||||
some depth to the rays of sunshine. We use the \c y property to position the
|
||||
images at fixed locations on the y axis and the
|
||||
\c {anchors.horizontalCenter} property to center them horizontally in
|
||||
relation to their parent.
|
||||
|
||||
We use an \l {Image} type to display an image that adds a deepening shadow
|
||||
to the background:
|
||||
|
||||
\skipto Image
|
||||
\printuntil }
|
||||
|
||||
We set the \c opacity property of the image to \c 0.5 to make the background
|
||||
visible behind the shadow.
|
||||
|
||||
To make the background more interesting, we animate some of the objects we
|
||||
added to it.
|
||||
|
||||
\section1 Animating Background Objects
|
||||
|
||||
We use NumberAnimation to move the waves horizontally across the screen in
|
||||
opposite directions and SequentialAnimation with NumberAnimation to move
|
||||
them up and down.
|
||||
|
||||
We apply the number animation to the \c x property of \c wave as a property
|
||||
value source to animate the x value from its current value to the
|
||||
\c -(wave.width), over 16 seconds. We set the \c loops property to
|
||||
\c {Animation.Infinite} to repeat the animation indefinitely:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto wave.width
|
||||
\printuntil }
|
||||
|
||||
We apply the sequential animation to the \c y property of the image as a
|
||||
property value source to animate the y value. We use one number animation
|
||||
to animate the image from the y position of two below the value of y to two
|
||||
above it, over 1600 milliseconds. We use another number animation to
|
||||
subsequently animate the image in the opposite direction, again over 1600
|
||||
milliseconds. The animation is repeated indefinitely:
|
||||
|
||||
\skipto SequentialAnimation
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We use the easing curve of the type \c {Easing.InOutQuad} for a quintic
|
||||
(t^5) function to accelerate the motion until halfway and then decelerate
|
||||
it.
|
||||
|
||||
We use sequential animation and number animation to animate \c wave2
|
||||
similarly to \c wave, but in the opposite direction:
|
||||
|
||||
\skipto SequentialAnimation
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We use sequential animation to rotate the rays of sunlight in degrees
|
||||
clockwise around an origin point that we set to \c {Item.Top} in the
|
||||
\c transformOrigin property. The animation is repeated indefinitely:
|
||||
|
||||
\skipto transformOrigin
|
||||
\printuntil to: -10
|
||||
\printuntil }
|
||||
|
||||
We use one number animation to rotate the image from \c -10 degrees to
|
||||
\c 10 degrees over 8 seconds and another to subsequently rotate it from
|
||||
\c 10 degrees to \c -10 degrees over the same duration.
|
||||
|
||||
We use the easing curve of the type \c {Easing.InOutSine} for a sinusoidal
|
||||
(sin(t)) function to accelerate the motion until halfway and then decelerate
|
||||
it.
|
||||
|
||||
We use sequential animation and number animation to animate another
|
||||
sunlight.png image similarly, but in the opposite direction:
|
||||
|
||||
\skipto transformOrigin
|
||||
\printuntil to: 10
|
||||
\printuntil }
|
||||
|
||||
For examples of using SequentialAnimation and NumberAnimation on the \c x
|
||||
and \c y properties and the \c width and \c height properties, see
|
||||
NewGameScreen.qml.
|
||||
|
||||
\section1 Emitting Particles
|
||||
|
||||
In addition to animation, we use particles to generate motion on the game
|
||||
screens. We use the ParticleSystem QML type in maroon.qml to make bubbles
|
||||
appear at the bottom of the new game screen and game canvas and slowly float
|
||||
towards the top on varying trajectories.
|
||||
|
||||
To use the ParticleSystem type, we must import \l{Qt Quick Particles}:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto Particles
|
||||
\printuntil 0
|
||||
|
||||
To have the particles appear on the game background, we place the
|
||||
ParticleSystem type within the \l{Image} type that displays the game
|
||||
background:
|
||||
|
||||
\skipto Image
|
||||
\printuntil anchors.fill
|
||||
|
||||
In the ParticleSystem, we use an \l{Emitter} type to emit particles from the
|
||||
location of the emitter at the rate of two per second with the life span of
|
||||
15 seconds:
|
||||
|
||||
\skipto Emitter
|
||||
\printuntil sizeVariation
|
||||
\printuntil }
|
||||
|
||||
The \c acceleration property uses the PointDirection type to
|
||||
specify random variation of the x and y coordinates, so that the bubbles
|
||||
appear inside a rectangular area around the emitter that is anchored to the
|
||||
bottom of the image.
|
||||
|
||||
The \c size property sets the base size of the particles at the beginning of
|
||||
their life to 24 pixels and the \c sizeVariation property randomly increases
|
||||
or decreases the particle size by up to 16 pixels, so that we get bubbles in
|
||||
different sizes.
|
||||
|
||||
As emitters have no visualization, we use the ImageParticle type to render
|
||||
the catch.png image at the particle location:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto ImageParticle
|
||||
\printuntil }
|
||||
|
||||
A \l{Wander} type applies a random trajectory to the particles, so that the
|
||||
bubbles follow random routes from the bottom to the top.
|
||||
|
||||
\printuntil }
|
||||
|
||||
For another example of using the ParticleSystem type, see the
|
||||
GameOverScreen.qml file, where an ImageParticle type is used to make clouds
|
||||
move across the sky.
|
||||
|
||||
\section1 Using Timers
|
||||
|
||||
\image qtquick-demo-maroon-med-4.jpg
|
||||
|
||||
In maroon.qml, we use the \l{Timer} type with a \l{Repeater} type to display
|
||||
a countdown sequence before using another timer to start a new game. Both
|
||||
timers are started simultaneously in the \c "gameOn" state, that is when the
|
||||
players tap the New Game button and \c passedSplash is \c true. This is
|
||||
explained in more detail in \l{Managing Game States}.
|
||||
|
||||
We use the \c countdownTimer to display the countdown sequence:
|
||||
|
||||
\skipto Timer
|
||||
\printuntil }
|
||||
|
||||
The \c onTriggered signal handler is called when the timer is triggered to
|
||||
increment the value of the the \c countdown custom property.
|
||||
|
||||
We set the \c repeat property to \c true to specify that the timer is
|
||||
triggered at the interval of 1 second as long as the value of \c countdown
|
||||
is less than 5.
|
||||
|
||||
The \c countdown property is defined in the root item with an initial value
|
||||
of \c 10, so that \c countdownTimer is not running by default:
|
||||
|
||||
\skipto countdown:
|
||||
\printuntil 10
|
||||
|
||||
Each time the timer is triggered, an image from the countdown sequence is
|
||||
displayed. We use a \l{Repeater} type to instantiate the \l{Image} delegate
|
||||
in the context of the repeater's parent, \c canvasArea item, seeded with
|
||||
data from the \c model:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto Repeater
|
||||
\printuntil scale
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We scale the images from \c 0.0 to \c 1.0 and use the \c visible property to
|
||||
hide the images for the previous steps as the countdown progresses. We also
|
||||
raise the opacity of the image that matches the current countdown step,
|
||||
keeping the others nearly transparent.
|
||||
|
||||
By animating the changes in the \c opacity and \c scale properties using a
|
||||
\l Behavior type, we achieve a countdown sequence where numbers zoom in
|
||||
towards the players.
|
||||
|
||||
\section1 Constructing the Game Board
|
||||
|
||||
To construct the game board, we use the GameCanvas custom type that is
|
||||
defined in GameCanvas.qml.
|
||||
|
||||
In maroon.qml, we use the GameCanvas type to display the game canvas
|
||||
at the position of 32 on the x axis and 20 pixels from the bottom of
|
||||
its parent item, \c canvasArea:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto GameCanvas
|
||||
\printuntil }
|
||||
|
||||
We set the \c focus property to \c true to give \c canvas active focus on
|
||||
startup.
|
||||
|
||||
In GameCanvas.qml, we use an \l Item type and define custom properties for
|
||||
it to create a grid of equally sized squares divided to 4 columns on 6 rows:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameCanvas.qml
|
||||
\skipto Item
|
||||
\printuntil canvas
|
||||
|
||||
We use the custom properties to set the \c width and \c height of the
|
||||
\c grid item as the amount of columns and rows multiplied by square size:
|
||||
|
||||
\skipto width
|
||||
\printuntil height
|
||||
|
||||
We use an \l{Image} type with a MouseArea type to display a help button
|
||||
that the players can tap to view an image that contains instructions for
|
||||
playing the game:
|
||||
|
||||
\skipuntil endGame
|
||||
\skipto Image
|
||||
\printuntil bottomMargin
|
||||
\printuntil }
|
||||
|
||||
We declare the \c goAway() private function to disable the mouse area and
|
||||
make the image fully transparent and a \c comeBack() function to enable the
|
||||
mouse area and make the button fully opaque. We use a \l {Behavior} type on
|
||||
the \c opacity property to apply the default number animation when the value
|
||||
of \c opacity changes.
|
||||
|
||||
When the players tap the help button, the \c onClicked signal handler is
|
||||
called to hide the help button by setting the \c {helpButton.visible}
|
||||
property to \c false and to show the help image by setting the
|
||||
\c {helpImage.visible} property to \c false.
|
||||
|
||||
\image qtquick-demo-maroon-med-6.jpg
|
||||
|
||||
We use anchoring to position the help button at the bottom center of the
|
||||
game canvas.
|
||||
|
||||
We use another \l{Image} type to to display the help image:
|
||||
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
To hide the help image when the players tap it, the \c onClicked signal
|
||||
handler within the MouseArea type is called to set the \c{helpImage.visible}
|
||||
property to \c true.
|
||||
|
||||
To ensure that the images are placed on top when they are visible, we set
|
||||
a high value for their \c z property.
|
||||
|
||||
The following sections describe how to use timers to add animated objects to
|
||||
the game board and how to create a menu dialog from which the players can
|
||||
add more objects to it.
|
||||
|
||||
\section1 Animating Objects on the Game Board
|
||||
|
||||
We use sprite animation to animate objects on the game board. The Qt Quick
|
||||
\l{Sprite Animations}{sprite engine} is a stochastic state machine combined
|
||||
with the ability to chop up images containing multiple frames of an
|
||||
animation.
|
||||
|
||||
\section2 Spawning Fish
|
||||
|
||||
We use a \l{Timer} element with the \c tick() function in GameCanvas.qml to
|
||||
spawn mobs of fish in waves at an increasing rate, starting at 16
|
||||
milliseconds:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameCanvas.qml
|
||||
\skipto Timer
|
||||
\printuntil }
|
||||
|
||||
We use the MobBase custom type that is defined in MobBase.qml to
|
||||
animate mobs of fish that swim inside bubbles. We use an \l{Item} type with
|
||||
custom properties and private functions to create the fish and the bubbles
|
||||
and to define the actions that can be applied to them:
|
||||
|
||||
\quotefromfile demos/maroon/content/mobs/MobBase.qml
|
||||
\skipto Item
|
||||
\printuntil }
|
||||
\dots
|
||||
|
||||
We use a SpriteSequence type to animate the fish:
|
||||
|
||||
\skipto SpriteSequence
|
||||
\printuntil goalSprite
|
||||
|
||||
The SpriteSequence type renders and controls a list of animations
|
||||
defined by \l{Sprite} types:
|
||||
|
||||
\skipto Sprite {
|
||||
\printuntil name: "right"
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
In the \c fishSprite sprite sequence, each sprite defines one frame within
|
||||
the mob-idle.png file, which shows a fish facing right, front, and left:
|
||||
|
||||
\image ../../content/gfx/mob-idle.png
|
||||
|
||||
We use the \c frameWidth, \c frameHeight, and \c frameX properties to
|
||||
determine that the first 64x64-pixel square of the image is framed in the
|
||||
\c "left" sprite, the second in the \c "front" sprite, and the third in the
|
||||
\c "right" sprite. For each sprite, the \c frameCount property is set to
|
||||
\c 1 to specify that the sprite contains one frame.
|
||||
|
||||
We use the \c frameDuration and \c frameDurationVariation properties to
|
||||
specify that the duration of an animation can vary from \c 400 to \c 1200
|
||||
milliseconds.
|
||||
|
||||
The \c to property specifies that the sprites have weighted transitions to
|
||||
other sprites. The \c "left" and \c "right" sprites always transfer to the
|
||||
\c "front" sprite. When the \c "front" animation finishes, the sprite engine
|
||||
chooses \c "left" or \c "right" randomly, but at roughly equal proportions,
|
||||
because they both have the weight \c 1.
|
||||
|
||||
When the fish are set free, we want them to swim away in the direction they
|
||||
are facing until they get off the screen. If they were facing front, we use
|
||||
the \c jumpTo method with the JavaScript \c {Math.random()} method in the
|
||||
\c die() private function to randomly jump to the \c "left" or \c "right"
|
||||
sprite:
|
||||
|
||||
\quotefromfile demos/maroon/content/mobs/MobBase.qml
|
||||
\skipto die()
|
||||
\printuntil }
|
||||
|
||||
We then use the \c start() function to run a NumberAnimation that applies a
|
||||
number animation to the x value from its current value to \c -360 or \c 360,
|
||||
depending on whether the \c goingLeft custom property is \c true, in 300
|
||||
milliseconds:
|
||||
|
||||
\skipto NumberAnimation
|
||||
\printuntil }
|
||||
|
||||
\section2 Bursting Bubbles
|
||||
|
||||
We use another SpriteSequence to animate the bubbles so that they
|
||||
become smaller and finally burst when they are attacked by a shooter or
|
||||
a melee. For this effect, we set the value of the \c scale property to
|
||||
decrease by \c 0.2 each time the custom \c hp property changes:
|
||||
|
||||
\skipto SpriteSequence
|
||||
\printuntil goalSprite
|
||||
|
||||
We use a \l{Behavior} type to apply a NumberAnimation when the value of
|
||||
\c scale changes. We use the \c{Easing.OutBack} easing type for a back
|
||||
(overshooting cubic function: (s+1)*t^3 - s*t^2) easing out curve that
|
||||
decelerates the motion to zero velocity in 150 milliseconds:
|
||||
|
||||
\skipto Behavior
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
The SpriteSequence consist of two sprites that display different images. The
|
||||
first sprite, \c "big", uses the catch.png image to display an empty bubble:
|
||||
|
||||
\skipto Sprite
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We set the \c to property to \c "burst" with the weight \c 0 to make the
|
||||
second sprite, \c "burst", a valid goal for the \c jumpTo method that we use
|
||||
in the \c die() private function to jump directly to the \c "burst" sprite
|
||||
without playing the first sprite.
|
||||
|
||||
In the \c "burst" sprite, we set the \c frameCount property to \c 3 and the
|
||||
\c frameX property to \c 64 to specify that the animation starts at pixel
|
||||
location 64 and loads each frame for the duration of 200 milliseconds.
|
||||
|
||||
\skipto Sprite
|
||||
\printuntil }
|
||||
|
||||
Within the SpriteSequence, we use SequentialAnimation with NumberAnimation
|
||||
to animate the transitions between the frames. To create a pulsating effect
|
||||
on the bubbles, we apply a sequential animation on the \c width property
|
||||
with two number animations to first increase the bubble width from
|
||||
\c{* 1} to \c{* 1.1} over 800 milliseconds and then bring it back over 1
|
||||
second:
|
||||
|
||||
\skipto SequentialAnimation
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
Similarly, we increase the bubble height from \c{* 1} to \c{* 1.15} over
|
||||
1200 milliseconds and then bring it back over 1 second:
|
||||
|
||||
\skipto SequentialAnimation
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We use yet another SpriteSequence to display the effect of squid ink on the
|
||||
bubbles. For more examples of using sprite sequences, see the QML files in
|
||||
the \c towers directory.
|
||||
|
||||
\section1 Adding Dialogs
|
||||
|
||||
\image qtquick-demo-maroon-med-5.jpg
|
||||
|
||||
In GameCanvas.qml, we use an \l{Image} type with some custom properties to
|
||||
create a menu where the players can buy tower objects:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameCanvas.qml
|
||||
\skipto Image
|
||||
\printuntil towerExists
|
||||
|
||||
We set the \c visible property to \c false to hide the menu by default. The
|
||||
\c z property is set to 1500 to ensure that the menu is displayed in front
|
||||
of all other items when it is visible.
|
||||
|
||||
We use a MouseArea type to open or close the menu when players tap on the
|
||||
canvas:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameCanvas.qml
|
||||
\skipto MouseArea
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We set the \c anchors.fill property to \c parent to allow the players to tap
|
||||
anywhere on the game canvas. We use a condition in the \c onClicked
|
||||
signal handler to call the \c {finish()} function if the menu is visible
|
||||
and the \c {open()} function otherwise.
|
||||
|
||||
The \c {finish()} function hides the menu by setting the \c shown custom
|
||||
property to \c false:
|
||||
|
||||
\skipto finish
|
||||
\printuntil }
|
||||
|
||||
The \c {open()} function displays the menu at the x and y position of the
|
||||
mouse pointer:
|
||||
|
||||
\printuntil }
|
||||
|
||||
If \c gameRunning is \c true, we call the JavaScript \c row() function to
|
||||
calculate the value of the \c targetRow custom property and the \c col()
|
||||
function to calculate the value of the \c targetCol custom property. If
|
||||
the value of \c targetRow equals \c 0, the y position is set to one square
|
||||
above the mouse pointer. Otherwise, it is set to one square below the mouse
|
||||
pointer.
|
||||
|
||||
We use the \c towerIdx() function to set the value of the \c towerExists
|
||||
custom property.
|
||||
|
||||
We set the \c shown custom property to \c true to show the menu and call the
|
||||
\c {helpButton.goAway()} function to hide the help button when the menu
|
||||
opens.
|
||||
|
||||
We use states and transitions to display the menu when the \c shown
|
||||
property is \c true and the \c gameOver property is \c false:
|
||||
|
||||
\printuntil OutElastic
|
||||
\printuntil }
|
||||
|
||||
To set the visibility of the menu to \c "visible" without animating the
|
||||
property change, we use a PropertyAction type. We do want to animate the
|
||||
changes in opacity and scale, though, so we use number animation to
|
||||
animate the value of the \c scale property from \c 0.9 to \c 1 and the
|
||||
value of \c opacity property from \c 0.7 to \c 1, over 500 milliseconds.
|
||||
We use the \c {Easing.outElastic} easing type for an elastic (exponentially
|
||||
decaying sine wave) function easing curve that decelerates from zero
|
||||
velocity.
|
||||
|
||||
To construct the menu, we use a BuildButton custom type that is defined in
|
||||
BuildButton.qml. In GameCanvas.qml, we create one build button for each
|
||||
tower object that the players can buy and position them in a \l{Row} layout
|
||||
in front of the menu background image, dialog.png:
|
||||
|
||||
\printuntil dialog-factory.png
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
For each build button, we set the values of \c towerType and \c index custom
|
||||
properties that we define in BuildButton.qml.
|
||||
|
||||
We use the \c canBuild custom property to prevent players from adding tower
|
||||
objects in locations where tower objects already exist.
|
||||
|
||||
We use the \c source property to display the image for the tower type.
|
||||
|
||||
The \c onClicked signal handler is called to execute the \c finish()
|
||||
function that closes the menu when the players tap an enabled build button.
|
||||
|
||||
Build buttons are enabled when the players have enough coins to buy the
|
||||
tower objects. We use an \l {Image} type in BuildButton.qml to display
|
||||
images on the buttons:
|
||||
|
||||
\quotefromfile demos/maroon/content/BuildButton.qml
|
||||
\skipto Image
|
||||
\printuntil }
|
||||
|
||||
We use the \c opacity property to make the buttons appear enabled. If
|
||||
\c canBuild is \c true and the value of the \c gameCanvas.coins property
|
||||
is larger than or equal to the cost of a tower object, the images are fully
|
||||
opaque, otherwise their opacity is set to \c 0.4.
|
||||
|
||||
We use a \l{Text} type to display the cost of each tower item, as specified
|
||||
by the the \c towerData variable, depending on \c towerType:
|
||||
|
||||
\skipto Text
|
||||
\printuntil }
|
||||
|
||||
To display a pointer on the screen at the position where the tower object
|
||||
will be added, we use the \l {Image} type. We use the \c visible property
|
||||
to determine whether the dialog-pointer.png image should be positioned below
|
||||
or above the menu. When the value of the \c col property equals the \c index
|
||||
and the value or the \c row property is not \c 0, we anchor the image to the
|
||||
bottom of its parent, BuildButton.
|
||||
|
||||
When the value or the \c row property is \c 0, we anchor the image to the
|
||||
top of BuildButton to position the pointer above the menu and use the
|
||||
\c rotation property to rotate it by 180 degrees, so that it points upwards:
|
||||
|
||||
\skipto Image
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
\section1 Keeping Track of Game Statistics
|
||||
|
||||
To keep track of the game statistics, we use the InfoBar custom type (that
|
||||
is defined in InfoBar.qml) in maroon.qml:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto InfoBar
|
||||
\printuntil }
|
||||
|
||||
We use the \c {anchors.bottom} and \c {anchors.bottomMargin} properties to
|
||||
position the info bar at 6 points from the top of the game canvas. We bind
|
||||
the \c width property of the info bar to that of its parent.
|
||||
|
||||
In InfoBar.qml, we use an \l{Item} type to create the info bar. Within it,
|
||||
we use a \l{Row} layout type to display the number of lives the players have
|
||||
left, the number of fish that have been saved, and the amount of coins that
|
||||
are available for use.
|
||||
|
||||
We use the \c anchors property to position the rows in relationship to their
|
||||
parent and to each other. In the first \l{Row} object, we use the
|
||||
\c {anchors.left} and \c {anchors.leftMargin} properties to position the
|
||||
heart icons at 10 points from the left border of the parent item:
|
||||
|
||||
\quotefromfile demos/maroon/content/InfoBar.qml
|
||||
\skipto Item
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We use a \l{Repeater} type with a \c model and a \c delegate to display as
|
||||
many hearts as the players have lives left. We use the \c spacing property
|
||||
to leave 5 pixels between the displayed icons.
|
||||
|
||||
In the second \l{Row} object, we use the \c {anchors.right} and
|
||||
\c {anchors.rightMargin} properties to position the number of fish saved at
|
||||
20 points left of the third \l{Row} object that displays the number of coins
|
||||
available (and has the id \c points):
|
||||
|
||||
\skipto Row
|
||||
\printuntil /^\}/
|
||||
|
||||
In these objects, we set spacing to 5 pixels to separate the icons from the
|
||||
numbers that we display by using a \l{Text} type.
|
||||
|
||||
In GameCanvas.qml, we define custom properties to hold the game statistics:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameCanvas.qml
|
||||
\skipto score
|
||||
\printuntil lives
|
||||
|
||||
We declare the \c freshState() function to set the initial game statistics
|
||||
when a new game starts:
|
||||
|
||||
\skipto freshState()
|
||||
\printuntil }
|
||||
|
||||
We use the \c {Logic.gameState.score} variable in the \c die() function
|
||||
that we declare in MobBase.qml to increase the score by one when the players
|
||||
set a fish free:
|
||||
|
||||
\quotefromfile demos/maroon/content/mobs/MobBase.qml
|
||||
\skipto score
|
||||
\printuntil ;
|
||||
|
||||
\section1 Managing Game States
|
||||
|
||||
In maroon.qml, we use a \l{State} type and JavaScript to switch between
|
||||
screens according to the game state. The logic.js file contains definitions
|
||||
for the functions. To use the functions in a QML file, we import logic.js as
|
||||
the \c Logic namespace in that file:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto logic.js
|
||||
\printuntil Logic
|
||||
|
||||
The base state displays the new game screen when the application starts.
|
||||
In addition, we call the Component.onCompleted signal handler to initialize
|
||||
a new game:
|
||||
|
||||
\skipto newGameState
|
||||
\printuntil ;
|
||||
|
||||
In NewGameScreen.qml we use the \c onClicked signal handler to emit the
|
||||
\c startButtonClicked() signal when the players tap the New Game button:
|
||||
|
||||
\quotefromfile demos/maroon/content/NewGameScreen.qml
|
||||
\skipto to: 150
|
||||
\skipto Image
|
||||
\printuntil }
|
||||
|
||||
In maroon.qml, we use the \c onStartButtonClicked signal handler to set the
|
||||
\c passedSplash property of the \c root item to \c true:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto NewGameScreen
|
||||
\printuntil }
|
||||
|
||||
We then use the \c passedSplash property in the \c when property of the
|
||||
\c gameOn state to trigger the \c gameStarter timer:
|
||||
|
||||
\skipto State {
|
||||
\printuntil gameStarter
|
||||
\printuntil }
|
||||
|
||||
We also switch to the \c "gameOn" state and move to the y position
|
||||
\c {-(height - 960)} to display the game canvas.
|
||||
|
||||
In the \c gameStarter \l{Timer} object we use the \c onTriggered signal
|
||||
handler to call the \c startGame() function that starts a new game:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto property int
|
||||
\skipto Timer
|
||||
\printuntil }
|
||||
|
||||
The game continues until \c gameState.gameOver is set to \c true and
|
||||
\c gameState.gameRunning is set to \c false by calling the \c endGame()
|
||||
function when the value of the \c gameState.lives property becomes less
|
||||
than or equal to \c 0.
|
||||
|
||||
In GameOverScreen.qml, we use a MouseArea type and an \c onClicked signal
|
||||
handler within an \l{Image} type to return to the game canvas when the
|
||||
players tap the New Game button:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameOverScreen.qml
|
||||
\skipto opacity: 0.5
|
||||
\skipto Image
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
The \c onClicked signal handler triggers a state change in maroon.qml to
|
||||
display the game canvas:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.qml
|
||||
\skipto target: gameStarter
|
||||
\skipto State
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
\section1 Playing Sound Effects
|
||||
|
||||
The app can play sound effects if the \l{Qt Multimedia} module is installed.
|
||||
In the SoundEffect.qml file, we proxy a SoundEffect type:
|
||||
|
||||
\quotefromfile demos/maroon/content/SoundEffect.qml
|
||||
\skipto Item
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
We add the \c qtHaveModule() qmake command to the app .pro file, maroon.pro,
|
||||
to check whether the \l{Qt Multimedia} module is present:
|
||||
|
||||
\quotefromfile demos/maroon/maroon.pro
|
||||
\skipto QT
|
||||
\printuntil multimedia
|
||||
|
||||
In each QML file that defines a custom type used on the game canvas, we
|
||||
use a SoundEffect type to specify the audio file to play for that type
|
||||
of objects. For example, in Bomb.qml, we specify the sound that a bomb
|
||||
makes when it explodes:
|
||||
|
||||
\quotefromfile demos/maroon/content/towers/Bomb.qml
|
||||
\skipto SoundEffect
|
||||
\printuntil }
|
||||
|
||||
To play the sound effect when a bomb explodes, we call the \c sound.play()
|
||||
function that we declare as a member of the private \c fire() function
|
||||
within the TowerBase custom type:
|
||||
|
||||
\quotefromfile demos/maroon/content/towers/Bomb.qml
|
||||
\skipto fire()
|
||||
\printuntil }
|
||||
|
||||
For more examples of playing sound effects, see the QML files in the
|
||||
\c towers directory and MobBase.qml.
|
||||
|
||||
\section1 Adding Keyboard Shortcuts
|
||||
|
||||
This is a touch example, so you should not really need to handle key
|
||||
presses. However, we do not want you to have to spend more time playing the
|
||||
game than you want to while testing it, so we use the \c {Keys.onPressed}
|
||||
signal handler to specify keyboard shortcuts. You can press Shift+Up to
|
||||
increment the values of the \c coins property to add coins, Shift+Left to
|
||||
increment the value of \c lives, Shift+Down to increment the value of the
|
||||
\c waveProgress property to spawn mobs of fish faster, and Shift+Right to
|
||||
call the \c endGame() function to quit the game:
|
||||
|
||||
\quotefromfile demos/maroon/content/GameCanvas.qml
|
||||
\skipto Keys
|
||||
\printuntil }
|
||||
|
||||
\section1 Packaging Resources for Deployment
|
||||
|
||||
To be able to run the app on mobile devices, we package all QML, JavaScript,
|
||||
image, and sound files into a Qt resource file (.qrc). For more information,
|
||||
see \l{The Qt Resource System}.
|
||||
|
||||
\sa {QML Applications}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue