Related
How do I make the square move when pressing the "d" button (for example) on the keyboard?
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
x = 0
y = 0
def snake(x, y):
field.create_rectangle(10, 20, 30, 40)
field.grid(row=x, column=y)
x += 1
y += 1
return(x, y)
root.bind("<KeyPress>", snake(x=x, y=y))
root.mainloop()
An easy way is to use event.char. It returns the character of the button pressed. Then check which button it was and move it if it is w,a,s,d -
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
rect = field.create_rectangle(10, 20, 30, 40)
field.grid(row=0, column=0)
def snake(event):
x = 0 # Default
y = 0
if event.char == 'w':
y = -10
if event.char == 'a':
x = -10
if event.char == 's':
y = 10
if event.char == 'd':
x = 10
field.move(rect,x,y)
root.bind("<Key>", snake)
root.mainloop()
This is one way you can do it:
import tkinter as tk
from tkinter import Canvas
root = tk.Tk()
root.title('Snake')
root.geometry("450x450")
w = 400
h = 400
x = w//2
y = h//2
field = Canvas(root, width=w, heigh=h, bg="white")
field.pack(pady=5)
my_rectangle = field.create_rectangle(10, 20, 30, 40)
def left(event):
x = -10
y = 0
field.move(my_rectangle, x, y)
def right(event):
x = 10
y = 0
field.move(my_rectangle, x, y)
def up(event):
x = 0
y = -10
field.move(my_rectangle, x, y)
def down(event):
x = 0
y = 10
field.move(my_rectangle, x, y)
root.bind("<Left>", left)
root.bind("<Right>", right)
root.bind("<Up>", up)
root.bind("<Down>", down)
root.mainloop()
First create root, and give geometry to it, in this case it is 450x450.
After that create variables that will store height and width, and coordinates x and y. Then, create canvas (field) and in canvas specify where canvas will be located and geometry of canvas (and background color). When canvas is created, we create rectangle. Functions left, right, up and down will cover movement of rectangle on canvas. field.move(my_rectangle, x, y) - this line of code will move my_rectangle on canvas by x and y, or by left, right up or down, depends what is passed. root.bind("<Left>", left) - bind left arrow key to left function. Thats why event is parameter of fucntion.
An alternative method
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
SIZE = 50
class Snake:
def __init__(self,x,y,canvas):
self.x = x
self.y = y
self.canvas = canvas
self.direction = (SIZE,0)
def keypress(self,event):
if event.keysym == 'Right':
self.direction = (SIZE,0)
if event.keysym == 'Left':
self.direction = (-SIZE,0)
if event.keysym == 'Up':
self.direction = (0,-SIZE)
if event.keysym == 'Down':
self.direction = (0,SIZE)
def move(self):
self.x += self.direction[0]
self.y += self.direction[1]
def redraw(self):
self.canvas.delete('snake')
self.move()
self.canvas.create_rectangle(self.x,self.y,self.x+SIZE,self.y+SIZE,fill="green",tag="snake")
def update_screen():
snake.redraw()
root.after(1000,update_screen)
snake = Snake(0,0,field)
root.bind("<KeyPress>", lambda event: snake.keypress(event))
root.after_idle(update_screen)
field.grid()
root.mainloop()
This uses an object for the snake with methods to react to key presses and redraw the snake. This deletes the existing rectangles and draws new ones rather than moving (might prove more useful for showing the different snake segments).
Advantage of this method, is that the snake can be made to move regardless of whether the user has pressed a button or not. If no button has been pressed the snake will continue in the same direction.
Once you have a more complex game, I'd expect your Snake class to contain a list of the different snake segments that get drawn each time. When the snake moves over a fruit, a new segment is added. See below for a moving snake with multiple segments
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
SIZE = 10
class Snake:
def __init__(self,x,y,canvas):
self.x = x
self.y = y
self.canvas = canvas
self.direction = (SIZE,0)
self.segments = [(self.x,self.y)]
self.length = 5
def keypress(self,event):
if event.keysym == 'Right':
self.direction = (SIZE,0)
if event.keysym == 'Left':
self.direction = (-SIZE,0)
if event.keysym == 'Up':
self.direction = (0,-SIZE)
if event.keysym == 'Down':
self.direction = (0,SIZE)
def move(self):
self.x += self.direction[0]
self.y += self.direction[1]
new_segment = (self.segments[-1][0]+self.direction[0],self.segments[-1][1]+self.direction[1])
self.segments.append(new_segment)
if len(self.segments) > self.length:
self.segments.pop(0)
def redraw(self):
self.canvas.delete('snake')
self.move()
for seg in self.segments:
self.canvas.create_rectangle(seg[0],seg[1],seg[0]+SIZE,seg[1]+SIZE,fill="green",tag="snake")
#self.canvas.create_rectangle(self.x,self.y,self.x+SIZE,self.y+SIZE,fill="green",tag="snake")
def update_screen():
snake.redraw()
root.after(1000,update_screen)
snake = Snake(0,0,field)
root.bind("<KeyPress>", lambda event: snake.keypress(event))
root.after_idle(update_screen)
field.grid()
root.mainloop()
I am working on a homework problem for my Python course that is supposed to display a car (got that) and then the car is supposed to simulate driving across the window until it reaches the end, and then restarting at the beginning. I have a race function that is supposed to be doing this but it's not doing anything. I'm having a hell of a time with classes and tkinter. I know that I need the value of x to be incremented each time it loops and updates so that the car looks like its moving, I'm just not sure how to implement that. Is my boolean not written correctly? I've tried changing things around a few different ways with the function but I can't seem to get it to do anything, so I must be missing something.
UPDATE: I have moved the race method into the class, and called it within the constructor. I put a print statement to get the value of x in the race method and its showing that x is being incremented correctly, but when I run the program my car disappears. So it's updating the value of x as it should, but its not displaying the graphic.
Code updated below
Any suggestions are appreciated!
# Import tkinter
from tkinter import *
# Set the height and Width of the window
width = 800
height = 800
# Create race car class with canvas as the argument
class RacingCar(Canvas):
# Constructor
def __init__(self, master, width, height):
# Constructor
Canvas.__init__(self, master, width = width, height = height)
# Create x and y variables for starting position
self.x = 10
self.y = 40
# Display the car
self.display_car()
self.race()
# Function to display car
def display_car(self):
# Delete original car
self.delete("car")
# Create first wheel
self.create_oval(self.x + 10, self.y - 10, self.x + 20,\
self.y, fill = "black", tags = "car")
# Create the second wheel
self.create_oval(self.x + 30, self.y - 10, self.x + 40,\
self.y, fill = "black", tags = "car")
# Create the body
self.create_rectangle(self.x, self.y - 20, self.x + 50,\
self.y - 10, fill = "green", tags = "car")
# Create the roof
self.create_polygon(self.x + 10, self.y - 20, self.x + 20,\
self.y - 30, self.x + 30, self.y - 30,\
self.x + 40, self.y - 20, fill = "green",\
tags = "car")
def race():
while True:
if self.x < width:
self.x += 2
else:
self.x = 0
self.after(88)
self.update()
window = Tk()
window.title("Racing Car")
racecar = RacingCar(window, width = 240, height = 50 )
racecar.pack()
window.mainloop()
This might not get it done (so I make this answer a community wiki), but I suggest you move the race method in the class:
class RaceCar(Canvas):
# Your current code
def race(self):
while True:
if self.x < width:
self.x += 2
else:
self.x = 0
self.after(88)
self.update()
i begin to use TKinter and i try to make multiple boucing balls as a training exercise. When i create an unique oval and make it move, everything works but when i create a Ball class with a move method in it, the ball doesn't move at all. I receive no error message but it refuse to move.
"""
Initialisation
"""
from tkinter import *
import time
w_height = 600
w_width = 800
xspeed = 10
yspeed = 10
window = Tk()
window.geometry ("{}x{}".format(w_width, w_height))
window.title("Bouncing Balls")
canvas = Canvas(window, width = w_width - 50,height = w_height - 50, bg="black")
"""
If i create the ball like that and make it move it works fine
"""
ball1 = canvas.create_oval(10, 10, 50, 50, fill ="red")
while True:
canvas.move(ball1, xspeed, yspeed)
window.update()
time.sleep(0.05)
"""
But if i try to create the ball using a class it doesn't work anymore...
"""
class Ball:
def __init__(self, posx, posy, r):
canvas.create_oval(posx-r, posy-r, posx+r, posy+r, fill ="red")
def move(self, dx, dy):
canvas.move(self, dx, dy)
ball1 = Ball(50,50,10)
while True:
ball1.move(xspeed, yspeed)
window.update()
time.sleep(0.05)
I tought it would give the same result but in the first case the ball moves and in the second it doesn't and i can't figure out why.
In your code, canvas.create_oval() function returns an object that can be then moved my called canvas.move(object, ...) function. But as you can see you are passing self in the class method move.
def move(self, dx, dy):
canvas.move(*self*, dx, dy)
This is the instance of the class Ball, in this case ball1 variable, that you defined(actually reassigned) by doing ball1 = Ball(50, 50, 10).
To get this working change your class to this.
class Ball:
def __init__(self, posx, posy, r):
self.ball = canvas.create_oval(posx-r, posy-r, posx+r, posy+r, fill ="red")
def move(self, dx, dy):
canvas.move(self.ball, dx, dy)
Here you define a class field that will get what returns canvas.create_oval() function and then use it to move the object.
I am trying to animate a ball, that is be moving forward. In addition, if the user presses up or down button, it should move respectively.
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class Movement:
def __init__(self, _frame, _canvas):
self.frame = _frame
self.canvas = _canvas
self.x = 10
self.y = 150
self.count = 0
self.update()
self.frame.bind_all("<Up>", lambda event: self.move_up())
self.frame.bind_all("<Down>", lambda event: self.move_down())
pass
def move_up(self):
self.y -= 10
pass
def move_down(self):
self.y += 10
pass
def update(self):
canvas.delete("obj")
self.count += 10
x2 = self.x + 50
y2 = self.y + 50
self.canvas.create_oval(self.x + self.count, self.y, x2 + self.count, y2, fill="red", tag="obj")
root.after(1000, self.update)
pass
root = tk.Tk()
width = 400
height = 400
canvas = tk.Canvas(root, width=width, height=height)
frame = tk.Frame(root, width=width, height=height)
if __name__ == '__main__':
Movement(frame, canvas)
canvas.grid()
root.mainloop()
There's an issue, though. Given the fact that the ball is moving every 1 second, it doesn't respond to the key press fast enough. As a result, if you click the up kick, the screen is updating after one second.
I know there's a .move() function inside Tkinter. However, due to many reason I cannot use that.
My main concern is the interval it take for the screen to be updated.
From what I understand you want to be able to keep the timer moving forward consistent but also be able to move up and down at any point even outside of the timer.
to accomplish this I moved the timer to its own function and allowed the up down functions to call the object destroy/create function.
I have updated you code to reflect that.
Take a look at this code and let me know if it helps.
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class Movement:
def __init__(self, _frame, _canvas):
self.frame = _frame
self.canvas = _canvas
self.x = 10
self.y = 150
self.count = 0
self.object_timer()
self.frame.bind_all("<Up>",lambda event: self.move_up())
self.frame.bind_all("<Down>",lambda event: self.move_down())
def move_up(self):
self.y -= 10
self.update()
def move_down(self):
self.y += 10
self.update()
def update(self):
canvas.delete("obj")
x2 = self.x + 50
y2 = self.y + 50
self.canvas.create_oval(self.x + self.count, self.y, x2 + self.count, y2, fill="red", tag="obj")
def object_timer(self):
self.update()
self.count += 10
root.after(1000, self.object_timer)
root = tk.Tk()
width = 400
height = 400
canvas = tk.Canvas(root, width=width, height=height)
frame = tk.Frame(root, width=width, height=height)
if __name__ == '__main__':
Movement(frame, canvas)
canvas.grid()
root.mainloop()
It's all just math. You can call update twice as much, and move the ball half as much if you want to keep the ball moving at the same speed while reacting to keypresses faster.
For example, instead of moving 10 pixels every second, have it move 1 pixel every tenth of a second. The end result is still 10 pixels every second. The animation will be smoother, and it will react to keypresses much faster.
By the way, there's no need to delete and recreate the oval every iteration. It's possible to move items on the canvas. Deleting and recreating will eventually cause performance problems do to how the canvas is implemented internally. Each object you create gets a new unique id, and the canvas will slow down when it has a large number of ids. The canvas does not re-use an id.
I would also recommend against using lambda. Unless you need it, it adds complexity without adding any value. Just add an extra parameter to move_up and move_down:
self.frame.bind_all("<Up>",self.move_up)
self.frame.bind_all("<Down>", self.move_down)
...
def move_up(self, event=None):
...
def move_down(self, event=None):
...
I am trying to create a simple tactical RPG in python, similar to Fire Emblem or Advanced Wars, but in ASCII. I have been following this tutorial, which uses the libtcod module, and modifying the tutorial code for my own purposes
Right now I am struck trying to display movement tiles for each character. Basically I want the game to display the tiles each character can move to, whenever the user clicks his mouse over that character. I have created a function (get_total_squares) that generates the coordinates of the tiles to be highlighted based on the character's coordinates and movement range (hard-coded to 5, for now) and a function (show_move_tiles) to highlight these tiles. To record the click coordinates I have another function target_tile and a function to see if the coordinates match with a character's (target_character)
The program runs without errors, but clicking the mouse over a character does nothing (no tiles are shown).
Here is the code:
import libtcodpy as libtcod
#actual size of the window
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50
#size of the map
MAP_WIDTH = 80
MAP_HEIGHT = 43
LIMIT_FPS = 20 #20 frames-per-second maximum
color_dark_wall = libtcod.Color(0, 0, 50)
color_dark_ground = libtcod.Color(5, 50, 0)
color_move_tile = libtcod.Color (100, 0, 0)
class Tile:
#a tile of the map and its properties
def __init__(self, blocked, block_sight = None):
self.blocked = blocked
#by default, if a tile is blocked, it also blocks sight
if block_sight is None: block_sight = blocked
self.block_sight = block_sight
def target_tile():
#return the position of a tile left-clicked, or (None,None) if right-clicked.
global key, mouse
while True:
libtcod.console_flush()
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE, key, mouse)
render_all()
(x, y) = (mouse.cx, mouse.cy)
if mouse.rbutton_pressed or key.vk == libtcod.KEY_ESCAPE:
return (None, None) #cancel if the player right-clicked or pressed Escape
if mouse.lbutton_pressed:
return (x, y)
print "works!"
def target_character():
#Shows movement tiles if character is on click-coordinate
(x, y) = target_tile()
#return the first clicked character
for obj in objects:
if obj.x == x and obj.y == y:
obj.show_move_tiles()
def make_map():
global map
#fill map with "unblocked" tiles
map = [[ Tile(False)
for y in range(MAP_HEIGHT) ]
for x in range(MAP_WIDTH) ]
map[30][22].blocked = True
map[30][22].block_sight = True
map[50][22].blocked = True
map[50][22].block_sight = True
class Object:
#this is a generic object: the player, a monster, an item, the stairs...
#it's always represented by a character on screen.
def __init__(self, x, y, char, color):
self.x = x
self.y = y
self.char = char
self.color = color
def move(self, dx, dy):
#move by the given amount
if not map[self.x + dx][self.y + dy].blocked:
self.x += dx
self.y += dy
def draw(self):
#set the color and then draw the character that represents this object at its position
libtcod.console_set_default_foreground(con, self.color)
libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
def show_move_tiles(self):
global mouse
global color_move_tile
(x,y) = (mouse.cx, mouse.cy)
coord_in_range = get_total_squares(5, self.x, self.y)
for [x,y] in coord_in_range:
if [x,y] is not [self.x, self.y]:
libtcod.console_set_char_background(con, x, y, color_move_tile, libtcod.BKGND_SET)
def clear(self):
#erase the character that represents this object
libtcod.console_put_char_ex(con, self.x, self.y, '.', libtcod.white, libtcod.black)
def handle_keys():
#key = libtcod.console_check_for_keypress() #real-time
global key #turn-based
if key.vk == libtcod.KEY_ENTER and key.lalt:
#Alt+Enter: toggle fullscreen
libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
elif key.vk == libtcod.KEY_ESCAPE:
return True #exit game
def get_total_squares(dist, playerx, playery):
coord_in_range = []
for x in range(-dist,dist+1):
for y in range(-dist, dist+1):
if abs(x)+abs(y) <= dist:
coord_in_range.append([playerx+x, playery+y])
return coord_in_range
def render_all():
global color_dark_wall
global color_dark_ground
#go through all tiles, and set their background color
for y in range(MAP_HEIGHT):
for x in range(MAP_WIDTH):
wall = map[x][y].block_sight
if wall:
libtcod.console_put_char_ex(con, x, y, '#', libtcod.white, libtcod.black)
else:
libtcod.console_put_char_ex(con, x, y, '.', libtcod.white, libtcod.black)
#draw all objects in the list and show movement if mouse hovers over player
for item in objects:
item.draw()
if mouse.cx == item.x and mouse.cy == item.y:
item.show_move_tiles()
#blit the contents of "con" to the root console
libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
#############################################
# Initialization & Main Loop
#############################################
libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'python/libtcod tutorial', False)
libtcod.sys_set_fps(LIMIT_FPS)
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)
#create object representing the player
player = Object(30, 15, '#', libtcod.white)
#create an NPC
npc = Object(SCREEN_WIDTH/2 - 5, SCREEN_HEIGHT/2, '#', libtcod.yellow)
#the list of objects with those two
objects = [npc, player]
make_map()
mouse = libtcod.Mouse()
key = libtcod.Key()
while not libtcod.console_is_window_closed():
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE,key,mouse)
#render the screen
render_all()
target_tile()
libtcod.console_flush()
#erase all objects at their old locations, before they move
for object in objects:
object.clear()
#handle keys and exit game if needed
exit = handle_keys()
if exit:
break
EDIT: I solved the previous problem of the movement tiles not showing up when I hovered the mouse over the character, but have updated the problem with a new one: trying to toggle the movement tiles on and off with a mouse click