Offer me a good QML exercise



  • Hi,

    I've read this online book until this section, end of chapter 5. And also practiced almost all examples. Now I want to test myself by an exercise. I haven't started chapter 6 yet.

    According to these 5 chapters, what exercise do you offer me please?
    The exercise should be good but not too hard. :)
    I may also request assistance during solving it if I can't cope with that.

    Tanks.



  • @tomy hi,
    the first thing, that comes to my mind, seeing states and animations covered in this section, would be a basic an intersection similator.

    • Implementing the logic of trafic lights for the most efficient trafficflow
    • Car animation, picture of car, accelerating, decelerating or consistant speed depending on the current state of the traffic light
    • If you're feeling adventurous add a button for the User to insert cars into the system.

    Last part would most likly the most difficult one.

    But, thats just my idea, maybe someone else has a better one :)



  • Hi! If you really have no idea, make a good old pocket calculator. Otherwise I'd say, don't waste your time on throw-away-code but start building something useful.



  • @J.Hilk
    Hi,
    It seems very good. A wide picture! It will be 3D I think.
    (Although I didn't mean only the last chapter, 5, all chapters 1 to 5!) :)

    Is it useful as well, or just a test criterion for me!? I'm ready to get my hands dirty but what about this idea:
    Up to now I've created a very good calculator program and a spreadsheet app using Qt. Although I'm not as familiar with QML as Qt but what's your opinion to plan the infrastructure of a good & useful QML app?
    If you agree, of course, I can fill the blanks of the puzzle (the whole useful program) using what I've learnt until now, and go on and complete the app alongside with reading and progressing with the next chapters of the book, until the program will be completed and useful for users.



  • @J.Hilk
    Do you agree with my idea or is it better to have that intersection?



  • @Wieland
    I just saw that picture. That calculator is more advanced than mine that I've written by Qt. I bet it's very complex using QML. but anyway, since I have another calculator, this time I would like to work on some other thing.



  • @tomy
    hi, sory for the late reply, I was away, friday till monday.

    3d is cool, I myself have jet to really do anything in 3D, so I would not be able to help you much.

    If you have a clear idea of an program in mind, that would be useful for you, than there is no need to go for the intersection.

    The intersection is just an idea for when you're bored. Go with your idea :-)



  • @J.Hilk
    Hi my friend. Thanks for having you.

    What about a simple game. I've seen it but have forgotten its name for the time being but allow me please to describe it.

    There is room in which there is a ball. The gamer has a plate in hand at the bottom of the room. The ball comes down from up, naturally. The gamer moves the plate at the bottom of the room to the position the ball is coming down that way hitting and making it go up again. If the user doesn't move the plate properly and fast, the ball hits the bottom of the room and the game is over. With passing the time and the gamer's not allowing the ball to hit the bottom of the room, the speed of moving of the ball increases and the score of the user also goes upper! The winner between gamers is the one who gets the highest score.
    What do you think of this? :)



  • @tomy you mean Pong ? :-)

    Go for it, always fun to recreate classic games in a fraction of the time the original took.



  • @J.Hilk
    Pong is much cooler but how to play such a two-player game on a smartphone? We need joysticks. So I go for the one-player version of the game.



  • @tomy ok,
    but fyi, Most, if not all, smartphones have a multi touch screen, So it should be possible to play with 2 players.

    alternatively, this could also be a good option for you to get into networking, to play on 2 phones via bluetooth or wlan. ;-)



  • @J.Hilk

    alternatively, this could also be a good option for you to get into networking, to play on 2 phones via bluetooth or wlan. ;-)

    I guess we have two parts, the first is designing the stuff and the last part is networking (via wlan).
    I think it's too advanced for me, but I like it and go for that.



  • @J.Hilk

    I started by a project named PingPong! I've reached that point up to now:

    main.qml:

    import QtQuick 2.8
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 800
        height: 600
        title: qsTr("The Ping Pong game")
    
        Rectangle {
            id: root
            width: 700; height: 500
            border.width: 10
            border.color: "black"
            color: "royalblue"
            property int duration: 1000
    
       Racket {
           x: 630; y: 100
           color: "red"
       }
    
       Racket {
           x: 50; y: 100
           color: "black"
       }
    
       Ball {
           id: ball
           x: root.width/2 - 50
           y: root.height/2
       }
    
       Rectangle {
           x: root.width/2
           y: 10
           width: 10
           height: root.height - 20
           color: "white"
        }
           
      }
    
    }
    

    Ball.qml:

    import QtQuick 2.8
    
    Rectangle {
        width: 20; height: 20
        x: 250; y: 250
        color: "white"
        radius: width/2
    }
    

    Racket.qml:

    import QtQuick 2.8
    
    Rectangle {
        id: root
        width: 15; height: 50
        x: 400; y: 100
        color: "red"
    
        MouseArea {
            anchors.fill: parent
            drag.target: root
            drag.axis: Drag.YAxis
            drag.minimumY: 10
            drag.maximumY: 440
        }
    }
    

    I have this up to now:
    0_1512464003457_Capture.PNG

    The rackets work good with the mouse button but will they work as well with touching on smartphones?

    Now what I need is animating the ball using Easing.Linear. The ball also should start animating when the program starts and recognizes the rackets and the top and down part of the table to reflect.
    what should I read for these please?



  • @tomy
    Hiho,

    Internaly Qt does not differ between mouse clicks and touch events, so if it works fine with the mouse, than it should work fine on the phone display.

    I don't think there is much to read upon for you, you should be able to do that "collision" with what you know.

    E.g
    You monitor the x-position of your ball. If it hits the x-position of one of the rackets, check if the y-positions of ball and racket overlap.
    If yes : Stop old animation of ball and start new one if not, let animation finish and change score counter.



  • @J.Hilk

    For this point I changed the main.qml to this:

    import QtQuick 2.8
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 800
        height: 600
        title: qsTr("The Ping Pong game")
    
        Rectangle {
            id: root
            width: 700; height: 500
            border.width: 10
            border.color: "black"
            color: "royalblue"
            property int duration: 1000
            property real xPos: root.width
            property real yPos: Math.random()
    
       Racket {
           id: redRacket
           x: 630; y: 100
           color: "red"
       }
    
       Racket {
           id: blackRacket
           x: 50; y: 100
           color: "black"
       }
    
       Ball {
           id: ball
           x: root.width/2 - 50
           y: root.height/2
       }
    
       Column {
           spacing: 3
           x: root.width/2
           y: 10
    
             Repeater {
               model: 21
                 delegate:  Rectangle {
                          width: 5
                          height: 20
                          color: "white"
                    }
                 }
         }
    
       ParallelAnimation {
                id: anim1
                   NumberAnimation
                   {
                       target: ball
                       properties: "x"
                       to: root.xPos
                       duration: root.duration
                       easing.type: Easing.Linear
                   }
                   NumberAnimation
                   {
                       target: ball
                       properties: "y"
                       to: root.yPos
                       duration: root.duration
                       easing.type: Easing.Linear
                   }
              }
       function nextPos()
       {
         if(ball.x == redRacket.x && ball.y == redRacket.y)
         {
             ParallelAnimation {
                id: anim2
                   NumberAnimation
                   {
                       target: ball
                       properties: "x"
                       to: -root.xPos
                       duration: root.duration
                       easing.type: Easing.Linear
                   }
                   NumberAnimation
                   {
                       target: ball
                       properties: "y"
                       to: root.yPos
                       duration: root.duration
                       easing.type: Easing.Linear
                   }
              }
          }
       }
       
        MouseArea {
            anchors.fill: ball
            onClicked:
            { anim1.restart(); anim2.restart() }
        }
      }
    }
    

    The net is better and also I tried to make the ball animate on two opposite directions. But I get these errors:

    qrc:/main.qml:76 Expected token ,'
    qrc:/main.qml:78 Expected token `}'

    By the way, the Math.random() function seems not to work!



  • @tomy
    first of let me tell you, as a person with slight ocd-issues, the fact that your indenting is off, is driving me nuts x)

    Anyway:

    you define a function nextPos() and insinde the function-body you try to define a ParallelAnimation item and NumberAnimation items. That can't work.

    only calculate assign values&properties in functions! Not new items.
    In C++ you would, kind of, get way with it, but defenitly not in QML :-)



  • @J.Hilk
    Sorry, I don't know if there is an option that offer automatic indenting on Qt Creator. Do you mean the code looks ugly? :-)

    Anyway,
    The functions Math.random() and nextPos() and if-condition are fixed:

    import QtQuick 2.8
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 800
        height: 600
        title: qsTr("The Ping Pong game")
    
        Rectangle {
            id: root
            width: 700; height: 500
            border.width: 10
            border.color: "black"
            color: "royalblue"
            property int duration: 1000
            property real xPos: root.width
            property real yPos: Math.random() * root.height
    
       Racket {
           id: redRacket
           x: 630; y: 100
           color: "red"
       }
    
       Racket {
           id: blackRacket
           x: 50; y: 100
           color: "black"
       }
    
       Ball {
           id: ball
           x: root.width/2 - 50
           y: root.height/2
       }
    
       Column {
           spacing: 3
           x: root.width/2
           y: 10
              Repeater {
                model: 21
                 delegate: Rectangle {
                          width: 5
                          height: 20
                          color: "white"
                       }
                 }
           }
    
       ParallelAnimation {
          id: anim1
              NumberAnimation {
                       target: ball
                       properties: "x"
                       to: root.xPos
                       duration: root.duration
                       easing.type: Easing.Linear
                   }
              NumberAnimation {
                       target: ball
                       properties: "y"
                       to: root.yPos
                       duration: root.duration
                       easing.type: Easing.Linear
                   }
              }
    
       ParallelAnimation {
          id: anim2
             NumberAnimation {
                 target: ball
                 properties: "x"
                 to: -root.xPos
                 duration: root.duration
                 easing.type: Easing.Linear
             }
             NumberAnimation {
                 target: ball
                 properties: "y"
                 to: root.yPos
                 duration: root.duration
                 easing.type: Easing.Linear
             }
        }
    
       function nextPos() {
    
         if(ball.x >= redRacket.x && ball.x <= redRacket.x + redRacket.height)
         {
           anim1.stop();
           anim2.start();
         }
       }
    
        MouseArea {
            anchors.fill: ball
            onClicked: { anim1.start(); root.nextPos(); }
        }
      }
    }
    

    The x position of the ball is for when it hasn't been animated yet so it won't work in the condition. I need something like:

    if (hit(ball,redRacket))
    

    The second is that in:

    anim1.stop();
    anim2.start();
    

    anim2.start() doesn't let anim1.start() work!



  • @tomy Hiho

    So, I took a look at your code. I'm not quite sure why it's not working like you want it to.
    My guess would be, its quite difficult to click upon the ball when its in the window when its in the Racket.

    So I simpliefied the situation a bit

    I added 2 Properties and a timer to help:
    The timer was needed in my testing, otherwise it would change animation an

    property bool toTheRight: true
    property bool delay: false
    
    Timer{
            id: delayTimer
            interval: 500; running:  false; repeat: false
            onTriggered: delay = false
    }
    onToTheRightChanged: {
            delay = true
            delayTimer.start()
    }
    

    nextPos() was slighly changed, it currently only checks if the ball is past a line:

    function nextPos() {
            if(!delay){
                if(ball.x + ball.width >= redRacket.x || ball.x <= blackRacket.x + blackRacket.width){
                    toRight = !toRight
                    console.log("Ball hits a racket")
                    if(anim1.running)
                        anim1.stop()
                    else
                        anim1.start()
    
                    if(anim2.running)
                        anim2.stop()
                    else
                        anim2.start()
                }
           }
    }
    

    The Ball item calls nextPos():

    Ball {
           id: ball
           x: root.width/2 - 50
           y: root.height/2
           onXChanged: nextPos()
    }
    ...
    MouseArea {
            anchors.fill: ball
            onClicked: { anim1.start(); /*root.nextPos();*/}
        }
    

    This works fine for me, its results in the ball pinging from one wall to the other.
    Next step would be to add the player input conditions.

    hope this helps.



  • @J.Hilk
    Thank you.
    Timer was very useful. I wrote this:

    import QtQuick 2.8
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 800
        height: 600
        title: qsTr("The Ping Pong game")
    
        Rectangle {
            id: table
            width: 700; height: 500
            border.width: 10
            border.color: "black"
            color: "royalblue"
    
       Racket {
           id: redRacket
           x: 630; y: 100
           color: "red"
       }
    
       Racket {
           id: blackRacket
           x: 50; y: 100
           color: "black"
       }
    
       Ball {
           id: ball
           x: table.width/2 - 50
           y: table.height/2
           property double ran: Math.random() + 0.5
           property double xincrement: ran
           property double yincrement: ran
       }
    
       Column {
           spacing: 3
           x: table.width/2
           y: 10
              Repeater {
                model: 21
                 delegate: Rectangle {
                          width: 5
                          height: 20
                          color: "white"
                       }
                 }
           }
    
       Timer {
         interval: 5; repeat: true; running: true
    
            onTriggered: {
                ball.x = ball.x + (ball.xincrement * 2.0);
                ball.y = ball.y + (ball.yincrement * 2.0);
    
                if((ball.x + ball.width  >= redRacket.x)  &&
                  ((ball.y + ball.height >= redRacket.y)  &&
                   (ball.y + ball.height <= redRacket.y + redRacket.height)))
                       ball.xincrement *= (-1);
    
                if((ball.x - ball.width  <= blackRacket.x)  &&
                  ((ball.y + ball.height >= blackRacket.y)  &&
                   (ball.y + ball.height <= blackRacket.y + blackRacket.height)))
                       ball.xincrement *= (-1);
    
                 if(ball.y <= 0 || ball.y + ball.height >= table.height)
                      ball.yincrement *= (-1);
               }
            }
        }
     }
    

    It works fine but the players can't shoot the ball towards a target they want! The rackets are this way only for defending. I need to alter the code so that when the racket has speed upward or downward hitting the ball, the ball goes and gets acceleration on that direction. The real game has that feature.
    How can I do that, please?

    After that I'll go for other missing parts for the game.



  • @tomy
    well, first you'll need to somehow monitor if the racket moved up or down as its last movement.

    I don't see how in the code examples you posted how you manage the "Racket-Movement" .

    but it doesn't matter much, it can be a property of the Racket Item

    #untested Stuff
    e.g:

    Rectangle {
           id: redRacket
           x: 630; y: 100
           color: "red"
    
           property int oldY: 100
           
           property bool upMovement: false
    
          onYChanged:{
              upMovement = y -oldY < 0  ? true : false
              oldY = y
          }
    
           width: 50
           height: 100
       }
    

    and when the Ball hits the racket you decrease the duration, and depending on the up or down movement you change the angle

    function onCollisionWithRacket(){
        root.duration = root.duration -10
        if(redRacket.upMovement){
            //Angle to the Top
        }else{
            //Angle to the bottom
        }
    }
    


  • @J.Hilk
    Thanks.
    I tried to make the work done and here is the code.
    I don't use the prior versions of the code (above) but this one.

    Ball.qml and Racket.qml are as before but main.qml is completely as below.

    As you see I haven't used a function and I don't know if it's needed or not.
    If possible please run that code to see its function. I don't think it's flawless.
    The code also to me is very messy. If it can make the work done using little changes, what to do for removing the problems from that please?

    import QtQuick 2.8
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 800
        height: 600
        title: qsTr("The PingPong Game")
    
        Rectangle {
            id: table
            width: 700; height: 500
            border.width: 10
            border.color: "black"
            color: "royalblue"
    
       Racket {
           id: redRacket
           x: 630; y: 100
           color: "red"
           property int  rOldy: y
           property bool rYmovement: false
           onYChanged: {
               rYmovement = y - rOldy < 0 ? true : false
               rOldy = y
           }
       }
    
       Racket {
           id: blackRacket
           x: 50; y: 100
           color: "black"
           property int  bOldy: y
           property bool bYmovement: false
           onYChanged: {
               bYmovement = y - bOldy < 0 ? true : false
               bOldy = y
           }
       }
    
       Ball {
           id: ball
           x: table.width/2 - 50
           y: table.height/2
           property double ran: Math.random() + 0.5
           property double xincrement: ran
           property double yincrement: ran
       }
    
       Column {
           spacing: 3
           x: table.width/2
           y: 10
              Repeater {
                model: 21
                 delegate: Rectangle {
                          width: 5
                          height: 20
                          color: "white"
                       }
                 }
           }
    
       Timer {
         interval: 15; repeat: true; running: true
    
            onTriggered: {
                ball.x = ball.x + (ball.xincrement * 2.0);
                ball.y = ball.y + (ball.yincrement * 2.0);
    
                if((ball.x + ball.width  >= redRacket.x)  &&
                  ((ball.y + ball.height >= redRacket.y)  &&
                   (ball.y + ball.height <= redRacket.y + redRacket.height)))
                    if(redRacket.rYmovement) {
                         ball.yincrement *= -1
                         ball.xincrement *= -1
                        redRacket.rYmovement = false
                       }
                     else ball.xincrement *= -1
    
                if((ball.x - ball.width  <= blackRacket.x)  &&
                  ((ball.y + ball.height >= blackRacket.y)  &&
                   (ball.y + ball.height <= blackRacket.y + blackRacket.height)))
                    if(blackRacket.bYmovement) {
                         ball.yincrement *= -1
                         ball.xincrement *= -1
                        blackRacket.bYmovement = false
                       }
                     else ball.xincrement *= -1
    
                 if(ball.y <= 0 || ball.y + ball.height >= table.height)
                      ball.yincrement *= -1
               }
           }
        }
     }
    
    


  • @tomy

    to make it a bit cleaner, if you for example moved some stuff to the Ball and Racket qml-files

    //Racket
    import QtQuick 2.8
    
    Rectangle {
        id: root
        width: 15; height: 50
        x: 400; y: 100
        color: "red"
    
        property int oldY: y
        property bool yMovement: false
        onYChanged: {
            yMovement = y - oldY < 0 ? true : false
            oldY = y
        }
    
        MouseArea {
            anchors.fill: parent
            drag.target: root
            drag.axis: Drag.YAxis
            drag.minimumY: 10
            drag.maximumY: 440
        }
    }
    
    //Ball.qml
    import QtQuick 2.8
    
    Rectangle {
        width: 20; height: 20
        x: 250; y: 250
        color: "white"
        radius: width/2
    
        property double ran: Math.random() + 0.5
        property double xincrement: ran
        property double yincrement: ran
    }
    

    Besides that I don't see what the problem is, theres at least 1 bug I run into whilst playing, x) but thats part of coding.

    If you thinks the animation is a but stuttery thats to be expected, you replaced a propertyanimation with a Timer and x/y changes.

    I believe, PropertyAnimation does some internal stuff to make the animation as smooth as possible, a change each refresh frame e.g. Where as with a timer aproeach you are fixed to 5 ms updates and in that you only increase the distance the ball is moved. That gonna look like jumps eventually ;-)



  • @J.Hilk

    I replaced the PropertyAnimation with Timer because I didn't know how to achieve what I have now without Timer but with PropertyAnimation.
    The Timer works fine with smoothness but in this example I'd changed the interval from 2 to 15 to slow the movement of the ball for better testing.

    The game has defects even in this point unfortunately. And it's the feature of targeting.
    The if(redRacket.rYmovement) conditions code don't work as expected. I should think over them so that they work as needed.
    But a question here, does the y coordinate change relate to the "last" movement of the racket before the ball hits it?



  • @tomy

    I changed the timer stuff a bit, to make it more readable, what happens

       Timer {
         interval: 15; repeat: true; running: true
    
         function hitsRightRacket(){
             if(ball.x +ball.width >= redRacket.x  && ball.x < redRacket.x + redRacket.width){
                 if(ball.y >= 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){
                 if(ball.y >= blackRacket.y && ball.y <= blackRacket.y+blackRacket.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
         }
    
         function hitsUpperOrLowerWall(){
             if(ball.y <= 0 || ball.y + ball.height >= table.height)
                  return true
             return false
         }
    
         property bool lastHitLeft: false
         property bool lastHitRight: false
    
            onTriggered: {
                if(hitsRightWall()){
                    console.log("Point Left Side")
                    running = false
                }else if(hitsLeftWall()){
                    console.log("Point Right Side")
                    running = false
                }else if(hitsLeftRacket() && !lastHitLeft){
                    console.log("Hits Black Racket",blackRacket.yMovement)
                    lastHitLeft = true
                    lastHitRight = false
                    if(blackRacket.yMovement){
                        ball.yincrement *= -1
                        ball.xincrement *= -1
                     }else
                        ball.xincrement *= -1
                }else if(hitsRightRacket() && !lastHitRight){
                    console.log("Hits Red Racket",redRacket.yMovement)
                    lastHitLeft = false
                    lastHitRight = true
                    if(redRacket.yMovement) {
                         ball.yincrement *= -1
                         ball.xincrement *= -1
                       }
                     else ball.xincrement *= -1
                }else if(hitsUpperOrLowerWall())
                    ball.yincrement *= -1
    
                //Move Ball
                ball.x = ball.x + (ball.xincrement * 2.0);
                ball.y = ball.y + (ball.yincrement * 2.0);
               }
           }
    

    This showed me that you'll need a better case handleing for ball movement and racket movement,

    • ball moves towards y = 0 and Racket moves towards y = 0
    • ball moves towards y = 0 and Racket moves towards y = table height
    • ball moves towards y = table and Racket moves towards y = 0
    • ball moves towards y = table Racket moves towards y = table height

    currently you're only checking in what direction the racket moved,



  • @J.Hilk
    Hi,
    Thank you for your assistance.

    If we divide the task into part I and II, the first part I think is almost done! Please take a look at the code below and if possible please run the program on your system. It works fine for me.

    In part I, I need two extra components I think: one, a series of buttons for starting, resuming and finishing the game when it's not over yet. And second, some sound for goals and when a player wins the game.
    If we can complete part I, I assume part II will be about networking! :)

    Ball.qml:

    import QtQuick 2.9
    
    Rectangle {
        width: 12; height: 12
        x: 250; y: 250
        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: 40; height: 50
        color: "lightskyblue"
        property int count: 0
    
        Text {
            id: text
            anchors.centerIn: parent
            text: count.toString()
            color: "gray"
            font.bold: true
            font.pixelSize: 30
        }
    }
    

    Light.qml:

    import QtQuick 2.9
    
    Rectangle {
        width: 50; height: 50
        border.width: 5
        border.color: "silver"
        radius: width/2
        property bool start: false
    }
    
    

    Racket.qml:

    import QtQuick 2.9
    
    Rectangle {
        id: root
        width: 12; height: 40
        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.y + table.height - 60
        }
    }
    
    

    WinBox.qml:

    import QtQuick 2.9
    
    Text {
         width: 50; height: 50
         text: "WINNER"
         color: "royalblue"
         font.bold: true
         font.pixelSize: 40
         visible: false
    }
    

    main.qml:

    import QtQuick 2.9
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 820
        height: 620
        title: qsTr("The PingPong Game")
        color: "gray"
    
        Rectangle {
            x: 10; y: 10
            width: 800; height: 500
            color: "white"
        }
    
        Rectangle {
            id: table
            x: 20; y: 20
            width: 780; height: 480
            color: "royalblue"
            property int count: 1
            property bool turn: false
            property bool lightState: false
    
            Racket {
                id: redRacket
                x: table.width - 30; y: 100
                color: "red"
            }
    
            Racket {
                id: blackRacket
                x: table.x; y: 100
                color: "black"
            }
    
            Ball {
                id: ball
                x: table.width/2
                y: table.height/2
            }
    
            Column {
                spacing: 3
                x: table.width/2
                Repeater {
                    model: 21
                    delegate: Rectangle {
                        width: 5
                        height: 20
                        color: "white"
                    }
                }
            }
    
            Counter {
                id: rightCounter
                x: table.width / 2 + 90
                y: 50
            }
    
            Counter {
                id: leftCounter
                x: table.width / 4 + 70
                y: 50
            }
    
            Light {
                id: rightLight
                x: table.width/2 + 80
                y: table.height + 20
                color: "silver"
            }
    
            Light {
                id: leftLight
                x: table.width/2 - 120
                y: table.height + 20
                color: "lime"
            }
    
            WinBox {
                id: rightWin
                x: table.width/2 + 160
                y: table.height + 40
            }
    
            WinBox {
                id: leftWin
                x: table.width/2 - 350
                y: table.height + 40
            }
    
            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: in_timer
                interval: duration; repeat: true; running: true
                property int duration: 2
    
                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
                }
    
                property bool lastHitLeft: false
                property bool lastHitRight: 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
                    }
    
                    ball.x = ball.x + (ball.xincrement * 2.0);
                    ball.y = ball.y + (ball.yincrement * 2.0);
    
                    if ((ball.x + ball.width >= redRacket.x  &&
                         ball.x < redRacket.x + redRacket.width / 3) &&
                            (ball.y + ball.height >= redRacket.y - 10 &&
                             ball.y <= redRacket.y + redRacket.height))
                    {
                        if(redRacket.yUwards) {
                            if(ball.yincrement == 0)
                                ball.yincrement = -ball.ran
                            else if(ball.yincrement > 0)
                                ball.yincrement *= -1
                            interval = duration/2
                        }
                        else if (redRacket.yDwards) {
                            if(ball.yincrement == 0)
                                ball.yincrement = ball.ran
                            else if(ball.yincrement < 0)
                                ball.yincrement *= -1
                            interval = duration/2
                        }
                        else {
                            ball.yincrement = 0
                            interval = duration
                        }
    
                        ball.xincrement *= -1
                    }
    
                    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))
                    {
                        if(blackRacket.yUwards) {
                            if(ball.yincrement == 0)
                                ball.yincrement = -ball.ran
                            else if(ball.yincrement > 0)
                                ball.yincrement *= -1
                            interval = duration/2
                        }
                        else if (blackRacket.yDwards) {
                            if(ball.yincrement == 0)
                                ball.yincrement = ball.ran
                            else if(ball.yincrement < 0)
                                ball.yincrement *= -1
                            interval = duration/2
                        }
                        else {
                            ball.yincrement = 0
                            interval = duration
                        }
    
                        ball.xincrement *= -1
                    }
    
                    if(ball.x <= 0 || ball.x + ball.width >= table.width)
                        ball.xincrement *= (-1);
    
                    if(ball.y <= 0 || ball.y + ball.height >= table.height)
                        ball.yincrement *= -1
    
                    if(rightCounter.count + leftCounter.count == 21) {
                           in_timer.stop()
                        if(rightCounter.count > leftCounter.count)
                            rightWin.visible = true
                        else if (rightCounter.count < leftCounter.count)
                            leftWin.visible = true
                        else {
                            rightWin.visible = true
                            leftWin.visible = true
                        }
                    }
                }            
            }
        }
    }
    
    


  • Looks great! You're defenitly on the right track.

    However I have a bug to report. I managed to catch the ball in the racket. That's the reasdon why I introtuced the 2 properties lastHitLeft and lastHitRight in my previous post. You still have them in your code, but they are not used.
    0_1513750776832_catch.PNG



  • @J.Hilk
    Hi, thanks.

    Well, I laid those factors and ran the code. Unfortunately it made the issue more prominent! The ball passes through the ball obliquely.

    So I think I must return back to the version without them and the issue is subtle! It happens when a racket tries to hit the ball from the top/down moving downwards/upwards and the ball gets stuck in the upper/lower edge of the racket. I think I should work on this.

    Previously I was thinking of making the racket thinner to prevent this bug. But I try to find a more logical way.



  • Hi,

    For a while I was trying to use contains(point p), mapFromItem(...) or mapToItem(...). But unfortunately, no success! So I got back to the prior method, which was using the X and Y coordinates as follows.

    Please run it on your system. It works fine for me. The bug is also solved.
    But there's another bug!

    • Moving the racket 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 racket affects the speed of ball's movement as though their movements have been tied together.

    I also tried to manipulate the MouseArea in the Racket.qml to cope with that, but no change in the result! :(

    Please check it and express your idea how to solve that issue.

    QML files Ball, Counter, Light and Winbox are as before.

    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
            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
        }
    }
    
    
    

    main.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"
            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"
                    }
                }
            }
            
            Counter {
                id: leftCounter
                x: table.width / 2 - height - 100
                y: 50
            }
            Counter {
                id: rightCounter
                x: table.width / 2 + 100
                y: 50
            }
            
            
            Light {
                id: leftLight
                x: table.width/2 - 280
                y: table.height + 10
                color: "lime"
            }
            Light {
                id: rightLight
                x: table.width/2 + 220
                y: table.height + 10
                color: "silver"
            }
            
            WinBox {
                id: rightWin
                x: table.width/2 + 380
                y: table.height + 10
            }
            WinBox {
                id: leftWin
                x: table.x + 40
                y: table.height + 10
            }
            
            Button {
                x: table.width/2 - 45
                y: table.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: table.width/2 - 150
                y: table.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: table.width/2 + 60
                y: table.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 {
                x: window.x; y: table.height + 50
                color: "white"
                text: "Ver: 1.1"
                font.family: "Helvetica"
                font.pointSize: 15
            }
            Text {
                x: table.width - 200; y: table.height + 50
                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
                    }
                }
            }
        }
    }
    
    


  • Hi @tomy , sorry for the late response.

    I had a lengthly break, sickness and holiday - thankfully not overlapping. But I'm back now and will take a look later today. I actually managed not to touch my laptop during that time, that was difficult I'll tell ya.

    Any progress in the last 23 days ?

    Greetings.



  • @J.Hilk
    Hi J.Hilk, welcome back. :)
    I'm very glad that your sickness and holiday haven't had overlapping. :)
    I hope you're very well now. And Happy New Year to you. :)

    I see and it's OK. I'm appreciative for your assistance. :) Thanks.
    Welcome back once again.

    Any progress in the last 23 days ?

    That last version was actually modified many times. :) That is, each time I modified it and edited the code here in that last version making it the most recent one for you when you'll get back.

    The problem was that and I found no remedy so I thought of using a ParallelAnimation as the tool for moving the ball hoping it resolves the issue with the effect of moving the rackets on the ball's movement.

    I'm still working on it and today or tomorrow will test this new version and post it here too. Can it be a good solution to the problem in your point of view please?



  • Well, to cope with that problem I tried Animations in the code the way below.
    Other components are as before and here is the code after the stopGame() function in main.qml:

    ...
    ParallelAnimation {
               id: anim
               NumberAnimation {
                   target: ball
                   properties: "x"
                   to: in_timer.xMove
               }
               NumberAnimation {
                   target: ball
                   properties: "y"
                   to: in_timer.yMove
               }
           }
           
           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: {
                   in_timer.xMove += ball.xincrement * table.step
                   in_timer.yMove += ball.yincrement * table.step
                   anim.restart()
                   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: {
                   in_timer.xMove += ball.xincrement * table.step
                   in_timer.yMove += ball.yincrement * table.step
                   anim.restart()
                   if(ball.x > blackRacket.x + blackRacket.width) {
                       running = false
                       in_timer.running = true
                   }
               }
           }
           Timer {
               id: in_timer
               interval: table.duration; repeat: true; running: false
               property double xMove: ball.x
               property double yMove: ball.y
               
               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)
                       return true
                   else if (ball.y + ball.height >= table.height)
                       return true
                   return false
               }
               function hitsRightWall() {
                   if (ball.x + ball.width >= table.width)
                       return true
                   return false
               }
               function hitsLeftWall() {
                   if (ball.x <= 0) return true
                   else return false
               }
               
               onTriggered: {
                   if (hitsRightWall()) {
                       stop()
                       leftCounter.count++
                       out_timer.interval = 2000
                       out_timer.running = true
                   }
                   else if (hitsLeftWall()) {
                       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 = ball.yincrement > 0 ?
                                           -ball.yincrement: ball.yincrement
                           interval = (table.duration/4)
                       }
                       else if (redRacket.yDwards) {
                           if (ball.yincrement == 0)
                               ball.yincrement = ball.ran
                           else if (ball.yincrement < 0)
                               ball.yincrement = ball.yincrement > 0 ?
                                           -ball.yincrement: ball.yincrement
                           interval = (table.duration/4)
                       }
                       else {
                           ball.yincrement = 0
                           interval = (table.duration/2)
                       }
                       ball.xincrement = ball.xincrement > 0 ?
                                   -ball.xincrement: ball.xincrement
                       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 = ball.yincrement > 0 ?
                                           -ball.yincrement: ball.yincrement
                           interval = (table.duration/4)
                       }
                       else if (blackRacket.yDwards) {
                           if (ball.yincrement == 0)
                               ball.yincrement = ball.ran
                           else if (ball.yincrement < 0)
                               ball.yincrement = ball.yincrement > 0 ?
                                           -ball.yincrement: ball.yincrement
                           interval = (table.duration/4)
                       }
                       else {
                           ball.yincrement = 0
                           interval = (table.duration/2)
                       }
                       ball.xincrement = ball.xincrement > 0 ?
                                   -ball.xincrement: ball.xincrement
                       running = false
                       left_return_timer.running = true
                   }
                   else if (hitsUpperOrLowerWall())
                       ball.yincrement = ball.yincrement > 0 ?
                                   -ball.yincrement: ball.yincrement
                   
                   // Move Ball
                   xMove += (ball.xincrement * table.step);
                   yMove += (ball.yincrement * table.step);
                   anim.restart()
                   
                   if(rightCounter.count + leftCounter.count == 21) {
                       stop()
                       if (rightCounter.count > leftCounter.count)
                           rightWin.visible = true
                       else
                           leftWin.visible = true
                   }
               }
           }
       }
    }
    

    This code also has several problems! One of them is that the ball doesn't stop when it hits the right or left wall. I tired several statements, like:
    stop()
    in_timer.stop()
    running = false
    anim.stop()
    but neither works!



  • hey @tomy ,

    I used the code from your previous post, the one before the last one, and brought it on my iphone.

    For me it works flawlessly, no slowdown at all.

    Take a look at this video:
    Video
    Edit: video link fixed, thanks to @mrjj

    In fact, the animation/movement of the ball is too fast in my opinion.

    Did you compile the release version for your android device? Debug versions are ressource demanding and may result in slow animations on old devices.

    The racket movment invokes a property binding recalculations, that is executed as soon as the event loop returns, I believe.
    The Movement of the ball and checks are all depending on timers, during the property binding induced calculations those timers may queue up, that then results in a slow Ball-Animation.


  • Qt Champions 2016

    Hi
    Video link didnt work for me but i could extract
    https://vimeo.com/251451461



  • @J.Hilk

    For me it works flawlessly, no slowdown at all.
    Take a look at this video:
    Video
    Edit: video link fixed, thanks to @mrjj

    Hallo, Thank you very much.

    In fact, the animation/movement of the ball is too fast in my opinion.

    I think by increasing table.duration it will be solved

    Did you compile the release version for your android device? Debug versions are ressource demanding and may result in slow animations on old devices.

    Well, when testing on the Desktop kit on my Windows, the program has no problem. It's fine. When switching to "Android for armeabi-v7a (GCC 4.9, Qt 5.10.0 for Android armv7)" kit in both Debug and Release modes, I get these two issues in the Issues window which are the same apparently. I don't know if it's related to the problem on Android devices or not.

    :-1: warning: "D:\android-ndk-r10e\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-gcc" is used by qmake, but "D:\android-ndk-r10e\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-gcc.exe" is configured in the kit.
    Please update your kit or choose a mkspec for qmake that matches your target environment better.

    :-1: warning: "D:\android-ndk-r10e\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-g++" is used by qmake, but "D:\android-ndk-r10e\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-g++.exe" is configured in the kit.
    Please update your kit or choose a mkspec for qmake that matches your target environment better.

    The racket movment invokes a property binding recalculations, that is executed as soon as the event loop returns, I believe.
    The Movement of the ball and checks are all depending on timers, during the property binding induced calculations those timers may queue up, that then results in a slow Ball-Animation.

    I'm not sure I can understand it well. Probably it's a too advanced for me. If it can be a lead, is there any solution for that please?



  • @tomy mmh, my Idea would be to marry your QMl-program with a cpp class, that does the heavy stuff.

    Your cpp-class could/should have Q_properties that are bound to ball.x and ball.y and right/leftracket position.

    That class could emit signals when the ball hits something and trigger the new ball position etc.

    Thats a bit more work than I can justify right now, but I'll look into it a bit more during lunch-break ;-)



  • @J.Hilk
    Hi, thank you for your suggestion. I've used C++ for console applications on Visual Studio and also used it on Qt Creator for a few Qt programs. And also have used QML on Qt Creator as in this program. But I've never used C++ in a QML program! So it seems hard for me. But if it's the solution to make the program run correctly on Android devices, OK, I will go for that. But where to start?



  • @tomy

    theres a very basic example in the docu, that is really good to get general understanding of a c++ backend for a qml program:

    Let me link that for you



  • heres an quick example how your Backend could look like:

    //PropertyBase.h
    #ifndef PROPERTYBASE_H
    #define PROPERTYBASE_H
    
    #include <QObject>
    
    class PropertyBase : public QObject
    {
        Q_OBJECT
        
        Q_PROPERTY(double xBall READ xBall WRITE setXBall NOTIFY xBallChanged)
        Q_PROPERTY(double yBall READ yBall WRITE setYBall NOTIFY yBallChanged)
        Q_PROPERTY(double wBall READ wBall WRITE setWBall NOTIFY wBallChanged)
        Q_PROPERTY(double hBall READ hBall WRITE setHBall NOTIFY hBallChanged)
        
        Q_PROPERTY(double xRedRacket READ xRedRacket WRITE setXRedRacket NOTIFY xRedRacketChanged)
        Q_PROPERTY(double yRedRacket READ yRedRacket WRITE setYRedRacket NOTIFY yRedRacketChanged)
        Q_PROPERTY(double wRedRacket READ wRedRacket WRITE setWRedRacket NOTIFY wRedRacketChanged)
        Q_PROPERTY(double hRedRacket READ hRedRacket WRITE setHRedRacket NOTIFY hRedRacketChanged)
        
        Q_PROPERTY(double xBlackRacket READ xBlackRacket WRITE setXBlackRacket NOTIFY xBlackRacketChanged)
        Q_PROPERTY(double yBlackRacket READ yBlackRacket WRITE setYBlackRacket NOTIFY yBlackRacketChanged)
        Q_PROPERTY(double wBlackRacket READ wBlackRacket WRITE setWBlackRacket NOTIFY wBlackRacketChanged)
        Q_PROPERTY(double hBlackRacket READ hBlackRacket WRITE setHBlackRacket NOTIFY hBlackRacketChanged)
        
        Q_PROPERTY(double wScreen READ wScreen WRITE setWScreen NOTIFY wScreenChanged)
        Q_PROPERTY(double hScreen READ hScreen WRITE setHScreen NOTIFY hScreenChanged)
        
        inline double xBall(){return m_xBall;}
        inline double yBall(){return m_yBall;}
        inline double wBall(){return m_wBall;}
        inline double hBall(){return m_hBall;}
        
        inline double xRedRacket(){return m_xrRacket;}
        inline double yRedRacket(){return m_yrRacket;}
        inline double wRedRacket(){return m_wrRacket;}
        inline double hRedRacket(){return m_hrRacket;}
        
        inline double xBlackRacket(){return m_xbRacket;}
        inline double yBlackRacket(){return m_ybRacket;}
        inline double wBlackRacket(){return m_wbRacket;}
        inline double hBlackRacket(){return m_hbRacket;}
        
        inline double wScreen(){return m_wScreen;}
        inline double hScreen(){return m_hScreen;}
        
        void setXBall(const double &);
        void setYBall(const double &);
        void setWBall(const double &);
        void setHBall(const double &);
        
        void setXRedRacket(const double &);
        void setYRedRacket(const double &);
        void setWRedRacket(const double &);
        void setHRedRacket(const double &);
        
        void setXBlackRacket(const double &);
        void setYBlackRacket(const double &);
        void setWBlackRacket(const double &);
        void setHBlackRacket(const double &);
        
        void setWScreen(const double &);
        void setHScreen(const double &);
    public:
        explicit PropertyBase(QObject *parent = nullptr);
    
    signals:
        void xBallChanged();
        void yBallChanged();
        void wBallChanged();
        void hBallChanged();
        
        void xRedRacketChanged();
        void yRedRacketChanged();
        void wRedRacketChanged();
        void hRedRacketChanged();
        
        void xBlackRacketChanged();
        void yBlackRacketChanged();
        void wBlackRacketChanged();
        void hBlackRacketChanged();
        
        void wScreenChanged();
        void hScreenChanged();
    protected:
        double m_xBall;
        double m_yBall;
        double m_wBall;
        double m_hBall;
        
        double m_xrRacket;
        double m_yrRacket;
        double m_wrRacket;
        double m_hrRacket;
        
        double m_xbRacket;
        double m_ybRacket;
        double m_wbRacket;
        double m_hbRacket;
        
        double m_wScreen;
        double m_hScreen;
    };
    
    #endif // PROPERTYBASE_H
    

    //

    //PropertyBase.cpp
    #include "propertybase.h"
    
    
    PropertyBase::PropertyBase(QObject *parent) : QObject(parent)
    {
    
    }
    
    void PropertyBase::setXBall(const double &value)
    {
        if(value != m_xBall){
            m_xBall = value;
            emit xBallChanged();
        }
    }
    
    void PropertyBase::setYBall(const double &value)
    {
        if(value != m_YBall){
            m_yBall = value;
            emit yBallChanged();
        }
    }
    
    void PropertyBase::setWBall(const double &value)
    {
        if(value != m_wBall){
            m_wBall = value;
            emit wBallChanged();
        }
    }
    
    void PropertyBase::setHBall(const double &value)
    {
        if(value != m_hBall){
            m_hBall = value;
            emit hBallChanged();
        }
    }
    
    void PropertyBase::setXRedRacket(const double &value)
    {
        if(value != m_xrRacket){
            m_xrRacket = value;
            emit xRedRacketChanged();
        }
    }
    
    void PropertyBase::setYRedRacket(const double &value)
    {
        if(value != m_yrRacket){
            m_yrRacket = value;
            emit yRedRacketChanged();
        }
    }
    
    void PropertyBase::setWRedRacket(const double &value)
    {
        if(value != m_wrRacket){
            m_wrRacket = value;
            emit wRedRacketChanged();
        }
    }
    
    void PropertyBase::setHRedRacket(const double &value)
    {
        if(value != m_hrRacket){
            m_hrRacket = value;
            emit hRedRacketChanged();
        }
    }
    
    void PropertyBase::setXBlackRacket(const double &value)
    {
        if(value != m_xbRacket){
            m_xbRacket = value;
            emit xBlackRacketChanged();
        }
    }
    
    void PropertyBase::setYBlackRacket(const double &value)
    {
        if(value != m_ybRacket){
            m_ybRacket = value;
            emit yBlackRacketChanged();
        }
    }
    
    void PropertyBase::setWBlackRacket(const double &value)
    {
        if(value != m_wbRacket){
            m_wbRacket = value;
            emit wBlackRacketChanged();
        }
    }
    
    void PropertyBase::setHBlackRacket(const double &value)
    {
        if(value != m_hbRacket){
            m_hbRacket = value;
            emit hBlackRacketChanged();
        }
    }
    
    void PropertyBase::setWScreen(const double &value)
    {
        if(value != m_wScreen){
            m_wScreen = value;
            emit wScreenChanged();
        }
    }
    
    void PropertyBase::setHScreen(const double &value)
    {
        if(value != m_hScreen){
            m_hScreen = value;
            emit hScreenChanged();
        }
    }
    
    //Backend.h
    #ifndef BACKEND_H
    #define BACKEND_H
    
    #include "propertybase.h"
    #include <QTimer>
    
    class BackEnd : public PropertyBase
    {
        Q_OBJECT
        
    public:
        explicit BackEnd(PropertyBase *parent = nullptr);
    
        Q_INVOKABLE void timerStart(){m_BallTimer.start(m_timerInterval);}
        Q_INVOKABLE void timerStop(){m_BallTimer.stop();}
        
        Q_INVOKABLE void setInterval(int & interval){
            m_timerInterval = interval;
            m_BallTimer.setInterval(interval);
        }
        
    signals:
        void rightWallhit();
        void leftWallHit();
        void rightRacketHit();
        void leftRacketHit();
        void upperOrLowerWallHit();
        
        void moveBall();
    
    public slots:
        
    private:
        bool hitsRightRacket();
        bool hitsLeftRacket();
        
        bool hitsUpperOrLowerWall();
        bool hitsRightWall();
        bool hitsLeftWall();
        
    private slots:
        void doCalculation();
        
    protected:
        int m_timerInterval = 10;
        QTimer m_BallTimer;
    };
    
    #endif // BACKEND_H
    
    //Backend.cpp
    #include "backend.h"
    
    BackEnd::BackEnd(PropertyBase *parent) : PropertyBase(parent)
    {
        connect(&m_BallTimer, &QTimer::timeout, this, &BackEnd::doCalculation);
    }
    
    bool BackEnd::hitsRightRacket()
    {
        if( (xBall() + wBall() >= xRedRacket() && xBall() <= xRedRacket() + wRedRacket() ) &&
            (yBall() + hBall() >= yRedRacket() && yBall() <= yRedRacket() + hRedRacket() ))
            return true;
        
        return false;
    }
    
    bool BackEnd::hitsLeftRacket()
    {
        if( (xBall() + wBall() >= xBlackRacket() && xBall() <= xBlackRacket() + wBlackRacket() ) &&
            (yBall() + hBall() >= yBlackRacket() && yBall() <= yBlackRacket() + hBlackRacket() ))
            return true;
        
        return false;
    }
    
    bool BackEnd::hitsUpperOrLowerWall()
    {
        if(yBall() <= 0 || yBall() +hBall() >= hScreen())
            return true;
        
        return false;
    }
    
    bool BackEnd::hitsRightWall()
    {
        if(xBall() + wBall() >= wScreen())
            return true;
        
        return false;
    }
    
    bool BackEnd::hitsLeftWall()
    {
        if(xBall() <= 0)
            return true;
        
        return false;
    }
    
    void BackEnd::doCalculation()
    {
        if(hitsRightWall()){
            timerStop();
            emit rightWallhit();
        }
        
        if(hitsLeftWall()){
            timerStop();
            emit leftWallHit();
        }
        
        if(hitsRightRacket())
            emit rightRacketHit();
        
        if(hitsLeftRacket())
            emit leftRacketHit();
        
        if(hitsUpperOrLowerWall())
            emit upperOrLowerWallHit();
        
        emit moveBall();
    }
    
    
    //main.cpp
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    
    #include "backend.h"
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
        
        qmlRegisterType<BackEnd>("myBackend",1,0,"BackEnd");
    
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    
    
    //main.qml
    Window {
        id: window
        visibility: Window.Maximized
        title: qsTr("The PingPong Game - A QML Game")
        color: "gray"
        
        
        BackEnd{
            id:myBackend
            
            xBall: ball.x
            yBall: ball.y
            wBall: ball.width
            hBall: ball.height
            
            xRedRacket: redRacket.x
            yRedRacket: redRacket.y
            //....
            
            onRightWallhit: {
                leftCounter.count++
                out_timer.interval = 2000
                out_timer.running = true
            }
            onLeftWallHit: {
                rightCounter.count++
                out_timer.interval = 2000
                out_timer.running = true
            }
            onRightRacketHit: {
                if (redRacket.yUwards) {
                    if (ball.yincrement == 0)
                        ball.yincrement = -ball.ran
                    else if (ball.yincrement > 0)
                        ball.yincrement *= -1
                    
                }
                else if (redRacket.yDwards) {
                    if (ball.yincrement == 0)
                        ball.yincrement = ball.ran
                    else if (ball.yincrement < 0)
                        ball.yincrement *= -1
                    setInterval(table.duration/4)
                }
            }
             ....
        }
    .....
    }
    


  • @J.Hilk
    Thank you many times. :-)
    I must analyse the code for both being familiar with the code and also the way C++ and QML have been mixed. :-)



  • @tomy
    great,

    but keep in mind, its just an example.
    When I wrote it down, I realized quickly, that I would move a lot more into the c++ class.

    You could(should) do the new Ball-Position calculation also in the backend.

    you can set the Balls position through the QProperty-binding

    xBall: ball.x
    yBall: ball.y
    

    via calling setXBall() and setYBall inside the cpp-class.
    In the end there should than be no need for any QML-side timers.

    That should clean up and speed up your QML part significantly.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.