PyGame collide_rect is detecting a non-existent collision - python

I'm still pretty new to pygame, and coding in general. I'm making a game that requires collision detection, and I seem to be having a problem with it. Every time I run my program, it detects non-existent collisions. Here are some snippets from my code:
class Player(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = width
self.height = height
self.right = False
self.left = False
self.up = False
self.down = False
self.surf = pygame.Surface((50,50))
self.rect = self.surf.get_rect()
def draw(self):
pygame.draw.rect(screen, (0,0,0), (self.x, self.y, self.width, self.height))
def collision_test(self):
if pygame.sprite.collide_rect(self, block1):
print("a collision is detected")
The above is my player class.
class Block1(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = width
self.height = height
self.surf = pygame.Surface((self.width,self.height))
self.rect = self.surf.get_rect()
def draw(self):
pygame.draw.rect(screen, (150,150,150), (self.x, self.y, self.width, self.height))
And that's my class for the obstacle that my player is supposed to collide with. I'm running a print command in the collision detection for debugging. Like I said, it just constantly prints the message I gave it, even when they aren't colliding. There are no error messages though. Any help would be appreciated! Thanks in advance :)
EDIT:
I changed the collision_test method and added the block1 argument. Now it is this:
def collision_test(self, block1):
if pygame.sprite.collide_rect(self, block1):
print("a collision is detected")
My player and block1 sprites are initiated just before the mainloop, and look like this:
player = Player(50,50,50,50)
block1 = Block1(200, 200, 100, 100)
I am calling the function, collision_test, at the end of the mainloop. In case you need it, here is my full code: https://pastebin.com/LTQdLMuV

What happens is that you forgot to update the position of the rectangles of your objects.
From pygame docs:
get_rect()
get the rectangular area of the Surface
get_rect(**kwargs)-> Rect
Returns a new rectangle covering the entire surface. This rectangle will always start at 0, 0 with a width. and height the same size as the image.
In both classes Player and Block1 you have a line:
self.rect = self.surf.get_rect()
To use colliderect() the rect attribute must be updated to the position (in pixels) of the image on the screen, otherwise there is a mismatch between the coordinates used by your draw() method and the rectangle used to check the collisions. Do instead:
self.rect = self.surf.get_rect().move(x, y)
so that, when the object is created, the rect attribute corresponds to the real position of the object on the screen.
Remember to update the position of player.rect when you move the player square. Edit your move_player() function too, for example by adding:
player.rect.x = player.x
player.rect.y = player.y
So that rect corresponds on what you have on the screen.
EDIT after comments
If your goal is how to preveent orverlapping between surfaces, it's more complicated. Detecting the collision is only part of the process. The full steps are:
move the player object.
detecting not only if there is a collision, but also the sides which collided.
once the side is detected, move back the player object on that axis.
redraw.

Related

Problem with slowing down ball in pong game

I've been making pong with pygame and I got the ball to bounce around the screen and on the paddles. However, the speed is too high and I want to decrease it. This is what the code looks like for the Ball object:
import pygame as pg
BLACK = (0, 0, 0)
class Ball(pg.sprite.Sprite):
def __init__(self, color, width, height, radius):
super().__init__()
self.x_vel = 1
self.y_vel = 1
self.image = pg.Surface([width * 2, height * 2])
self.image.fill(BLACK)
self.image.set_colorkey(BLACK)
pg.draw.circle(self.image, color, center=[width, height], radius=radius)
self.rect = self.image.get_rect()
def update_ball(self):
self.rect.x += self.x_vel
self.rect.y += self.y_vel
If I try to set the velocity as a float, it stops the ball completely. Can someone help me?
Use pygame.time.Clock to control the frames per second and thus the game speed.
The method tick() of a pygame.time.Clock object, delays the game in that way, that every iteration of the loop consumes the same period of time. See pygame.time.Clock.tick():
This method should be called once per frame.
That means that the loop:
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
runs 100 times per second.
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the movement of the object is assigned to the Rect object.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:
class Ball(pg.sprite.Sprite):
def __init__(self, color, width, height, radius):
# [...]
self.rect = self.image.get_rect()
self.x = self.rect.x
self.y = self.rect.y
def update_ball(self):
self.x += self.x_vel
self.y += self.y_vel
self.rect.x = round(self.x)
self.rect.y = round(self.y)

How to implement wall collision when walls are one group and my sprites are in another

I am making a game where a player moves through a level, the target moves randomly through the level and there are guards (not part of game yet). I am having trouble implementing a wall collision as my sprites are circles and my walls are rectangles so I do not know how to do this also, the walls and the sprites are in different classes and groups.
I've thought about using masks for my sprites but I am not confident with how to do this. I also don't know how to design the method for wall collisions as the current one sends my player (I have not got it working for the target as well) to the bottom of any wall.
class characters(pygame.sprite.Sprite):
def __init__(self, colour, width, height):
super().__init__()
self.colour = colour
self.width = width
self.height = height
self.image = pygame.Surface([self.width, self.height])
self.image.fill(white)
self.image.set_colorkey(white)
self.rect = self.image.get_rect()
pygame.draw.circle(self.image, self.colour, [int(self.width/2), int(self.height/2)], int(self.width/2))
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveUp(self, pixels):
self.rect.y -= pixels
def moveDown(self, pixels):
self.rect.y += pixels
def moveRandom(self, pixels, clockrate, count, xdirection, ydirection):
self.rect.x += pixels * xdirection
self.rect.y += pixels * ydirection
#print(self.rect.x,self.rect.y)
def wallCollide(self):
x = self.rect.x
for xpos in [-5,5]:
self.rect.x += xpos
if pygame.sprite.spritecollide(self, wallSet, False, pygame.sprite.collide_rect):
self.rect.x -= xpos
else:
self.rect.x = x
y = self.rect.y
for ypos in [-5,5]:
self.rect.y += ypos
if pygame.sprite.spritecollide(self, wallSet, False, pygame.sprite.collide_rect):
self.rect.y -= ypos
else:
self.rect.y = y
I attempted a mask in the wall class below, don't think it works.
class walls(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
super().__init__()
self.width = width
self.height = height
self.x = x
self.y = y
self.image = pygame.Surface([width,height])
self.image.fill(black)
#self.image.set_colorkey(black) Using this will make the walls invisible as I have textured walls drawn over
self.rect = self.image.get_rect(topleft=(self.x, self.y))
pygame.mask.from_surface(self.image)
I have only given one wall and reduced the resolution as it is far easier whilst I write my code. The resolution should be 1920 by 1080. Use 600 by 650 to see the two walls.
characterSet = pygame.sprite.Group()
player = characters(black, 30, 30)
target = characters(green, 30, 30)
characterSet.add(player, target)
wallSet = pygame.sprite.Group()
Wall1 = walls(288,176, 200,142)
Wall2 = walls(286,427, 201,140)
wallSet.add(Wall1,Wall2)
The main game loop
while not end:
for event in pygame.event.get():
if event.type == pygame.QUIT:
end = True
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.moveLeft(5)
if keys[pygame.K_RIGHT]:
player.moveRight(5)
if keys[pygame.K_UP]:
player.moveUp(5)
if keys[pygame.K_DOWN]:
player.moveDown(5)
if count%clockrate == 0:
xdirection = random.choice(direction)
ydirection = random.choice(direction)
target.moveRandom(2, clockrate, count, xdirection, ydirection)
count += 1
if pygame.sprite.spritecollide(player, wallSet, False, pygame.sprite.collide_rect):
player.wallCollide()
gameDisplay.blit(level1,[0,0])
wallSet.draw(gameDisplay)
gameDisplay.blit(innerWalls,[0,0])
characterSet.draw(gameDisplay)
If I understand correctly you have a list of wall objects and a list of character objects and you want to check collision between all of them. this can be accomplished quickly by looping over both lists:
for wall in WallSet:
for character in CharacterSet:
character.checkCollision(wall)
The checkCollision() method can be very simple, checking if the rectangles around your objects overlap would just be a few if statements. If you want this checkCollisions() method to work well for rounded characters you'll need to write something fancier. I would suggest:
Write a function that will give the point inside a rectangle that is closest to another point (the hard part! Though I'm sure there are tutorials on how to do this online)
Take that point inside the wall's rectangle, and check if that is far enough away from the character's middle position to not be colliding.
Hopefully this helps! For future questions I would suggest trying to very clearly ask 1 question and reduce the code you are posting further. For the question you are asking, the amount of code you posted is too much, and probably turned away potential answerers for these past 2 days.

Pygame overlapping Sprites (draw order) based on location

I'm still relatively new to Pygame and Python in general, so hopefully this isn't too out there.
I'm making a top-down RPG, and I have two Sprite objects with images that look (for example) like these:
that use rects that do not represent the entirety of the image:
class NPC(pygame.sprite.Sprite):
def __init__(self, image, pos, *groups):
super().__init__(*groups)
self.image = pygame.load(image)
self.rect = self.image.get_rect()
self.collisionRect = pygame.Rect(self.rect.left, self.rect.top + 12, self.image.get_width(), self.image.get_width())
#12 is the number of pixels that will overlap in the y-dimension
I'm doing this because I want the top few pixels of the NPC to overlap with other sprites. The collisionRect in each object is used over the rect whenever I detect a collision, so that I can create this effect.
However, I need a way to redraw them within my update() function so that one draws over the other based on their relative locations to each other.
So, when one NPC is above the other it looks like this:
But, when it's the other way around, it should look like this:
Which means that the images need to be drawn in a different order depending on which sprite is 'below' the other.
I've thought about possibly cutting the sprites into separate sprites and just have the 'head' sprites draw last, but I was wondering if there was a simpler (or at least a reliable) way to detect whether a sprite should be drawn last or not, based on whether or not it is both overlapping another sprite and immediately below it in the y-dimension.
I apologize if this question is too broad or needs more context; can provide those if needed.
As Kingsley already said in a comment, sorting your sprites by their y coordinate is a common way to do this.
Here's a full, running example (I named your images guy.png and gal.png). Since you already use sprites, I used a simple pygame.sprite.Group-subclass:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.pos = pygame.Vector2(pos)
self.rect = self.image.get_rect(center=self.pos)
def update(self, events, dt):
pass
class Player(Actor):
def __init__(self, image, pos):
super().__init__(image, pos)
def update(self, events, dt):
pressed = pygame.key.get_pressed()
move = pygame.Vector2((0, 0))
if pressed[pygame.K_w]: move += (0, -1)
if pressed[pygame.K_a]: move += (-1, 0)
if pressed[pygame.K_s]: move += (0, 1)
if pressed[pygame.K_d]: move += (1, 0)
if move.length() > 0: move.normalize_ip()
self.pos += move*(dt/5)
self.rect.center = self.pos
class YAwareGroup(pygame.sprite.Group):
def by_y(self, spr):
return spr.pos.y
def draw(self, surface):
sprites = self.sprites()
surface_blit = surface.blit
for spr in sorted(sprites, key=self.by_y):
self.spritedict[spr] = surface_blit(spr.image, spr.rect)
self.lostsprites = []
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
dt = 0
sprites = YAwareGroup(Player(pygame.image.load('guy.png').convert_alpha(), (100, 200)),
Actor(pygame.image.load('gal.png').convert_alpha(), (200, 200)))
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
dt = clock.tick(60)
if __name__ == '__main__':
main()
If you need custom drawing logic, it's usually not the worst idea to subclass pygame's Group classes. You can find their source here to see how they work.

Why is my cube moving faster to the left than to the right?

I'm trying to learn game programming (and programming in general), therefore im making a simple sidescroller in Python using pygame.
The problem is, when I'm moving the character (a simple cube) to the left it moves faster than when im moving to the right, as shown in console later on.
Here is picture of the game.
The main class with the loop (I've cut out some of it, because it doesn't have relevance, I think, but I can post it if necessary):
player.speed = 135
player = Player.Cube([255,0,0],600, screen.get_height()*0.8-screen.get_height()*0.125,screen.get_height()*0.125,screen.get_height()*0.125)
running = True
clock = pygame.time.Clock()
while running:
deltaTime = clock.tick(60) / 1000
screen.blit(background, (0, 0))
screen.blit(player.image,(player.rect.x,player.rect.y))
screen.blit(grass,(0,screen.get_height()-grass.get_height()))
keystate = pygame.key.get_pressed()
if keystate[K_RIGHT]:
player.moveRight(deltaTime)
elif keystate[K_LEFT]:
player.moveLeft(deltaTime)
pygame.display.flip()
Cube class:
class Cube(pygame.sprite.Sprite):
speed = 0
def __init__(self, color, left,top, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.left = left
self.top = top
self.image = pygame.Surface([width,height])
self.image.fill(color)
self.rect = pygame.Rect(left, top, width, height)
def moveRight(self,deltaTime):
oldX=self.rect.x
self.rect.x += self.speed*deltaTime
print(self.speed*deltaTime)
print("deltaX: " + str(self.rect.x-oldX))
def moveLeft(self,deltaTime):
oldX = self.rect.x
self.rect.x -= self.speed*deltaTime
print(self.speed * deltaTime)
print("deltaX: " + str(self.rect.x-oldX))
As you can see, I'm trying to print out, how many pixels I moved to the right vs how many to the left:
Speed times deltaTime, right: 2.2950000000000004
deltaX, right: 2
exiting game
Speed times deltaTime, left: 2.2950000000000004
deltaX, left: -3
I don't understand one bit of this, does anyone now what the cause of this is?
Also, my movement is stuttering a bit, why is that?
That happens because pygame.Rects can only have integers as their coordinates, and if you add or subtract a floating point number the result gets truncated afterwards.
For example if the rect.x is 10 and you add 1.5 to rect.x the new value is 11.5 which is then converted to 11. If you subtract 1.5 from 10 you get 8.5 and after truncating it 8. So in the first case you move 1 px to the right and in the second you move 2 px to the left.
The solution is to store the actual position as a separate variable or attribute, add the speed to it and then assign this value to the rect.
pos_x += speed
rect.x = pos_x
I usually use vectors to store the position and the velocity.
import pygame as pg
from pygame.math import Vector2
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0, 0)
self.pos = Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos

How would I handle checking for collision detection for multiple sprites of the same type?

I'm trying to handle collision checking on multiple sprites checking it against a player character. Here's the relevant code, the Enemy class creates a new sprite that is supposed to be represented by an image, and the Character class is similar, except it is the sprite the player can control. Here's the relevant code I've snipped from the project.
self.all_sprites_list = pygame.sprite.Group()
sprite = Character(warrior, (500, 500), (66, 66))
enemies = []
for i in range(10):
enemy = Enemy("evilwizard")
enemies.append(enemy)
self.all_sprites_list.add(enemy)
self.all_sprites_list.add(sprite)
class Enemy(pygame.sprite.Sprite):
# This class represents the types of an enemy possible to be rendered to the scene
def __init__(self, enemy_type):
super().__init__() # Call sprite constructor
# Pass in the type of enemy, x/y pos, and width/height (64x64)
self.image = pygame.Surface([76, 76])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(10, 1150) # random start
self.rect.y = random.randrange(10, 590) # random start
self.speed = 2
self.move = [None, None] # x-y coordinates to move to
self.image = pygame.image.load(FILE_PATH_ENEMY + enemy_type + ".png").convert_alpha()
self.direction = None # direction to move the sprite`
class Character(pygame.sprite.Sprite):
def __init__(self, role, position, dimensions):
"""
:param role: role instance giving character attributes
:param position: (x, y) position on screen
:param dimensions: dimensions of the sprite for creating image
"""
super().__init__()
# Call the sprite constructor
# Pass in the type of the character, and its x and y position, width and height.
# Set the background color and set it to be transparent.
self.image = pygame.Surface(dimensions)
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.image = pygame.image.load(FILE_PATH_CHAR + role.title + ".png").convert_alpha()
# Draw the character itself
# position is the tuple (x, y)
self.rect = self.image.get_rect()
self.rect.x, self.rect.y = position
self.attack = role.attack
self.health = role.health
self.title = role.title
Pygame has pygame.sprite.spritecollide to check collision between pygame.sprite.Sprite() and pygame.sprite.Group()
It has also other functions to check collisions - see doc for Sprite

Categories