PDA

View Full Version : An unusual effect of a component over another in a QML program



franky
5th February 2019, 11:52
Hi all,

I tested the QML program below on an Android device (a tablet running Android 4.4.2). The problem is that, when rackets are not moving the ball moves smoothly (and it’s OK), but when I move either racket, it affects the speed of the ball, strangely, and reduces ball's speed! These two must be independent in terms of movement and speed, but in effect this issue takes place. I don’t know why.

Will you if possible test this code on your Android device too, to see if the problem exits there as well? If yes, what could be the source of the issue and how to remedy that, please?

main.qml:


import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5

Window {
id: window
visible: true
width: 1000; height: 800
color: "gray"

// The components
// --------------

Rectangle {
id: table
anchors.fill: parent
anchors.margins: 10
y: 10
anchors.horizontalCenter: parent.horizontalCenter
color: "royalblue"
}
Racket {
id: blackRacket
anchors.left: table.left
anchors.leftMargin: width * 2
y: table.height / 2
color: "black"
}
Racket {
id: redRacket
anchors.right: table.right
anchors.rightMargin: width * 2
y: table.height / 2
color: "red"
}
Ball {
id: ball
x: 150
y: 150
}
}


Ball.qml:


import QtQuick 2.12

Rectangle {
width: 18; height: 18
color: "white"
radius: width/2

Timer { // This timer's job is merely moving the ball
id: timer
interval: 22; repeat: true; running: true

onTriggered: {
parent.x += 0.88
parent.y += 0.88
}
}
}

Racket.qml:


import QtQuick 2.12

Rectangle {
id: root
width: 15; height: 65

MouseArea {
anchors.fill: root

drag.target: root
drag.axis: Drag.YAxis
drag.minimumY: table.y
drag.maximumY: table.height - root.height - 10
}
}

anda_skoa
5th February 2019, 17:24
Most likely the timer event processing is delayed by user input processing, so the spacing becomes more than 22ms.

Two ideas:

1) instead of using a timer, calculate the duration needed for the whole movement and use an animation (or a pair of animations) to control the actual movement

2) Instead of assuming mono spaced time events, take the current time stamp in onTriggered, calculate how much time has really elapsed since the last invocation and adjust the position accordingly

Cheers,
_

franky
5th February 2019, 19:13
Thanks so much for your guidance.
Unfortunately I'm very new in QML. If you think the first idea is better, then I go for that. But since I'm novice, if possible, please explain it a little more so that I can understand it comprehensively (say, how to calculate the duration needed for the whole movement?). And then I go for the Docs to learn the stuff needed (Animations) for the program to solve its problem. :)

Added after 57 minutes:

Up to now I got to this point:
I removed the Timer inside the Ball component and tried to use a ParallelAnimation inside main.qml like this:



{
...
...
ParallelAnimation {
property int step: 5
running: true
loops: Animation.Infinite
NumberAnimation {
target: ball
property: "x"
from: ball.x
to: ball.x + step
duration: 22
}
NumberAnimation {
target: ball
property: "y"
from: ball.y
to: ball.y + step
duration: 22
}
step +=5
}
}


Is it a correct way to solve the problem please?

Also "step +=5" doesn't work there unfortunately! :(

anda_skoa
6th February 2019, 07:51
Unfortunately I'm very new in QML. If you think the first idea is better, then I go for that.

That's just one of the options that came to my mind.

Basically if you know where the ball should end up at and how long this should take, then the actual movement could be done with animations.

I guess that is not really the case here, so maybe try the other option.

Something like this



// Saved as Mover.qml
import QtQuick 2.9

Timer {
repeat: true

property Item target

// speed of movement in pixels per millisecond
property real velocityX: 0.0
property real velocityY: 0.0

property date lastUpdate

function startMove() {
lastUpdate = new Date();
restart();
}

onTriggered: {
var now = new Date();
var elapsed = now.getTime() - lastUpdate.getTime();

target.x += velocityX * elapsed;
target.y += velocityY * elapsed;

lastUpdate = now;
}
}


Use like this


Mover {
id: mover

target: ball

velocityX: 0.01
velocityY: 0.01
interval: 20
}

// starting the move
mover.startMove();


Cheers,
_

franky
6th February 2019, 08:01
Thank you very much.
But how about the ParallelAnimation I offered in the prior post, please?
The only thing that we need to change is step, because the animation runs forever. If we can increase the step, the ball moves and probably will have no problem with rackets' movements. Now how to increase/increment step to be each time used in the Animation loop, please?

By the way, you're using a Timer once again there in that new code. I fear this will keep the problem we had in the first place by Timer! :(

anda_skoa
6th February 2019, 10:44
But how about the ParallelAnimation I offered in the prior post, please?

Well, as I said, it is only applicable if you can calculate end positions and duration of a movement.



The only thing that we need to change is step, because the animation runs forever. If we can increase the step, the ball moves and probably will have no problem with rackets' movements. Now how to increase/increment step to be each time used in the Animation loop, please?


Why do you need to increase "step"?
Do you want the movement to accelerate?



By the way, you're using a Timer once again there in that new code. I fear this will keep the problem we had in the first place by Timer! :(

Have you tried it?
"Mover" is only using the Timer to trigger updates, the distance of each update is calculated for the actually passed time

Cheers,
_

franky
6th February 2019, 12:55
Well, as I said, it is only applicable if you can calculate end positions and duration of a movement.

We don't know the finish point. Think of the ball as in a real ping pong game. The players hit the ball by their rackets and the ball goes towards that direction each time hit by the users. So you say, without knowing the final point (to be used for the to: property of the NumberAnimation) can't we use the ParallelAnimation to moves until the ball, for instance, hit lower or upper walls?



Why do you need to increase "step"?
Do you want the movement to accelerate?
We will also need that too. I used interval changes (lower intervals) to accelerate the ball's movement. But as I said, Timers create the problem I said in the first post. I think timers at least in this program are not good for that task (moving the ball) nor does their task make the ball move as smoothly as by animations.


Have you tried it?
"Mover" is only using the Timer to trigger updates, the distance of each update is calculated for the actually passed time

I used the Mover this way in main.qml:


Mover {
id: mover
target: ball

velocityX: 0.08 // I wanted it to move faster
velocityY: 0.08
interval: 4

Component.onCompleted: {
mover.startMove()
}
}

I tested that on PC and it's fine. I must test it on an Android device too.

Added after 1 28 minutes:

I tested it on an Android device. Since what that does the real task of moving the ball still is a Timer, the effect still exists unfortunately. As you said, "the timer event processing is delayed by user input processing".
Your first choice for solving the issue can solve the problem I think:

instead of using a timer, calculate the duration needed for the whole movement and use an animation (or a pair of animations) to control the actual movement

Why don't we stick to animations instead of a Timer to move the ball?

By the way, a more complete version of the program is here (https://github.com/AbbasiBukan/Tennis.git). If possible please test it and what's your opinion on focusing on Animations for the task please?

anda_skoa
7th February 2019, 12:01
So you say, without knowing the final point (to be used for the to: property of the NumberAnimation) can't we use the ParallelAnimation to moves until the ball, for instance, hit lower or upper walls?


Well, you can calculate where the ball will hit the wall, which gives you end coordinates.
And you can calulate the distance between that point and the current position.
And with a known speed you can calculate how long it will take to travel that distance.

You would only need to stop the animation if the ball is intercepted by the racket.




But as I said, Timers create the problem I said in the first post

Well yes, that's why the mover calculates the actual time that has passed between two timer signals and moves more or less than "distance per tick".





Mover {
id: mover
target: ball

velocityX: 0.08 // I wanted it to move faster
velocityY: 0.08
interval: 4

Component.onCompleted: {
mover.startMove()
}
}

I would go for larger intervals. Event at 60 fps each frame will have 16ms time slices.


Since what that does the real task of moving the ball still is a Timer, the effect still exists unfortunately. As you said, "the timer event processing is delayed by user input processing".

Interesting.
The time calculation should have compensated that.
Even if a timer event happens delayed, the time calculation should just result in a larger movement.

Cheers,
_