I am using Pymunk to generate videos of balls bouncing in a frame. To render the frames, I run:
import pygame
from pymunk.pygame_util import DrawOptions
screen = pygame.display.set_mode((500, 500))
draw_options = DrawOptions(screen)
# define `space` with six balls
# ...
space.debug_draw(draw_options)
which gives the following output:
As we can see, it draws a black line going from the center to the edge of the ball; I suppose it is to indicate the rotation of each ball.
I would like NOT to have this line drawn: i.e. explicitly pass a flag to prevent pygame from drawing it, or specify the color of this line to be the same as the rest of the ball.
Would anyone know how to do that?
The debug_draw method should really be used mainly for debugging. I usually create sprite subclasses for my physics objects and give them a Pymunk body and a shape as attributes. Then I can update the self.rect in the update method by setting the coordinates to the coords of the body and also use it to rotate the image.
import math
import random
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk physics to pygame coordinates."""
return Vec2d(p[0], -p[1]+800)
class Entity(pg.sprite.Sprite):
def __init__(self, pos, space, radius, mass=1):
super().__init__()
# The surface is a bit bigger, so that the circle fits better.
self.orig_image = pg.Surface((radius*2+2, radius*2+2), pg.SRCALPHA)
self.image = self.orig_image
# Draw a circle onto the image.
pg.draw.circle(
self.image,
pg.Color(random.randrange(256),
random.randrange(256),
random.randrange(256)),
(radius+1, radius+1), # +1 looks a bit better.
radius)
self.rect = self.image.get_rect(topleft=pos)
# Create a Pymunk body and a shape and add them to the space.
moment = pm.moment_for_circle(mass, radius, radius)
self.body = pm.Body(mass, moment)
self.shape = pm.Circle(self.body, radius)
self.shape.friction = .1
self.shape.elasticity = .99
self.body.position = pos
self.space = space
self.space.add(self.body, self.shape)
def update(self):
# Update the rect because it's used to blit the image.
self.rect.center = flipy(self.body.position)
# Use the body's angle to rotate the image.
self.image = pg.transform.rotozoom(self.orig_image, math.degrees(self.body.angle), 1)
self.rect = self.image.get_rect(center=self.rect.center)
if self.rect.left < 0 or self.rect.right > 1280 or self.rect.y > 790:
self.space.remove(self.body, self.shape)
self.kill()
class Game:
def __init__(self):
pg.init()
self.screen = pg.display.set_mode((1280, 800))
self.done = False
self.clock = pg.time.Clock()
# Pymunk stuff
self.space = pm.Space()
self.space.gravity = Vec2d(0.0, -900.0)
self.space.damping = .9
self.static_lines = [
pm.Segment(self.space.static_body, flipy((60.0, 780.0)), flipy((650.0, 780.0)), .0),
pm.Segment(self.space.static_body, flipy((650.0, 780.0)), flipy((1218.0, 660.0)), .0)
]
for lin in self.static_lines:
lin.friction = 0.2
lin.elasticity = 0.99
self.space.add(self.static_lines)
self.all_sprites = pg.sprite.Group()
def run(self):
while not self.done:
self.dt = self.clock.tick(60) / 1000
self.handle_events()
self.run_logic()
self.draw()
self.current_fps = self.clock.get_fps()
pg.quit()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1: # Left mouse button.
# Spawn an entity.
radius = random.randrange(20, 50)
self.all_sprites.add(Entity(flipy(pg.mouse.get_pos()), self.space, radius))
if pg.mouse.get_pressed()[2]: # Right mouse button.
radius = random.randrange(20, 50)
self.all_sprites.add(Entity(flipy(pg.mouse.get_pos()), self.space, radius))
def run_logic(self):
self.space.step(1/60)
self.all_sprites.update()
def draw(self):
self.screen.fill(pg.Color(140, 120, 110))
self.all_sprites.draw(self.screen) # Draw the images of all sprites.
# Draw the static lines.
for line in self.static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.lines(self.screen, pg.Color('lightgray'), False, (p1, p2), 5)
pg.display.flip()
if __name__ == '__main__':
Game().run()
I also tried to find a way to hide these direction/rotation lines that debug_draw creates and found out that the lines are colored in the draw_options.shape_outline_color and the circles use the draw_options.shape_dynamic_color by default. So, when you set
draw_options.shape_outline_color = draw_options.shape_dynamic_color
the circles are completely blue. However, if you assign a specific color to each shape, it will still use the blue color for the lines.
And with the draw_options.flags attribute, I could only turn off the collision points or the shapes completely but not the lines:
# This is how you can turn off the collision points.
draw_options.flags ^= draw_options.DRAW_COLLISION_POINTS
# Stops drawing the constraints.
draw_options.flags ^= draw_options.DRAW_CONSTRAINTS
# Stops drawing all shapes.
draw_options.flags ^= draw_options.DRAW_SHAPES
Related
I am new to Pygame and I am in the experimentation phase.
What I have here is a simple display surface onto which I have rendered a few overlapping Rectangles with different colors with the same width and height (96 by 144) to simulate playing cards.
I have written some code in order to be able to highlight a card with the color Yellow when I click on it. If you run the code, you'll notice that when I click on the visible part of one of the cards, all the other cards that are behind it, as well as all of those that are overlapping it are being highlighted. I cannot figure out, what should I do to click the visible part of the card and only that card to highlight.
import pygame, sys
class Card(pygame.sprite.Sprite):
def __init__(self, color):
super().__init__()
self.color = color
self.is_highlight = False
def show_card(self, x, y):
self.rect = pygame.rect.Rect(x, y, 96, 144)
pygame.draw.rect(display_surf, self.color, self.rect)
def highlight_card(self):
pos = pygame.mouse.get_pos()
if self.rect.collidepoint(pos):
self.is_highlight = True
else:
self.is_highlight = False
def update(self):
if self.is_highlight:
pygame.draw.rect(display_surf, 'Yellow', self.rect.inflate(5, 5), width=3, border_radius=5)
class Deck(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.card_deck = []
def build_deck(self):
self.card_deck.append(Card('Green'))
self.card_deck.append(Card('Red'))
self.card_deck.append(Card('Blue'))
self.card_deck.append(Card('brown'))
self.card_deck.append(Card('cadetblue'))
self.card_deck.append(Card('cornsilk'))
self.card_deck.append(Card('darkolivegreen'))
self.card_deck.append(Card('darkorchid'))
self.card_deck.append(Card('darksalmon'))
return self.card_deck
pygame.init()
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
display_surf = pygame.display.set_mode((WINDOW_WIDTH,WINDOW_HEIGHT))
pygame.display.set_caption('Simple Card Game')
clock = pygame.time.Clock()
card_deck = Deck().build_deck()
while True:
display_surf.fill((0, 150,0))
distance = 20
for card in card_deck:
card.show_card(distance, 20)
distance += 20
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
for card in card_deck:
card.highlight_card()
for card in card_deck:
card.update()
pygame.display.update()
So I think I have solved my own question and wish to share the solution.
In the show_card method I have added a new scaled_rect which represents only the visible part of the colored card with a width of only 20, while I draw on the display surface the color using the original full sized rectangle:
def show_card(self, x, y):
self.rect = pygame.rect.Rect(x, y, 96, 144)
self.scaled_rect = pygame.rect.Rect((x, y), (20, 144))
pygame.draw.rect(display_surf, self.color, self.rect)
In the highlight_card method I have made only the visible part of the color interactable with the mouse click, by using the new scaled_rect:
def highlight_card(self):
pos = pygame.mouse.get_pos()
if self.scaled_rect.collidepoint(pos):
self.is_highlight = True
else:
self.is_highlight = False
In the end, in the update method that draws the yellow colored highlight, I use the original full sized rectangle:
def update(self):
if self.is_highlight:
pygame.draw.rect(display_surf, 'Yellow', self.rect.inflate(5, 5), width=3, border_radius=5)
This question already has answers here:
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
(1 answer)
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 2 years ago.
I am trying to make a canvas for pixel art.
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
Here I am trying to generate and draw 1830 unique surfaces and this works. I then tried implementing collision detection between each block and the mouse and failed.
def collided(self, pos):
for i in range(1380):
block = self.__blocks[i].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
Then I did different tests on why it might be failing. Here is one of them. I will change a single block's color, in our case the 10th block self.__blocks[10].fill((255, 0, 0)) to red so we know which box to click on. Then we will try to check for collision for that particular block.
def testBlock(self, pos):
block = self.__blocks[10].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x)
And it doesn't work, but the weird thing is it works for the first block(in the 0th index) and only the first block no matter which surface I test. Any idea on how to fix this would be appreciated. The following is copy and paste code.
import pygame
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
self.testBlock = 10
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
self.__blocks[self.testBlock].fill((255, 0, 0)) # Changing the color for testing
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
canvas = Canvas()
while True:
D.fill((0, 0, 0))
pygame.event.get()
mousepos = pygame.mouse.get_pos()
canvas.draw(D)
canvas.test(mousepos)
win.flip()
When you call .get_rect() on a Surface, it does not know its current position, because that is not Surface information. So you need to assign the location to the Rect before collision detection.
With your current code layout, you could do this during the construction. With the Canvass blocks position now held in the __rects list, the __positions list becomes superfluous.
class Canvass:
def __init__(self):
self.__blocks = []
self.__rects = []
for y in range( 30 ):
for x in range( 61 ):
self.__blocks.append(pygame.Surface((20, 20)).convert())
self.__rects.append( self.__blocks[-1].get_rect() )
self.__rects[-1].topleft = ( x, y )
self.__color = False
self.testBlock = 10
This gives you a simple test:
def collided(self, pos):
hit = False
for i in range( len( self.__rects ) ):
if ( self.__rects[i].collidepoint( pos[0], pos[1] ) ):
print( "Click on block %d" % ( i ) )
hit = True
break
return hit, i
.get_rect() gives rect with block's size but with position (0, 0)
you have real position in __positions and you would need
.get_rect(topleft=self.__positions[self.testBlock])
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect(topleft=self.__positions[self.testBlock])
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
But it would be better to get rect and set its position at start and later not use get_rect().
You could also create class Pixel similar to class Sprite with self.image to keep surface and self.rect to keep its size and position. And then you could use Group to check collision with all pixels.
EDIT:
Example which uses class pygame.sprite.Sprite to create class Pixel and it keeps all pixels in pygame.sprite.Group
It also handle events (MOUSEBUTTONDOWN) to change color in any pixel when it is clicked.
import pygame
# --- classes ---
class Pixel(pygame.sprite.Sprite):
def __init__(self, x, y, color, width=20, height=20):
super().__init__()
self.color_original = color
self.color = color
self.image = pygame.Surface((20, 20)).convert()
self.image.fill(self.color)
self.rect = pygame.Rect(x, y, width, height)
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
if self.color != self.color_original:
self.color = self.color_original
else:
self.color = (255,0,0)
self.image.fill(self.color)
# event handled
return True
# event not handled
return False
class Canvas:
def __init__(self):
# create group for sprites
self.__blocks = pygame.sprite.Group()
# create sprites
self.__color = False
for y in range(30):
y *= 20
for x in range(61):
x *= 20
self.__color = not self.__color
if self.__color:
color = (200, 200, 200)
else:
color = (50, 50, 50)
self.__blocks.add(Pixel(x, y, color))
# changing the color for testing
self.testBlock = 10
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
block.image.fill((255, 0, 0))
def draw(self, window):
# draw all sprites in group
self.__blocks.draw(window)
def test(self, pos):
# test collision with one sprite
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
if block.rect.collidepoint(pos):
print(block.rect.x, block.rect.y)
def handle_event(self, event):
for item in self.__blocks:
if item.handle_event(event):
# don't check other pixels if event already handled
return True
# --- main ---
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
canvas = Canvas()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
canvas.handle_event(event)
#mousepos = pygame.mouse.get_pos()
#canvas.test(mousepos)
# draws (without updates, etc)
#D.fill((0, 0, 0)) # no need clean screen if it will draw all elements again
canvas.draw(D)
win.flip()
I want to make a circular button in pygame.
I had made a class for the rectangle button. (below)
But I want to make it as a circle (and also other polygon).
I know there is no get_cir in Surface and it even doesn't work with it.
So how could I make it?
import pygame
class Button():
def __init__(self, text, x, y, width, height, normal_color, hovered_color,
label_color, font_type, font_size, command = None):
self.text = text
self.normal_color = normal_color
self.hovered_color = hovered_color
self.label_color = label_color
self.font_type = font_type
self.font_size = font_size
self.command = command
self.image_normal = pygame.Surface((width, height))
self.image_normal.fill(self.normal_color)
self.image_hovered = pygame.Surface((width, height))
self.image_hovered.fill(self.hovered_color)
self.image = self.image_normal
self.rect = self.image.get_rect()
font = pygame.font.SysFont(self.font_type, self.font_size)
text_image = font.render(text, True, self.label_color)
text_rect = text_image.get_rect(center = self.rect.center)
self.image_normal.blit(text_image, text_rect)
self.image_hovered.blit(text_image, text_rect)
self.rect.topleft = (x, y)
self.hovered = False
def update(self):
if self.hovered:
self.image = self.image_hovered
else:
self.image = self.image_normal
def draw(self, surface):
surface.blit(self.image, self.rect)
def handle_event(self, event):
if event.type == pygame.MOUSEMOTION:
self.hovered = self.rect.collidepoint(event.pos)
elif event.type == pygame.MOUSEBUTTONDOWN:
if self.hovered == True:
self.command()
You can keep the (fast and efficient) rectangular collision detection, and when that indicates a click, then check the event's (x,y) position distance from the centre-point of the circle.
The distance formula gives us a function:
def lineLength( pos1, pos2 ):
"""Return the distance between the points (pos1) and (pos2)"""
x1,y1 = pos1
x2,y2 = pos2
# length = sqrt((x2 - x1)^2 + (y2 - y1)^2)
x_squared = ( x2 - x1 ) * ( x2 - x1 )
y_squared = ( y2 - y1 ) * ( y2 - y1 )
length = math.sqrt( x_squared + y_squared )
return length
So then add an extra check to work out the distance between the centre of your button, and the mouse-click-point. This should be less than the button's radius for the hover/click to be on the button:
def handle_event( self, event ):
if event.type == pygame.MOUSEMOTION:
self.hovered = False
if ( self.rect.collidepoint( event.pos ) ):
# check click is over the circular button ~
radius = self.width / 2
click_distance = lineLength( self.rect.center, event.pos )
if ( click_distance <= radius ):
self.hovered = True
There is no problem to draw circle or use image with circle. The only problem is to check mouse collision with circle object.
There is sprite.collide_circle which uses circle areas to check collision between two sprites so you would need to create sprite with mouse position and check collision with button's sprite.
OR you can use circle definition
x**2 + y**2 =< R
so calculate distance between mouse and circle's center and it has to be equal or smaller then radius.
You can use even pygame.math.Vector2.distance_to() for this
For other figures the only idea is to use sprite.collide_mask which uses black&white bitmaps to check collisions between sprites. But it would need to create bitmap with filled figure and it can make problem if you want to draw polygon instead of use bitmap with polygon.
is it possible to get the coordinates of the place where the sprites are colliding?, and if it more than one is it possible to get both?
Thanks a lot for the solvers
You can use the Pymunk physics library to get the contact points. Of course that means you'll have to familiarize yourself with this library first (it can be a bit difficult for beginners). You especially need to know how collision handlers, arbiters and callback functions work.
So you create a collision handler which checks if there were collisions between shapes of two specified collision types, e.g. handler = space.add_collision_handler(1, 1). When a collsion occurs the handler calls some callback functions (in the example I set handler.post_solve to the callback function) which receive an arbiter object as an argument that holds the collision data. Then you can extract the needed information from this arbiter, add it to a list or other data structure and use it in your main loop.
import sys
import random
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk coordinates to pygame coordinates."""
return Vec2d(p[0], -p[1]+600)
class Ball(pg.sprite.Sprite):
def __init__(self, space, pos, mass=5, radius=30, elasticity=0.9):
super().__init__()
self.image = pg.Surface((60, 60), pg.SRCALPHA)
pg.draw.circle(self.image, pg.Color('royalblue'), (30, 30), radius)
self.rect = self.image.get_rect(center=pos)
# Set up the body and shape of this object and add them to the space.
inertia = pm.moment_for_circle(mass, 0, radius, (0, 0))
self.body = pm.Body(mass, inertia)
self.body.position = flipy(pos)
self.shape = pm.Circle(self.body, radius, (0, 0))
self.shape.elasticity = elasticity
# This type will be used by the collision handler.
self.shape.collision_type = 1
self.space = space
self.space.add(self.body, self.shape)
def update(self):
pos = flipy(self.body.position)
self.rect.center = pos
if pos.y > 600:
self.space.remove(self.body, self.shape)
self.kill()
def main():
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
done = False
contact_points = []
def contact_callback(arbiter, space, data):
"""Append the contact point to the contact_points list."""
if arbiter.is_first_contact:
for contact in arbiter.contact_point_set.points:
contact_points.append(contact.point_a)
# Pymunk stuff.
space = pm.Space()
space.gravity = Vec2d(0, -900)
# This collision handler will be used to get the contact points.
# It checks if shapes with `collision_type` 1 collide with others
# that also have type 1.
handler = space.add_collision_handler(1, 1)
# After a collision is solved, the callback funtion will be called
# which appends the contact point to the `contact_points` list.
handler.post_solve = contact_callback
# Create some static lines.
static_lines = [
pm.Segment(space.static_body, (170, 200), (0, 300), .1),
pm.Segment(space.static_body, (170, 200), (500, 200), .1),
pm.Segment(space.static_body, (500, 200), (600, 260), .1),
]
for line in static_lines:
line.elasticity = 0.9
space.add(static_lines)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
all_sprites.add(Ball(space, event.pos))
contact_points = []
space.step(1/60) # Update the physics space.
all_sprites.update()
screen.fill((60, 70, 80))
all_sprites.draw(screen) # Draw the sprite group.
# Draw static_lines.
for line in static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.line(screen, pg.Color('gray68'), p1, p2, 5)
# Draw contact_points.
for point in contact_points:
x, y = flipy(point)
x, y = int(x), int(y)
pg.draw.circle(screen, pg.Color('orange'), (x, y), 8)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
I've been banging my head against this for a while. I am trying to make a game with PyGame and I got up to the collision segment and have been stuck for a while and have checked a few threads.
This is the code I have (removed other methods and conditional statements in between, but left the relevant parts). I am a little confused by the error because I do a self.imageRect = self.image.get_rect() in both classes init, yet I have this error. The error was specifically:
"AttributeError: 'Pebble' object has no attribute 'rect'" when the program attempts to carry out the collision detection part in the dog class. What have I been doing wrong?
import random
import pygame, sys
pygame.init()
clock = pygame.time.Clock() # fps clock
screenSize = WIDTH, HEIGHT = [800, 600]
screen = pygame.display.set_mode(screenSize)
background = pygame.Surface(screen.get_size())
bgColorRGB = [153, 204, 255]
background.fill(bgColorRGB)
pebbleGroup = pygame.sprite.Group()
pebbleSingle = pygame.sprite.GroupSingle()
dogSingle = pygame.sprite.GroupSingle()
#----------------------------------------------------------------------
class Dog(pygame.sprite.Sprite):
def __init__(self, path, speed):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image = pygame.image.load(path) # load sprite from path/file loc.
self.imageRect = self.image.get_rect() # get bounds of image
self.imageWidth = self.image.get_width()
self.imageHeight = self.image.get_height()
self.speed = speed
# sets location of the image, gets the start location of object
# sets the start location as the image's left and top location
def setLocation(self, location):
self.imageRect.left, self.imageRect.top = location
def checkCollision(self, pebble, dogGroup):
if pygame.sprite.spritecollide(pebble, dogGroup, False):
print "collided"
#---------------------------------------------------------------------
class Pebble(pygame.sprite.Sprite):
def __init__(self, path, speed, location):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(path)
self.imageRect = self.image.get_rect()
self.imageWidth = self.image.get_width()
self.imageHeight = self.image.get_height()
self.imageRect.left, self.imageRect.top = location
self.speed = speed # initialize speed
self.isDragged = False
#----------------------------------------------------------------------
def startGame():
pebblePaths = ['images/pebble/pebble1.jpg', 'images/pebble/pebble2.jpg']
for i in range(3):
pebblePath = pebblePaths[random.randrange(0, len(pebblePaths))]
pebbleSpeed = [random.randrange(1, 7), 0]
pebbleLocation = [0, random.randrange(20, HEIGHT - 75)]
pebble = Pebble(pebblePath, pebbleSpeed, pebbleLocation)
pebbleGroup.add(pebble)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for pebble in pebbleGroup:
dog.checkCollision(pebble, dogSingle)
pygame.display.flip()
clock.tick(30) # wait a little before starting again
startGame()
It's expecting Sprite.rect, so change from
self.imageRect = self.image.get_rect()
#to
self.rect = self.image.get_rect()
Note
self.imageWidth = self.image.get_width()
self.imageHeight = self.image.get_height()
self.imageRect.left, self.imageRect.top = location
These are not necessary, since rect's have many properties, like self.rect.width or self.rect.topleft = location . Another useful one is centerx.
full list at: pygame Rect docs