Search code examples
pythonkivyscatterpong

How to use Scatter to move paddles in the official Kivy Pong tutorial?


Having completed the Pong Game tutorial from the official Kivy site I went on with their Crash Course. There in the very first video I saw this magic thing they call Scatter that pretty much enables you out-of-the-box to make UI things move with your mouse.

I thought this should provide a smoother way to control paddles in the Pong Game. The original way was to put the paddles-moving logic in on_touch_move() method of PongGame object (PongGame class inherits from Widget) and it was a simple:

if touch.x < self.width / 3:  # if you clicked in 1/3 of the window to the left
    player1.center_y = touch.y  # level the first paddle's center vertically with the mouse click position

This results in a jarring start if you happen to start moving your mouse cursor way below or above a paddle. I thought Scatter would work better. Alas so far I've failed to make it work.

What I started with was commenting out the on_touch_move() method, then adding a Scatter object as a child of PongGame class in the pong.kv file and making the existing PongPaddle objects children of the Scatter object. Like this:

<PongGame>:
    Scatter:
        do_scale: False
        do_rotation: False
        do_translation_x: False

        PongPaddle:
            id: player_left
            x: root.x
            center_y: root.center_y  

        PongPaddle:
            id: player_right
            x: root.width - self.width
            center_y: root.center_y

As I used a single Scatter object and both paddles need to move independently I envisioned this will probably cause a problem (clicking one would make both move at the same time) but thought nevertheless it would be a good start.

No luck! This does not make paddles move with a mouse cursor. They still bounce the ball (even though they moved down in the widget tree and I haven't changed the Python code other than commenting out the on_touch_move() method in the PongGame class body - I guess the references to ObjectProperty instances for paddles hooked up in the pong.kv file still work), but they won't move.

Whole runnable code (my own version with the scatter)

Whole runnable code (my own version without the scatter)

Any ideas how to make it work?


Solution

  • So the problem is that the paddle jumps to a new location and the on_touch_move method is responsible for that. In your runnable code without scatter I changed lines 84-88 to:

    def on_touch_move(self, touch):
        if touch.x < self.width / 3:
            self.player1.center_y += touch.dy
        if touch.x > self.width - self.width / 3:
            self.player2.center_y += touch.dy
    

    Basically, the touch contains delta values for y (how much the y changed) and so you can just move the paddle as much as you moved the mouse instead of moving the paddle center to the mouse's y. This makes the game really smooth and nice. I actually wonder why they didn't make it like so in the first place.

    There is one problem though - the paddles can go way off the game screen now. This can be easily fixed by checking if the paddle's center is off the screen (use the PongGame height). I'll leave this as an exercise, but feel free to ask if you get stuck.

    So, since you're curious, there is a way to do this with Scatter. So, first of all, Scatter itself is the widget which is dragged and resized and rotated, it's not a layout (it can be, but we only need the paddles themselves to move, not the whole screen). So, the Paddle inherits from Scatter. Remove the on_touch we used for moving the paddles.

    Now you'll notice there is a visual bug once you do this. Scatter is weird in some ways. Remove the pos: self.pos of the Paddle in the .kv file. This post sums it up well:

    ...specific behavior makes the scatter unique, and there are some advantages / constraints that you should consider:

    • The children are positioned relative to 0, 0. The scatter position has no impact of the position of children.

    So the canvas's position in the Paddle is relative to the Paddle (scatter), not the screen.

    Now take a minute to appreciate the game you got. The paddles can be moved anywhere as well as rotated and such. You can do this with the mouse by using right click to set an imaginary "touch" indicated by a red dot and then do the mobile kind of gestures to resize and rotate. Have some fun, you deserve it. We will fix these "bugs" after your break.

    Okay, so there's also some functionality of the Scatter you don't really need. Disable scaling, rotation, and dragging by the x in the .py file PongPaddle class:

    do_rotation = do_scale = do_translation_x = Property(False)
    

    Not sure if I got everything, Scatter does many things, some of which you don't need or want. Compared to the previous version, Scatter pong requires more precision. You'll still need the code to check if the paddle is out of boundaries as well. Overall, I liked the previous solution more.

    Here you will find the full code with Scatter.