Reputation: 189
I wanted to create a block physics based game, but the fps drops down really fast. I tried to optimise it as much as I could and it is still pretty low fps (200 block give me about 20 fps). Does anyone have any idea how to optimise my block game ? I was planning to add more but seeing this problem i might give up.
This is my code:
from tkinter import *
import math
import random
import time
master = Tk()
w = Canvas(master,width=600,height=600)
w.pack()
tileSize = 20
def randomColor():
r = math.floor(random.random() * 255)
g = math.floor(random.random() * 255)
b = math.floor(random.random() * 255)
return '#%02x%02x%02x' % (r, g, b)
class newElement:
def __init__(self,x,y):
x = math.floor(x/tileSize) * tileSize
y = math.floor(y/tileSize) * tileSize
self.x = x
self.y = y
self.vx = 0
self.vy = 0
self.width = tileSize
self.height = tileSize
self.id = random.random()
self.color = randomColor()
elements = []
def mouse(event):
global mouseX,mouseY
mouseX,mouseY = event.x,event.y
canPlace = True
for y in range(0,len(elements)):
e = elements[y]
dx = e.x + e.width/2 - mouseX
dy = e.y + e.height/2 - mouseY
if math.sqrt(dx*dx+dy*dy) < 20:
canPlace = False
break
if canPlace:
elements.append(newElement(mouseX,mouseY))
mouseX = 0
mouseY = 0
w.bind("<B1-Motion>",mouse)
elements.append(newElement(300,50))
elements.append(newElement(300,100))
def collision(rect1,rect2):
return rect1.x < rect2.x + rect2.width and rect1.x + rect1.width > rect2.x and rect1.y < rect2.y + rect2.height and rect1.height + rect1.y > rect2.y
def distance(rect1,rect2):
dx = rect1.x - rect2.x
dy = rect2.y - rect2.y
return math.sqrt(dx*dx+dy*dy)
lastCall = time.time()
def engine():
global lastCall
w.delete("all")
w.create_line(0,590,600,590)
for i in range(0,len(elements)):
e = elements[i]
gravity = 0.1
e.vy += gravity
e.x += e.vx
e.y += e.vy
if e.y + e.height >= 590:
e.y = 590-e.height
e.vy = 0
for x in range(0,len(elements)):
e1 = elements[x]
if e.id == e1.id or distance(e,e1) > 14.14:
continue
col = collision(e,e1)
if col:
e.y = e1.y - e1.height
e.vy = 0
w.create_rectangle(e.x,e.y,e.x+e.width,e.y+e.height,fill=e.color)
w.create_text(10,10,anchor=NW,text=len(elements))
fps = 60
if time.time() - lastCall != 0:
fps = round(1/(time.time() - lastCall))
w.create_text(600,10,anchor=NE,text=fps)
lastCall = time.time()
master.after(16,engine)
engine()
mainloop()
Upvotes: 2
Views: 3030
Reputation: 385870
Tkinter is capable of doing 60fps for a simple program like this, but performance will degrade depending on the algorithms you choose. I've outlined several things you can do to improve the performance of your code.
When I write a program similar to yours but with the changes outlined below, I'm able to get 60fps with over a thousand items.
The biggest problem is that you are deleting and creating the rectangles several several times a second. This is very inefficient. Compounding that, the canvas has performance issues once you have created many thousands of items, even if you later delete them. I've successfully animated many thousands of items, but if you create tens of thousands it can start to get sluggish.
What you should be doing instead is creating the items once, and then giving each item a move
method so that you move the existing item rather than deleting and then recreating it.
The second problem is that you are moving all the objects in every frame, even though they have a zero velocity. There's no need to do the calculations for objects that won't move.
The third problem is that your algorithm for finding collisions is extremely inefficient. If you have 300 objects, you're doing 300x300 comparisons. For example, you'll check whether item 1 collides with item 2, then item 3, then item 4, etc. Then, you check whether item 2 collides with item 1, item 3, then item 4, etc. Since you've already determined whether item 1 and 2 have collided, there's no reason to see if items 2 and 1 have collided. Also, since the items are always moving straight down, you really only need to check collisions with items immediately below the current item.
You can solve the collision problem by letting tkinter do the work for you. Given an object, you can get a list of all objects that it overlaps with the find_overlapping method of the canvas. This is likely orders of magnitude faster than comparing every object to every object, since it's done internally by tkinter on the internal canvas data structures (ie: it's in C code rather than python code).
Upvotes: 7
Reputation: 7006
This doesn't solve your exact problem but will show you how to move objects rather than deleting and redrawing which will improve your performance. It also gives you an example of how you can perhaps make more use of classes and OOP to control the behavior of your object. This simple example will move the rectangle when the w,s,a and d keys are pressed.
#!/usr/bin/env python3
#Press w,s,a&d to move box
import tkinter as tk
class Box:
def __init__(self,canvas,x1,y1,x2,y2,fill):
self.canvas = canvas
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
self.fill = fill
self.objId = None
self.drawBox()
def drawBox(self):
self.objId = self.canvas.create_rectangle(self.x1,self.y1,self.x2,self.y2,fill=self.fill)
def moveBox(self,dx,dy):
self.x1 += dx
self.x2 += dx
self.y1 += dy
self.y2 += dy
self.canvas.coords(self.objId,self.x1,self.y1,self.x2,self.y2)
def keypress(e):
if e.char == 'a':
box.moveBox(-2,0)
elif e.char == 'd':
box.moveBox(2,0)
elif e.char == 'w':
box.moveBox(0,-2)
elif e.char == 's':
box.moveBox(0,2)
root = tk.Tk()
w = tk.Canvas(root,width=200,height=200)
w.pack()
box = Box(w,50,100,160,180,'yellow')
root.bind('<KeyRelease>',keypress)
root.mainloop()
You can have an some extra code in your program that uses the after method to periodically update the position of the box (replicate gravity) and to iterate though a list of objects.
Upvotes: 1