pygame bitmask collision won't work - python
Though it the collision is detected when I use pygame.sprite.collide_rect or pygame.sprite.collide_circle, when I try to assign a bitmask to a sprite, and run it as so, the collision is not detected. (The wall.collision_action() makes the first circle move at the same speed and direction of the other circle) Though this is not a concern now,
as I am using images of circles which makes the pygame.sprite.collide_circle work fine, in the future when I am using more detailed and non-circular sprites it will be.
code:
import pygame, sys, math
from pygame.locals import *
pygame.init()
class ball_class(pygame.sprite.Sprite):
def __init__(self, surface):
self.surface = surface
self.x = 250
self.y = 250
self.vy = 0
self.vx = 0
self.sprite = pygame.sprite.Sprite()
self.sprite.image = pygame.image.load("ball.png").convert()
self.sprite.rect = self.sprite.image.get_rect(center = (self.x, self.y))
self.sprite.rect.topleft = [int(self.x), int(self.y)]
self.sprite.mask = pygame.mask.from_surface(self.sprite.image)
def event(self, event):
if event.key == K_UP:
self.vy = -1
self.vx = 0
elif event.key == K_DOWN:
self.vy = 1
self.vx = 0
elif event.key == K_LEFT:
self.vx = -1
self.vy = 0
elif event.key == K_RIGHT:
self.vx = 1
self.vy = 0
def move(self):
self.y += self.vy
self.x += self.vx
self.sprite.rect.topleft = [int(self.x), int(self.y)]
self.sprite.mask = pygame.mask.from_surface(self.sprite.image)
def draw(self, surface):
surface.blit(self.sprite.image, self.sprite.rect)
def position(self):
return self.sprite.rect()
class wall_class(pygame.sprite.Sprite):
def __init__(self, surface):
self.surface = surface
self.x = 250
self.y = 100
self.vy = 0
self.sprite = pygame.sprite.Sprite()
self.sprite.image = pygame.image.load("ball.png").convert()
self.sprite.rect = self.sprite.image.get_rect(center = (self.x, self.y))
self.sprite.rect.topleft = [int(self.x), int(self.y)]
self.sprite.mask = pygame.mask.from_surface(self.sprite.image)
def draw(self, surface):
surface.blit(self.sprite.image, self.sprite.rect)
def collision_action(self):
self.vy = ball.vy
self.vx = ball.vx
self.x += self.vx
self.y += self.vy
self.sprite.rect.topleft = [int(self.x), int(self.y)]
def gameQuit():
pygame.quit()
sys.exit()
screen = pygame.display.set_mode((500, 500), 0, 32)
ball = ball_class(screen)
wall = wall_class(screen)
clock = pygame.time.Clock()
while True:
screen.fill((0, 0, 0))
ball.move()
ball.draw(screen)
wall.draw(screen)
is_a_collision = pygame.sprite.collide_mask(wall.sprite, ball.sprite)
if is_a_collision:
wall.collision_action()
for event in pygame.event.get():
if event.type == QUIT:
gameQuit()
elif event.type == KEYDOWN:
ball.event(event)
clock.tick(100)
pygame.display.update()
The bitmask collision is working in your code, but I think the code is making it more difficult because the sprite objects ball_class and wall_class, have separate sub-sprite objects defined inside them.
class ball_class(pygame.sprite.Sprite):
def __init__(self, surface):
self.surface = surface
self.x = 250
self.y = 250
self.vy = 0
self.vx = 0
self.sprite = pygame.sprite.Sprite() # <-- HERE
...
Generally when the code "subclasses" an existing class/object it becomes one of the same. So it is not necessary to have a secondary sprite contained within the class. This also makes the code and logic more involved, tricky and confusing.
Please consider this re-working of your ball_class:
class ball_class(pygame.sprite.Sprite):
def __init__(self, surface):
pygame.sprite.Sprite.__init__(self) # <-- HERE Must initialise sprites
self.x = 250
self.y = 250
self.vy = 0
self.vx = 0
self.image = pygame.image.load("ball_64.png").convert_alpha()
self.rect = self.image.get_rect(center = (self.x, self.y))
self.mask = pygame.mask.from_surface(self.image)
Note the image, rect and mask member variables. Previously the code was using these off the contained sprite. But the PyGame library is written to expect these as members of the class, and it doesn't work properly if they're not defined. Also your two sprite classes were not calling the base sprite initialiser function.
Here is a re-working of your code, which - when the ball hits the corner of the wall, collides properly (with bit-masks).
ball_64.png
wall.png
import pygame, sys, math
from pygame.locals import *
pygame.init()
class ball_class(pygame.sprite.Sprite):
def __init__(self, surface):
pygame.sprite.Sprite.__init__(self)
self.surface = surface
self.x = 250
self.y = 250
self.vy = 0
self.vx = 0
self.image = pygame.image.load("ball_64.png").convert_alpha()
self.rect = self.image.get_rect(center = (self.x, self.y))
# self.sprite.rect.topleft = [int(self.x), int(self.y)]
self.mask = pygame.mask.from_surface(self.image)
def event(self, event):
if event.key == K_UP:
self.vy = -1
self.vx = 0
elif event.key == K_DOWN:
self.vy = 1
self.vx = 0
elif event.key == K_LEFT:
self.vx = -1
self.vy = 0
elif event.key == K_RIGHT:
self.vx = 1
self.vy = 0
def move(self):
self.y += self.vy
self.x += self.vx
self.rect.topleft = [int(self.x), int(self.y)]
#self.mask = pygame.mask.from_surface(self.sprite.image)
def draw(self, surface):
surface.blit(self.image, self.rect)
def position(self):
return self.rect()
class wall_class(pygame.sprite.Sprite):
def __init__(self, surface):
pygame.sprite.Sprite.__init__(self)
self.x = 250
self.y = 100
self.vy = 0
self.image = pygame.image.load("wall.png").convert_alpha()
self.rect = self.image.get_rect(center = (self.x, self.y))
# self.sprite.rect.topleft = [int(self.x), int(self.y)]
self.mask = pygame.mask.from_surface( self.image )
def draw(self, surface):
surface.blit(self.image, self.rect)
def collision_action(self, by_sprite_at ):
print("wall_class.collision_action( by_sprite_at=%s )" % ( str( by_sprite_at ) ) )
# self.vy = ball.vy
# self.vx = ball.vx
# self.x += self.vx
# self.y += self.vy
# self.sprite.rect.topleft = [int(self.x), int(self.y)]
def gameQuit():
pygame.quit()
sys.exit()
screen = pygame.display.set_mode((500, 500), 0, 32)
ball = ball_class(screen)
wall = wall_class(screen)
clock = pygame.time.Clock()
while True:
screen.fill((128,128,128))
ball.move()
ball.draw(screen)
wall.draw(screen)
is_a_collision = pygame.sprite.collide_mask(wall, ball)
if is_a_collision:
wall.collision_action( ball.rect.center )
for event in pygame.event.get():
if event.type == QUIT:
gameQuit()
elif event.type == KEYDOWN:
ball.event(event)
clock.tick(100)
pygame.display.update()
Related
I am having trouble coding my collision check function for my fps game. How can I write my collision function?
I am stuck on how to write my collision function in my player class. Also should I put my collision check function in my player class or should it be in my bullet class? I honestly don't know. I mean common sense says my bullet should have the collision check function in it, just because I want the top of my bullet checking if it hits a falling cement block sprite, but I don't know.... I would appreciate an answer on how to code my collision function. This would really help me out. My collision function is right below my shoot function in my player class. My code: import pygame pygame.init() #screen settings WIDTH = 1000 HEIGHT = 400 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("AutoPilot") screen.fill((255, 255, 255)) #fps FPS = 120 clock = pygame.time.Clock() #load images bg = pygame.image.load('background/street.png').convert_alpha() # background bullets = pygame.image.load('car/bullet.png').convert_alpha() debris_img = pygame.image.load('debris/cement.png') #define game variables shoot = False #player class class Player(pygame.sprite.Sprite): def __init__(self, scale, speed): pygame.sprite.Sprite.__init__(self) self.bullet = pygame.image.load('car/bullet.png').convert_alpha() self.bullet_list = [] self.speed = speed #self.x = x #self.y = y self.moving = True self.frame = 0 self.flip = False self.direction = 0 #load car self.images = [] img = pygame.image.load('car/car.png').convert_alpha() img = pygame.transform.scale(img, (int(img.get_width()) * scale, (int(img.get_height()) * scale))) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() self.update_time = pygame.time.get_ticks() self.movingLeft = False self.movingRight = False self.rect.x = 465 self.rect.y = 325 #draw car to screen def draw(self): screen.blit(self.image, (self.rect.centerx, self.rect.centery)) #move car def move(self): #reset the movement variables dx = 0 dy = 0 #moving variables if self.movingLeft and self.rect.x > 33: dx -= self.speed self.flip = True self.direction = -1 if self.movingRight and self.rect.x < 900: dx += self.speed self.flip = False self.direction = 1 #update rectangle position self.rect.x += dx self.rect.y += dy #shoot def shoot(self): bullet = Bullet(self.rect.centerx + 18, self.rect.y + 30, self.direction) bullet_group.add(bullet) #def collision(self): #write code here #bullet class class Bullet(pygame.sprite.Sprite): def __init__(self, x, y, direction): pygame.sprite.Sprite.__init__(self) self.speed = 5 self.image = bullets self.rect = self.image.get_rect() self.rect.center = (x,y) self.direction = direction def update(self): self.rect.centery -= self.speed #check if bullet has gone off screen if self.rect.top < 1: self.kill() #debris class class Debris(pygame.sprite.Sprite): def __init__(self, x, y, scale, speed): pygame.sprite.Sprite.__init__(self) self.scale = scale self.x = x self.y = y self.speed = speed self.vy = 0 self.on_ground = True self.move = True self.health = 4 self.max_health = self.health self.alive = True #load debris self.image = debris_img self.rect = self.image.get_rect() self.rect.center = (x,y) ######################CAR/DEBRIS########################## player = Player(1,5) debris = Debris(300,15,1,5) ########################################################## #groups bullet_group = pygame.sprite.Group() debris_group = pygame.sprite.Group() debris_group.add(debris) #game runs here run = True while run: #draw street screen.blit(bg, [0, 0]) #update groups bullet_group.update() bullet_group.draw(screen) debris_group.update() debris_group.draw(screen) #draw car player.draw() player.move() for event in pygame.event.get(): if event.type == pygame.QUIT: run = False #check if key is down if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: run = False if event.key == pygame.K_a: player.movingLeft = True if event.key == pygame.K_d: player.movingRight = True if event.key == pygame.K_SPACE: player.shoot() shoot = True #check if key is up if event.type == pygame.KEYUP: if event.key == pygame.K_a: player.movingLeft = False if event.key == pygame.K_d: player.movingRight = False #update the display pygame.display.update() pygame.display.flip() clock.tick(FPS) pygame.quit()
I suggest reading How do I detect collision in pygame?. Use pygame.sprite.spritecollide() for the collision detection. Set the dokill argument True. So the bullets gat automatically destroyed. The following code is base on one of your previous questions: Check collision of bullet sprite hitting cement block sprite class Car(pygame.sprite.Sprite): # [...] def collision(self, debris_group): for debris in debris_group: if pygame.sprite.spritecollide(debris, bullet_group, True): debris.health -= 1 if debris.health <= 0: debris.kill()
Im stuck on doing an explosion animation for my cement block sprites [duplicate]
This question already exists: I want to do an explosion animation. I have 4 png files number 0 - 3. In my debris class I loaded them into an image list using a for loop [duplicate] Closed 1 year ago. I want to do an explosion animation. I have 4 png files number 0 - 3. In my debris class I loaded them into an image list using a for loop. Then I made an explosion function but when I load my pygame windows and start shooting my cement block sprites, my cement block sprites disappear which is fine but they don't do the exploding animation I want. How can I fix this problem. Would appreciate the help. My code: import random import pygame import pygame.freetype pygame.init() #screen settings WIDTH = 1000 HEIGHT = 400 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("AutoPilot") screen.fill((255, 255, 255)) #fps FPS = 120 clock = pygame.time.Clock() #load images bg = pygame.image.load('background/street.png').convert_alpha() # background bullets = pygame.image.load('car/bullet.png').convert_alpha() debris_img = pygame.image.load('debris/cement.png') #define game variables shoot = False #player class class Player(pygame.sprite.Sprite): def __init__(self, scale, speed): pygame.sprite.Sprite.__init__(self) self.bullet = pygame.image.load('car/bullet.png').convert_alpha() self.bullet_list = [] self.speed = speed #self.x = x #self.y = y self.moving = True self.frame = 0 self.flip = False self.direction = 0 self.score = 0 #load car self.images = [] img = pygame.image.load('car/car.png').convert_alpha() img = pygame.transform.scale(img, (int(img.get_width()) * scale, (int(img.get_height()) * scale))) self.images.append(img) self.image = self.images[0] self.rect = self.image.get_rect() self.update_time = pygame.time.get_ticks() self.movingLeft = False self.movingRight = False self.rect.x = 465 self.rect.y = 325 #draw car to screen def draw(self): screen.blit(self.image, (self.rect.centerx, self.rect.centery)) #move car def move(self): #reset the movement variables dx = 0 dy = 0 #moving variables if self.movingLeft and self.rect.x > 33: dx -= self.speed self.flip = True self.direction = -1 if self.movingRight and self.rect.x < 900: dx += self.speed self.flip = False self.direction = 1 #update rectangle position self.rect.x += dx self.rect.y += dy #shoot def shoot(self): bullet = Bullet(self.rect.centerx + 18, self.rect.y + 30, self.direction) bullet_group.add(bullet) #check collision def collision(self, debris_group): for debris in debris_group: if pygame.sprite.spritecollide(debris, bullet_group, True): debris.health -= 1 if debris.health <= 0: self.score += 1 debris.kill() #player stats def stats(self): myfont = pygame.font.SysFont('comicsans', 30) scoretext = myfont.render("Score: " + str(self.score), 1, (0,0,0)) screen.blit(scoretext, (100,10)) #bullet class class Bullet(pygame.sprite.Sprite): def __init__(self, x, y, direction): pygame.sprite.Sprite.__init__(self) self.speed = 5 self.image = bullets self.rect = self.image.get_rect() self.rect.center = (x,y) self.direction = direction def update(self): self.rect.centery -= self.speed #check if bullet has gone off screen if self.rect.centery < 1: self.kill() #debris class class Debris(pygame.sprite.Sprite): def __init__(self,scale,speed): pygame.sprite.Sprite.__init__(self) self.scale = scale self.x = random.randrange(100,800) self.speed_y = 10 self.y = 15 self.speed = speed self.vy = 0 self.on_ground = True self.move = True self.health = 4 self.max_health = self.health self.alive = True self.velocity = random.randrange(1,2) self.speed_x = random.randrange(-3,3) self.moving_down = True #load debris self.image = debris_img self.rect = self.image.get_rect() self.rect.x = random.randrange(100, 800) self.rect.y = random.randrange(-150, -100) self.rect.center = (self.x,self.y) #load explosion self.images = [] for i in range(4): self.images.append(pygame.image.load(f'explosion/{i}.png')) self.index = 0 self.img = self.images[self.index] #spawn new debris def spawn_new_debris(self): self.rect.x = random.randrange(100, 800) self.rect.y = random.randrange(-150, -100) self.velocity = random.randrange(1, 2) self.speed_x = random.randrange(-3, 3) #respawn debris when they go of the screen def boundaries(self): if self.rect.left > WIDTH + 10 or self.rect.right < -10 or self.rect.top > HEIGHT + 10: self.spawn_new_debris() #update image def update(self): self.rect.y += self.velocity self.rect.x += self.speed_x self.boundaries() #make debris fall down def falldown(self): self.rect.centery += self.velocity if self.moving_down and self.rect.y > 350: self.kill() #explosion def explode(self): if self.max_health <= 0: self.index += 1 if self.index >= len(self.images): self.index = 0 self.img = self.images[self.index] ######################CAR/DEBRIS########################## player = Player(1,5) ########################################################## #groups bullet_group = pygame.sprite.Group() debris_group = pygame.sprite.Group() all_sprites = pygame.sprite.Group() for i in range(50): d = Debris(1,5) debris_group.add(d) all_sprites.add(d) #game runs here run = True while run: #draw street screen.blit(bg, [0, 0]) #update groups bullet_group.update() bullet_group.draw(screen) debris_group.update() debris_group.draw(screen) #draw car player.draw() player.move() player.collision(debris_group) player.stats() #update all sprites all_sprites.update() all_sprites.draw(screen) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False #check if key is down if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: run = False if event.key == pygame.K_a: player.movingLeft = True if event.key == pygame.K_d: player.movingRight = True if event.key == pygame.K_SPACE: player.shoot() shoot = True #check if key is up if event.type == pygame.KEYUP: if event.key == pygame.K_a: player.movingLeft = False if event.key == pygame.K_d: player.movingRight = False #update the display pygame.display.update() pygame.display.flip() clock.tick(FPS) pygame.quit()
Shooting tank bullet in python/pygame
I'm working on python project right now using pygame library and I need help with this project I make. I want my tank to shoot bullets and so those bullets ricochet from the walls. What is the best way possible to do this? I'm sorry that my code looks so messy, I've been watching different youtube tutorials and they all do differently. Here is my code import pygame pygame.init() # ======================= Variables ======================= # ------------------------ Screen ------------------------- screenWidth = 1060 screenHeight = 798 screenSize = (screenWidth, screenHeight) display = pygame.display.set_mode((screenWidth, screenHeight)) pygame.display.set_caption("Tank game") bg = pygame.image.load("sprites/background/background1.png") bg = pygame.transform.scale(bg, (screenWidth, screenHeight)) # ------------------------ Player ------------------------- # ------------------------ Enemy ------------------------- # ----------------------- Other --------------------------- red = (155, 0, 0) clock = pygame.time.Clock() fps = 60 # ========================= Clases ======================== class player(pygame.sprite.Sprite): def __init__(self, location, angle, vel, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("sprites/player/player_tank.png") self.x = x self.y = y self.vel = vel self.angle = angle self.rect = self.image.get_rect() self.rect.left, self.rect.top = location if self.angle == 360: self.angle = 0 def rotate(self): rot_image = pygame.transform.rotate(self.image, self.angle) rot_rect = rot_image.get_rect(center=self.rect.center) return rot_image, rot_rect def moving_after_angle_change(self): x = round(math.cos(math.radians(self.angle + 90)), 1) * self.vel y = round(math.sin(math.radians(self.angle - 90)), 1) * self.vel return x, y class enemy(pygame.sprite.Sprite): def __init__(self, x, y, width, height, end): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("sprites/enemy/enemy_tank.png") self.x = x self.y = y self.width = width self.height = height self.end = end self.path = [self.x, self.y, self.end] self.vel = 5 def draw(self, display): self.move() display.blit(self.image, (self.x, self.y)) def move(self): if self.vel > 0: pass # Bullet bullet = pygame.image.load("sprites/bullet/bullet.png") bullet = pygame.transform.scale(bullet, (16, 16)) bullet_x = 0 bullet_y = 480 bullet_x_change = 0 bullet_y_change = 10 bullet_state = 'ready' # ======================= Functions ======================= def redrawGameWindow(): display.blit(bg, (0, 0)) display.blit(player_tank.image, player_tank.rect) display.blit(enemy_tank.image, (enemy_tank.x, enemy_tank.y)) #display.blit(bullet, (player_tank.x + 160, player_tank.y + 100)) pygame.display.flip() def fireBullet(x, y): global bullet_state bullet_state = 'fire' display.blit(bullet, (x + 16, y + 10)) player_location = [70, 570] player_angle = 270 player_angle_change = 0 player_vel = 0 player_x_change = 0 player_y_change = 0 player_tank_x_change_store = 0 player_tank_y_change_store = 0 run = True while run: clock.tick(fps) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: player_vel = 2 elif event.key == pygame.K_DOWN: player_vel = -2 elif event.key == pygame.K_LEFT: player_angle_change = 2 elif event.key == pygame.K_RIGHT: player_angle_change = -2 if event.key == pygame.K_SPACE: display.blit(bullet, (player_tank.x + 160, player_tank.y + 100)) if event.type == pygame.KEYUP: if event.key == pygame.K_UP: player_vel = 0 elif event.key == pygame.K_DOWN: player_vel = 0 elif event.key == pygame.K_LEFT: player_angle_change = 0 elif event.key == pygame.K_RIGHT: player_angle_change = 0 player_angle += player_angle_change player_tank = player(player_location, player_angle, player_vel, player_x_change, player_y_change) enemy_tank = enemy(800, 170, 64, 64, 700) player_tank.image, player_tank.rect = player_tank.rotate() player_tank.x_change, player_tank.y_change = player_tank.moving_after_angle_change() player_tank_x_change_store += player_tank.x_change player_tank_y_change_store += player_tank.y_change player_tank.rect.centerx += player_tank_x_change_store player_tank.rect.centery += player_tank_y_change_store # Bullet movement if bullet_state == "fire": fireBullet(player_tank.x, bullet_y) bullet_y -= bullet_y_change redrawGameWindow() pygame.quit() quit()
One way to do this is to make the bullets velocity flip when it hits a wall. Now I'm assuming the walls are the edge of the screen so what you need to do is in the loop, check if the bullet is about to hit a wall (the edge of the screen) and if it is, flip its x_change and y_change. So something like this: if bullet_y <= 0: bullet_y_change *= -1 bullet_y = 0 if bullet_y >= screenHeight: bullet_y_change *= -1 bullet_y = screenHeight if bullet_x <= 0: bullet_x_change *= -1 bullet_x = 0 if bullet_x >= screenWidth: bullet_x_change *= 1 bullet_x = screenWidth
Here's a full example, based on an old answer: import pygame class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.Surface((32, 32)) self.image.fill((0, 0, 0)) self.image.set_colorkey((0, 0, 0)) pygame.draw.polygon(self.image, pygame.Color('dodgerblue'), ((0, 0), (32, 16), (0, 32))) self.org_image = self.image.copy() self.angle = 0 self.direction = pygame.Vector2(1, 0) self.rect = self.image.get_rect(center=(200, 200)) self.pos = pygame.Vector2(self.rect.center) def update(self, events, dt): for e in events: if e.type == pygame.KEYDOWN: if e.key == pygame.K_SPACE: self.groups()[0].add(Projectile(self.rect.center, self.direction.normalize())) pressed = pygame.key.get_pressed() if pressed[pygame.K_a]: self.angle += 3 if pressed[pygame.K_d]: self.angle -= 3 self.direction = pygame.Vector2(1, 0).rotate(-self.angle) self.image = pygame.transform.rotate(self.org_image, self.angle) self.rect = self.image.get_rect(center=self.rect.center) class Projectile(pygame.sprite.Sprite): def __init__(self, pos, direction): super().__init__() self.image = pygame.Surface((8, 8)) self.image.fill((0, 0, 0)) self.image.set_colorkey((0, 0, 0)) pygame.draw.circle(self.image, pygame.Color('orange'), (4, 4), 4) self.rect = self.image.get_rect(center=pos) self.direction = direction self.pos = pygame.Vector2(self.rect.center) self.lives = 15 def update(self, events, dt): # Bounding box of the screen screen_r = pygame.display.get_surface().get_rect() # where we would move next next_pos = self.pos + self.direction * dt # we hit a hall if not screen_r.contains(self.rect): # after 15 hits, destroy self self.lives -= 1 if self.lives == 0: return self.kill() # horizontal reflection if next_pos.x > screen_r.right or next_pos.x < screen_r.left: self.direction.x *= -1 # vertical reflection if next_pos.y > screen_r.bottom or next_pos.y < screen_r.top: self.direction.y *= -1 # move after applying reflection next_pos = self.pos + self.direction * dt # set the new position self.pos = next_pos self.rect.center = self.pos def main(): pygame.init() screen = pygame.display.set_mode((500, 500)) sprites = pygame.sprite.Group(Player()) clock = pygame.time.Clock() dt = 0 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() See how using the Vector2 class makes this kind of tasks very easy to solve. Also, look at the proper usage of the Sprite class. Each Sprite minds its own business: the Player class handles rotating the player and creating projectiles, Projectile handles moving the projectiles and bouncing of the wall.
Pygame collision with masks
I have made a putt-putt game and now I want to add a slanted wall type. Because of this, I need to use masks for the collision (until now I have just used rects). I have spent hours learning about masks and trying to figure out why my code won't work. There are no errors, the collision just isn't detected. I have simplified my code down to something much smaller just as a way for me to test it efficiently. From everything I've seen this seems like it should work, but it doesnt. Here it is: import pygame # Pygame init stuff pygame.init() wind_width = 1200 wind_height = 700 gameDisplay = pygame.display.set_mode((wind_width, wind_height)) pygame.display.set_caption("Mini Golf!") pygame.display.update() gameExit = False clock = pygame.time.Clock() # Class setups class Ball: def __init__(self, x, y): self.x = x self.y = y self.image = pygame.image.load("sball.png") self.rect = self.image.get_rect() self.mask = pygame.mask.from_surface(self.image) def render(self): self.rect.topleft = (self.x, self.y) gameDisplay.blit(self.image, self.rect) class Slant: def __init__(self, x, y): self.x = x self.y = y self.image = pygame.image.load("posslant.png") self.rect = self.image.get_rect() self.mask = pygame.mask.from_surface(self.image) def render(self): self.rect.topleft = (self.x, self.y) gameDisplay.blit(self.image, self.rect) # Creating objects ball = Ball(250, 250) slant = Slant(270, 250) # Game loop gameExit = False while not(gameExit): # Moves ball for event in pygame.event.get(): if event.type == pygame.QUIT: gameExit = True elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: ball.y -= 1 elif event.key == pygame.K_DOWN: ball.y += 1 elif event.key == pygame.K_LEFT: ball.x -= 1 elif event.key == pygame.K_RIGHT: ball.x += 1 # Collision detection offset_x, offset_y = (slant.rect.x - ball.rect.x), (slant.rect.y - ball.rect.y) if slant.mask.overlap(ball.mask, (offset_x, offset_y)): print("hit") # Draws everything gameDisplay.fill((0, 0, 0)) ball.render() slant.render() pygame.display.update() clock.tick(100)
The offset parameter of the method overlap() is the relative position of the othermask in relation to the pygame.mask.Mask object. So the offset is calculated by subtracting the coordinates of slant from the coordinates of ball: offset_x, offset_y = (slant.rect.x - ball.rect.x), (slant.rect.y - ball.rect.y) offset = (ball.rect.x - slant.rect.x), (ball.rect.y - slant.rect.y) if slant.mask.overlap(ball.mask, offset): print("hit") When you create the mask images, then I recommend to ensure that the image has per pixel alpha format by calling .convert_alpha(): class Ball: def __init__(self, x, y): self.x = x self.y = y self.image = pygame.image.load("sball.png") self.rect = self.image.get_rect() self.mask = pygame.mask.from_surface(self.image.convert_alpha()) # <--- class Slant: def __init__(self, x, y): self.x = x self.y = y self.image = pygame.image.load("posslant.png") self.rect = self.image.get_rect() self.mask = pygame.mask.from_surface(self.image.image.convert_alpha()) # <--- Minimal example: repl.it/#Rabbid76/PyGame-SurfaceMaskIntersect See also: Mask
Bullet not colliding with enemy Pygame (Python 3)
I want to make a mega man similar game where you jump around and shooting stuff. But I've noticed there's something wrong with the collision, I have a video below: https://youtu.be/p2VCtbBkefo I'm planning to make this project open source, so anybody can customize it. Please don't steal this code, but you may use chunks of it to help you with something. because I haven't put it on GitHub publicly yet. main.py: import pygame as pg from player import * from settings import * from levels import * from block import * from enemy import * class Game: def __init__(self): pg.init() pg.mixer.init() self.screen = pg.display.set_mode((width, height)) pg.display.set_caption("wait until realesed.") self.clock = pg.time.Clock() self.enemiesList = [] self.running = True self.shootRight = True def loadLevel(self, level, enemies, group, group2, group3): for y in range(0, len(level)): for x in range(0, len(level[y])): if (level[y][x] == 1): blockList.append(Block(x*32, y*32)) group.add(Block(x*32, y*32)) group2.add(Block(x*32, y*32)) for amount in range(0, enemies): group2.add(FlyingEnemy(self)) group3.add(FlyingEnemy(self)) self.enemies.add(FlyingEnemy(self)) self.enemiesList.append(FlyingEnemy(self)) def new(self): self.platforms = pg.sprite.Group() self.all_sprites = pg.sprite.Group() self.enemies = pg.sprite.Group() self.bullets = pg.sprite.Group() self.player = Player() self.loadLevel(level1["platform"], level1["enemies"], self.platforms, self.all_sprites, self.enemies) self.all_sprites.add(self.player) self.run() def shoot(self): if self.shootRight: self.bullet = Bullet(self.player.rect.centerx, self.player.rect.centery) self.bullet.speed = 10 self.all_sprites.add(self.bullet) self.bullets.add(self.bullet) print(self.bullet) elif self.shootRight == False: self.bullet = Bullet(self.player.rect.centerx, self.player.rect.centery) self.bullet.speed = -10 self.all_sprites.add(self.bullet) self.bullets.add(self.bullet) print(self.bullet) def run(self): self.playing = True while self.playing: self.clock.tick(FPS) self.events() self.update() self.draw() def update(self): self.all_sprites.update() self.enemy_hits = pg.sprite.spritecollide(self.player, self.enemies, False) #print(enemy_hits) if self.enemy_hits: pass #print("hit") self.bullet_hits = pg.sprite.groupcollide(self.enemies, self.bullets, True, True) if self.bullet_hits: print(self.bullet_hits) pygame.quit() hits = pg.sprite.spritecollide(self.player, self.platforms, False) if hits: self.player.pos.y = hits[0].rect.top + 1 self.player.vel.y = 0 def events(self): for event in pg.event.get(): if event.type == pg.QUIT: if self.playing: self.playing = False self.running = false if event.type == pg.KEYDOWN: if event.key == pg.K_UP: self.player.jump() if event.key == pg.K_SPACE: self.shoot() if event.key == pg.K_RIGHT: self.shootRight = True if event.key == pg.K_LEFT: self.shootRight = False def draw(self): self.screen.fill((255, 255, 255)) self.all_sprites.draw(self.screen) pg.display.flip() def show_start_screen(self): pass def show_go_screen(self): pass g = Game() g.show_start_screen() while g.running: g.new() g.show_go_screen() pg.quit() """width = 800 height = 600 FPS = 60 pg.init() pg.mixer.init() screen = pg.display.set_mode((width, height)) pg.display.set_caption("doom room") clock = pg.time.Clock() running = True while running: for event in pg.event.get(): clock.tick(FPS) if event.type == pg.QUIT: running = false screen.fill((255, 255, 255)) pg.display.flip() pg.quit()""" import pygame class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface((20, 10)) self.image.fill((240, 43, 12)) self.rect = self.image.get_rect() self.rect.bottom = y self.rect.centerx = x self.speed = -10 def update(self): self.rect.x += self.speed if self.rect.bottom < 0: self.kill() player.py import pygame as pg from settings import * from laser import * vec = pg.math.Vector2 class Player(pg.sprite.Sprite): def __init__(self): pg.sprite.Sprite.__init__(self) self.image = pg.Surface((40, 40)) self.image.fill((80, 123, 255)) self.rect = self.image.get_rect() self.rect.center = (width / 2, height / 2) self.pos = vec(width / 2, height / 2) self.vel = vec(0, 0) self.acc = vec(0, 0) #self.vx = 0 #self.vy = 0 def jump(self): self.vel.y = -15 def update(self): self.acc = vec(0, player_gravity) keys = pg.key.get_pressed() if keys[pg.K_LEFT]: self.acc.x = -player_acc if keys[pg.K_RIGHT]: self.acc.x = player_acc self.acc.x += self.vel.x * player_friction self.vel += self.acc self.pos += self.vel + 0.5 * self.acc if self.pos.x > width: self.pos.x = 0 if self.pos.x < 0: self.pos.x = width if self.pos.y <= 0: self.pos.y += 15 self.rect.midbottom = self.pos enemy.py import pygame as pg from random import * from settings import * class FlyingEnemy(pg.sprite.Sprite): def __init__(self, game): pg.sprite.Sprite.__init__(self) self.game = game self.image = pg.Surface((45, 45)) self.image.fill((20, 203, 50)) self.rect = self.image.get_rect() self.rect.centerx = choice([-100, width + 100]) self.vx = randrange(4, 7) if self.rect.centerx > width: self.vx *= -1 self.rect.y = height / 4 self.rect.x = 0 self.vy = 0 self.dy = 0.5 def update(self): if self.rect.x > width: self.rect.x = 0 if self.rect.x < 0: self.rect.x = width self.rect.x += self.vx self.vy += self.dy if self.vy > 3 or self.vy < -3: self.dy *= -1 center = self.rect.center if self.dy < 0: pass #print("bobbed up") else: pass #print("bobbed down") settings.py import pygame blockList = [] player_acc = 1.0 player_friction = -0.12 player_gravity = 0.5 bullets = pygame.sprite.Group() true = True false = False width = 800 height = 600 FPS = 60 levels.py level1 = { "platform": [ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1], [1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], [0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0], [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], ], "enemies": 5 } block.py import pygame as pg class Block(pg.sprite.Sprite): def __init__(self, x, y): pg.sprite.Sprite.__init__(self) self.image = pg.Surface((32, 32)) self.image.fill((0, 0, 0)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y Thanks all help is appreciated.
This part of the loadLevel method causes the problem: for amount in range(0, enemies): group2.add(FlyingEnemy(self)) group3.add(FlyingEnemy(self)) self.enemies.add(FlyingEnemy(self)) self.enemiesList.append(FlyingEnemy(self)) You're adding 4 different FlyingEnemy objects to these groups and the list (btw, the list is useless), so the sprites in the self.all_sprites group and in the self.enemies group are not the same. Since you're only updating the all_sprites and not the enemies, the sprites in the enemies group, which are used for the collision detection, stay at the left screen edge all the time and are also invisible, because you don't draw this group. To solve the problem, create one instance and add this instance to the two groups: for amount in range(0, enemies): enemy = FlyingEnemy(self) self.enemies.add(enemy) self.all_sprites.add(enemy) I found the bug by printing the rect of one enemy sprite in the self.enemies group. Then I checked the update method of this sprite, but it looked correct, so I went to the instantiation part in loadLevel and noticed the mistake.