Related
This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 1 year ago.
I wanted to create a chess program using OOP. So I made a superclass Pieces, a subclass Bishop, and a UI class GameUI. I created a canvas in the class GameUI. I wanted, that when I instantiate an object bishop in the class GameUI, it shows an Image from a bishop, on the canvas.
The problem is, when I instantiate the Bishop, I don't see any image. So I tried to do the same with a text : instead of using the method create_image from the class Canvas, I used the method create_text, and it worked : I saw a text on the canvas. That means, the problem comes from the method create_image, and I don't understand it.
If I create an Image directly in the class GameUi, it works! but that's not what I want...
So I don't have any error message. I see the canvas (with a blue background), but no image on it.
Here's the code :
from tkinter import PhotoImage, Tk, Canvas
class Pieces:
def __init__(self, can, color, x_position, y_position):
self.color = color
self.x_position = x_position
self.y_position = y_position
class Bishop(Pieces):
def __init__(self, can, color, x_position, y_position):
super().__init__(can, color, x_position, y_position)
if color == "black":
icon_path = 'black_bishop.png'
elif color == "white":
icon_path = 'white_bishop.png'
icon = PhotoImage(file=icon_path) # doesn't see the image
can.create_image(x, y, image=icon)
class GameUI:
def __init__(self):
self.windows = Tk()
self.windows.title("My chess game")
self.windows.geometry("1080x720")
self.windows.minsize(300, 420)
self.can = Canvas(self.windows, width=1000, height=600, bg='skyblue')
icon = PhotoImage(file=icon_path) # here I create the image in this class, and
can.create_image(x, y, image=icon) # we can see it very well
self.bishop = Bishop(self.can, "black", 50, 50)
self.can.pack()
self.windows.mainloop()
app = GameUI()
To make your code work, I decided to sort of rewrite it based on this answer. It works now, but really the only thing that you needed to add was self.icon instead of icon. icon gets garbage collected since there is no further reference to it, while self.icon remains. Also, it's not entirely the same as yours was, so it probably needs a bit of rewriting too.
from tkinter import *
from random import randint
class Piece:
def __init__(self, canvas, x1, y1):
self.x1 = x1
self.y1 = y1
self.canvas = canvas
class Bishop(Piece):
def __init__(self, canvas, x1, y1, color):
super().__init__(canvas, x1, y1)
if color == "black":
icon_path = 'black_bishop.png'
elif color == "white":
icon_path = 'white_bishop.png'
self.icon = PhotoImage(file=icon_path)
self.ball = canvas.create_image(self.x1, self.y1, image=self.icon)
def move_piece(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball, deltax, deltay)
self.canvas.after(50, self.move_piece)
class GameUI:
def __init__(self):
# initialize root Window and canvas
root = Tk()
root.title("Chess")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
# create two ball objects and animate them
bishop1 = Bishop(canvas, 10, 10, 'white')
bishop2 = Bishop(canvas, 60, 60, 'black')
bishop1.move_piece()
bishop2.move_piece()
root.mainloop()
app = GameUI()
I am making a game in which you dodge falling object by using the tkinter library. In my code, I am trying to make an object fall by binding a canvas.move() function with pressing the down arrow key, then using pyautogui to hold down the key. Here is my code:
from tkinter import *
from random import randint
import pyautogui
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.initWindow()
def initWindow(self):
self.master.title('Dodger')
self.pack(fill=BOTH, expand=1)
self.master.geometry('600x800')
self.master.config(bg='black')
menu = Menu(self.master)
self.master.config(menu=menu)
def clientExit():
exit()
file = Menu(menu)
file.add_command(label='Exit', command=clientExit)
file.add_command(label='Start', command=self.game)
menu.add_cascade(label='File', menu=file)
def game(self):
canvas = Canvas(self.master, width='600', height='800', borderwidth='0', highlightthickness='0')
canvas.pack()
canvas.create_rectangle(0, 0, 600, 800, fill='black', outline='black')
character = canvas.create_rectangle(270, 730, 330, 760, fill='magenta', outline='cyan', width='2')
def left(event):
cord = canvas.coords(character)
if not cord[0] <= 5:
canvas.move(character, -10, 0)
def right(event):
cord = canvas.coords(character)
if not cord[2] >= 595:
canvas.move(character, 10, 0)
self.master.bind('<Left>', left)
self.master.bind('<Right>', right)
class variables:
sizeMin = 10
sizeMax = 80
y = 10
minX = 5
maxX = 545
def createShape():
size = randint(variables.sizeMin, variables.sizeMax)
x = randint(variables.minX, variables.maxX)
topLeft = [x, variables.y]
bottomRight = [x + size, variables.y + size]
shape = canvas.create_rectangle(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1],
fill='red', outline='red')
return shape
def moveShape(event):
cord = canvas.coords(x)
if cord[1] != 800:
canvas.move(x, 0, 10)
x = createShape()
self.master.bind('<Down>', moveShape)
pyautogui.keyDown('down')
root = Tk()
app = Window(root)
app.mainloop()
As you can see, at the bottom of the class, I binded the down arrow key and moving a shape down. However, the pyautogui does not work; the object does not move down unless I manually press the down arrow key. Am I forgetting something or is pyautogui not compatible with bind()? I know there are more efficient ways to move the object down, however with all the methods I have tried, none show the actual movement of the object heading down the screen; they just show the object being re-created in another position. Please let me know how I can fix this.
I wouldn't bother with it. Maybe it misses the _all argument. Try to simply bind canvas.move() function to a canvas.bind_all()
I am trying to wrap my head around parallel animations.
In the following code, clicking on a square will cause a small animation.
But declaring 2 boxes (or more) makes things more difficult: The animation called last will run and cause the other to pause and resume only after it is complete.
How to change my code so that all animation calls can run independently and in parallel?
#!python3
import tkinter as tk
import time
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create a canvas
self.canvas = tk.Canvas(width=400, height=400)
self.canvas.pack()
# create a couple of movable objects
self._create_token(100, 100, "green")
self._create_token(200, 100, "black")
def _create_token(self, x, y, color):
self.canvas.create_rectangle(x-25, y-25, x+25, y+25, outline=color, fill=color, tags=color)
self.canvas.tag_bind(color, "<ButtonPress-1>", self.on_token_press)
def on_token_press(self,event):
Rx = self.canvas.find_closest(event.x, event.y)
x = 0
y = 5
for i in range(25):
time.sleep(0.025)
self.canvas.move(Rx, x, y)
self.canvas.update()
for i in range(25):
time.sleep(0.025)
self.canvas.move(Rx, x, -y)
self.canvas.update()
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack()
root.mainloop()
You should not use blocking tasks in a GUI, they live inside an event loop that allows them to verify events such as keyboard, mouse, etc, if you use those tasks the GUI will probably freeze. what you should do is use after() for periodic tasks, in the following solution I have proposed to create a class that manages the animation in a simple way.
#!python3
import tkinter as tk
import time
class AbstractAnimation:
def __init__(self, canvas, id_item, duration, _from = 0, _to = 1):
self.canvas = canvas
self.id_item = id_item
self._progress = 0
self._from = _from
self._to = _to
self.t = max(10, int(duration/(self._to -self._from)))
def start(self):
self.canvas.after(self.t, self.on_timeout)
def on_timeout(self):
if self._from <= self._progress < self._to:
self.interpolated(self._from, self._to, self._progress)
self._progress += 1
self.canvas.after(self.t, self.on_timeout)
def interpolated(self, _from, _to, _progress):
pass
class Animation(AbstractAnimation):
def interpolated(self, _from, _to, _progress):
x, y = 0, 5
if _progress < 25:
self.canvas.move(self.id_item, x, y)
else:
self.canvas.move(self.id_item, x, -y)
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create a canvas
self.canvas = tk.Canvas(width=400, height=400)
self.canvas.pack()
# create a couple of movable objects
self._create_token(100, 100, "green")
self._create_token(200, 100, "black")
def _create_token(self, x, y, color):
self.canvas.create_rectangle(x-25, y-25, x+25, y+25, outline=color, fill=color, tags=color)
self.canvas.tag_bind(color, "<ButtonPress-1>", self.on_token_press)
def on_token_press(self,event):
Rx = self.canvas.find_closest(event.x, event.y)
animation = Animation(self.canvas, Rx, 1250, 0, 50)
animation.start()
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack()
root.mainloop()
As someone mention is the comments and #eyllanesc also mentions in his answer, you generally shouldn't call time.sleep() in a tkinter program because doing so temporarily halts its mainloop() which essentially halts the running GUI for the duration. Instead you should use the universal after() method.
However, you don't really need to use to get delays to animation. Instead it can be used to periodically run an arbitrary function within the mainloop(), which provides the leverage to animate things if desired.
In the code below, this is done by first defining a Token class to encapsulate the values associated with one, and then creating a list of them named _self._tokens, and finally using after() to schedule moving all the items in it that are currently active. The function to be called by after() in this case is the Example._update_tokens() method.
Here's code showing how to implement this approach:
import tkinter as tk
UPDATE_RATE = 10 # Updates-per-second.
UPDATE_DELAY = 1000//UPDATE_RATE # msec delay between updates.
class Token:
WIDTH, HEIGHT, INCR = 25, 25, 1
def __init__(self, canvas, x, y, color, max_value, dx, dy):
self.canvas, self.x, self.y = canvas, x, y
self.color, self.max_value, self.dx, self.dy = color, max_value, dx, dy
self.value, self.moving, self.saved_direction = 0, 0, 1
self.id = self.canvas.create_rectangle(x-self.WIDTH, y-self.HEIGHT,
x+self.WIDTH, y+self.HEIGHT,
outline=color, fill=color)
self.canvas.tag_bind(self.id, "<ButtonPress-1>", self._toggle)
def _toggle(self, _event):
""" Start movement of object if it's paused otherwise reverse its
direction.
"""
if self.moving:
self.moving = -self.moving # Reverse movement.
else: # Start it moving.
self.moving = self.saved_direction
def start(self):
self.moving = self.saved_direction
def pause(self):
if self.moving:
self.saved_direction = self.moving
self.moving = 0
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(width=400, height=400)
self.canvas.pack()
# Create list of movable objects.
self._tokens = []
self._tokens.append(Token(self.canvas, 100, 100, "green", 25, 0, 5))
self._tokens.append(Token(self.canvas, 200, 100, "black", 25, 0, 5))
tk.Button(self, text='Go', command=self._start_paused_tokens).pack(side=tk.LEFT)
tk.Button(self, text='Pause', command=self._pause_tokens).pack(side=tk.LEFT)
# Start the updating of active objects in _tokens list.
self.after(UPDATE_DELAY, self._update_tokens)
def _start_paused_tokens(self):
""" Start any paused Tokens. """
for token in self._tokens:
if token.moving == 0:
token.start()
def _pause_tokens(self):
""" Stop any moving Tokens. """
for token in self._tokens:
if token.moving != 0:
token.pause()
def _update_tokens(self):
""" Update any objects in Tokens lst that aren't paused. """
for token in self._tokens:
if token.moving > 0:
if token.value < token.max_value:
token.value += token.INCR
token.canvas.move(token.id, token.dx, token.dy)
else:
token.value = token.max_value
token.moving = -token.moving # Reverse moving.
token.canvas.move(token.id, token.dx, -token.dy)
elif token.moving < 0:
if token.value > 0:
token.value -= token.INCR
token.canvas.move(token.id, token.dx, -token.dy)
else:
token.value = 0
token.moving = -token.moving # Reverse moving.
token.canvas.move(token.id, token.dx, token.dy)
self.after(UPDATE_DELAY, self._update_tokens) # Continue doing updates.
def on_token_press(self, event):
closest_token = self.canvas.find_closest(event.x, event.y)
dx, dy = 0, 5
for i in range(25):
time.sleep(0.025)
self.canvas.move(closest_token, dx, dy)
self.canvas.update()
for i in range(25):
time.sleep(0.025)
self.canvas.move(closest_token, dx, -dy)
self.canvas.update()
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack()
root.mainloop()
I am trying to set the Boolean Variable "self.running" to True in order as a check to show that the application is running, and will refresh the canvas. However, whenever I run this code, I get back the error message:
"line 29, in mainloop
if self.running == True:
AttributeError: 'Game' object has no attribute 'running'"
the actual code for this is simply
self.running = True
I don't see the actual issue here, as I am a newbie to python and coding world in general, I searched around but found different scenarios then the one I had and couldn't apply their solutions to my issue. Thank you.
edit:Code
class Game:
def _init_(self):
self.tk = Tk()
self.running = False
self.tk.title("Man runs to door. Wins nobel prize.")
self.tk.resizable(0, 0)
self.tk.wm_attributes("-topmost", 1)
self.canvas = Canvas(self.tk, width=500, height=500, \
highlightthickness=0)
self.canvas.pack()
self.tk.update()
self.canvas_height = 500
self.canvas_width = 500
self.bg = PhotoImage(file="background.gif")
w = self.bg.width()
h = self.bg.height()
for x in range(0, 5):
for y in range(0, 5):
self.canvas.create_image(x * w, y * h, \
image=self.bg, anchor='nw')
self.sprites = []
self.running = True
def mainloop(self):
while 1:
if self.running == True:
for sprite in self.sprites:
sprite.move()
self.tk.update_idletasks()
self.tk.update()
time.sleep(0.01)
this is the entire initialization area and mainloop.
would this be a correct version of the code then?
class Game:
def _init_(self):
self.tk = Tk()
self.running = False
self.tk.title("Man runs to door. Wins nobel prize.")
self.tk.resizable(0, 0)
self.tk.wm_attributes("-topmost", 1)
self.canvas = Canvas(self.tk, width=500, height=500, \
highlightthickness=0)
self.canvas.pack()
self.tk.update()
self.canvas_height = 500
self.canvas_width = 500
self.bg = PhotoImage(file="background.gif")
w = self.bg.width()
h = self.bg.height()
for x in range(0, 5):
for y in range(0, 5):
self.canvas.create_image(x * w, y * h, \
image=self.bg, anchor='nw')
self.sprites = []
self.running = True
def mainloop(self):
while 1:
if self.running == True:
for sprite in self.sprites:
sprite.move()
self.tk.update_idletasks()
self.tk.update()
time.sleep(0.01)
I guess you might have a class called Game in your code and you just miss the "self.running" in the initialization:
class Game:
def __init__(self):
self.running = False
From the updated question - your mainloop is not inside your Game class, so self doesn't work as you expect. You need to properly indent your code:
class Game
def mainloop(self):
while 1:
if self.running == True:
for sprite in self.sprites:
sprite.move()
self.tk.update_idletasks()
self.tk.update()
time.sleep(0.01)
game = Game()
game.mainloop()
Ensure that mainloop is indented 4 spaces inside of Game.
This error message says your self.running has not been initialized.
I am not sure where do you assign self.running = True.
The best way to do is add this code into your __init__ method
The problem seems to be on your def __init__(self):
Note that the init is surrounded by two underscores and not just one like your example code.
The rest looks fine, but it would be more pythonic to just write
if self.running:
instead of comparing it to True.
Also, be sure to check your identation as pointed out by Martin Konecny.
I am trying to make a simple game with pyglet, and it has to include an intro screen. Unfortunately, it's been proving more difficult than I expected.
The following code is a simpler version of what I am trying to do.
import pyglet
from game import intro
game_window = pyglet.window.Window(800, 600)
intro.play(game_window)
#game_window.event
def on_draw():
game_window.clear()
main_batch.draw()
def update(dt):
running = True
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1/120.0)
main_batch = pyglet.graphics.Batch()
score_label = pyglet.text.Label(text = 'RUNNING GAME', x = 400, y = 200, batch=main_batch)
pyglet.app.run()
Where game/intro.py has the following written in it:
import pyglet
from time import sleep
def play(game_window):
game_window.clear()
studio = pyglet.text.Label('foo studios', font_size=36, font_name='Arial', x=400, y=300)
studio.draw()
sleep(5)
This opens a window (the intro window) and waits 5 seconds, after which the message "RUNNING GAME" appears, but the "foo studios" message does not appear.
Clearly I am doing something wrong.
I am not very experienced with pyglet, but I managed to get the game running (needs a bit of tweaking, but it's essentially done). All I need left is the intro screen.
If anyone knows a good way of doing an intro screen (just with text, I don't need any animations of music for now), I would be very grateful.
You're better off creating classes based on for instance pyglet.sprite.Sprite and using those objects as "windows" or "screens".
Feels like i'm pasting this code everywhere but use this, and in "def render()` put the different "scenarios"/"windows" you'd wish to be rendered at the time.
import pyglet
from time import time, sleep
class Window(pyglet.window.Window):
def __init__(self, refreshrate):
super(Window, self).__init__(vsync = False)
self.frames = 0
self.framerate = pyglet.text.Label(text='Unknown', font_name='Verdana', font_size=8, x=10, y=10, color=(255,255,255,255))
self.last = time()
self.alive = 1
self.refreshrate = refreshrate
def on_draw(self):
self.render()
def render(self):
self.clear()
if time() - self.last >= 1:
self.framerate.text = str(self.frames)
self.frames = 0
self.last = time()
else:
self.frames += 1
self.framerate.draw()
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
event = self.dispatch_events() # <-- This is the event queue
sleep(1.0/self.refreshrate)
win = Window(23) # set the fps
win.run()
What does it is the fact that you have a rendering function that clears and flips the entire graphical memory X times per second and you descide which objects are included in that render perior in the render function.
Try it out and see if it helps.
Here is a example using the above example, it consists of 3 things:
* A main window
* A Intro screen
* A Menu screen
You can ignore class Spr() and def convert_hashColor_to_RGBA(), these are mere helper functions to avoid repetative code further down.
I will also go ahead and mark the important bits that actually do things, the rest are just initation-code or positioning things.
import pyglet
from time import time, sleep
__WIDTH__ = 800
__HEIGHT__ = 600
def convert_hashColor_to_RGBA(color):
if '#' in color:
c = color.lstrip("#")
c = max(6-len(c),0)*"0" + c
r = int(c[:2], 16)
g = int(c[2:4], 16)
b = int(c[4:], 16)
color = (r,g,b,255)
return color
class Spr(pyglet.sprite.Sprite):
def __init__(self, texture=None, width=__WIDTH__, height=__HEIGHT__, color='#000000', x=0, y=0):
if texture is None:
self.texture = pyglet.image.SolidColorImagePattern(convert_hashColor_to_RGBA(color)).create_image(width,height)
else:
self.texture = texture
super(Spr, self).__init__(self.texture)
## Normally, objects in graphics have their anchor in the bottom left corner.
## This means that all X and Y cordinates relate to the bottom left corner of
## your object as positioned from the bottom left corner of your application-screen.
##
## We can override this and move the anchor to the WIDTH/2 (aka center of the image).
## And since Spr is a class only ment for generating a background-image to your "intro screen" etc
## This only affects this class aka the background, so the background gets positioned by it's center.
self.image.anchor_x = self.image.width / 2
self.image.anchor_y = self.image.height / 2
## And this sets the position.
self.x = x
self.y = y
def _draw(self):
self.draw()
## IntoScreen is a class that inherits a background, the background is Spr (our custom background-image class)
## IntoScreen contains 1 label, and it will change it's text after 2 seconds of being shown.
## That's all it does.
class IntroScreen(Spr):
def __init__(self, texture=None, width=300, height = 150, x = 10, y = 10, color='#000000'):
super(IntroScreen, self).__init__(texture, width=width, height=height, x=x, y=y, color=color)
self.intro_text = pyglet.text.Label('Running game', font_size=8, font_name=('Verdana', 'Calibri', 'Arial'), x=x, y=y, multiline=False, width=width, height=height, color=(100, 100, 100, 255), anchor_x='center')
self.has_been_visible_since = time()
def _draw(self): # <-- Important, this is the function that is called from the main window.render() function. The built-in rendering function of pyglet is called .draw() so we create a manual one that's called _draw() that in turn does stuff + calls draw(). This is just so we can add on to the functionality of Pyglet.
self.draw()
self.intro_text.draw()
if time() - 2 > self.has_been_visible_since:
self.intro_text.text = 'foo studios'
## Then we have a MenuScreen (with a red background)
## Note that the RED color comes not from this class because the default is black #000000
## the color is set when calling/instanciating this class further down.
##
## But all this does, is show a "menu" (aka a text saying it's the menu..)
class MenuScreen(Spr):
def __init__(self, texture=None, width=300, height = 150, x = 10, y = 10, color='#000000'):
super(MenuScreen, self).__init__(texture, width=width, height=height, x=x, y=y, color=color)
self.screen_text = pyglet.text.Label('Main menu screen', font_size=8, font_name=('Verdana', 'Calibri', 'Arial'), x=x, y=y+height/2-20, multiline=False, width=300, height=height, color=(100, 100, 100, 255), anchor_x='center')
def _draw(self):
self.draw()
self.screen_text.draw()
## This is the actual window, the game, the glory universe that is graphics.
## It will be blank, so you need to set up what should be visible when and where.
##
## I've creates two classes which can act as "screens" (intro, game, menu etc)
## And we'll initate the Window class with the IntroScreen() and show that for a
## total of 5 seconds, after 5 seconds we will swap it out for a MenuScreeen().
##
## All this magic is done in __init__() and render(). All the other functions are basically
## just "there" and executes black magic for your convencience.
class Window(pyglet.window.Window):
def __init__(self, refreshrate):
super(Window, self).__init__(vsync = False)
self.alive = 1
self.refreshrate = refreshrate
self.currentScreen = IntroScreen(x=320, y=__HEIGHT__/2, width=50) # <-- Important
self.screen_has_been_shown_since = time()
def on_draw(self):
self.render()
def on_key_down(self, symbol, mod):
print('Keyboard down:', symbol) # <-- Important
def render(self):
self.clear()
if time() - 5 > self.screen_has_been_shown_since and type(self.currentScreen) is not MenuScreen: # <-- Important
self.currentScreen = MenuScreen(x=320, y=__HEIGHT__-210, color='#FF0000') # <-- Important, here we switch screen (after 5 seconds)
self.currentScreen._draw() # <-- Important, draws the current screen
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
event = self.dispatch_events()
sleep(1.0/self.refreshrate)
win = Window(23) # set the fps
win.run()