3

EDIT: QuadTrees (http://www.pygame.org/wiki/QuadTree?parent=CookBook) may be my answer here, going to try this out and will report back. Please don't hesitate to give me some advice anyway!

I have a series of obstacles in pygame that I am converting to Rect objects, looping over the list before every tick of the frame to see if my Hero Rect object has collided.

I believe this is causing me performance issues. I also know that I could try switching to "Dirty rect animation" instead of updating the whole screen every tick, but before I go down that road I wanted to see if I could do this more efficiently.

The particular code in question occurs near the very bottom (if pillars.checkCollision(hero.rect):), but I provided everything for context:

import sys
import pygame
from pygame.locals import *
import pyganim
from decimal import *
import math

## start pymgame
pygame.init()

## window and lines
windowWidth = 387
windowHeight = 340
lineThickness = 3
animationSpeed = .15

## fps and speed
increaseSpeed = 1
fps = 60
fpsClock = pygame.time.Clock()

## window
##windowSurface = pygame.display.set_mode((windowWidth, windowHeight), pygame.FULLSCREEN)
windowSurface = pygame.display.set_mode((windowWidth, windowHeight))

class Pillars():
    def __init__(self):
        self.image = pyganim.PygAnimation([("grave.png", animationSpeed)])
        self.xSize = self.image.getMaxSize()[0]
        self.ySize = self.image.getMaxSize()[1]
        self.numberOfColumns = int(math.ceil(Decimal((windowWidth - (self.xSize * 4))) / Decimal((self.xSize * 3))))
        self.numberOfRows = int(math.ceil(Decimal((windowHeight - (self.ySize * 4))) / Decimal((self.ySize * 3))))
        self.rects = []
        self.image.play()

    def draw(self):
        xPos = (self.xSize * 2)
        yPos = (self.ySize * 2)
        columnCounter = 1
        for num in range(0, (self.numberOfColumns * self.numberOfRows)):
            self.rects.append(Rect(xPos, yPos, self.xSize, self.ySize))
            self.image.blit(windowSurface, (xPos, yPos))
            xPos += (self.xSize * 3)
            if columnCounter >= self.numberOfColumns:
                columnCounter = 0
                xPos = (self.xSize * 2)
                yPos += (self.ySize * 3)
            columnCounter += 1

    def checkCollision(self, Rect):
        for pillarRect in self.rects:
            if pillarRect.colliderect(hero.rect):
                return True



class Hero():
    def __init__(self):
        self.standingLeft = pyganim.PygAnimation([("left_standing_link.png", animationSpeed)])
        self.standingRight = pyganim.PygAnimation([("right_standing_link.png", animationSpeed)])
        self.standingUp = pyganim.PygAnimation([("up_standing_link.png", animationSpeed)])
        self.standingDown = pyganim.PygAnimation([("down_standing_link.png", animationSpeed)])
        self.walkingUp = pyganim.PygAnimation([("up_standing_link.png", animationSpeed), ("up_walking_link.png", animationSpeed)])
        self.walkingDown = pyganim.PygAnimation([("down_standing_link.png", animationSpeed), ("down_walking_link.png", animationSpeed)])
        self.walkingLeft = pyganim.PygAnimation([("left_standing_link.png", animationSpeed), ("left_walking_link.png", animationSpeed)])
        self.walkingRight = pyganim.PygAnimation([("right_standing_link.png", animationSpeed), ("right_walking_link.png", animationSpeed)])
        self.walkingRight.play()
        self.walkingLeft.play()
        self.standingLeft.play()
        self.standingRight.play()
        self.standingUp.play()
        self.standingDown.play()
        self.walkingUp.play()
        self.walkingDown.play()
        self.x = 100
        self.y = 100
        self.dirX = 0 ## -1 = left 1 = right
        self.dirY = 0 ## -1 = up 1 = down
        self.orientation = 0 ## 0 = right, 1 = down, 2 = left, 3 = up
        self.rect = None

    def move(self, animationObj):
        self.animationObject = animationObj
        animationObj.x = self.x + (self.dirX * increaseSpeed)
        animationObj.y = self.y + (self.dirY * increaseSpeed)
        self.x = animationObj.x
        self.y = animationObj.y
        if self.dirX == 1:
            self.orientation = 0
        elif self.dirX == -1:
            self.orientation = 2
        elif self.dirY == -1:
            self.orientation = 3
        elif self.dirY == 1:
            self.orientation = 1
        tempRect = animationObj.getRect()
        self.rect = Rect(animationObj.x, animationObj.y, tempRect[2], tempRect[3])

    def draw(self, animationObj):
        if self.y > (windowHeight - (lineThickness * 5.5)):
            self.y = windowHeight - (lineThickness * 5.5)
        elif self.y < lineThickness:
            self.y = lineThickness
        elif self.x > (windowWidth - (lineThickness * 6)):
            self.x = windowWidth - (lineThickness * 6)
        elif self.x < lineThickness:
            self.x = lineThickness        
        animationObj.blit(windowSurface, (self.x, self.y))    

    def moveAndDrawWalkingRight(self):

        self.dirX = 1
        self.dirY = 0
        self.move(self.walkingRight)
        self.draw(self.walkingRight)

    def moveAndDrawWalkingLeft(self):

        self.dirX = -1
        self.dirY = 0
        self.move(self.walkingLeft)
        self.draw(self.walkingLeft)

    def moveAndDrawWalkingUp(self):

        self.dirX = 0
        self.dirY = -1
        self.move(self.walkingUp)
        self.draw(self.walkingUp)

    def moveAndDrawWalkingDown(self):

        self.dirX = 0
        self.dirY = 1
        self.move(self.walkingDown)
        self.draw(self.walkingDown) 

    def drawStanding(self):
        if self.orientation == 0:
            self.dirX = 0
            self.dirY = 0
            self.move(self.standingRight)
            self.draw(self.standingRight)
        elif self.orientation == 2:
            self.dirX = 0
            self.dirY = 0
            self.move(self.standingLeft)
            self.draw(self.standingLeft)
        elif self.orientation == 1:
            self.dirX = 0
            self.dirY = 0
            self.move(self.standingDown)
            self.draw(self.standingDown)
        elif self.orientation == 3:
            self.dirX = 0
            self.dirY = 0
            self.move(self.standingUp)
            self.draw(self.standingUp)  

def drawArena():
    windowSurface.fill((0,0,0))

hero = Hero()
pillars = Pillars()

while True: # main loop

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()
##            if event.key == K_RIGHT:
##                hero.moveAndDrawWalkingRight()
##            elif event.key == K_LEFT:
##                hero.moveAndDrawWalkingLeft()
##            elif event.key == K_UP:
##                hero.moveAndDrawWalkingUp()
##            elif event.key == K_DOWN:
##                hero.moveAndDrawWalkingDown()
##        else:
##            hero.drawStanding()

    drawArena()
    pillars.draw()

    priorHeroX = hero.x
    priorHeroY = hero.y

    keys = pygame.key.get_pressed()  #checking pressed keys

    if keys[pygame.K_RIGHT]:
        hero.moveAndDrawWalkingRight()
    elif keys[pygame.K_LEFT]:
        hero.moveAndDrawWalkingLeft()
    elif keys[pygame.K_UP]:
        hero.moveAndDrawWalkingUp()
    elif keys[pygame.K_DOWN]:
        hero.moveAndDrawWalkingDown()
    else:
        hero.drawStanding()

    if pillars.checkCollision(hero.rect):
        hero.x = priorHeroX
        hero.y = priorHeroY

    pygame.display.update()
    fpsClock.tick(fps)

1 Answer 1

1

Instead of checking every Rect with .colliderect(..), you could use .collidelist(..) like so:

  .. # in class Pillars ..
def checkCollision(self): # `Rect` was in there as an argument; I'm not sure what it did...?
    if hero.rect.collidelist(self.rects) != -1:  # collidelist returns a list index if it collides, or -1 if it does not.
        return True

...or even just...

def checkCollision(self):
    return False if hero.rect.collidelist(self.rects) == -1 else True
    # Be aware that this returns False instead of nothing if there is no collision.

I'm not sure if Pygame has a more efficient iterator than your loop, but possibly? If nothing else, it saves you a few lines of code. If you need to know which Rect hero has collided with specifically, consider this:

def getColliding(self):  # this returns something slightly different...
    idx = hero.rect.collidelist(self.rects)
    return None if idx == -1 else self.rects[idx]

That'll send back the Rect object that hero collided with, or the first such Rect on the list if there is more than one, or None if there are no colliding rects at all.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.