I'm currently working on my first project and I have been programming in python for 4 days now. I currently have a dilemma where my code is running too slowly. I don't want you to make a full review, I am mostly interested in advice on how to optimise my code. Here is my code:
Note that the comments aren't perfect English :)
import sys
import pygame
from pygame.locals import *
from random import randint
# window properties
WINDOWWIDTH = 800
WINDOWHEIGHT = 600
#colors
RED = (150, 0, 0)
LRED = (255, 0, 0)
GREEN = (0, 150, 0)
LGREEN = (0, 255, 0)
BLUE = (0, 0, 150)
LBLUE = (0, 0, 255)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
PURPLE = (150, 0, 150)
LPURPLE = (255, 0, 255)
COLORS = [RED, LRED, GREEN, LGREEN, BLUE, LBLUE, WHITE, PURPLE, LPURPLE]
# So i can use pygame things
pygame.init()
Mainclock = pygame.time.Clock()
# images
action_box_image = pygame.image.load('towers/actionbox.png')
background_rectangle = pygame.Rect(0, 0, WINDOWWIDTH, WINDOWHEIGHT)
image_map1 = pygame.image.load('maps/Map1.png')
light_image_map1 = pygame.image.load('maps/Map1_light.png')
fantower_image = pygame.image.load('towers/fan.png')
# function for starting a wave
def wave(quantity, size, distance):
for i in range(quantity):
MainWindow.enemy.append(Enemy(800 + (distance + size)*i, 100- size/2, size, size))
# function that creates texts
def text_objects(text, font):
textSurface = font.render(text, True, WHITE)
return textSurface, textSurface.get_rect()
# used for making some buttons
def button_text(msg, x, y, width, height, mouselse, mouseover, action = None, Text = True):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if x+width > mouse[0] > x and y+height > mouse[1] > y:
pygame.draw.rect(MainWindow.Gamewindow, mouseover,(x,y,width,height))
if click[0] == 1 and action != None:
action()
else:
pygame.draw.rect(MainWindow.Gamewindow, mouselse,(x,y,width,height))
smallText = pygame.font.Font("freesansbold.ttf",20)
textSurf, textRect = text_objects(msg, smallText)
textRect.center = ((x+(width/2)), (y+(height/2)))
MainWindow.Gamewindow.blit(textSurf, textRect)
# making a special button for towers on the side
def button_tower(x, y, width, height, mouse, click, image, action = None):
if x+width > mouse[0] > x and y+height > mouse[1] > y:
if click[0] == 1 and action != None:
MainWindow.action_box = action
Rectangle_buytower = pygame.Rect(x, y, width, height)
MainWindow.Gamewindow.blit(image, Rectangle_buytower)
# creating a tower at mosue coordinates
def tower(width=30, height=30):
MainWindow.tower.append(Tower(MainWindow.mouse[0], MainWindow.mouse[1], width, height))
class Main:
# ...+100 things is there so i can have a side bar on the screen
def __init__(self, width = WINDOWWIDTH+100, height = WINDOWHEIGHT + 100):
pygame.display.set_caption('Tower defense game')
self.startwave = False
self.width = width
self.height = height
self.Gamewindow = pygame.display.set_mode((self.width, self.height))
# start a new wave
def wave(self):
self.startwave = True
# the intro loop
def Intro(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# creating a black background and some letters
self.Gamewindow.fill(BLACK)
largeText = pygame.font.Font('freesansbold.ttf',30)
TextSurf, TextRect = text_objects("Simple tower defense game", largeText)
TextRect = (100, 100)
self.Gamewindow.blit(TextSurf, TextRect)
# creating buttons
button_text("New game", 100, 200, 400, 50, GREEN, LGREEN, MainWindow.MainLoop)
button_text("Continue", 100, 300, 400, 50, RED, LRED)
button_text("Exit", 100, 400, 400, 50, BLUE, LBLUE, quit)
pygame.display.update()
# main game
def MainLoop(self):
self.enemy = []
self.tower = []
# This is used for creating towers etc...
self.action_box = None
while True:
Mainclock.tick(60)
# mouse position
self.mouse = pygame.mouse.get_pos()
self.click = pygame.mouse.get_pressed()
# so you can quit the game with the cross in the top right corner
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == ord('t'):
tower()
# check if a wave is alredy out and starts a new wave
if self.startwave == True and len(self.enemy)==0:
wave(10, 20, 5)
self.startwave = False
# updating enemys and killing them if they walk outside the screen
for index,ene in enumerate(self.enemy):
ene.update()
ene.move()
if ene.rect.top >= 600:
del self.enemy[index]
# drawing everything to the screen
self.Gamewindow.fill(BLACK)
self.Gamewindow.blit(image_map1, background_rectangle)
button_tower(800, 0, 50, 50, self.mouse, self.click, fantower_image, tower)
# this creates a nice looking box around the mouse so you can see how big the tower is gonna be
if pygame.mouse.get_pressed()[0] == 1 and self.action_box != None:
rectangle30 = pygame.Rect(self.mouse[0]-15, self.mouse[1]-15, 30, 30)
self.Gamewindow.blit(action_box_image, rectangle30)
# this executes the action_box when you release the mouse button
elif self.action_box != None:
self.action_box()
self.action_box = None
for object_enemy in self.enemy:
pygame.draw.rect(self.Gamewindow, object_enemy.color(randint(0, len(COLORS)-1)), object_enemy.rect)
for object_tower in self.tower:
self.Gamewindow.blit(object_tower.image, object_tower.rect)
self.Gamewindow.blit(light_image_map1, background_rectangle)
button_text("Start next wave", 0, 600, WINDOWWIDTH, 100, PURPLE, LPURPLE, MainWindow.wave)
pygame.display.update()
class Enemy:
def __init__(self, x, y, width, height):
self.rect = pygame.Rect(x, y, width, height)
self.dir = 4
self.movement = [(100, 100, 2), (100, 200, 6), (700, 200, 2), (700, 500, 4), (600, 500, 8), (600, 300, 4), (100, 300, 2), (100, 500, 6), (500, 500, 2)]
def move(self):
# movement numbers is the same as numpad "directions"
if self.dir == 8:
self.rect.centery -= 1
if self.dir == 4:
self.rect.centerx -= 1
if self.dir == 6:
self.rect.centerx += 1
if self.dir == 2:
self.rect.centery += 1
# this method is changing the direciton of the enemy
def update(self):
for pos in self.movement:
if self.rect.center == (pos[0], pos[1]):
self.dir = pos[2]
# its for future use, if i want enemys to be different colors
def color(self, colorid):
return COLORS[colorid]
class Tower:
def __init__(self, x, y, width, height):
self.rect = pygame.Rect(x-15, y-15, width, height)
self.image = pygame.transform.scale(fantower_image, (30, 30))
MainWindow = Main()
MainWindow.Intro()
1 Answer 1
Currently the font for the text is setup multiple times every loop. I would move these lines to the general settings above the functions, or to the init function:
smallText = pygame.font.Font("freesansbold.ttf",20)
largeText = pygame.font.Font('freesansbold.ttf',30)
Apparently doublebuffer helps a bit (according to the answer here):
from pygame.locals import *
flags = FULLSCREEN | DOUBLEBUF
screen = pygame.display.set_mode(resolution, flags, bpp)
You should avoid having to draw your entire screen every loop. Rather move what needs to be moved and then paint the bg image over all points where stuff moved away from. I haven't done anything in pygame in a while, so I can't give you concrete code to do this here.
You should use .convert()
on your images after loading:
action_box_image = pygame.image.load('towers/actionbox.png').convert()
image_map1 = pygame.image.load('maps/Map1.png').convert()
light_image_map1 = pygame.image.load('maps/Map1_light.png').convert()
fantower_image = pygame.image.load('towers/fan.png').convert()
This converts the image's pixel format to be the same as the surface's you are going to blit it on. Otherwise this conversion will have to happen on-the-fly everytime you blit()
. For more information see here.
If you need transparency, use .convert_alpha()
instead.
-
1\$\begingroup\$ just using the convert() I raised my fps with 160 :) I will definitely check out how to update only the things that have moved. Thank you so much for your answer! \$\endgroup\$TawnyPeach04– TawnyPeach042016年07月14日 13:10:19 +00:00Commented Jul 14, 2016 at 13:10