I am trying to make 2D shooter with Python tkinter.
Here are my progress:
from tkinter import *
root = Tk()
c = Canvas(root, height=500, width=500, bg='blue')
c.pack()
circle1x = 250
circle1y = 250
circle2x = 250
circle2y = 250
circle1 = c.create_oval(circle1x, circle1y, circle1x + 10, circle1y + 10, outline='white')
circle2 = c.create_rectangle(circle2x, circle2y,circle2x + 10, circle2y + 10)
pos1 = c.coords(circle1)
pos2 = c.coords(circle2)
c.move(circle1, 250 - pos1[0], 250 - pos1[2])
c.move(circle2, 250 - pos1[0], 250 - pos1[2])
beginWall = c.create_rectangle(0, 200, 500, 210, outline='white')
def move_circle(event):
pass
c.bind('<Motion>', move_circle)
root.mainloop()
But I am trying to make the function called move_circle make circle1 and circle2 follow the mouse pointer . Something like this c.goto(circle1, x, y).
You can do it by modifying the coordinates of the two "circles" in the move_circle() event handler function. A simple calculation is done to make it so the centers of these two objects are positioned at the "tip" of the mouse pointer (see image below).
Note, I also modified your code to more closely follow the PEP 8 - Style Guide for Python Code coding guidelines.
import tkinter as tk
# Constants
CIRCLE1_X = 250
CIRCLE1_Y = 250
CIRCLE2_X = 250
CIRCLE2_Y = 250
SIZE = 10 # Height and width of the two "circle" Canvas objects.
EXTENT = SIZE // 2 # Their height and width as measured from center.
root = tk.Tk()
c = tk.Canvas(root, height=500, width=500, bg='blue')
c.pack()
circle1 = c.create_oval(CIRCLE1_X, CIRCLE1_Y,
CIRCLE1_X + SIZE, CIRCLE1_Y + SIZE,
outline='white')
circle2 = c.create_rectangle(CIRCLE2_X, CIRCLE2_Y,
CIRCLE2_X + SIZE, CIRCLE2_Y + SIZE)
pos1 = c.coords(circle1)
pos2 = c.coords(circle2)
c.move(circle1, 250-pos1[0], 250-pos1[2])
c.move(circle2, 250-pos1[0], 250-pos1[2])
begin_wall = c.create_rectangle(0, 200, 500, 210, outline='white')
def move_circles(event):
# Move two "circle" widgets so they're centered at event.x, event.y.
x0, y0 = event.x - EXTENT, event.y - EXTENT
x1, y1 = event.x + EXTENT, event.y + EXTENT
c.coords(circle1, x0, y0, x1, y1)
c.coords(circle2, x0, y0, x1, y1)
c.bind('<Motion>', move_circles)
root.mainloop()
Here's a screenshot of it running on my Windows computer:
Related
I want to create a rectangle with a checkbutton on a canvas, and then having sliders to adjust the height and width of this rectangle, but it seems that the rectangle I create does not carry over to the other functions, as I just create the triangle, and when adjusting the height and width, nothing happens.
from tkinter import *
def rect():
rectangle = canvas.create_rectangle(20,50, 40, 10, fill="green")
def width(e, rectangle):
x0, y0, x1, y1 = canvas.coords(rectangle) # get the coords of rect
x1 = float(e) # calc new coords
canvas.coords(rectangle, x0, y0, x1, y1) # set new coords
def height(h, rectangle):
x0, y0, x1, y1 = canvas.coords(rectangle)
y1 = float(h) + 40
canvas.coords(rectangle, x0,y0,x1,y1)
root = Tk()
frame = Frame(root)
frame.pack()
create_rect = Checkbutton(frame, text='rect', variable=IntVar, command = rect)
create_rect.pack()
slider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="blue",command = width)
slider.pack()
slider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="green",command = height)
slider.pack()
canvas = Canvas(root,height=500,width=360)
canvas.pack()
root.mainloop()
In order to access rectangle outside rect(), you can make it a global variable. Below is the modified code:
from tkinter import *
rectangle = None # initialize rectangle
def rect():
# tell Python rectangle is a global variable
global rectangle
if cbvar.get() == 1:
# create the rectangle if checkbutton is checked
if rectangle is None:
rectangle = canvas.create_rectangle(20, 10, 20+wslider.get(), 10+hslider.get(), fill="green")
else:
# destroy the rectangle if checkbutton is not checked
if rectangle:
canvas.delete(rectangle)
rectangle = None
def width(e):
# make sure rectangle exists
if rectangle:
x0, y0, x1, y1 = canvas.coords(rectangle) # get the coords of rect
x1 = x0 + float(e) # calc new coords
canvas.coords(rectangle, x0, y0, x1, y1) # set new coords
def height(h):
# make sure rectangle exists
if rectangle:
x0, y0, x1, y1 = canvas.coords(rectangle)
y1 = y0 + float(h)
canvas.coords(rectangle, x0, y0, x1, y1)
root = Tk()
frame = Frame(root)
frame.pack()
cbvar = IntVar()
create_rect = Checkbutton(frame, text='rect', variable=cbvar, command=rect)
create_rect.pack()
wslider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="blue", command=width)
wslider.pack()
hslider = Scale(frame, from_=10 , to=100, orient = HORIZONTAL, bg="green", command=height)
hslider.pack()
canvas = Canvas(root, height=500, width=360)
canvas.pack()
root.mainloop()
I'm a newbie in programming, trying to learn python and I decided to make Tic Tac Toe for my first project. I've made functions for drawing X and O, but I have trouble drawing them in the box I'm clicking. Here's the code so far:
ttt = Tk()
ttt.title("Tic Tac Toe")
w = Canvas(ttt, width = 902, height = 902)
w.configure (bg = "white")
w.pack()
m = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
def drawx(event):
x, y = event.x, event.y
w.create_line(49, 49, 249, 249, fill = "black")
w.create_line(49, 249, 249, 49, fill = "black")
def drawo(event):
x2, y2 = event.x, event.y
x0 = x2 - 100
y0 = y2 - 100
x1 = x2 + 100
y1 = y2 + 100
return w.create_oval(x0, y0, x1, y1)
w.create_line(0, 300, 901, 300, fill = "black")
w.create_line(0, 601, 901, 601, fill = "black")
w.create_line(300, 0, 300, 901, fill = "black")
w.create_line(601, 0, 601, 901, fill = "black")
Here, the shapes will be drawn based on my cursor coordinates, which I know should be modified. Any help or suggestion would be appreciated.
You can "discretize" the x and y values by integer-dividing and then multiplying with the width of the individual cells (and adding some offset for the center).
def drawo(event):
x2, y2 = event.x, event.y
x2 = x2 // 300 * 300 + 150
y2 = y2 // 300 * 300 + 150
...
Same for drawx.
Alternatively, you could use different canvas elements (or buttons, labels or similar) for the different cells in the grid and get the clicked widget from the event.
You need to bind the mouse click event to canvas:
w.bind('<Button-1>', on_click)
Then determine which cell is clicked in on_click handler:
SIZE = 300
player = 1 # 1 for O, 2 for X
def on_click(event):
global m
global player
row = event.y // SIZE
col = event.x // SIZE
# check whether the cell is not filled yet
if m[row][col] == 0:
# calculate the center of the cell
cx = col * SIZE + SIZE // 2
cy = row * SIZE + SIZE // 2
# draw X or O based on current player
if player == 1:
draw_O(cx, cy)
else:
draw_X(cx, cy)
# set cell is filled
m[row][col] = player
# now you need to check whether current player wins
...
# if no one wins, toggle player
player = 2 if player == 1 else 1
def draw_O(x, y):
radius = SIZE // 3
w.create_oval(x-radius, y-radius, x+radius, y+radius, width=5, tag='cell') # tag is used for resetting the game
def draw_X(x, y):
radius = SIZE // 3
w.create_line(x-radius, y-radius, x+radius, y+radius, width=5, tag='cell')
w.create_line(x+radius, y-radius, x-radius, y+radius, width=5, tag='cell')
If you want to reset the game:
def reset_game():
global m
global player
# remove all 'O' and 'X'
w.delete('cell')
m = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
player = 1
For better design, put all the above logic in a class (inherited from Canvas). Then you can use instance variables instead of global variables.
To add more detail to tobias_k's answer, the below code will draw an X when the right mouse button is clicked and an O when the left mouse button is clicked.
An improvement in the code could be to use a variable to define the size of the canvas and each X/O rather than hard coding 200 or 300.
from tkinter import *
ttt = Tk()
ttt.title("Tic Tac Toe")
w = Canvas(ttt, width = 902, height = 902)
w.configure (bg = "white")
w.pack()
m = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
def drawx(event):
print("drawx")
x, y = event.x, event.y
x0 = x // 300 * 300 + 50
y0 = y // 300 * 300 + 50
x1 = x0 + 200
y1 = y0 + 200
#return w.create_oval(x0, y0, x1, y1)
w.create_line(x0,y0,x1,y1, fill = "black")
w.create_line(x0,y0+200,x1,y1-200, fill = "black")
def drawo(event):
print("drawo")
x, y = event.x, event.y
x0 = x // 300 * 300 + 50
y0 = y // 300 * 300 + 50
x1 = x0 + 200
y1 = y0 + 200
return w.create_oval(x0, y0, x1, y1)
w.create_line(0, 300, 901, 300, fill = "black")
w.create_line(0, 601, 901, 601, fill = "black")
w.create_line(300, 0, 300, 901, fill = "black")
w.create_line(601, 0, 601, 901, fill = "black")
w.bind('<Button-1>',drawo)
w.bind('<Button-3>',drawx)
ttt.mainloop()
I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:
import tkinter
class celestial:
def __init__(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1
top = tkinter.Tk()
c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()
sol = c.create_oval(coord_sol, fill='black', outline='white')
top.mainloop()
Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).
import math
try:
import tkinter as tk
except ImportError:
import Tkinter as tk # Python 2
DELAY = 100
CIRCULAR_PATH_INCR = 10
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
class Celestial(object):
# Constants
COS_0, COS_180 = cos(0), cos(180)
SIN_90, SIN_270 = sin(90), sin(270)
def __init__(self, x, y, radius):
self.x, self.y = x, y
self.radius = radius
def bounds(self):
""" Return coords of rectangle surrounding circlular object. """
return (self.x + self.radius*self.COS_0, self.y + self.radius*self.SIN_270,
self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)
def circular_path(x, y, radius, delta_ang, start_ang=0):
""" Endlessly generate coords of a circular path every delta angle degrees. """
ang = start_ang % 360
while True:
yield x + radius*cos(ang), y + radius*sin(ang)
ang = (ang+delta_ang) % 360
def update_position(canvas, id, celestial_obj, path_iter):
celestial_obj.x, celestial_obj.y = next(path_iter) # iterate path and set new position
# update the position of the corresponding canvas obj
x0, y0, x1, y1 = canvas.coords(id) # coordinates of canvas oval object
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2 # current center point
dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy # amount of movement
canvas.move(id, dx, dy) # move canvas oval object that much
# repeat after delay
canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)
top = tk.Tk()
top.title('Circular Path')
canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()
sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)
orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter) # prime generator
top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()
Here's what it looks like running:
I am currently using the following code to create a grid the size of the window within python using the tkinter module
import tkinter as tk
class Frame():
def __init__(self, *args, **kwargs):
# Setup Canvas
self.c = tk.Canvas(root, height=500, width=500, bg='white')
self.c.pack(fill=tk.BOTH, expand=True)
self.c.bind('<Configure>', self.createGrid)
self.pixel_width = 20
self.pixel_height = 20
# Setup binds
self.c.bind("<ButtonPress-1>", self.leftClick)
def leftClick(self, event):
items = self.c.find_closest(event.x, event.y)
if items:
rect_id = items[0]
self.c.itemconfigure(rect_id, fill="red")
def createGrid(self, event=None):
for x in range(0, self.c.winfo_width()):
for y in range(0, self.c.winfo_height()):
x1 = (x * self.pixel_width)
x2 = (x1 + self.pixel_width)
y1 = (y * self.pixel_height)
y2 = (y1 + self.pixel_height)
self.c.create_rectangle(x1,y1,x2,y2)
self.c.update()
root = tk.Tk()
gui = Frame(root)
root.mainloop()
If I set the canvas height and width to something like 50 this loads quite quickly, although when the size is increased to 500 x 500 like is set here it takes about 5 seconds to create the grid. I have tried creating the grid with lines but the problem with that is I need squares as I am then planning to change the colour of a square that is selected. Is there any way I can make this more efficient?
I think you created way more rectangles than you need. The below two lines:
for x in range(0, self.c.winfo_width()):
for y in range(0, self.c.winfo_height()):
Will create 504x504 rectangles = 254016. It will work fine if you reduce it to just fill your current screen:
def createGrid(self, event=None):
for x in range(0, int(self.c.winfo_width()/20+1)):
for y in range(0, int(self.c.winfo_height()/20+1)):
x1 = (x * self.pixel_width)
x2 = (x1 + self.pixel_width)
y1 = (y * self.pixel_height)
y2 = (y1 + self.pixel_height)
self.c.create_rectangle(x1,y1,x2,y2)
self.c.update()
I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:
import tkinter
class celestial:
def __init__(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1
top = tkinter.Tk()
c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()
sol = c.create_oval(coord_sol, fill='black', outline='white')
top.mainloop()
Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).
import math
try:
import tkinter as tk
except ImportError:
import Tkinter as tk # Python 2
DELAY = 100
CIRCULAR_PATH_INCR = 10
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
class Celestial(object):
# Constants
COS_0, COS_180 = cos(0), cos(180)
SIN_90, SIN_270 = sin(90), sin(270)
def __init__(self, x, y, radius):
self.x, self.y = x, y
self.radius = radius
def bounds(self):
""" Return coords of rectangle surrounding circlular object. """
return (self.x + self.radius*self.COS_0, self.y + self.radius*self.SIN_270,
self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)
def circular_path(x, y, radius, delta_ang, start_ang=0):
""" Endlessly generate coords of a circular path every delta angle degrees. """
ang = start_ang % 360
while True:
yield x + radius*cos(ang), y + radius*sin(ang)
ang = (ang+delta_ang) % 360
def update_position(canvas, id, celestial_obj, path_iter):
celestial_obj.x, celestial_obj.y = next(path_iter) # iterate path and set new position
# update the position of the corresponding canvas obj
x0, y0, x1, y1 = canvas.coords(id) # coordinates of canvas oval object
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2 # current center point
dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy # amount of movement
canvas.move(id, dx, dy) # move canvas oval object that much
# repeat after delay
canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)
top = tk.Tk()
top.title('Circular Path')
canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()
sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)
orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter) # prime generator
top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()
Here's what it looks like running: