Strange things with tkinter's canvas.after() - python

Ive been having some issues where if the shot is fired multiple times before it leaves the canvas it will speed up each time it is redrawn until its faster than the canvas can update it. Ive attributed the problem to the canvas.after command because if I impliment this as a while loop using the time.sleep command it works fine (unfortunately I can use the implementation of the code because it works as two separate loops)
#imports
from tkinter import *
#The Game
class Game:
def __init__(self):
#creating the static canvas background
self.window = Tk()
self.window.title('Shoot your friends')
self.canvas = Canvas(width= 900,
height= 900,
cursor= 'circle')
self.canvas.pack()
self.canvas.create_line(450, 900,
450, 0,
dash = (10))
self.p1_ship = PhotoImage(file = "red_ship.gif")
self.p2_ship = PhotoImage(file = "blue_ship.gif")
self.p1_laser = PhotoImage(file = "red_laser.gif")
self.p2_laser = PhotoImage(file = "blue_laser.gif")
#Buttons at the bottom
self.frame = Frame(self.window)
self.frame.pack()
#Determining the state of edge teleporting and toggling it
self.etb = True
def et():
if self.etb == True:
self.etb = False
Et["text"] = "Turn On Edge Teleporting"
else:
self.etb = True
Et["text"] = "Turn Off Edge Teleporting"
print ("Edge Telepoting Toggled")
Et = Button(self.frame, text="Turn Off Edge Teleporting", command = et, cursor= 'double_arrow')
Et.grid(row=0,column=0)
self.Rfb = False
def rf():
if self.Rfb == True:
self.Rfb = False
Rf["text"] = "Turn On Rapid Fire "
else:
self.Rfb = True
Rf["text"] = "Turn Off Rapid Fire"
print ("Rapid Fire Toggled")
Rf = Button(self.frame, text="Turn On Rapid Fire", command = rf, cursor= 'cross')
Rf.grid(row=0,column=1)
def restart():
print ('restart')
restart_b = Button(self.frame, text="Restart Game", command = restart, fg='Blue', bg= 'red', cursor='exchange' )
restart_b.grid(row=0,column=2)
self.y_p1 = 400
self.y_p2 = 400
self.ship_p1 = self.canvas.create_image(40, 450, image=self.p1_ship)
self.ship_p2 = self.canvas.create_image(860, 450, image=self.p2_ship)
self.canvas.move(self.ship_p1,0,0)
self.canvas.move(self.ship_p2,0,0)
# Functions that handle movement of the ships taking into account multiple variables
#For example If edge teleporting is ON the ship will teleport to the top of the screen if it is at the bottom and the down key is pressed and vice versa
#My implementation of this may not be the most efficient but I like the options it gives me for adding future features and it looks cool.
def p1_up(event):
if self.etb == True and self.y_p1 >= 100:
self.canvas.move(self.ship_p1,0,-100)
self.y_p1 += -100
elif self.etb == True:
self.canvas.move(self.ship_p1,0,+800)
self.y_p1 += +800
elif self.y_p1 >= 100:
self.canvas.move(self.ship_p1,0,-100)
self.y_p1 += -100
def p1_down(event):
if self.etb == True and self.y_p1 <= 799:
self.canvas.move(self.ship_p1,0,+100)
self.y_p1 += 100
elif self.etb == True:
self.canvas.move(self.ship_p1,0,-800)
self.y_p1 += -800
elif self.y_p1 <= 799:
self.canvas.move(self.ship_p1,0,+100)
self.y_p1 += 100
def p2_up(event):
if self.etb == True and self.y_p2 >= 100:
self.canvas.move(self.ship_p2,0,-100)
self.y_p2 += -100
elif self.etb == True:
self.canvas.move(self.ship_p2,0,+800)
self.y_p2 += +800
elif self.y_p2 >= 100:
self.canvas.move(self.ship_p2,0,-100)
self.y_p2 += -100
def p2_down(event):
if self.etb == True and self.y_p2 <= 799:
self.canvas.move(self.ship_p2,0,+100)
self.y_p2 += 100
elif self.etb == True:
self.canvas.move(self.ship_p2,0,-800)
self.y_p2 += -800
elif self.y_p2 <= 799:
self.canvas.move(self.ship_p2,0,+100)
self.y_p2 += 100
# Functions for shooting
self.p1_shot_out = False
self.p2_shot_out = False
def p1_shoot(event):
if self.p1_shot_out == True:
self.canvas.delete(self.laser_p1)
#draws the laser
self.laser_p1 = self.canvas.create_image(50, self.y_p1 +50, image=self.p1_laser)
self.x_p1_laser = 50
self.p1_shot_out = True
self.window.after(1, p1_shoot_move)
def p1_shoot_move():
#moves the laser until its outside the canvas
if self.x_p1_laser >= 930:
pass
else:
self.canvas.move(self.laser_p1,5,0)
self.x_p1_laser += 5
self.canvas.update()
self.window.after(3, p1_shoot_move)
def p2_shoot(event):
if self.p2_shot_out == True:
self.canvas.delete(self.laser_p2)
#draws the laser
self.laser_p2 = self.canvas.create_image(750, self.y_p2 +50, image=self.p2_laser)
self.x_p2_laser = 750
self.p2_shot_out = True
self.window.after(4, p2_shoot_move)
def p2_shoot_move():
#moves the laser until its outside the canvas
if self.x_p2_laser <= -110:
pass
else:
self.canvas.move(self.laser_p2,-5,0)
self.x_p2_laser += -5
self.canvas.update()
self.window.after(4, p2_shoot_move)
# Key bindings that trigger their respective functions
self.canvas.bind('w', p1_up)
self.canvas.bind('s', p1_down)
self.canvas.bind('<Up>', p2_up)
self.canvas.bind('<Down>', p2_down)
self.canvas.bind('<space>', p1_shoot)
self.canvas.bind('<Control_R>', p2_shoot)
self.canvas.focus_set()
# this mainloop thing is some sort of witchcraft! OH MY!!!
self.window.mainloop()
Game()

The problem is that each time you "shoot", you call after. If you "shoot" five times in rapid succession, you'll have 5 "moves" queued up. Each adds 5 to the x location, and they all will run just a few ms apart, so the laser will appear to be moving very fast. The five will run in rapid succession, then 3ms later they will run in rapid succession again, and so on.
What you need to do is keep a reference to the thing you schedule with after, and cancel it before starting a new shot. That, or keep an independent record of the x/y coordinate for each shot if you want more than one shot being active at a time.
For example:
def p1_shoot(event):
....
# cancel any previous shot
if self.p1_job is not None:
self.window.after_cancel(self.p1_job)
self.p1_job = self.window.after(1, p1_shoot_move)
Of course, you need to set self.p1_job every time you call after, and you need to initialize it to None at the start, and when the bullet is destroyed.

Related

Every time I run this function it is slower and slower

I am trying to do code a simple game in python using tkinter where a block jumps over obstacles, however I got stuck on the jumping part. Every time I call the jump function it jumps slower and slower, and I don't know the reason. Ty in advance.
import time
import tkinter
import random
bg = "white"
f = 2
k=0
t = 0.01
groundLevel = 550
root = tkinter.Tk()
root.geometry("1000x600")
canvas = tkinter.Canvas(root,width = 1000,height = 1000,bg = bg)
canvas.pack(fill= tkinter.BOTH, expand= True)
posX = 50
posY= 530
startButton = tkinter.Button(canvas,text=" Start ")
def startPlayer(xx,yy):
canvas.create_rectangle(xx-20,yy-22,xx+20,yy+18,fill = "orange")
return(xx,yy)
def move(x,y,x2,y2,direction,fill,outline):
global f
#direction 0 = up
#direction 1 = down
#direction 2 = left
#direction 3 = right
if direction == 0:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y-f,x2,y2-f,fill=fill,outline=outline)
if direction == 1:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y+f,x2,y2+f,fill=fill,outline=outline)
if direction == 2:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y,x2,y2,fill=fill,outline=outline)
if direction == 3:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y,x2,y2,fill=fill,outline=outline)
def playerJump():
global groundLevel, f, k,posX,posY,t
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 0, fill = "orange",outline = "black")
posY -= 2
canvas.update()
if (posY) == 480:
k = 1
time.sleep(t)
k = 0
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 1, fill = "orange",outline = "black")
posY += 2
canvas.update()
if (posY) == 530:
k = 1
time.sleep(t)
k = 0
def start():
canvas.create_rectangle(0,0,1000,600,fill="cyan")
canvas.create_line(0,550,1000,550,width = 3)
startButton.destroy()
startPlayer(50,530)
startGameButton = tkinter.Button(canvas, text ="Go!",command = playerJump)
startGameButton.place(x = 35, y=400)
return(startGameButton)
def resetButton():
global startGameButton
startGameButton.destroy()
startGameButton = tkinter.Button(canvas, text ="Go!",command = playerJump)
startGameButton.place(x = 35, y=400)
startImage = tkinter.PhotoImage(file="C:/Users/marti/OneDrive/Desktop/Wheel finder/startSign.png")
canvas.create_rectangle(0,0,1000,1000,fill="green")
startButton.config(image = startImage,command = start)
startButton.place(x = 130, y= 25)
canvas.create_rectangle(300,400,700,500,fill="#113B08",outline = "black",width = 3)
canvas.create_text(500,450,text = "By: --------", font = "Arial 30",fill ="white")
I shrinking the sleep time every time it runs so its faster, but that is only a temporary solution and it didn't even work.
Problem with your code is you are always adding new items into your canvas. When you jump you update orange rectangle and repaint its old place. However they stack top of each other and handling too many elements makes slower your program.
We create player and return it to main function.
def startPlayer(xx,yy):
player=canvas.create_rectangle(xx-20,yy-22,xx+20,yy+18,fill = "orange")
return player
This is new start function. Check that we get player and send to playerjump function.
def start():
canvas.create_rectangle(0,0,1000,600,fill="cyan")
canvas.create_line(0,550,1000,550,width = 3)
startButton.destroy()
player = startPlayer(50,530)
startGameButton = tkinter.Button(canvas, text ="Go!",command = lambda :playerJump(player))
startGameButton.place(x = 35, y=400)
return(startGameButton)
And this is playerjump function.We get player and sent to move function.
def playerJump(player):
global groundLevel, f, k,posX,posY,t
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 0, fill = "orange",outline = "black",player=player)
posY -= 2
canvas.update()
if (posY) == 480:
k = 1
time.sleep(t)
k = 0
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 1, fill = "orange",outline = "black",player=player)
posY += 2
canvas.update()
if (posY) == 530:
k = 1
time.sleep(t)
k = 0
Except move lines, I didn't change anything in this function.
Okay now let's check key part.
def move(x,y,x2,y2,direction,fill,outline,player):
global f
#direction 0 = up
#direction 1 = down
#direction 2 = left
#direction 3 = right
if direction == 0:
canvas.coords(player,x,y-f,x2,y2-f)
if direction == 1:
canvas.coords(player,x,y+f,x2,y2+f)
if direction == 2:
canvas.coords(player,x,y,x2,y2)
if direction == 3:
canvas.coords(player,x,y,x2,y2)
look that instead of creating new rectangles,we updated existing one. which is much more stable
Also you forgot adding root.mainloop in your code snippet.

Swinging pendulum does not work

I am having a little trouble with this project. I have to create a pendulum using key handles and the code I have for the key's up and down don't seem to be working. "up" is suppose to make the pendulum go faster and "down" makes it go slower. This is the code that I have so far. can somebody please help.
from tkinter import * # Import tkinter
import math
width = 200
height = 200
pendulumRadius = 150
ballRadius = 10
leftAngle = 120
rightAngle = 60
class MainGUI:
def __init__(self):
self.window = Tk() # Create a window, we may call it root, parent, etc
self.window.title("Pendulum") # Set a title
self.canvas = Canvas(self.window, bg = "white",
width = width, height = height)
self.canvas.pack()
self.angle = leftAngle # Start from leftAngle
self.angleDelta = -1 # Swing interval
self.delay = 200
self.window.bind("<Key>",self.key)
self.displayPendulum()
self.done = False
while not self.done:
self.canvas.delete("pendulum") # we used delete(ALL) in previous lab
# here we only delete pendulum object
# in displayPendulum we give the tag
# to the ovals and line (pendulum)
self.displayPendulum() # redraw
self.canvas.after(self.delay) # Sleep for 100 milliseconds
self.canvas.update() # Update canvas
self.window.mainloop() # Create an event loop
def displayPendulum(self):
x1 = width // 2;
y1 = 20;
if self.angle < rightAngle:
self.angleDelta = 1 # Swing to the left
elif self.angle > leftAngle:
self.angleDelta = -1 # Swing to the right
self.angle += self.angleDelta
x = x1 + pendulumRadius * math.cos(math.radians(self.angle))
y = y1 + pendulumRadius * math.sin(math.radians(self.angle))
self.canvas.create_line(x1, y1, x, y, fill="blue", tags = "pendulum")
self.canvas.create_oval(x1 - 2, y1 - 2, x1 + 2, y1 + 2,
fill = "red", tags = "pendulum")
self.canvas.create_oval(x - ballRadius, y - ballRadius,
x + ballRadius, y + ballRadius,
fill = "green", tags = "pendulum")
def key(self,event):
print(event.keysym)
print(self.delay)
if event.keysym == 'up':
print("up arrow key pressed, delay is",self.delay)
if self.delay >10:
self.delay -= 1
if event.keysym == 'Down':
print("Down arrow key pressed,delay is",self.delay)
if self.delay < 200:
self.delay += 1
if event.keysym=='q':
print ("press q")
self.done = True
self.window.destroy()
MainGUI()
The root of the problem is that the event keysym is "Up" but you are comparing it to the all-lowercase "up". Naturally, the comparison fails every time. Change the if statement to if event.keysym == 'Up':
Improving the animation
In case you're interested, there is a better way to do animation in tkinter than to write your own infinite loop. Tkinter already has an infinite loop running (mainloop), so you can take advantage of that.
Create a function that draws one frame, then have that function arrange for itself to be called again at some point in the future. Specifically, remove your entire "while" loop with a single call to displayFrame(), and then define displayFrame like this:
def drawFrame(self):
if not self.done:
self.canvas.delete("pendulum")
self.displayPendulum()
self.canvas.after(self.delay, self.drawFrame)
Improving the bindings
Generally speaking, your code will be marginally easier to manage and test if you have specific bindings for specific keys. So instead of a single catch-all function, you can have specific functions for each key.
Since your app supports the up key, the down key, and the "q" key, I recommend three bindings:
self.window.bind('<Up>', self.onUp)
self.window.bind('<Down>', self.onDown)
self.window.bind('<q>', self.quit)
You can then define each function to do exactly one thing:
def onUp(self, event):
if self.delay > 10:
self.delay -= 1
def onDown(self, event):
if self.delay < 200:
self.delay += 1
def onQuit(self, event):
self.done = True
self.window.destroy()

Python screen is black for a small amount of time then closes

The code in this gist (https://gist.github.com/tobias76/8dc2e1af90f1916a2106) is completely broken and nothing happens excl. a black screen and closure after seconds, I would be more specific with the code but there is absolutely no error message. As far as the program is concerned, it worked. As requested, here is my code (For some reason stack overflow doesn't like all of the code being formatted);
==============================================
import pygame
import sys
import random
from pygame.locals import *
pygame.init()
FPS = 60
FPSClock = pygame.time.Clock()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Graphical Slot Machine")
reelgroupone = pygame.sprite.Group()
reelgrouptwo = pygame.sprite.Group()
reelgroupthree = pygame.sprite.Group()
reelone = (reelgroupone, 1)
reeltwo = (reelgrouptwo, 2)
reelthree = (reelgroupthree, 3)
image = "assets/apple.png"
image2 = "assets/bar.png"
image3 = "assets/cherry.png"
image4 = "assets/orange.png"
background = "background.jpg"
class Reels(pygame.sprite.Sprite):
def __init__(self, reelgroup, reelnumbers):
self.reelgroup = reelgroup
self.reelnumber = reelnumbers
self.reellist = [0, 1, 2, 3, 4, 5]
self.reelmove = 1
self.reelnudge = 0
self.stoptime = 60
def start(self):
self.reelmove = 1
self.stoptime = 60
def stop(self):
self.reelmove = 0
def update(self):
if self.stoptime > 0:
self.stoptime -= 1
self.reelgroup.update()
else:
self.stop()
if self.reelnudge == 1:
self.reelmove = 0
def draw(self):
self.reelgroup.draw(screen)
class Fruit(pygame.sprite.Sprite):
def __init__(self, reelgroup, reel, FruitID):
pygame.sprite.Sprite.__init__(self)
self.reelgroup = reelgroup
self.FruitID = FruitID
self.machineReel = reel
# Depending on Reel ID use a specific image, place the file path in the string
if self.FruitID == 1:
self.reelImage = "assets/apple.png"
if self.FruitID == 2:
self.reelImage = "assets/bar.png"
if self.FruitID == 3:
self.reelImage = "assets/cherry.png"
if self.FruitID == 4:
self.picture = "assets/orange.png"
self.pic = pygame.image.load(self.reelImage).convert_alpha()
self.where = ((self.machineReel * 155) - 30, 490)
self.rectangle = self.pic.get_rect()
self.rectangle.TopLeft = self.where
# Make reels faster / slower here.
self.reelSpeed = 8
self.reelgroup.add(self)
def fruitupdate(self):
self.rectangle.y -= self.reelSpeed
if self.rectangle.y < 110:
self.kill()
class main():
def __init__(self):
self.money = 10
self.counter = 5
self.fruitlist = [[0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]]
self.finished = 0
self.message = ""
def splash(self):
pass
def machine(self):
while self.credits > 0:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
self.keys = pygame.key.get_pressed()
# Change this key to whatever you want
if self.keys[K_p] and reelone.reelmove == reeltwo.reelmove == reelthree.reelmove == 0:
self.money -= 1
self.counter = 5
self.finished = 0
reelone.start()
reeltwo.start()
reelthree.start()
self.message = ""
if self.keys[K_ESCAPE]:
pygame.quit()
sys.exit()
# Put your image and font blitting here.
screen.blit(background,(0,0))
if self.money >= 10:
self.counter = 1
if reelone.reelMove == 1 and reelone.stopTime % 10:
fruit = Fruit(reelgroupone, 1, random.randint(1,4))
del self.fruitlist[0][0]
self.fruitlist[0].append(fruit.ID)
if reeltwo.reelMove == 1 and reeltwo.stopTime % 10:
fruit = Fruit(reelgrouptwo, 2, random.randint(1,4))
del self.fruitlist[1][0]
self.fruitlist[1].append(fruit.ID)
if reelthree.reelMove == 1 and reelthree.stopTime % 10:
fruit = Fruit(reelgroupthree, 3, random.randint(1,4))
del self.fruitlist[2][0]
self.fruitlist[2].append(fruit.ID)
else:
self.counter += 1
if reelone.reelmove == 1:
reelone.update()
if reeltwo.reelmove == 1:
reeltwo.update()
if reelthree.reelmove == 1:
reelthree.update()
# If all the reels are moving, check if the player has won and add the credits to their
# account if so.
if reelone.reelmove == reeltwo.reelmove == reelthree.reelmove == 0 and self.finished == 0:
if self.fruitlist[0][2] == self.fruitlist[1][2] == self.fruitlist[2][2]:
self.message = "Winner, want to play again?"
if self.fruitlist[0][2] == 1:
self.credits += 10
if self.fruitlist[0][2] == 1:
self.credits += 100
if self.fruitlist[0][2] == 3:
self.credits += 1000
if self.fruitlist[0][2] == 4:
self.credits += 10000
# Now this sets the state to finished
self.finished = 1
else:
self.message = "Sorry, no win this time!"
self.end = 1
reelgroupone.draw(screen)
reelgrouptwo.draw(screen)
reelgroupthree.draw(screen)
FPSClock.tick(FPS)
pygame.display.update()
fruit = main()
You're not actually running the main() as a function in your code, main is a class instead. Change main to a function, and instead, write this at the bottom of the program:
If __name__ == "__main__":
main()
This way, the code actually runs. Also, get rid of fruit = main(). It just makes it more confusing and you're going to be running it anyways with this, hence why it says that the process finished. You may also want to move things previously in the main class in the main function. For example, take functions inside of the class such as the machine() function and put it as a separate function outside of your code, and call it in the main function with machine().

Why my ball Sprite doesnt always bounce on my Sprite bar?!

I have some problems and I cant figure out how to fix them...
It is really simple game. A bar moved by the mouse and one ball is (or some balls) bouncing.
The user just need to keep the ball bouncing.
The user can choose how many balls he wants (1,2,3), the size of the bar (small, medium,large) and the speed of the balls (slow, normal, fast).
Problems:
- sometimes everything works fine, and sometimes the ball (or balls) just goes through the bar. Like if the collision function does not work. Is there any other way I can do?
everytime there is a collision, the score should add 10 points for the total displayed on top of the screen, but this score is been overwriting all the time.
For this game be run, I just have to call the function (startGame) from other file (settings), where it also sends the value of number of balls, size of bar and speed of balls.
If anyone can help I appreciate.
Thanks
from livewires import games, color
from tkinter import*
import random
games.init(screen_width = 735, screen_height = 350, fps = 60)
class Bounce(games.Sprite):
global total_score
total_score = 0
def update(self):
global total_score
if self.bottom == 315 and self.overlapping_sprites:
self.dy = -self.dy
total_score += 10
the_score = games.Text(value = 0, size = 25,
color = color.gray,x = 700, y = 20)
games.screen.add(the_score)
if self.right > games.screen.width or self.left < 0:
self.dx = -self.dx
if self.top < 0:
self.dy = -self.dy
if self.bottom == 400:
lose_message = games.Message(value = " - GAME OVER -",
size = 50,
color = color.gray,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 300,
after_death = games.screen.quit)
games.screen.add(lose_message)
class Bar_moving(games.Sprite):
def update(self):
self.x = games.mouse.x
self.y = 315
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
class have_settings():
def __init__(self):
global the_ball_speed
global the_ball_number
global the_bar_size
if "the_ball_speed" not in globals():
the_ball_speed = "normal"
if "the_bar_size" not in globals():
the_bar_size = "medium"
if "the_ball_number" not in globals():
the_ball_number = 1
def set_all(self, number, size, speed):
global the_ball_speed
global the_bar_size
global the_ball_number
if speed in ("slow","normal","fast"):
the_ball_speed = speed
if size in ("small","medium","large"):
the_bar_size = size
if number in (1,2,3):
the_ball_number = number
def startGame():
call = have_settings()
background = games.load_image("BG.jpg", transparent = False)
games.screen.background = background
#-------------------------------------SPEED
sp_is = 0
if the_ball_speed == "slow":
sp_is = 2
elif the_ball_speed == "normal":
sp_is = 3
elif the_ball_speed == "fast":
sp_is = 4
#-------------------------------------BALL NUMBER
if the_ball_number in (1,2,3):
for n in range(the_ball_number):
position_x_list =(50,150,250,350,400,450,500,550)
position_x = random.choice(position_x_list)
position_y_list =(50,100,150,200,225,250)
position_y = random.choice(position_y_list)
vert_speed_list = (-2,2)
vert_speed = random.choice(vert_speed_list)
ball_img = games.load_image("ball.bmp")
ball = Bounce(image = ball_img,
x = position_x,
y = position_y,
dx = vert_speed,
dy = - sp_is)
games.screen.add(ball)
#-------------------------------------BAR SIZE
if the_bar_size in ("small","medium","large"):
if the_bar_size == "small":
bar_pic = "bar_small.jpg"
elif the_bar_size == "medium":
bar_pic = "bar_medium.jpg"
elif the_bar_size == "large":
bar_pic = "bar_large.jpg"
bar = games.load_image(bar_pic, transparent = False)
the_bar = Bar_moving(image = bar, x = games.mouse.x)
games.screen.add(the_bar)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
You should do a greater than check rather than an equals check as follows:
if self.bottom >= 315 and self.overlapping_sprites:
^^
instead of
if self.bottom == 315 and self.overlapping_sprites:
This is because rarely will the ball's y position ever perfectly line up with the bottom. In some cases it may go from y==314 to y==316. In such cases, your method above wouldn't work. Therefore, you should be a greater than test rather than an equality test.
You can apply similar changes everywhere else and it should work.

How to draw 10000+ 2d graphical objects quickly?

I need to draw tens of thousands, in the future probably hundreds of thousands of simple 2d objects (circles, rectangles, some filled, labeled...) to a widget.
In the same program I need GUI widgets (buttons, text input, checkboxes).
I tried Gtk, Qt and SDL with C++ and Python. First result was to be expected: C++ and Python show the same performance, as they call the same C or C++ routines in the backend.
Second result is that none of the libraries made a big difference. In numbers: 22500 rectangles (150*150) needed approximately a second to update. As there will be constant upadating due to (a) new data, i.e. more rectangles, and (b) user interaction, i.e. zooming, panning etc., a second is way to long!
What would be a faster way. Small examples are very much appreciated. Python and C++ is good. Other libraries should be easily accessible and installable on Linux.
Maybe I am just doing it wrong.
ps. I am not posting my test codes, because I don't want to bias answers. And I don't want my code to be corrected, I want to the fastest way to do it...
edit:
Alright, I will add my gtk test:
#!/bin/env python2
import gtk
import gobject
import gtk.gdk
class GraphWidget(gtk.DrawingArea):
__gsignals__ = {
'expose-event': 'override',
'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gtk.gdk.Event))
}
def __init__(self,window):
gtk.DrawingArea.__init__(self)
#self.win = window
self.zoom_ratio = 1.0
self.dx = 40
self.dy = 40
def do_expose_event(self, event):
cr = self.window.cairo_create()
cr.set_source_rgba(1.0, 0.9, 0.8, 1.0)
cr.paint()
cr.translate(self.dx, self.dy)
cr.scale(self.zoom_ratio,self.zoom_ratio)
self.draw(cr)
def draw(self, cr):
n = 150
cr.set_source_rgba(0.,1.,1.,1.0)
for i in range(n):
for j in range(n):
cr.arc(i*30, j*30, 10, 0, 6.2832)
cr.close_path()
cr.fill()
cr.set_source_rgba(0.,0.,1.,1.0)
for i in range(n):
for j in range(n):
cr.arc(i*30, j*30, 10, 0, 6.2832)
cr.move_to(i*30-10, j*30)
cr.show_text("hu")
cr.stroke()
def on_zoom(self, zoom_factor):
self.zoom_ratio *= zoom_factor
self.queue_draw()
def on_translate(self,dx,dy):
self.dx += dx
self.dy += dy
self.queue_draw()
class TestWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.widget = GraphWidget(self)
self.add(self.widget)
self.show_all()
# connect key press events
self.connect('key-press-event', self.on_key_press_event)
self.connect('destroy', gtk.main_quit)
self.widget.queue_draw()
def on_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.space and not (event.state & gtk.gdk.CONTROL_MASK):
self.on_run(widget)
return True
elif event.keyval == gtk.keysyms.r:
self.on_refresh(widget)
return True
elif event.keyval == gtk.keysyms.Left:
self.widget.on_translate(-100, 0)
elif event.keyval == gtk.keysyms.Right:
self.widget.on_translate(100, 0)
elif event.keyval == gtk.keysyms.Up:
self.widget.on_translate(0, -100)
elif event.keyval == gtk.keysyms.Down:
self.widget.on_translate(0, 100)
elif event.keyval == gtk.keysyms.Page_Down:
self.widget.on_zoom(0.7)
elif event.keyval == gtk.keysyms.Page_Up:
self.widget.on_zoom(1.3)
if __name__ == '__main__':
win = TestWindow()
gtk.main()
And the SDL experiment:
#!/usr/bin/env python
import sdl2
import sdl2.ext as sdl2ext
dx = 0
dy = 0
zoom_factor = 1.
n_objects = 150
sdl2ext.init()
window = sdl2ext.Window('hallo',
size=(800, 600),
flags= sdl2.SDL_WINDOW_RESIZABLE)
window.show()
renderer = sdl2ext.RenderContext(window)
renderer.color = sdl2ext.Color(255,155,25)
def draw():
renderer.clear()
for i in xrange(n_objects):
for j in xrange(n_objects):
renderer.fill([int((i*30+dx)*zoom_factor),
int((j*30+dy)*zoom_factor),
int(20*zoom_factor),
int(20*zoom_factor)],
sdl2ext.Color(255,25,55))
renderer.draw_rect([int((i*30+dx)*zoom_factor),
int((j*30+dy)*zoom_factor),
int(20*zoom_factor),
int(20*zoom_factor)],
sdl2ext.Color(255,255,255))
renderer.present()
draw()
running = True
while running:
for e in sdl2ext.get_events():
if e.type == sdl2.SDL_QUIT:
running = False
break
if e.type == sdl2.SDL_KEYDOWN:
if e.key.keysym.sym == sdl2.SDLK_ESCAPE:
running = False
break
elif e.key.keysym.sym == sdl2.SDLK_RIGHT:
dx += 50
draw()
elif e.key.keysym.sym == sdl2.SDLK_LEFT:
dx -= 50
draw()
elif e.key.keysym.sym == sdl2.SDLK_UP:
dy += 50
draw()
elif e.key.keysym.sym == sdl2.SDLK_DOWN:
dy -= 50
draw()
elif e.key.keysym.sym == sdl2.SDLK_PAGEUP:
zoom_factor *= 1.2
draw()
elif e.key.keysym.sym == sdl2.SDLK_PAGEDOWN:
zoom_factor /= 1.2
draw()
The Qt test was done by my colleague so I don't have the code right now...
Looks like you're drawing every single object regardless of whether or not it's in the viewable area. Have you considered storing an array of the positions of each object, and then test each to determine if it's in the screen's viewable area before drawing it?
I did a quick and dirty test to try it out (the code could be much cleaner, I'm sure), and it's much more responsive drawing only what's visible:
First I created some variables to determine the visible boundary (you would update the boundaries every time move and zoom, window resize, etc happen):
boundX = [-60, 160]
boundY = [-60, 160]
In the draw event, I'm checking if the position is within the boundry before drawing. I also consolidated your loop to increase efficiency, you can draw and add text in the same iteration. Even testing this with n = 50000 is more responsive than what was there before.
def draw(self, cr):
n = 150
for i in range(n):
if min(boundX) < i * 30 < max(boundX):
for j in range(n):
if min(boundY) < j * 30 < max(boundY):
cr.set_source_rgba(0.,1.,1.,1.0)
cr.arc(i*30, j*30, 10, 0, 6.2832)
cr.close_path()
cr.fill()
cr.set_source_rgba(0.,0.,1.,1.0)
cr.arc(i*30, j*30, 10, 0, 6.2832)
cr.move_to(i*30-10, j*30)
cr.show_text("hu")
cr.stroke()
And in the keypress events, just increment and decrement the boundry by the amount the widget is being translated:
def on_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.space and not (event.state & gtk.gdk.CONTROL_MASK):
self.on_run(widget)
return True
elif event.keyval == gtk.keysyms.r:
self.on_refresh(widget)
return True
elif event.keyval == gtk.keysyms.Left:
boundX[0] += 100
boundX[1] += 100
self.widget.on_translate(-100, 0)
elif event.keyval == gtk.keysyms.Right:
boundX[0] -= 100
boundX[1] -= 100
self.widget.on_translate(100, 0)
elif event.keyval == gtk.keysyms.Up:
boundY[0] += 100
boundY[1] += 100
self.widget.on_translate(0, -100)
elif event.keyval == gtk.keysyms.Down:
boundY[0] -= 100
boundY[1] -= 100
self.widget.on_translate(0, 100)
elif event.keyval == gtk.keysyms.Page_Down:
self.widget.on_zoom(0.7)
elif event.keyval == gtk.keysyms.Page_Up:
self.widget.on_zoom(1.3)

Categories

Resources