I'm making a little alien project to help to learn graphics in tkinter and I have come across a problem. I am trying to make the aliens eyeball stay inside the eye but still move around however that requires me to detect the edge of the eyeball which is a circle. Not really sure how coords work in tkinter (other than the basics) so any help appreciated. Thanks!
from tkinter import *
from threading import Timer
import random
import time
global canvas, root
root = Tk()
root.title("Alien")
root.attributes("-topmost", 1)
canvas = Canvas(root, height=300, width =400)
canvas.pack()
Blinking = False
class Alien:
def __init__(self):
global canvas, root
self.body = canvas.create_oval(100,150,300,250, fill = "green")
self.eye = canvas.create_oval(170,70,230,130, fill = "white")
self.eyeball = canvas.create_oval(190,90,210,110, fill = "black")
self.mouth = canvas.create_oval(150,220,250,240, fill = "red")
self.neck = canvas.create_line(200,150,200,130)
self.hat = canvas.create_polygon(180,75,220,75,200,20, fill = "blue")
self.words = canvas.create_text(200,800, text = "I'm an alien!", anchor="nw")
root.update()
def openMouth(self):
global canvas, root
canvas.itemconfig(self.mouth, fill = "black")
root.update()
def closeMouth(self):
global canvas, root
canvas.itemconfig(self.mouth, fill = "red")
root.update()
def burp(self, event):
self.openMouth()
canvas.itemconfig(self.words, text = "BURRRRP!")
time.sleep(0.5)
self.closeMouth()
def moveEye(self,event):
global root, canvas
canvas.move(alien.eyeball , random.randint(-1,1) , random.randint(-1,1))
root.update()
def blink(self,event):
canvas.itemconfig(self.eye, fill = "green")
canvas.itemconfig(self.eyeball, state=HIDDEN)
Blinking = True
root.update()
def unblink(self,event):
canvas.itemconfig(self.eye, fill = "white")
canvas.itemconfig(self.eyeball, state=NORMAL)
Blinking = False
root.update()
alien = Alien()
alien.openMouth()
time.sleep(1)
alien.closeMouth()
canvas.bind_all("<Button-1>", alien.burp)
canvas.bind_all("<KeyPress-a>", alien.blink)
Timer(2, alien.moveEye).start()
while not Blinking:
alien.moveEye(event)
if alien.moveEye.event.x > 190:
canvas.move(alien.eyeball, -1 , 0)
Small circle with radius r lies inside big circle with radius R, if
(BigCenter.X - SmallCenter.x)^2 + (BigCenter.Y - SmallCenter.Y)^2 < (R - r)^2
^2 denotes squared value
Related
I was making a game recently using tkinter and it's Canvas widget, but ran into a problem when trying to move an image. I set the canvas.create_image to a variable called character but noticed it turned it into an object. This causes issues as when I try and use the canvas.move(character), it comes back with "character not defined". Here is the full code:
from tkinter import *
from PIL import ImageTk, Image
import pygame
import threading
import time
gamestate= 'title'
waiting = False
#window init
root = Tk()
WIDTH=1280
HEIGHT=720
canvas = Canvas(root, width=WIDTH, height=HEIGHT, bg='black')
canvas.pack(expand=1, fill=BOTH)
logo = PhotoImage(file='Ghost clipart.png')
root.iconphoto(False, logo)
#setting up all the images
title_image = PhotoImage(file='title.png')
board_image = Image.open('board.png')
done_board = board_image.resize((1920,1080), Image.ANTIALIAS)
board_image = ImageTk.PhotoImage(done_board)
character_image = ImageTk.PhotoImage(file='first_run_left.png')
#display the title screen
def title_screen():
title = canvas.create_image(WIDTH/2,HEIGHT/2, anchor=CENTER, image=title_image)
#define the events after clicking play
def play():
canvas.delete("all")
board = canvas.create_image(WIDTH/2 + 100,HEIGHT/2 - 360, anchor=CENTER, image=board_image)
character = canvas.create_image(556,304,anchor=CENTER,image=character_image)
#testing for what should happen the the mouse button is clicked
def click(event):
if gamestate == 'title':
print(event.x, event.y)
if event.x > 475 and event.x < 804 and event.y > 213 and event.y < 337:
play()
elif event.x > 475 and event.x < 804 and event.y > 404 and event.y < 527:
print("skin")
def up(event):
canvas.move(character,0,10)
root.bind('<Button-1>', click)
root.bind('<Up>', up)
#audio module
def test_loop():
while True:
pygame.mixer.init()
pygame.mixer.music.load("nokia-ringtone-arabic.mp3")
pygame.mixer.music.play()
time.sleep(23)
thread = threading.Thread(target=test_loop)
thread.daemon = True
thread.start()
title_screen()
root.mainloop()
Here is a simple script that uses the arrow keys to move an image on the canvas. Note that the variable img (in your case character) must be accessible from the functions that act upon it, therefore its scope must be above these functions.
You can use this as a starting point to solve your problem.
image used:
import tkinter as tk
def up(event):
canvas.move(img, 0, -10)
def down(event):
canvas.move(img, 0, 10)
def left(event):
canvas.move(img, -10, 0)
def right(event):
canvas.move(img, 10, 0)
width = 500
height = 500
window = tk.Tk()
window.title("Moving image")
canvas = tk.Canvas(window, width=500, height=500)
canvas.pack()
my_image = tk.PhotoImage(file="pix.png")
img = canvas.create_image(260, 125, anchor=tk.NW, image=my_image)
window.bind("<Up>", up)
window.bind("<Down>", down)
window.bind("<Left>", left)
window.bind("<Right>", right)
window.mainloop()
Since character is a local variable inside play() function, it cannot be accessed inside up() function.
However you can use tags option of create_image():
def play():
...
canvas.create_image(556,304,anchor=CENTER,image=character_image, tags='character')
Then you can refer to that image item using the same tag:
def up(event):
canvas.move('character',0,-10) # use tag 'character'
create_image doesn't turn anything into an object. It simply returns an integer identifier. The original object is unchanged. character is undefined because it's a local variable and thus only visible in the method that created it.
I'm writing a program for my raspberry pi4 for my Master's project which sounded so simple at first but for some reason Python is causing me grief.
In short - I have created a tkinter canvas with a polygon centred on the canvas. The shape of this polygon will depend upon the value of a counter.
The count is controlled by a blink event from a neurosky mindwave headset - this count is working (mostly).
What I want to then do is update the canvas to put the new points for the polygon into the pack but nothing I have tried seems to work. The closest I got was trying a .redraw() command which drew an infinite number of windows before I pulled the plug.
I am not a complete novice to coding having taught many languages in my time but have never used python before and am clearly missing a very simple step which will cause everything to fall out.
I will try to modify the code to use a keyboard press rather than a headset and add it below later if folk think it will help.
import keyboard
import time
from tkinter import *
count = 0
points = [250,250,350,250,350,350,250,350]
root = Tk()
while True:
# set window to middle of screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
xcoord = screen_width/2-300
ycoord = screen_height/2 - 300
root.geometry("%dx%d+%d+%d" % (600,600,xcoord,ycoord))
#set up canvas size and background colour
canvas1 = Canvas(root, relief = FLAT,width = 600, height = 600, background = "blue")
#set up buttons shape and colour
button = canvas1.create_polygon(points, fill="darkgreen", outline="yellow")
canvas1.pack()
if keyboard.is_pressed("f"):
if count < 4:
count += 1
elif count == 4:
count = 0
time.sleep(0.1)
if count == 0:
points = [250,250,350,250,350,350,250,350]
elif count == 1:
points = [300,100,500,500,100,500]
elif count == 2:
points = [200,100,400,100,300,500]
elif count == 3:
points = [100,300,500,100,500,500]
elif count == 4:
points = [100,100,100,500,500,300]
print(count)
root.update()
You need to delete the old polygon and create new one. Also don't use while loop in tkinter application. For your case, you can bind a callback on <Key> event and update the polygon in the callback:
import tkinter as tk
count = 0
points = [
[250,250,350,250,350,350,250,350],
[300,100,500,500,100,500],
[200,100,400,100,300,500],
[100,300,500,100,500,500],
[100,100,100,500,500,300],
]
root = tk.Tk()
# set window to middle of screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
xcoord = screen_width//2 - 300
ycoord = screen_height//2 - 300
root.geometry("%dx%d+%d+%d" % (600,600,xcoord,ycoord))
#set up canvas
canvas1 = tk.Canvas(root, relief=tk.FLAT, background="blue")
canvas1.pack(fill=tk.BOTH, expand=1)
# create the polygon with tag "button"
canvas1.create_polygon(points[count], fill="darkgreen", outline="yellow", tag="button")
def on_key(event):
global count
if event.char == 'f':
count = (count + 1) % len(points)
print(count)
canvas1.delete("button") # delete the old polygon
canvas1.create_polygon(points[count], fill="darkgreen", outline="yellow", tag="button")
root.bind("<Key>", on_key)
root.mainloop()
You can update any parameters of a shape.
canvas.itemconfigure(shape1_id_or_tag, fill="green")
canvas.itemconfigure(shape2_id_or_tag, fill="#900", outline="red", width=3)
But for your situation try '.coords()':
canvas1.coords("Button", points[count])
Source: https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/canvas-methods.html
I am making a draggable point(a circle). The code works, however, while dragging, if the mouse motion is quick the point stops moving. I have taken help from this code for making this program. I will be using this point later on for other purposes. Here is my full code,
from tkinter import *
import sys,os,string,time
class Point():
def __init__(self,canvas,x,y):
self.canvas = canvas
# It could be that we start dragging a widget
# And release it while its on another
# Hence when we land on a widget we set self.loc to 1
# And when we start dragging it we set self.dragged to 1
self.loc = self.dragged = 0
self.x = x
self.y = y
self.radius = 5
self.point = canvas.create_oval(self.x-self.radius,self.y-self.radius,self.x+self.radius,self.y+self.radius,fill="green",tag="Point")
canvas.tag_bind("Point","<ButtonPress-1>",self.down)
canvas.tag_bind("Point","<ButtonRelease-1>",self.chkup)
canvas.tag_bind("Point","<Enter>",self.enter)
canvas.tag_bind("Point","<Leave>",self.leave)
def down(self,event):
self.loc = 1
self.dragged = 0
event.widget.bind("<Motion>",self.motion)
canvas.itemconfigure(self.point,fill = "red")
def motion(self,event):
root.config(cursor = "exchange")
cnv = event.widget
cnv.itemconfigure(self.point,fill = "red")
self.x,self.y = cnv.canvasx(event.x), cnv.canvasy(event.y)
got = canvas.coords(self.point,self.x-self.radius,self.y-self.radius,self.x+self.radius,self.y+self.radius)
def enter(self,event):
canvas.itemconfigure(self.point,fill="blue")
self.loc = 1
if self.dragged == event.time:
self.up(event)
def up(self,event):
event.widget.unbind("<Motion>")
canvas.itemconfigure(self.point,fill="green")
self.canvas.update()
def chkup(self,event):
event.widget.unbind("<Motion>")
root.config(cursor = "")
canvas.itemconfigure(self.point,fill="green")
if self.loc: # is button released in the same widget as pressed
self.up(event)
else:
self.dragged = event.time
def leave(self,event):
self.up(event)
root = Tk()
root.title("Drag and Drop")
canvas = Canvas(root,width = 256, height = 256, borderwidth = 1)
point = Point(canvas,128,128)
canvas.pack()
root.mainloop()
Your problem is that your <Leave> binding can fire if you move the mouse outside of the tiny circle faster than you can process the move. That causes the binding for <Motion> to be disabled.
My recommendation is to a) don't bind on <Leave> to disable the binding, and b) bind on <B1-Motion> so that the binding is active only while the button is pressed.
I am looking to make the background of the tkinter canvas transparent, but still have Mouse events of the canvas, here is my code, I am on Windows 10, Python 3.6:
from tkinter import *
import time
WIDTH = 500
HEIGHT = 500
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
global old
old = ()
tk = Tk()
tk.title('Virtual whiteboard')
tk.wm_attributes('-transparentcolor', TRANSCOLOUR)
canvas = Canvas(tk, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
def buttonmotion(evt):
global old
if old == ():
old = (evt.x, evt.y)
return
else:
canvas.create_line(old[0], old[1], evt.x, evt.y, width=LINEWIDTH)
old = (evt.x, evt.y)
def buttonclick(evt):
global old
canvas.create_line(evt.x-1, evt.y-1, evt.x, evt.y, width=LINEWIDTH)
old = (evt.x, evt.y)
canvas.bind('<Button-1>', buttonmotion)
canvas.bind('<B1-Motion>', buttonclick)
while True:
tk.update()
time.sleep(0.01)
When run the code, it makes a transparent background, but I select the things under, instead of the canvas.
I build a little workaround with the help of the win api, here is my suggestion:
from tkinter import *
import time
import win32gui
import win32api
WIDTH = 500
HEIGHT = 500
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))
Shot explanation of the new code bits:
tk.lift()
tk.wm_attributes("-topmost", True)
...
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
...
tk.bind('<Visibility>', putOnTop)
tk.focus()
These lines ensure, that the window will be always be on top of all other windows.
global HWND_t
HWND_t = 0
...
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
This code bit will go through all the windows currently displayed and catches the handle of the whiteboard window (make sure the title is unique, or this could capture the wrong handle).
state_left = win32api.GetKeyState(0x01)
...
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
This
Checks, if the handle is found
Checks, if mouse button 1 is clicked or not
Checks, if mouse is inside the window
if all is true, it takes the mouse data and draws the line
the current mode is, that it doesn't draw anything till the button is clicked and then draws until the button is clicked again.
I'm sure you've thought of this, but have you tried setting the hexadecimal colour as ""?
i.e
canvas = tk.Canvas(width, height, bg = "")
That works for my version of python.
from Tkinter import *
import random
root = Tk()
width = 700
height = 600
canvas = Canvas(root, width = width, height = height, bg = "light blue")
canvas.pack()
pipes = []
class NewPipe:
def __init__(self, pipe_pos, pipe_hole):
self.pipe_pos = list(pipe_pos)
def update(self):
self.pipe_pos[0] -= 3
self.pipe_pos[2] -= 3
def draw(self):
canvas.create_rectangle(self.pipe_pos, fill = "green")
def get_pos(self):
return self.pipe_pos
def generate_pipe():
pipe_hole = random.randrange(0, height)
pipe_pos = [width - 100, 0, width, pipe_hole]
pipes.append(NewPipe(pipe_pos, pipe_hole))
draw_items()
canvas.after(2000, generate_pipe)
def draw_items():
for pipe in pipes:
if pipe.get_pos()[2] <= 0 - 5:
pipes.remove(pipe)
else:
pipe.draw()
pipe.update()
canvas.after(100, draw_items)
def jump(press):
pass
canvas.bind("<Button-1>", jump)
canvas.after(2000, generate_pipe)
draw_items()
mainloop()
Right now I am trying to make a game where you have to dodge rectangles, which are pipes. It is basically Flappy Bird, but on Tkinter. In this code I am trying to generate pipes and move them, but the pipes I have drawn before do not leave and they just stay there. This means that when the pipe moves, the position it was just in doesnt change and that shape stays there. Is there any way to delete past shapes, or another way to move them?
canvas.create_rectangle(self.pipe_pos, fill = "green") returns an ID.
You can use this ID to put it into methods like
canvas.coords
canvas.delete
canvas.itemconfigure
canvas.scale
canvas.type
...
Have a look at help(canvas).
The canvas is not a framebuffer on which you paint stuff for one frame. The painted stuff does not go away and you can move it and change all the parameters you can use when creating.