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.

Categories

Resources