Spawn moving objects from border - python

I'm trying to make objects from the Angryball class randomly spawn from the right border and move from right to left outside the screen. The idea is that these ogjects must spawn on random y coordinates from the window's right border (better if i can make them seem like if they were coming from outside the border, but that's another point i will check later) and then move at speed 5 until the reach the opposite border. Once one object gets out, another one is spawned again.
I don't have any error when i run this but it doesn't work as expected :) Can you help me figuring this out?
import pygame
import os
import random
size = width, height = 750, 422
screen = pygame.display.set_mode(size)
img_path = os.path.join(os.getcwd())
background_image = pygame.image.load('background.jpg').convert()
bg_image_rect = background_image.get_rect()
pygame.mixer.pre_init(44100, 16, 2, 4096)
pygame.display.set_caption("BallGame")
class Ball(object):
def __init__(self):
self.image = pygame.image.load("ball.png")
self.image_rect = self.image.get_rect()
self.image_rect.x
self.image_rect.y
self.facing = 'LEFT'
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 5
if key[pygame.K_DOWN] and self.image_rect.y < 321:
self.facing = 'DOWN'
self.image_rect.y += dist
elif key[pygame.K_UP] and self.image_rect.y > 0:
self.facing = 'UP'
self.image_rect.y -= dist
if key[pygame.K_RIGHT] and self.image_rect.x < 649:
self.facing = 'RIGHT'
self.image_rect.x += dist
elif key[pygame.K_LEFT] and self.image_rect.x > 0:
self.facing = 'LEFT'
self.image_rect.x -= dist
def draw(self, surface):
if self.facing == "RIGHT":
surface.blit(pygame.transform.flip(self.image, True, False),(self.image_rect.x,self.image_rect.y))
elif self.facing == "DOWN":
surface.blit(pygame.image.load("ball_down.png"),(self.image_rect.x,self.image_rect.y))
if self.facing == "UP":
surface.blit(pygame.image.load("ball_up.png"),(self.image_rect.x,self.image_rect.y))
elif self.facing == "LEFT":
surface.blit(self.image,(self.image_rect.x,self.image_rect.y))
mob_images = [pygame.image.load("image1.png").convert_alpha(),pygame.image.load("image2.png").convert_alpha(),pygame.image.load("image3.png").convert_alpha(),pygame.image.load("image4.png").convert_alpha(),pygame.image.load("image5.png").convert_alpha()]
mob_image = random.choice(mob_images)
class Angryball(pygame.sprite.Sprite):
def __init__(self, image, pos_x, pos_y):
super(Angryball, self).__init__()
self.image = image
self.rect = self.image.get_rect()
self.rect.x = pos_x
self.rect.y = pos_y
self.facing = 'LEFT'
def update(self, screen):
if self.rect.x <= 0:
self.rect.right = screen.get_rect().width
self.rect.top = random.randint(0, screen.get_rect().height)
else:
self.rect.move_ip(-5, 0)
pygame.init()
screen = pygame.display.set_mode((750, 422))
ball = Ball()
angryball = Angryball(mob_image, 700, random.randrange(400))
sprites = pygame.sprite.Group()
sprites.add(angryball)
clock = pygame.time.Clock()
pygame.mixer.music.load("bg_music.mp3")
pygame.mixer.music.play(-1, 0.0)
running = True
while running:
esc_key = pygame.key.get_pressed()
for event in pygame.event.get():
if esc_key[pygame.K_ESCAPE]:
pygame.display.quit()
pygame.quit()
running = False
ball.handle_keys()
sprites.update(screen)
sprites.draw(screen)
screen.blit(background_image, bg_image_rect)
screen.blit(background_image, bg_image_rect.move(bg_image_rect.width, 0))
bg_image_rect.move_ip(-2, 0)
if bg_image_rect.right <= 0:
bg_image_rect.x = 0
ball.draw(screen)
pygame.display.update()
clock.tick(60)

Use pygame's Sprites.
Let Angryball inherit from pygame.sprite.Sprite and rename image_rect to rect.
Give it an update function like this:
def update(self, screen):
if self.rect.x <= 0:
self.rect.right = screen.get_rect().width
self.rect.top = random.randint(0, screen.get_rect().height)
else:
self.rect.move_ip(-5, 0)
Instead of manually blitting it to the screen, use a Group:
angryball = Angryball(mob_image, 700, random.randrange(400))
sprites = pygame.sprite.Group()
sprites.add(angryball)
and in your main loop simply call
sprites.update(screen)
sprites.draw(screen)
Your code does not work because you try to use angryball_image_rect in the line before you actually declare it.

Related

Enemy collision with pygame.sprite.spritecollide()

In my game, there is a sprite player which I can control. It can move right or left, it can jump, shoot fireballs(bullets) and a breathe fire. I have added an enemy which can move on itself from right to left on a limited distance that I set. What I would like to do now is make my player loose health if it collides with the enemy sprite using pygame.sprite.spritecollide(). However it isn't working out well I don't know how to fix my issue which is the following: if I run my code below it says NameError: name 'enemy_list' is not defined. The errored line is in Sprite1.py in the Player class under the update function. How do I fix my code? I created my Enemy class and Level class with the following website: https://opensource.com/article/18/5/pygame-enemy. I'm open to all suggestions. Thanks beforehand! I separated my code into three files: main.py, settings.py and Sprite1.py. Here's main.py:
import pygame
import os
import sys
import time
from pygame import mixer
from Sprite1 import *
from settings import *
'''
Setup
'''
pygame.init()
clock = pygame.time.Clock()
pygame.mixer.music.load('.\\sounds\\Fairy.mp3')
pygame.mixer.music.play(-1, 0.0)
all_sprites = pygame.sprite.Group()
player = Player(all_sprites)
player.rect.x = 500
player.rect.y = 500
eloc = []
eloc = [400,500]
enemy_list = Level.bad( 1, eloc )
showStartScreen(surface)
x = 0
'''
Main loop
'''
main = True
while main == True:
background = pygame.image.load(os.path.join('images', 'Bg.png')).convert()
surface.blit(background, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
main = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.control(-steps,0)
if event.key == pygame.K_RIGHT:
player.control(steps,0)
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
player.control(steps,0)
if event.key == pygame.K_RIGHT:
player.control(-steps,0)
keys = pygame.key.get_pressed()
if not(isJump):
if keys[pygame.K_UP]:
isJump = True
else:
if jumpCount >= -10:
player.rect.y -= (jumpCount * abs(jumpCount)) * 1
jumpCount -= 2
else:
jumpCount = 10
isJump = False
# dt = time since last tick in milliseconds.
dt = clock.tick(60) / 1000
all_sprites.update(dt)
player.update(dt)
all_sprites.draw(surface) #refresh player position
enemy_list.draw(surface)
for e in enemy_list:
e.move()
pygame.display.flip()
Here's my settings.py:
import pygame
isJump = False
jumpCount = 10
width = 960
height = 720
fps = 40 # frame rate
pygame.display.set_caption('B.S.G.')
surface = pygame.display.set_mode((width, height))
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
PLAYER_JUMP = 20
PLAYER_LAYER = 2
PLATFORM_LAYER = 1
RED = (255, 0, 0)
steps = 10 # how fast to move
And here's my Sprite1.py:
import pygame
import sys
import os
import time
from pygame import mixer
from pygame.locals import *
from settings import *
vec = pygame.math.Vector2
def showStartScreen(surface):
show = True
while (show == True):
background = pygame.image.load(os.path.join('images', 'Starting_scr.png'))
# rect = surface.get_rect()
surface.blit(background, (0,0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
show = False
class Player(pygame.sprite.Sprite):
def __init__(self, all_sprites):
pygame.sprite.Sprite.__init__(self)
self.movex = 0
self.movey = 0
self.frame = 0
self.health = 10
self.jumping = False
self.images = []
self.imagesleft = []
self.imagesright = []
self.direction = "right"
self.alpha = (0,0,0)
self.ani = 4 # animation cycles
self.all_sprites = all_sprites
self.add(self.all_sprites)
self.fire_timer = .1
self.bullet_timer = .1
self.pos = vec(40, height - 100)
self.vel = vec(0, 0)
for i in range(1,5):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img.convert_alpha()
img.set_colorkey(self.alpha)
self.imagesright.append(img)
self.image = self.imagesright[0]
self.rect = self.image.get_rect()
for i in range(1,5):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img = pygame.transform.flip(img, True, False)
img.convert_alpha()
img.set_colorkey(self.alpha)
self.imagesleft.append(img)
self.image = self.imagesleft[0]
self.rect = self.image.get_rect()
def control(self,x,y):
'''
control player movement
'''
self.movex += x
self.movey -= y
def update(self, dt):
'''
Update sprite position
'''
self.rect.x = self.rect.x + self.movex
self.rect.y = self.rect.y + self.movey
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
for enemy in ennemy_hit_list:
self.health -= 1
print(self.health)
# moving left
if self.movex < 0:
self.frame += 1
if self.frame > 3*self.ani:
self.frame = 0
self.image = self.imagesleft[self.frame//self.ani]
self.direction = "left"
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > 3*self.ani:
self.frame = 0
self.image = self.imagesright[self.frame//self.ani]
self.direction = "right"
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.bullet_timer -= dt # Subtract the time since the last tick.
if keys[pygame.K_x]:
self.fire_timer -= dt
if self.bullet_timer <= 0:
self.bullet_timer = 100 # Bullet ready.
if keys: # Left mouse button.
# Create a new bullet instance and add it to the groups.
if self.direction == "right":
Bullet([self.rect.x + self.image.get_width(), self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
else:
Bullet([self.rect.x, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
self.bullet_timer = .5 # Reset the timer.
if self.fire_timer <= 0:
self.fire_timer = 100
if keys:
if self.direction == "right":
Fire([self.rect.x + 170, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
else:
Fire([self.rect.x - 90, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
self.fire_timer = .1
if self.health == 0:
self.kill()
class Enemy(pygame.sprite.Sprite):
'''
Spawn an enemy
'''
def __init__(self,x,y,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.counter = 0 # counter variable
def move(self):
'''
enemy movement
'''
distance = 20
speed = 15
if self.counter >= 0 and self.counter <= distance:
self.rect.x += speed
elif self.counter >= distance and self.counter <= distance*2:
self.rect.x -= speed
else:
self.counter = 0
self.counter += 1
class Bullet(pygame.sprite.Sprite):
IMAGE = None
FLIPPED_IMAGE = None
def __init__(self, pos, direction, *sprite_groups):
super().__init__(*sprite_groups)
# cache images
if not Bullet.IMAGE:
Bullet.IMAGE = pygame.image.load(os.path.join('images','fireball.png'))
Bullet.FLIPPED_IMAGE = pygame.transform.flip(Bullet.IMAGE, True, False)
if direction == "right":
self.vel = pygame.math.Vector2(750, 0)
self.image = Bullet.IMAGE
else:
self.vel = pygame.math.Vector2(-750, 0)
self.image = Bullet.FLIPPED_IMAGE
self.pos = pygame.math.Vector2(pos)
self.rect = self.image.get_rect(center=pos)
def update(self, dt):
# Add the velocity to the position vector to move the sprite
self.pos += self.vel * dt
self.rect.center = self.pos # Update the rect pos.
if not pygame.display.get_surface().get_rect().colliderect(self.rect):
self.kill()
class Fire(pygame.sprite.Sprite):
IMAGE = None
FLIPPED_IMAGE = None
def __init__(self, pos, direction, *sprite_groups):
super().__init__(*sprite_groups)
# cache images
if not Fire.IMAGE:
Fire.IMAGE = pygame.image.load(os.path.join('images','fire_drag.png'))
Fire.FLIPPED_IMAGE = pygame.transform.flip(Fire.IMAGE, True, False)
if direction == "right":
self.image = Fire.IMAGE
self.vel = pygame.math.Vector2(0, 0)
else:
self.image = Fire.FLIPPED_IMAGE
self.vel = pygame.math.Vector2(0, 0)
self.pos = pygame.math.Vector2(pos)
self.rect = self.image.get_rect(center=pos)
def update(self, dt):
self.too = True
self.pos += self.vel * dt
self.rect.center = self.pos # Update the rect pos.
if self.too == True:
self.kill()
class Level():
def bad(lvl,eloc):
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'cookie1.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
if lvl == 2:
print("Level " + str(lvl) )
return enemy_list
def loot(lvl,lloc):
print(lvl)
enemy_list is defined in global namespace, in main.py, thus it is not accessible in the module Sprite.py.
Add an additional argument to the update method of the class Player:
class Player(pygame.sprite.Sprite):
# [...]
def update(self, dt, enemy_list):
# [...]
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
# [...]
Since player is a member of all_sprites, you have to add the argument to the update methods of the other sprites (Enemy, Bullet), too.
Pass enemy_list to the update method all_sprites in the main application loop. Note the update method of Player is invoked by all_sprites.update, thus player.update(dt, enemy_list) is superflous:
while main == True:
# [...]
all_sprites.update(dt, enemy_list)
# [...]

Pygame Platformer Sprite Collisions only working in one way

I am currently working on a simple school project, where I am coding a Platformer. My Problem is, that the collisions between my player and my platform(s) are checked one after another, so if I check the X Collision first, the Collisions on the sides of the Platforms work completly fine, but as soon as I move, my character will be teleported to the side of the platform, because the gravity pulls me in the platform and i am moving left or right, so the program thinks I am hitting a platform from the side and sets the player rect onto the side of the platform. The same thing happens if I check for X Collision first, but with the Collisions on the sides of the platforms not working.
Is there a way of around it, so my collisions are working like in a Mario-like Game? I tried several things and removed the calculation with Friction and Acceleration for smooth movement, but nothing seems to work for me.
Here is my full code:
import random
import os
WIDTH = 640
HEIGHT = 600
FPS = 60
TITLE = "Game"
game_folder = os.path.dirname(__file__)
img_folder = os.path.join(game_folder,"textures")
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
DARKGREY = (40, 40, 40)
LIGHTGREY = (100, 100, 100)
BLUE = (0,0,255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.5
PGROUND = "dirtplatform.png"
PBLOCK = "dirt.png"
PLATFORM_LIST = [(0, HEIGHT - 64, PGROUND),
(WIDTH / 2, HEIGHT / 2, PBLOCK),
(WIDTH / 4, HEIGHT / 4, PBLOCK)]
vec = pygame.math.Vector2
class Player(pygame.sprite.Sprite):
def __init__(self, game):
pygame.sprite.Sprite.__init__(self)
self.game = game
self.image = pygame.image.load(os.path.join(img_folder,"player.png")).convert()
self.image.set_colorkey((130,255,230))
self.rect = self.image.get_rect()
self.rect.center = (WIDTH/2,HEIGHT/2)
self.vel = vec(0,0)
def jump(self):
self.rect.y += 1
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
self.rect.y -= 1
if hits:
self.vel.y = -20
def collision(self, direction):
if direction == 'x':
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
if hits:
print("Hit x")
if self.vel.x > 0:
self.rect.right = hits[0].rect.left
if self.vel.x < 0:
self.rect.left = hits[0].rect.right
self.vel.x = 0
if direction == 'y':
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
if hits:
print("Hit y")
if self.vel.y > 0:
self.rect.bottom = hits[0].rect.top
if self.vel.y < 0:
self.rect.top= hits[0].rect.bottom
self.vel.y = 0
def update(self):
self.acc = vec(0,PLAYER_GRAV)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.vel.x = -5
elif keys[pygame.K_RIGHT]:
self.vel.x = 5
self.rect.x = self.rect.x + self.vel.x
self.rect.y = self.rect.y + self.vel.y
self.collision('x')
self.collision('y')
print("vel",self.vel)
print("rectX",self.rect.x)
print("rectY",self.rect.y)
self.vel.y = self.vel.y + PLAYER_GRAV
self.vel.x = 0
class Platform(pygame.sprite.Sprite):
def __init__(self, x, y, image):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join(img_folder,image)).convert()
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Game:
def __init__(self):
pygame.init()
pygame.mixer.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
self.clock = pygame.time.Clock()
self.running = True
def new(self):
self.all_sprites = pygame.sprite.Group()
self.platforms = pygame.sprite.Group()
self.player = Player(self)
self.all_sprites.add(self.player)
for plat in PLATFORM_LIST:
p = Platform(*plat)
self.all_sprites.add(p)
self.platforms.add(p)
self.run()
def run(self):
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def collision(self):
if self.player.vel.y > 0 and pygame:
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.pos.y = hits[0].rect.top
self.player.vel.y = 0
def update(self):
self.all_sprites.update()
keys = pygame.key.get_pressed()
if self.player.rect.right >= WIDTH-100:
self.player.rect.x = WIDTH - 110
for plat in self.platforms:
plat.rect.right -= abs(self.player.vel.x)
if self.player.rect.left <= 100:
self.player.rect.x = 110
for plat in self.platforms:
plat.rect.right += abs(self.player.vel.x)
def events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
if self.playing:
self.playing = False
self.running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.player.jump()
def draw(self):
self.screen.fill(BLACK)
self.all_sprites.draw(self.screen)
pygame.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
pygame.quit()
Note: dirtplatform.png represents the ground of the game and dirt.png is a block, which the player should be able to jump on
Collisions are hard, there doesn't seem to be any perfect way of doing it, and every time i do it, it changes everytime. Saying that, this seems to work very well with the 2 mins of testing i just did:
def collision(self):
for platform in self.game.platforms: #check every platform
if self.rect.colliderect(platform.rect): #if the two rects collided
if self.rect.right <= platform.rect.left + self.vel.x:
self.rect.right = platform.rect.left
self.vel.x = 0
if self.rect.left >= platform.rect.right + self.vel.x:
self.rect.left = platform.rect.right
self.vel.x = 0
if self.rect.bottom <= platform.rect.top + self.vel.y:
self.rect.bottom = platform.rect.top
self.vel.y = 0
if self.rect.top >= platform.rect.bottom + self.vel.y:
self.rect.top = platform.rect.bottom
self.vel.y = 0

Play sound when objects collide

I'm trying to play the ouch sound when the object ball collides with an angryball, but the sound doesn't always play. It starts on the first collision when the two objects are on the same x or y coordinate but after that it doesn't play correctly for future collisions.
Maybe it's my fault on how I manage to handle their coordinates to check for the collision?
import pygame
import os
import random
size = width, height = 750, 422
screen = pygame.display.set_mode(size)
img_path = os.path.join(os.getcwd())
background_image = pygame.image.load('background.jpg').convert()
bg_image_rect = background_image.get_rect()
pygame.mixer.pre_init(44100, 16, 2, 4096)
pygame.display.set_caption("BallGame")
class Ball(object):
def __init__(self):
self.image = pygame.image.load("ball.png")
self.image_rect = self.image.get_rect()
self.image_rect.x
self.image_rect.y
self.facing = 'LEFT'
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 5
if key[pygame.K_DOWN] and self.image_rect.y < 321:
self.facing = 'DOWN'
self.image_rect.y += dist
elif key[pygame.K_UP] and self.image_rect.y > 0:
self.facing = 'UP'
self.image_rect.y -= dist
if key[pygame.K_RIGHT] and self.image_rect.x < 649:
self.facing = 'RIGHT'
self.image_rect.x += dist
elif key[pygame.K_LEFT] and self.image_rect.x > 0:
self.facing = 'LEFT'
self.image_rect.x -= dist
def draw(self, surface):
if self.facing == "RIGHT":
surface.blit(pygame.transform.flip(self.image, True, False),(self.image_rect.x,self.image_rect.y))
elif self.facing == "DOWN":
surface.blit(pygame.image.load("ball_down.png"),(self.image_rect.x,self.image_rect.y))
if self.facing == "UP":
surface.blit(pygame.image.load("ball_up.png"),(self.image_rect.x,self.image_rect.y))
elif self.facing == "LEFT":
surface.blit(self.image,(self.image_rect.x,self.image_rect.y))
mob_images = [pygame.image.load("image1.png").convert_alpha(),pygame.image.load("image2.png").convert_alpha(),pygame.image.load("image3.png").convert_alpha(),pygame.image.load("image4.png").convert_alpha(),pygame.image.load("image5.png").convert_alpha()]
class Angryball(pygame.sprite.Sprite):
def __init__(self, mob_images, pos_x, pos_y):
super(Angryball, self).__init__()
self.mob_images = mob_images
self.image = random.choice(self.mob_images)
self.rect = self.image.get_rect(x=pos_x, y=pos_y)
self.facing = 'LEFT'
def update(self, screen):
if self.rect.x <= 0:
self.rect.right = screen.get_rect().width
self.rect.top = random.randint(0, screen.get_rect().height)
self.image = random.choice(self.mob_images)
else:
self.rect.move_ip(-5, 0)
pygame.init()
screen = pygame.display.set_mode((750, 422))
ball = Ball()
angryball = Angryball(mob_images , 700, random.randrange(400))
sprites = pygame.sprite.Group()
sprites.add(angryball)
clock = pygame.time.Clock()
pygame.mixer.music.load("bg_music.mp3")
pygame.mixer.music.play(-1, 0.0)
ouch = pygame.mixer.Sound("border_sound.wav")
running = True
while running:
esc_key = pygame.key.get_pressed()
for event in pygame.event.get():
if esc_key[pygame.K_ESCAPE]:
pygame.display.quit()
pygame.quit()
running = False
if ball.image_rect.x == angryball.rect.x:
ouch.play()
if ball.image_rect.y == angryball.rect.y:
ouch.play()
ball.handle_keys()
screen.blit(background_image, bg_image_rect)
screen.blit(background_image, bg_image_rect.move(bg_image_rect.width, 0))
bg_image_rect.move_ip(-2, 0)
if bg_image_rect.right <= 0:
bg_image_rect.x = 0
sprites.update(screen)
sprites.draw(screen)
ball.draw(screen)
pygame.display.update()
clock.tick(60)
You need to replace these lines,
if ball.image_rect.x == angryball.rect.x:
ouch.play()
if ball.image_rect.y == angryball.rect.y:
ouch.play()
with this one (to check if the two rects collide):
if ball.image_rect.colliderect(angryball.rect):
ouch.play()
The problem now is that the sound will be played every frame in which the two objects collide and therefore can become quite loud (it will be played in up to 8 channels simultaneously). Also, if all channels are occupied, the sound playback can get skipped if several objects collide in short succession.
To prevent this, you could give the Angryball a collided attribute (boolean) and set it to True after the first collision. Then reset it to False after some time interval or at the same time when you reset the position.
if ball.image_rect.colliderect(angryball.rect) and not angryball.collided:
angryball.collided = True
ouch.play()
As suggested, i replaced the collision detection with
if ball.image_rect.colliderect(angryball.rect):
and it worked
You could also check for collisions at a specific point such as:
ball.rect.collidepoint(angryball.rect.x, angryball.rect.y)
What it does is test if a point is in fact inside a rect of a sprite. I find that it works nicely, it’s simple, and clear to use and understand.

Pygame collision sides trouble [duplicate]

So I was making a pygame platformer and I got stuck on one thing. I coudn't find a way to make the bottom of my platforms solid. The player could land on the top of it but when it tries to go through the bottom it bounces back down. I tried this but it didnt work:
hits = pg.sprite.spritecollide(player, platforms, False)
if hits:
if player.pos.y == hits[0].rect.top:
player.vel.y = 10
else:
player.pos.y = hits[0].rect.top + 1
player.vel.y = 0
Does anyone got a solution for me? Here's the complete program.
Here's a short platformer example. Especially the movement is important. You have to move along the x-axis first, check if the player collides with a wall and move it back if a collision occurred. Afterwards do the same with the y-axis. If you don't split the movement into these two parts, your player will jump to the sides, top or bottom of the wall if you press more than one movement key at the same time.
import pygame as pg
pg.init()
WINDOW_WIDTH, WINDOW_HEIGHT = 800, 600
screen = pg.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
GRAY = pg.Color('gray24')
GRAVITY = 800
class Player(pg.sprite.Sprite):
def __init__(self, pos, blocks):
super().__init__()
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color(0, 110, 170))
self.rect = self.image.get_rect(topleft=pos)
self.vel = pg.math.Vector2(0, 0)
self.pos = pg.math.Vector2(pos)
self.blocks = blocks
self.on_ground = False
def update(self, dt):
# Move along x-axis.
self.pos.x += self.vel.x * dt
self.rect.x = self.pos.x
collisions = pg.sprite.spritecollide(self, self.blocks, False)
for block in collisions: # Horizontal collision occurred.
if self.vel.x > 0: # Moving right.
self.rect.right = block.rect.left # Reset the rect pos.
elif self.vel.x < 0: # Moving left.
self.rect.left = block.rect.right # Reset the rect pos.
self.pos.x = self.rect.x # Update the actual x-position.
# Move along y-axis.
self.pos.y += self.vel.y * dt
# +1 to check if we're on a platform each frame.
self.rect.y = self.pos.y + 1
# Prevent air jumping when falling.
if self.vel.y > 0:
self.on_ground = False
collisions = pg.sprite.spritecollide(self, self.blocks, False)
for block in collisions: # Vertical collision occurred.
if self.vel.y > 0: # Moving down.
self.rect.bottom = block.rect.top # Reset the rect pos.
self.vel.y = 0 # Stop falling.
self.on_ground = True
elif self.vel.y < 0: # Moving up.
self.rect.top = block.rect.bottom # Reset the rect pos.
self.vel.y = 0 # Stop jumping.
self.pos.y = self.rect.y # Update the actual y-position.
# Stop the player at screen bottom.
if self.rect.bottom >= WINDOW_HEIGHT:
self.vel.y = 0
self.rect.bottom = WINDOW_HEIGHT
self.pos.y = self.rect.y
self.on_ground = True
else:
self.vel.y += GRAVITY * dt # Gravity
class Block(pg.sprite.Sprite):
def __init__(self, rect):
super().__init__()
self.image = pg.Surface(rect.size)
self.image.fill(pg.Color('paleturquoise2'))
self.rect = rect
def main():
clock = pg.time.Clock()
done = False
dt = 0
all_sprites = pg.sprite.Group()
blocks = pg.sprite.Group()
player = Player((300, 100), blocks)
all_sprites.add(player)
rects = ((300, 200, 30, 70), (100, 350, 270, 30),
(500, 450, 30, 170), (400, 570, 270, 30),
(500, 150, 70, 170), (535, 310, 270, 70))
for rect in rects: # Create the walls/platforms.
block = Block(pg.Rect(rect))
all_sprites.add(block)
blocks.add(block)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_a:
player.vel.x = -220
elif event.key == pg.K_d:
player.vel.x = 220
elif event.key == pg.K_w: # Jump
if player.on_ground:
player.vel.y = -470
player.pos.y -= 20
player.on_ground = False
elif event.type == pg.KEYUP:
if event.key == pg.K_a and player.vel.x < 0:
player.vel.x = 0
elif event.key == pg.K_d and player.vel.x > 0:
player.vel.x = 0
all_sprites.update(dt)
screen.fill(GRAY)
all_sprites.draw(screen)
pg.display.flip()
dt = clock.tick(60) / 1000
if __name__ == '__main__':
main()
pg.quit()
Here's a working version of the code that you've posted in the comments (only with vertical collisions, you need to add horizontal collisions as well). So when the player is jumping and collides with a platform, you have to set the player.rect.top to the platform.rect.bottom and change the vel.y.
import pygame as pg
from pygame.math import Vector2 as vec
pg.init()
WIDTH, HEIGHT = 800, 600
YELLOW = pg.Color('yellow')
GREEN = pg.Color('green')
BLACK = pg.Color('gray11')
screen = pg.display.set_mode((WIDTH,HEIGHT))
clock = pg.time.Clock()
FPS = 60
PLAYER_FRICTION = .95
PLAYER_ACC = .2
class Player(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((30, 40))
self.image.fill(YELLOW)
self.rect = self.image.get_rect(center=(WIDTH/2, HEIGHT-30))
self.pos = vec(WIDTH/2, HEIGHT/2)
self.vel = vec(0,0)
self.acc = vec(0,0)
def jump(self):
self.rect.y += 1
hits = pg.sprite.spritecollide(self, platforms, False)
self.rect.y -= 1
if hits:
self.vel.y = -13
def update(self):
self.acc = vec(0, 0.5)
keys = pg.key.get_pressed()
if keys[pg.K_a]:
self.acc.x = -PLAYER_ACC
if keys[pg.K_d]:
self.acc.x = PLAYER_ACC
# apply friction
self.vel.x *= PLAYER_FRICTION
self.vel += self.acc
self.pos += self.vel
# wrap around the sides of the screen
if self.pos.x > WIDTH:
self.pos.x = 0
if self.pos.x < 0:
self.pos.x = WIDTH
self.rect.midbottom = self.pos
class Platform(pg.sprite.Sprite):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w, h))
self.image.fill(GREEN)
self.rect = self.image.get_rect(topleft=(x, y))
all_sprites = pg.sprite.Group()
platforms = pg.sprite.Group()
player = Player()
all_sprites.add(player)
# spawns and adds platforms to group
p1 = Platform(0, HEIGHT - 40, WIDTH, 40)
p2 = Platform(WIDTH / 2 - 50, HEIGHT - 300, 100, 20)
p3 = Platform(WIDTH / 2 - 100, HEIGHT - 150, 200, 20)
all_sprites.add(p1, p2, p3)
platforms.add(p1, p2, p3)
running = True
while running:
clock.tick(FPS)
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
player.jump()
all_sprites.update()
# Check if we hit a wall/platform.
hits = pg.sprite.spritecollide(player, platforms, False)
for platform in hits: # Iterate over the collided platforms.
if player.vel.y > 0: # We're falling.
player.rect.bottom = platform.rect.top
player.vel.y = 0
elif player.vel.y < 0: # We're jumping.
player.rect.top = platform.rect.bottom
player.vel.y = 3
player.pos.y = player.rect.bottom
#Draw / render
screen.fill(BLACK)
all_sprites.draw(screen)
pg.display.flip()
pg.quit()
BTW, in the jump method you have to change self.rect.y not self.rect.x.

Pygame logic issue

I'm trying to develop a Brick Breaker/Breakout game using pygame, but I'm facing a annoying problem that blots the "ball" on the paddle(player) when I move with the arrows, i'm lefting the code for you to have a look.
import pygame
pygame.init()
isRunning = True
WIDTH = 800
HEIGHT = 600
FPS = 60
clock = pygame.time.Clock()
window = pygame.display.set_mode((WIDTH, HEIGHT))
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((60, 30))
self.image.fill((200, 255, 200))
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 30
def update(self):
self.speedx = 0
keystate = pygame.key.get_pressed()
if keystate[pygame.K_a]:
self.speedx = -5
if keystate[pygame.K_d]:
self.speedx = 5
self.rect.x += self.speedx
if self.rect.right + self.speedx > WIDTH:
self.rect.right = WIDTH
if self.rect.left + self.speedx < 0:
self.rect.left = 0
ball = Ball(self.rect.centerx, self.rect.top)
for i in all_sprites:
print(i)
all_sprites.add(ball)
def shoot(self):
ball = Ball(self.rect.centerx, self.rect.top)
all_sprites.add(ball)
balls.add(ball)
class Ball(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 10))
self.image.fill((100, 150, 200))
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedy = -3
def update(self):
pass
#self.rect.y += self.speedy
all_sprites = pygame.sprite.Group()
balls = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
while isRunning:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
isRunning = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()
window.fill((0, 0, 30))
all_sprites.update()
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
quit()
I'm not sure if I understand your question, but the problem with the code is that you're creating a ball every time you're updating the player. Just remove the last four lines in the player update method and uncomment the line in the ball's update method and everything works as it should.
Additional tips
Delete the balls when they exit the screen. This will prevent the game from running slow or crashing. It can be don by adding if self.rect.bottom < 0: self.kill() in the ball update method.
Use elif when appropriate.
In the player update method you define an attribute self.speedx which is only used inside that method, thus it's better to just use a local variable instead. Also, it's discourage to define attributes outside the __init__ method.
Here's your code slightly modified.
import pygame
pygame.init()
is_running = True # Use lowercase_and_underscore for variable names.
WIDTH = 800
HEIGHT = 600
FPS = 60
clock = pygame.time.Clock()
window = pygame.display.set_mode((WIDTH, HEIGHT))
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((60, 30))
self.image.fill((200, 255, 200))
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 30
def update(self):
speedx = 0 # Don't define attributes outside __init__. Local variable works in this case instead.
keystate = pygame.key.get_pressed()
if keystate[pygame.K_a]:
speedx = -5
elif keystate[pygame.K_d]: # Use elif so it doesn't need to check this condition if the above is true.
speedx = 5
self.rect.x += speedx
if self.rect.right + speedx > WIDTH:
self.rect.right = WIDTH
elif self.rect.left + speedx < 0: # Use elif so it doesn't need to check this condition if the above is true.
self.rect.left = 0
def shoot(self):
ball = Ball(self.rect.centerx, self.rect.top)
all_sprites.add(ball)
balls.add(ball)
class Ball(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 10))
self.image.fill((100, 150, 200))
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedy = -3
def update(self):
if self.rect.bottom < 0: # Chack if the ball has exit above the screen.
self.kill() # If it has it should delete itself.
self.rect.y += self.speedy
all_sprites = pygame.sprite.Group()
balls = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
while is_running:
clock.tick(FPS)
print(all_sprites)
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()
window.fill((0, 0, 30))
all_sprites.update()
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
quit()
EDIT: KEEPING THE BALL ON THE PADDLE AND LAUNCHING WITH SPACE
First you could create an attribute self.current_ball in the Player class which will reference the ball on the paddle. Then in the update method of the Player class you update the ball's position relative to the paddle.
To keep the ball at the paddle you have to change the ball's self.speedy to start at 0, otherwise it will move directly after being created. When you call the player.shoot() method you'll set self.speedy = -3 which will start the ball to move.
After it has been launched you just create a new ball on the paddle and repeat the same process.
import pygame
pygame.init()
is_running = True # Use lowercase_and_underscore for variable names.
WIDTH = 800
HEIGHT = 600
FPS = 60
clock = pygame.time.Clock()
window = pygame.display.set_mode((WIDTH, HEIGHT))
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((60, 30))
self.image.fill((200, 255, 200))
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 30
self.current_ball = Ball(self.rect.centerx, self.rect.top)
all_sprites.add(self.current_ball)
balls.add(self.current_ball)
def update(self):
speedx = 0 # Don't define attributes outside __init__. Local variable works in this case instead.
keystate = pygame.key.get_pressed()
if keystate[pygame.K_a]:
speedx = -5
elif keystate[pygame.K_d]: # Use elif so it doesn't need to check this condition if the above is true.
speedx = 5
self.rect.x += speedx
if self.rect.right + speedx > WIDTH:
self.rect.right = WIDTH
elif self.rect.left + speedx < 0: # Use elif so it doesn't need to check this condition if the above is true.
self.rect.left = 0
self.current_ball.rect.midbottom = self.rect.midtop # Set the ball position relative to paddle position.
def shoot(self):
self.current_ball.speedy = -3 # The ball should start moving.
self.current_ball = Ball(self.rect.centerx, self.rect.top) # Create a new ball on the paddle.
all_sprites.add(self.current_ball)
balls.add(self.current_ball)
class Ball(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 10))
self.image.fill((100, 150, 200))
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedy = 0 # The ball don't move from the beginning.
def update(self):
if self.rect.bottom < 0: # Chack if the ball has exit above the screen.
self.kill() # If it has it should delete itself.
self.rect.y += self.speedy
all_sprites = pygame.sprite.Group()
balls = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
while is_running:
clock.tick(FPS)
print(all_sprites)
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()
window.fill((0, 0, 30))
all_sprites.update()
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
quit()

Categories