6

Attempted to do simple movement in tkinter:

import tkinter as tk

class GameApp(object):
    """
    An object for the game window.

    Attributes:
        master: Main window tied to the application
        canvas: The canvas of this window
    """

    def __init__(self, master):
        """
        Initialize the window and canvas of the game.
        """

        self.master = master
        self.master.title = "Game"
        self.master.geometry('{}x{}'.format(500, 500))

        self.canvas = tk.Canvas(self.master)
        self.canvas.pack(side="top", fill="both", expand=True)

        self.start_game()

    #----------------------------------------------#


    def start_game(self):
        """
        Actual loading of the game.
        """

        player = Player(self)

    #----------------------------------------------#

#----------------------------------------------#


class Player(object):
    """
    The player of the game.

    Attributes:
        color: color of sprite (string)
        dimensions: dimensions of the sprite (array)
        canvas: the canvas of this sprite (object)
        window: the actual game window object (object)
        momentum: how fast the object is moving (array)
    """


    def __init__(self, window):

        self.color = ""
        self.dimensions = [225, 225, 275, 275]
        self.window = window
        self.properties()

    #----------------------------------------------#

    def properties(self):
        """
        Establish the properties of the player.
        """

        self.color = "blue"
        self.momentum = [5, 0]

        self.draw()
        self.mom_calc()

    #----------------------------------------------#

    def draw(self):
        """
        Draw the sprite.
        """

        self.sprite = self.window.canvas.create_rectangle(*self.dimensions, fill=self.color, outline=self.color)

    #----------------------------------------------#


    def mom_calc(self):
        """
        Calculate the actual momentum of the thing
        """

        self.window.canvas.move(self.sprite, *self.momentum)
        self.window.master.after(2, self.mom_calc)

    #----------------------------------------------#

#----------------------------------------------#


root = tk.Tk()

game_window = GameApp(root)

Where self.momentum is an array containing 2 integers: one for the x movement, and another for the y movement. However, the actual movement of the rectangle is really slow (about 5 movements per second), with the self.window.master.after() time not seeming to have an effect.

Previously on another tkinter project I had managed to get really responsive tkinter movement, so I'm just wondering if there is a way I can minimize that movement updating time in this case, by either using a different style of OOP, or just different code altogether.

UPDATE: Turns out the time in the .after() method does matter, and it actually stacks onto the real time of the method. After using timeit to time calling the method, I got this output:

>>> print(timeit.timeit("(self.window.master.after(2, self.mom_calc))", number=10000, globals={"self":self}))
0.5395521819053108

So I guess the real question is: Why is that .after() method taking so long?

UPDATE 2: Tested on multiple computers, movement is still slow on any platform.

Dova
  • 310
  • 3
  • 19
  • This looks like way more code than necessary to illustrate the probem. Please read and follow the advice here: [How to create a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve), or move your question to http://codereview.stackexchange.com/ – Bryan Oakley Mar 04 '17 at 03:34
  • 1
    Minimized it now. – Dova Mar 04 '17 at 04:45
  • You forgot about the "Complete" part of "Minimum, Complete, and Verifiable". – Bryan Oakley Mar 04 '17 at 13:01
  • Is this better? – Dova Mar 04 '17 at 20:58
  • Why don't you simply set for example `self.momentum = [10, 0]`? – nbro Mar 05 '17 at 01:16
  • I gave it a placeholder value in the "properties" method. – Dova Mar 05 '17 at 02:36
  • I can't reproduce this. I added a line to print the time elapsed between calls and it's 0.02 seconds every time. I tried on linux and windows, both python 2 and 3. If you are on a mac you may want to [read this](https://www.python.org/download/mac/tcltk/). – Novel Mar 07 '17 at 17:29
  • I'm on a windows, using Python 3.6. Same result on Python 3.5. – Dova Mar 07 '17 at 19:26
  • Interesting, you work without `mainloop`, who hold/handle your all app code ? You are awesome, call a sub_class variable from sub_class. Where your main my hero ? – dsgdfg Mar 10 '17 at 12:40
  • I do have a main in my base code, but it doesn't seem to affect the problem so I didn't add it here. I don't understand what you mean about the sub_class. – Dova Mar 13 '17 at 00:41
  • The code does run on my machine but since you does not have a mainloop, the code immediately exists. But when adding a mainloop, I see nothing slow about it. Also there is already a stackoverflow about this (with answear in a comment..) http://stackoverflow.com/questions/23999478/python-tkinter-call-to-after-is-too-slow – Kobbe Mar 13 '17 at 11:08

2 Answers2

3

"The default Windows timer resolution is ~15ms. Trying to fire a timer every 1ms is not likely to work the way you want, and for a game is probably quite unnecessary (a display running a 60FPS updates only every ~16ms). See Why are .NET timers limited to 15 ms resolution?"

Found the solution at Python - tkinter call to after is too slow where Andrew Medico gave a good answer (in a comment).

Community
  • 1
  • 1
Kobbe
  • 809
  • 5
  • 16
1

I don't see the problem you report on Windows 10 using Python 3.6 at least. I tested the program as shown and had to add a root.mainloop() at the end. This showed no rectangle because the object has moved off the canvas too fast to see.

So I modified this to bounce between the walls and added a counter to print the number of mom_calc calls per second. With the after timeout set at 20ms I get 50 motion calls per second, as expected. Setting this to 2ms as in your post I get around 425 per second so there is a little error here and its taking about 2.3 or 2.4 ms per call. This is a bit variable as other processes can take up some of the time at this granularity.

Here is the (slightly) modified code:

import tkinter as tk

class GameApp(object):
    """
    An object for the game window.

    Attributes:
        master: Main window tied to the application
        canvas: The canvas of this window
    """

    def __init__(self, master):
        """
        Initialize the window and canvas of the game.
        """

        self.master = master
        self.master.title = "Game"
        self.master.geometry('{}x{}'.format(500, 500))

        self.canvas = tk.Canvas(self.master, background="white")
        self.canvas.pack(side="top", fill="both", expand=True)

        self.start_game()

    #----------------------------------------------#


    def start_game(self):
        """
        Actual loading of the game.
        """

        player = Player(self)

    #----------------------------------------------#

#----------------------------------------------#


class Player(object):
    """
    The player of the game.

    Attributes:
        color: color of sprite (string)
        dimensions: dimensions of the sprite (array)
        canvas: the canvas of this sprite (object)
        window: the actual game window object (object)
        momentum: how fast the object is moving (array)
    """


    def __init__(self, window):

        self.color = ""
        self.dimensions = [225, 225, 275, 275]
        self.window = window
        self.movement = 0
        self.movement_last = 0
        self.properties()

    #----------------------------------------------#

    def properties(self):
        """
        Establish the properties of the player.
        """

        self.color = "blue"
        self.momentum = [5, 0]

        self.draw()
        self.mom_calc()
        self.velocity()

    #----------------------------------------------#

    def draw(self):
        """
        Draw the sprite.
        """

        self.sprite = self.window.canvas.create_rectangle(*self.dimensions, fill=self.color, outline=self.color)

    #----------------------------------------------#


    def mom_calc(self):
        """
        Calculate the actual momentum of the thing
        """

        pos = self.window.canvas.coords(self.sprite)
        if pos[2] > 500:
            self.momentum = [-5, 0]
        elif pos[0] < 2:
            self.momentum = [5, 0]

        self.window.canvas.move(self.sprite, *self.momentum)
        self.window.master.after(2, self.mom_calc)
        self.movement = self.movement + 1

    def velocity(self):
        print(self.movement - self.movement_last)
        self.movement_last = self.movement
        self.aid_velocity = self.window.master.after(1000, self.velocity)

    #----------------------------------------------#

#----------------------------------------------#


if __name__ == '__main__':
    root = tk.Tk()
    game_window = GameApp(root)
    root.mainloop()
patthoyts
  • 32,320
  • 3
  • 62
  • 93
  • `root.mainloop()` Feel like an idiot for forgetting that, and that seems to be the kicker. Thanks. – Dova Mar 13 '17 at 23:42