1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
..
---------------------------------------------------------------------------
Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
All rights reserved.
This work, unless otherwise expressly stated, is licensed under a
Creative Commons Attribution-ShareAlike 2.5.
The full license document is available from
http://creativecommons.org/licenses/by-sa/2.5/legalcode .
---------------------------------------------------------------------------
Moving targets
==============
In our `SpaceBurger` game, the flying hamburger* should hit *onion rings* moving toward the player as targets to increase the player's score. In this chapter, we will see how to implement the *onion rings*.
Onion Rings
-----------
Every onion ring* will be rendered using a `quad` with a semi transparent texture. To create the `quad`, we'll be using the `Qt3D` `Shapes` module, which comes with many predefined shapes such as `quads`, `cylinders`, `teapots` etc.
So first, we create a new `Target.qml` file to implement the onion ring component* which consist of a `Quad` element:
.. code-block:: js
.. code-block:: js
//Target.qml
import QtQuick 2.0
import Qt3D 1.0
import Qt3D.Shapes 1.0
Quad {
id: root
}
The `Quad` element is lying on the `(x,z)` plane by default. However, to face the camera, we need to apply a pretransform as follows:
.. code-block:: js
//Target.qml
...
Quad {
id: root
pretransform: [
Rotation3D { axis: "1,0,0"; angle: 90}
]
}
We also want to apply a semi transparent texture onto the `quad` where only the onion ring* part of the texture is visible. That means, however, that we need an image format that supports transparency. `PNG` format is a convenient choice.
Furthermore, we want to have some transparency on the non transparent parts of the onion ring*. For that we add a :qt5:`Material <qml-material.html>` with a `diffuseColor` that has an `alpha` value of `0.9` so that *onion ring* is slightly transparent. We also want to have the *onion ring* glowing a bit, so we add a red `emittedLight`:
.. code-block:: js
//Target.qml
...
Quad {
...
effect: Effect {
blending: true
material: Material {
textureUrl: "onion.png"
emittedLight: Qt.rgba(1,0.8,0.8,1)
diffuseColor: Qt.rgba(1,1,1,0.9)
}
}
...
}
Since we are using blending for the transparent objects, we have to consider few things: First of all the blending property in the `Effect` has to be set. This will also override the viewport specific setting for `alpha` blending. When using blending, items have to be painted from back to front. This means that items which are farther away from the viewer have to be painted first, which requires to sort the items. Fortunately, `Qt3D` does this for us automatically if we set the `sortChildren` property to `BackToFront` in the parent `Item3D` element.
.. code-block:: js
//game.qml
...
Item3D {
id: level
sortChildren: Item3D.BackToFront
...
}
...
.. note:: `BackToFront` sorting works only for one hierarchy level. This means only direct children of an `Item3D` are sorted and not the children's children.
Once a `Target` is created, it should immediately start moving toward the player. We can achieve this by a adding a `NumberAnimation` on the `z` property of the `Quad`.
.. code-block:: js
//Target.qml
...
Quad{
...
NumberAnimation on z{
running: true
duration: 10000
from: 200
to: -30
}
...
}
You can now test the `Target` component by manually adding it to the level. It should then be created in the distance and fly towards the player. However, later we should create `Target` objects dynamically.
.. code-block:: js
//game.qml
...
Item3D {
id: level
...
Target { }
...
}
...
.. image:: img/target.png
:scale: 60%
:align: center
Collision-detection
-------------------
Collision-detection is not yet supported by `Qt3D` and it is not possible to get a bounding box of an `Item3D`. But still, we can implement a simple collision detection on our own.
A collision test is only performed between two objects (i.e. in our game, a collision only occurs between the onion ring* and the *hamburger* and between the weapon fire and the enemy or player). But since we will also be using collision detection for other items, we will create a new component in a `BasicGameItem.qml` file, which implements the collision detection. This component will be used as a parent item for all the components that need to implement a collision detection.
To archieve the detection we will proceed as follows:
A target is specified for which the collision test is performed.
The target element has to define a `radius` property that specifies the size of the object.
The `BasicGameItem` should define a `radius` that specifies the size of the item.
Every time a `positionChanged` signal is emitted, a test for collision takes place.
If a collision is detected, a `collisionDetected` signal is emitted and `BasicGameItem` is destroyed afterwards.
And here is how our code looks like:
.. code-block:: js
//BasicGameItem.qml
import QtQuick 2.0
import Qt3D 1.0
Item3D {
id: gameItem
signal collisionDetected(variant object)
property variant collisionTarget: 0
property real radius: 0.5
//Test for a collision between the item and the target
function testCollision(){
if (Math.pow(x-collisionTarget.x,2)+Math.pow(y-collisionTarget.y,2)
+ Math.pow(z-collisionTarget.z,2)
< Math.pow(radius+collisionTarget.radius,2)) {
return true;
}
return false;
}
onPositionChanged: {
if (collisionTarget!=0) {
if (testCollision()) {
collisionDetected(gameItem)
gamenItem.destroy()
}
}
}
}
Now, the `Target.qml` file will look like this:
.. code-block:: js
// Target.qml
BasicGameItem {
id: root
Quad {
pretransform: [
Rotation3D { axis: "1,0,0"; angle: 90}
]
effect: Effect {
blending: true
material: Material {
textureUrl: "onion.png"
emittedLight: Qt.rgba(1,0.8,0.8,1)
diffuseColor: Qt.rgba(1,1,1,0.9)
}
}
}
NumberAnimation on z{
running: true
duration: 10000
from: 200
to: -30
onRunningChanged: {
if (running == false)
root.destroy()
}
}
}
Make sure you use the `NumberAnimation` on the `BasicGameItem` and not on the `Quad`. Otherwise the detection will fail.
The collision target of our `Target` component will be the `Player` object. So we have to define a `radius` property for in the `Player` component.
.. code-block:: js
//Player.qml
Item3D {
...
property real radius: 1.5
...
}
Dynamic Object Creation
-----------------------
As explained above, the onion ring* targets need to be created dynamically.
For that we will use a timer in `Gamelogic.qml` to create new target every 4 seconds that flies towards the player.
To create new `Target` objects, we need first to load the `Target` component. Then, we can create an instance of our `Target` component using the `createObject` method. Since we want to reuse the component several times, we will load it when starting the application in `GameLogig.qml`.
.. Note:: If the component is loaded over the network, we first need to wait for the component to be ready before calling `createObject`
First, we define two properties in `game.qml` to store the `score` and to count the number of targets:
.. code-block:: js
//game.qml
...
property int score: 0
property int targetCount: 0
...
Then we implement the target timer in the `GameLogic.qml`.
.. code-block:: js
//GameLogic.qml
...
property variant targetComponent: Qt.createComponent("Target.qml");
....
//Timer creates targets in a certain interval
Timer {
id: targetTimer
interval: 4000
repeat: true
running: true
onTriggered: {
targetCount++
var object = targetComponent.createObject(level,
{"position.x": (Math.random()-0.5) *8,
"position.y": (Math.random()-0.5) *6,
"scale": 3-0.2*targetCount, "collisionTarget": player})
object.collisionDetected.connect(targetCollision)
}
}
Once the object is created, we connect the `collisionDetected` signal to a function called `targetCollision` where the `score` property defined earlier is incremented by one.
.. code-block:: js
//GameLogic.qml
...
Item{
function targetCollision(sender) {
score++;
}
...
}
.. rubric:: What's Next?
Next we will see how to use `States` to handle the flow of our game.
|