3
\$\begingroup\$

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()
\$\endgroup\$
2
\$\begingroup\$

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.

|improve this answer|||||
\$\endgroup\$
  • 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 Jul 14 '16 at 13:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.