Inconsistences when testing a QML application on Android devices
-
Hi guys,
After a couple of days working on the following program on Windows, using Qt Creator (qt 5.10) in a Qt Qucik Application - Empty project, by help from @J.HILK, it's now working fine on my Desktop. My intention on using QML is actually building appications for smartphoes, so I Built the program (ctrl + B) and moved the
.apk
file onto two Android devices, versions: 4.3 and 4.4.2 for testing. But there is a problem!Here is the output of the screen on both devices:
Inconsistences in the position of the black racket, the upper side of the table and also the net. When I drag the rackets on those devices by my finger, the ball is also affected! Why those many inconsistences while I'm using QML which is built to program for smartphones, please? Don't those problems belong to the versions of Android on those devices that are not high? What about my SDK?
Please if you use QML for creating applications for mobile phones tell me how you program and how you solve these issues.
Ball.qml
:import QtQuick 2.9 Rectangle { width: 18; height: 18 x: border.width/2; y: border.height/2 color: "white" radius: width/2 property double ran: Math.random() + 0.5 property double xincrement: ran property double yincrement: ran }
Counter.qml
:import QtQuick 2.9 Rectangle { width: 65; height: 60 color: "lightskyblue" property int count: 0 Text { id: text anchors.centerIn: parent text: count.toString() color: "gray" font.bold: true font.pixelSize: 50 } }
Light.qml
:import QtQuick 2.9 Rectangle { width: 70; height: 70 border.width: 8 border.color: "silver" radius: width/2 property bool start: false }
Racket.qml
:import QtQuick 2.9 Rectangle { id: root width: 15; height: 65 property int oldY: y property bool yUwards: false property bool yDwards: false onYChanged: { if(y > oldY) yDwards = true else if (y < oldY) yUwards = true oldY = y } MouseArea { anchors.fill: parent drag.target: root drag.axis: Drag.YAxis drag.minimumY: table.y - 20 drag.maximumY: table.height - 65 } }
WinBox.aml
:import QtQuick 2.9 Text { width: 70; height: 70 text: "WINNER" color: "royalblue" font.bold: true font.pixelSize: 50 visible: false }
main.qml
:import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.1 Window { id: window visible: true visibility: Window.FullScreen title: qsTr("The PingPong Game - A QML Game") color: "gray" Rectangle { id: border x: window.x + window.width/14; y: window.y - 10 width: window.width -300; height: window.height - 120 color: "white" } Rectangle { id: table x: border.x + 10; y: border.y + 10 width: border.width - 20; height: border.height - 20 color: "royalblue" property int count: 1 property bool turn: false property bool lightState: false property double step: 3.0 property int duration: 4 Racket { id: redRacket x: table.width - 40; y: 100 color: "red" } Racket { id: blackRacket x: table.x - 120; y: 100 color: "black" } Ball { id: ball x: table.width/2 y: table.height/2 } Column { spacing: 3 x: border.width/2 Repeater { model: 37 delegate: Rectangle { width: 5 height: 21 color: "white" } } } Counter { id: rightCounter x: table.width / 2 + 140 y: 50 } Counter { id: leftCounter x: table.width / 3 + 70 y: 50 } Light { id: leftLight x: border.width/2 - 280 y: border.height + 10 color: "lime" } Light { id: rightLight x: border.width/2 + 220 y: border.height + 10 color: "silver" } WinBox { id: rightWin x: border.width/2 + 380 y: border.height + 10 } WinBox { id: leftWin x: border.x + 40 y: border.height + 10 } Button { x: border.width/2 - 45 y: border.height + 10 contentItem: Text { id: st_tx horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: qsTr("START") font.bold: true font.pixelSize: 20 color: "green" } background: Rectangle { implicitHeight: 60 implicitWidth: 100 color: "lightGrey" border.width: 8 border.color: "lightBlue" radius: 20 } onClicked: table.startGame() } Button { x: border.width/2 - 150 y: border.height + 10 contentItem: Text { id: pu_tx horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: qsTr("PAUSE") font.bold: true font.pixelSize: 20 color: "green" } background: Rectangle { id: pu_rec implicitHeight: 60 implicitWidth: 100 color: "lightGrey" border.width: 8 border.color: "lightBlue" radius: 20 } onClicked: table.pauseGame() } Button { x: border.width/2 + 60 y: border.height + 10 contentItem: Text { id: sp_tx horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: qsTr("STOP") font.bold: true font.pixelSize: 20 color: "green" } background: Rectangle { implicitHeight: 60 implicitWidth: 100 color: "lightGrey" border.width: 8 border.color: "lightBlue" radius: 20 } onClicked: table.stopGame() } Text { id: ver x: border.x - 130; y: table.height + 70 color: "white" text: "Ver: 1.1" font.family: "Helvetica" font.pointSize: 15 } Text { id: prog x: border.width - 100; y: ver.y + 15 color: "white" text: " Programmed By S.R Abbasi (Tomy)" font.family: "Helvetica" font.pointSize: 10 } function startGame() { st_tx.text = qsTr("START") if (leftWin.visible) leftWin.visible = false if (rightWin.visible) rightWin.visible = false in_timer.restart() } function pauseGame() { st_tx.text = qsTr("RESUME") in_timer.running = false } function stopGame() { st_tx.text = qsTr("START") in_timer.running = false rightCounter.count = 0 leftCounter.count = 0 ball.x = table.width/2 ball.y = table.height/2 table.count = 1 table.turn = false rightLight.color = "silver" leftLight.color = "lime" if(ball.xincrement < 0 ) ball.xincrement *= -1 } Timer { interval: 40; repeat: true; running: true onTriggered: { redRacket.yUwards = false redRacket.yDwards = false blackRacket.yUwards = false blackRacket.yDwards = false } } Timer { id: out_timer interval: 1; repeat: false; running: false function lightChange() { if (table.lightState) { rightLight.color = "silver" leftLight.color= "lime" } else { rightLight.color = "lime" leftLight.color= "silver" } table.lightState = !table.lightState } onTriggered: { ball.xincrement = Math.random() + 0.5 ball.yincrement = Math.random() + 0.5 if (table.count % 5 == 0) { table.turn = !table.turn lightChange() } if (table.turn) ball.xincrement *= -1 ball.x = table.width/2 ball.y = table.height/2 if (ball.yincrement > 1.1) ball.yincrement *= -1 in_timer.restart() table.count++ } } Timer { id: right_return_timer interval: 1; repeat: true; running: false onTriggered: { ball.x = ball.x + (ball.xincrement * table.step) ball.y = ball.y + (ball.yincrement * table.step) if(ball.x + ball.width < redRacket.x) { running = false in_timer.running = true } } } Timer { id: left_return_timer interval: 1; repeat: true; running: false onTriggered: { ball.x = ball.x + (ball.xincrement * table.step) ball.y = ball.y + (ball.yincrement * table.step) if(ball.x > blackRacket.x + blackRacket.width) { running = false in_timer.running = true } } } Timer { id: in_timer interval: table.duration; repeat: true; running: false function hitsRightRacket() { if ((ball.x + ball.width >= redRacket.x && ball.x <= redRacket.x + redRacket.width) && (ball.y + ball.height >= redRacket.y && ball.y <= redRacket.y + redRacket.height)) return true return false } function hitsLeftRacket() { if ((ball.x + ball.width >= blackRacket.x && ball.x < blackRacket.x + blackRacket.width) && (ball.y + ball.height >= blackRacket.y && ball.y <= blackRacket.y + blackRacket.height)) return true return false } function hitsUpperOrLowerWall(){ if (ball.y <= 0 || ball.y + ball.height >= table.height) return true return false } function hitsRightWall() { if (ball.x + ball.width >= table.width) return true else return false } function hitsLeftWall() { if (ball.x <= 0) return true else return false } onTriggered: { if (hitsRightWall()) { in_timer.stop() leftCounter.count++ out_timer.interval = 2000 out_timer.running = true } else if (hitsLeftWall()) { in_timer.stop() rightCounter.count++ out_timer.interval = 2000 out_timer.running = true } else if (hitsRightRacket()) { if (redRacket.yUwards) { if (ball.yincrement == 0) ball.yincrement = -ball.ran else if (ball.yincrement > 0) ball.yincrement *= -1 interval = (table.duration/4) } else if (redRacket.yDwards) { if (ball.yincrement == 0) ball.yincrement = ball.ran else if (ball.yincrement < 0) ball.yincrement *= -1 interval = (table.duration/4) } else { ball.yincrement = 0 interval = (table.duration/2) } ball.xincrement *= -1 running = false right_return_timer.running = true } else if (hitsLeftRacket()) { if(blackRacket.yUwards) { if (ball.yincrement == 0) ball.yincrement = -ball.ran else if (ball.yincrement > 0) ball.yincrement *= -1 interval = (table.duration/4) } else if (blackRacket.yDwards) { if (ball.yincrement == 0) ball.yincrement = ball.ran else if (ball.yincrement < 0) ball.yincrement *= -1 interval = (table.duration/4) } else { ball.yincrement = 0 interval = (table.duration/2) } ball.xincrement *= -1 running = false left_return_timer.running = true } else if (hitsUpperOrLowerWall()) ball.yincrement *= -1 // Move Ball ball.x = ball.x + (ball.xincrement * table.step); ball.y = ball.y + (ball.yincrement * table.step); if(rightCounter.count + leftCounter.count == 21) { in_timer.stop() if (rightCounter.count > leftCounter.count) rightWin.visible = true else leftWin.visible = true } } } } }
-
You are not anchoring your elements but using X / Y positions instead. On every device, there are different pixel density, resolution etc. and while you can hand-tweak the x, y positions to look good for one screen, it will fail on others. Try using more anchors or layouts instead. Or make sure that your x, y bindings are properly abstract - that they will work well on all screen sizes and DPI settings.
The reason why your black racket is in wrong position, just to pick one error, is that you tie it's position to table width - 40 pixels. However, the table position depends on width of the
border
element, which has X set to one 14th of window width. Now that dimension will be different on different screens.To see problems with positioning more clearly, try running your app on desktop in windowed mode and resize it to a few different sizes - you will see which elements go out of their proper places and should be fixed.
-
@sierdzio
Thank you very much.I removed the border rectangle and replaced its id with table then used anchors here:
main.qml
:... Window { id: window visibility: Window.Maximized title: qsTr("The PingPong Game - A QML Game") color: "gray" Rectangle { id: table width: window.width / 1.15; height: window.height / 1.15 x: window.x + 100; y: 10; border.width: 10 border.color: "white" color: "royalblue" property int count: 1 property bool turn: false property bool lightState: false property double step: 3.0 property int duration: 4 Racket { id: blackRacket anchors.left: table.left anchors.leftMargin: width * 2 y: height color: "black" } Racket { id: redRacket anchors.right: table.right anchors.rightMargin: width * 2 y: height color: "red" } Ball { id: ball x: table.width/2 y: table.height/2 } Column { spacing: 3 anchors.centerIn: table anchors.top: table.top Repeater { model: table.height/(blackRacket.height / 2 + 3) delegate: Rectangle { width: 5 height: blackRacket.height / 2 color: "white" } } } ...
Racket.qml
:... drag.maximumY: table.height - height - 10 ...
The output on the android device is fine:
The problem that bothers the players is that, moving the rackets affects the speed of the ball, by the way, dragging the racket is not easy. It's hard.
-
Hi,
- Weekend
- End of year holidays
- Preparing new year's eve celebrations
- Recovering from Christmas celebrations
- Others (too much sweets, kid's toys to construct, not enough presents, death, etc.)
Don't forget that this is a community driven forum. People come here on their own time providing help for free.
They have no obligation of answering at any specific rate.
-
@tomy said in Inconsistences when testing a QML application on Android devices:
The problem that bothers the players is that, moving the rackets affects the speed of the ball, by the way, dragging the racket is not easy. It's hard.
Make the area of the rackets bigger, then, for example by padding them with an invisible Item. Pseudo code:
Item { width: 200 height: 200 MouseArea { anchors.fill: parent } Racket {} // (better integrate that Item padding into Racket component itself, of course) }
-
@sierdzio
Hi,I used this code for
Racket.qml
. It logically should work but in practice, no!
What's its issue please?import QtQuick 2.9 Rectangle { id: root width: 15; height: 65 property int oldY: y property bool yUwards: false property bool yDwards: false onYChanged: { if(y > oldY) yDwards = true else if (y < oldY) yUwards = true oldY = y } Item { x: root.x - 50 y: root.y - 50 width: 100 height: 200 MouseArea { anchors.fill: parent drag.target: parent focus: true hoverEnabled: true pressAndHoldInterval: 0 drag.axis: Drag.YAxis drag.minimumY: table.y drag.maximumY: table.height - height - 10 } } }
-
@tomy said in Inconsistences when testing a QML application on Android devices:
drag.target: parent
You are dragging an invisible item instead of your racket.
-
@tomy said in Inconsistences when testing a QML application on Android devices:
drag.minimumY: table.y drag.maximumY: table.height - height - 10
Is "table" defined and visible by your Racket? No errors or warnings?
-
Is "table" defined and visible by your Racket? No errors or warnings?
table
is the first Rectangle in themain.qml
:import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.1 Window { id: window visibility: Window.Maximized title: qsTr("The PingPong Game - A QML Game") color: "gray" Rectangle { id: table // <<--- width: window.width / 1.15; height: window.height / 1.15 x: window.x + 100; y: 10; border.width: 10 border.color: "white" color: "royalblue" ...
Before widening the MouseArea the code used that and therefore recognized it.
As well as, I changed it, say, to:drag.minimumY: 0 drag.maximumY: 300
just for test. But no change again!
-
Well, the code this way was helpful:
import QtQuick 2.9 Rectangle { id: root width: 15; height: 65 property int oldY: y property bool yUwards: false property bool yDwards: false onYChanged: { if(y > oldY) yDwards = true else if (y < oldY) yUwards = true oldY = y } MouseArea { anchors.fill: parent anchors.margins: -root.height drag.target: root focus: true hoverEnabled: true pressAndHoldInterval: 0 drag.axis: Drag.YAxis drag.minimumY: table.y drag.maximumY: table.height - root.height - 10 } }
Now there is only one problem with the program which is:
- Moving the rackets when playing the game on the Desktop kit (Windows) doesn't affect the speed of ball's movement but when run on an Android device, moving the rackets affects the speed of ball's movement.
If you run the code on an Android device it will be obvious.
- Moving the rackets when playing the game on the Desktop kit (Windows) doesn't affect the speed of ball's movement but when run on an Android device, moving the rackets affects the speed of ball's movement.
-
I admit I havent check your code with much attention but here are a few ideias for you:
In rectangle id: table you have property int duration: 4, and in some timers you use
interval = (table.duration/4) wich mean you are setting a 1 ms timer trigger, wich means you're trying to get 1000fps (a little bit hight :) ).
Perhaps you could use int duration: 16, that will give you 60 fps that should be more than enought.You seem to have one rectangle for table and another for the table border ? Cant you make it simple and use just one rectangle and make use of border.width and border.color properties ?
In the rectangle border you have something like
Rectangle {
id: border
x: window.x + window.width/14; y: window.y - 10
width: window.width -300; height: window.height - 120
since you would want your game to fit in phones and tablet and all have very different resolutions, screen sizes should really use anchorings, something likeRectangle {
id: border
width: window.width * 0.8//just an example using 80% of the screen
height: window.height * 0.7//again just an example using 70% of the screen size
anchors.horizontalCenter: window.horizontalCenter
anchors.top: window top
anchors.topMargin: ...//use some margins if need, you get the pointWhenever I use qml, I tend to use dimensions in milimeters, not in pixel size, because things will get messy different sizes, in phones or tablet with diferente resolution.
For example in Ball. qml you have
Rectangle {
width: 18; height: 18
If you specify the size in milimeters instead of pixels you will get the same aprox. size in all type of screens and that is probably what you want. So how do you specify in size in milimeters ? Read on:Rectangle {
property real calibrationFactor: 1
// you can also use diferent values if want to use diferente sizes for different plataforms
// calibrationFactor: Qt.platform.os === "android" ? 0.4 : 0.6
property real mm: Screen.pixelDensity * calibrationFactor
width: 5 * mm; height: 5 * mmI usually tend to declare mm in main.qml and make general use of mm in setting width and height properties of all items (Button, Balls, ...) and usually have very good results.
In Ball.qml you have:
property double ran: Math.random() + 0.5
property double xincrement: ran
property double yincrement: ran
it seems the velocity will depend on the random ran value ? It doesnt look very acurate to me, make the velocity depend on some random factor.
I would do something like:property real angle: 45//initially angle property double ran: 3 * mm //fixed value //call move from the timer function move() { x += ran*Math.cos(angle) y += ran*Math.sin(angle) /* for example, within the timer, you can check the limits of the ball, and change the angle, to make it bounce back in the walls or rackets or you can set properties here in Ball, with the limits of the field and the position of the backets, and check here for collisions */ }
Happy conding.
-
@johngod
Thank you very much for your suggestions especially the one for moving.
Please take a look at this code. It's a very simplified version of a program disclosing the problem. I also tried your method in it:main.qml
:import QtQuick 2.9 import QtQuick.Window 2.2 Window { visible: true width: 720 height: 620 Rectangle { id: table anchors.fill: parent color: "gray" Rectangle { id: ball property real angle: 45 property real calibrationFactor: 1 property real mm: Screen.pixelDensity * calibrationFactor property double ran: 3 * mm property double xincrement: ran*Math.cos(angle) property double yincrement: ran*Math.sin(angle) width: 15 height: width radius: width / 2 color: "white" x: 300; y: 300 } Racket { id: myRacket x: table.width - 50 y: table.height/3 color: "blue" } Timer { interval: 15; repeat: true; running: true function move() { ball.x += ball.xincrement ball.y += ball.yincrement } onTriggered: { if(ball.x + ball.width >= table.width || ball.x <= 0) ball.xincrement *= -1 move() if(ball.y <= 0 || ball.y + ball.height >= table.height) ball.yincrement *= -1 } } } }
Racket.qml
:import QtQuick 2.9 Rectangle { id: root width: 15; height: 65 MouseArea { anchors.fill: root anchors.margins: -root.height drag.target: root drag.axis: Drag.YAxis drag.minimumY: 0 drag.maximumY: 600 } }
But when I test it on my Android device, the problem still exists! :-(