Collision with enemy in pygame making player lose multiple lives - python

I'm trying to make it so then when an enemy collides with the player, a single life is lost and the player is positioned at the center of the screen. It works about half of the time, but the other half of the time two or three lives are lost from one collision.
def collide(self):
for enemy in enemies:
if ((robot.hitbox[0] < enemy.x + 16 < robot.hitbox[0] + robot.hitbox[2]) or (robot.hitbox[0] < enemy.x - 16 < robot.hitbox[0] + robot.hitbox[2])) and ((robot.hitbox[1] < enemy.y + 16 < robot.hitbox[1] + robot.hitbox[3]) or (robot.hitbox[1] < enemy.y - 16 < robot.hitbox[1] + robot.hitbox[3])):
robot.alive = False
robot.x = 400
robot.y = 300
for enemy in enemies:
enemies.remove(enemy)
robot.lives -= 1
robot.alive = True
This is a function under the class Enemy which is called inside of the Enemy's draw function, which is called in the while loop.
while running:
## add if robot.alive == True into loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
userInput = pygame.key.get_pressed()
if len(enemies) <= 3:
randSpawnX = random.randint(32, 768)
randSpawnY = random.randint(77, 568)
if (robot.x - 100 <= randSpawnX <= robot.x + 100) or (robot.y - 100 <= randSpawnY <= robot.y + 100):
randSpawnX = random.randint(32, 768)
randSpawnY = random.randint(77, 568)
else:
enemy = Enemy(randSpawnX, randSpawnY)
enemies.append(enemy)
if robot.alive == True:
for enemy in enemies:
enemy.move()
robot.shoot()
robot.movePlayer(userInput)
drawGame()
Could anyone help me figure out why this is occurring? I believe it's because multiple collisions are registering but since I move the robot to the middle of the screen as soon as the first hit is registered and before the lives are lost, why is this happening?

Read How do I detect collision in pygame? and use pygame.Rect / colliderect() for the collision detection.
Read How to remove items from a list while iterating? and iterate through a copy of the list.
If you encounter a collision, it is not enough to change the player's x and y attributes. You also need to change the hitbox. Additionally break the loop when a collision is detected:
def collide(self):
robot_rect = pygame.Rect(*robot.hitbox)
for enemy in enemies[:]:
enemy_rect = pygame.Rect(enemy.x, enemy.y, 16, 16)
if robot_rect.colliderrect(enemy_rect):
enemies.remove(enemy)
robot.x, robot.y = 400, 300
robot.hitbox[0] = robot.x
robot.hitbox[1] = robot.y
robot.lives -= 1
break

Related

ValueError: list.remove(x): x not in list in pygame

Currently I'm removing bullets when either:
bullet collides with player
bullet goes out of map
bullet hits tile in map
for instance:
I remove the bullet on any of those scenarios, which most often works, however randomly the script pulls up the error above:
ValueError: list.remove(x): x not in list
This happens even after the script working previously
Current script:
class Range_Enemy(Enemy):
def __init__(self,x,y,health,x_speed,image):
super().__init__(x,y,health,x_speed,image)
# extra variables
self.shoot_last = 0
self.bullet_speed = 5
self.bullet_y_vel = -1
self.bullet_grav = 0.05
self.bullets_right = []
self.bullets_left = []
self.bullet_colour = (0,255,0)
def personal_update(self,world):
# checks enemy pos and changes depending on distance from player
if self.rect.x > player.rect.x + 300:
self.x_speed = -2
elif self.rect.x < player.rect.x -300:
self.x_speed = 2
elif self.rect.x < player.rect.x + 300:
self.x_speed = 0
elif self.rect.x > player.rect.x - 300:
self.x_speed = 0
# shoots every scheduled tick
shoot_now = p.time.get_ticks()
if shoot_now - self.shoot_last > 1500:
self.bullet_y_vel = -1.5
right_bullet = p.Rect(self.rect.x,self.rect.y + 5,8,4)
left_bullet = p.Rect(self.rect.x + 20,self.rect.y + 5,8,4)
self.bullets_right.append(right_bullet)
self.bullets_left.append(left_bullet)
self.shoot_last = shoot_now
# gets every bullet
for bullet in self.bullets_right:
# checks any collision
if bullet.colliderect(player):
player.health -= 1
self.bullets_right.remove(bullet)
for tile in world.tile_list:
if tile[1].colliderect(bullet):
self.bullets_right.remove(bullet)
if bullet.x > WIDTH:
self.bullets_right.remove(bullet)
# applies movement to bullet
bullet.x += self.bullet_speed
# applies gravity to bullet
self.bullet_y_vel += self.bullet_grav
bullet.y += self.bullet_y_vel
p.draw.rect(WIN,self.bullet_colour,bullet)
for bullet in self.bullets_left:
# checks for any collision
if bullet.x < 0:
self.bullets_left.remove(bullet)
if bullet.colliderect(player):
player.health -= 1
self.bullets_left.remove(bullet)
for tile in world.tile_list:
if tile[1].colliderect(bullet):
self.bullets_left.remove(bullet)
# applies movement to bullet
bullet.x -= self.bullet_speed
# applies gravity to bullet
self.bullet_y_vel += self.bullet_grav
bullet.y += self.bullet_y_vel
p.draw.rect(WIN,self.bullet_colour,bullet)
This creates an enemy which shoots bullets both right and left in a small curve.
I don't understand why this error comes up as it only removes the bullet from the list when one of the collisions is met, and this usually works. It is only randomly this error occurs however ruins the whole dynamic of the game.
The error suggests that the script is removing a bullet which doesn't exist, so is there a way to check if the bullet doesn't exist then just passes, or is there a way to simply stop this from happening?
Two things: First, you should put bullets you remove into another temporary list so that you don't skip over bullets while iterating. Second, you should continue after removing a bullet / marking a bullet for removal, as otherwise, multiple conditions might be true, yet the bullet doesn't exist when you try to remove it.
Here's the for loop for the right bullets with the fixes above: (you can adapt this for the left bullets)
# Temporary list of bullets to remove
to_be_removed_bullets_right = []
# gets every bullet
for bullet in self.bullets_right:
# checks any collision
if bullet.colliderect(player):
player.health -= 1
to_be_removed_bullets_right.append(bullet)
continue
# Flag for noting the bullet was removed because Python doesn't have
# labeled loops
bullet_removed = False
for tile in world.tile_list:
if tile[1].colliderect(bullet):
bullet_removed = True
to_be_removed_bullets_right.append(bullet)
break
if bullet_removed:
continue
# Alternatively, you could do this
# if any(tile[1].colliderect(bullet) for tile in world.tile_list):
# to_be_removed_bullets_right.append(bullet)
# continue
if bullet.x > WIDTH:
to_be_removed_bullets_right.append(bullet)
continue
# applies movement to bullet
bullet.x += self.bullet_speed
# applies gravity to bullet
self.bullet_y_vel += self.bullet_grav
bullet.y += self.bullet_y_vel
p.draw.rect(WIN,self.bullet_colour,bullet)
# Remove bullets marked for removal
for bullet in to_be_removed_bullets_right:
self.bullets_right.remove(bullet)

Pycharm, pygame ball bouncing program [duplicate]

Before you criticize me for not Googling or doing research before asking, I did research beforehand but to no avail.
I am trying to create the Atari Breakout game. I am currently stuck with making the ball bounce off walls. I did research on this and I found a lot of blogs and YouTube videos (and also Stack Overflow questions: this and this) talking about PyGame's vector2 class. I also read the PyGame documentation on vector2 but I can't figure out how to make it work.
I am currently writing a script to make the ball bounce off walls. In the beginning, the player is requested to press the spacebar and the ball will automatically move towards the north-east direction. It should bounce off the top wall when it hits it, but instead, it went inside. This is my approach:
import pygame
pygame.init()
screenWidth = 1200
screenHeight = 700
window = pygame.display.set_mode((screenWidth,screenHeight))
pygame.display.set_caption('Atari Breakout')
class Circle():
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
self.vel_x = 1
self.vel_y = 1
def check_hit():
global hit
if (((screenWidth-box.x)<=box.radius) or ((box.x)<=box.radius) or ((box.y)<=box.radius) or ((screenHeight-box.y)<=box.radius)):
# meaning hit either four walls
if (((screenWidth-box.x)<=box.radius) or ((box.x)<=box.radius)):
# hit right, left
print('hit right, left')
hit = True
elif (((box.y)<=box.radius) or ((screenHeight-box.y)<=box.radius)):
# hit top, bottom
print('hit top, bottom')
hit = True
# main loop
run = True
box = Circle(600,300,10)
hit = False
# (screenWidth-box.x)<=box.radius hit right wall
while run: # (box.x)<=box.radius hit left wall
# (box.y)<=box.radius hit top wall
pygame.time.Clock().tick(60) # (screenHeight-box.y)<=box.radius hit bottom wall
for event in pygame.event.get():
if event == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE] and (box.y)>box.radius:
while True:
box.y -= box.vel_y
box.x += box.vel_x
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
check_hit()
if hit == False:
continue
elif hit == True:
break
if (box.y)<=box.radius or (screenHeight-box.y)<=box.radius:
# hit top, bottom
box.vel_x *= 1
box.vel_y *= -1
print('changed')
if (box.y)<=box.radius:
# hit top
print('hi')
while True:
box.x += box.vel_x # <-- myguess is this is the problem
box.y += box.vel_y
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
elif (screenWidth-box.x)<=box.radius or (box.x)<=box.radius:
# hit right, left
box.vel_x *= -1
box.vel_y *= 1
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
print('Where are you going')
pygame.quit()
I guess the problem is where I marked. Which is here:
if (box.y)<=box.radius or (screenHeight-box.y)<=box.radius:
# hit top, bottom
box.vel_x *= 1
box.vel_y *= -1
print('changed')
if (box.y)<=box.radius:
# hit top
print('hi')
while True:
box.x += box.vel_x # <-- myguess is this is the problem
box.y += box.vel_y
window.fill((0,0,0))
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()
but I don't know why. My theory is: the ball travels upwards, it hit the top wall, check_hit() kicks in and make hit = True, then the vel_x and vel_y is changed accordingly (if hit top wall, vel_x should remain the same while vel_y should be multiplied by -1). Then it will move down, hence "bounce" off the top wall.
Note: for now I only have the top wall working. The other three will be done when I can figure out how to bounce off the top wall first.
Can you help me see what's the problem? And if this kind of operation requires the use of the vector2 class, can you explain it to me or give me a place to learn it?
The issue are the multiple nested loops. You have an application loop, so use it.
Continuously move the ball in the loop:
box.y -= box.vel_y
box.x += box.vel_x
Define a rectangular region for the ball by a pygame.Rect object:
bounds = window.get_rect() # full screen
or
bounds = pygame.Rect(450, 200, 300, 200) # rectangular region
Change the direction of movement when the ball hits the bounds:
if box.x - box.radius < bounds.left or box.x + box.radius > bounds.right:
box.vel_x *= -1
if box.y - box.radius < bounds.top or box.y + box.radius > bounds.bottom:
box.vel_y *= -1
See the example:
box = Circle(600,300,10)
run = True
start = False
clock = pygame.time.Clock()
while run:
clock.tick(120)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
start = True
bounds = pygame.Rect(450, 200, 300, 200)
if start:
box.y -= box.vel_y
box.x += box.vel_x
if box.x - box.radius < bounds.left or box.x + box.radius > bounds.right:
box.vel_x *= -1
if box.y - box.radius < bounds.top or box.y + box.radius > bounds.bottom:
box.vel_y *= -1
window.fill((0,0,0))
pygame.draw.rect(window, (255, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()

How to reflect the ball off paddle

Im trying out pygame for the first time, now ive gotten very comfortable with python as a language, and ive been trying to recreate Pong, like the old school game with 2 paddles and a ball. I cant seem to figure out how to get the ball to reflect off the paddle. Im not looking for angles and stuff yet, cos ill be able to figure that out on my own.
Buttt what ive thought of is to get a range of coordinates, which are the X & Y of the paddle and the x & y + the width and height, and if the ball enters these, it simply reflects as it does at a boundary. Ive tried doing multiple if statements, as you can see in the code below, but ive also tried doing it as a single statement, but that doesnt work. None of the debug prints ive put in actually print, but when i test the coord ranges with print they look fine :D
Ill paste my code here so you guys can run my game as is.
Id really appreciate your guys help!
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 10 #sets variables for the main paddle
height = 60
vel = 5
ballx = 250
bally = 250
radius = 5
direction = True #True is left, False is right
bvel = 4 #sets variables for the ball
angle = 0
coordxGT = 0
coordxLT = 0 #sets variables for the coordinate ranges of the first paddle for collision
coordyGT = 0
coordyLT = 0
def setCoords():
coordxGT = x
coordxLT = x + width
coordyGT = y #This function updates the coords throughout the main loop
coordyLT = y + height
coordxLT += radius
coordyLT += radius
run = True
while run == True:
pygame.time.delay(20)
for event in pygame.event.get(): #on quit, quit the game and dont throw up an error :)
if event.type == pygame.QUIT:
run = False
setCoords()
if direction == True: #Ball movement
ballx -= bvel
else:
ballx += bvel
if ballx<0:
ballx += bvel
direction = False
elif bally<0:
bally += bvel
elif ballx>495: #if the ball hits an edge
ballx -= bvel
direction = True
elif bally>495:
bally -= bvel
if ballx<coordxLT and ballx>coordxGT:
print("S1")
if bally<coordyLT and bally>coordyGT: #THE PART I CANT FIGURE OUT. If the ball lands within these ranges of coordinates, reflect it and change its direction
print("S2")
if direction == True:
print("YES")
ballx += bvel
direction = False
keys = pygame.key.get_pressed() #gets the keys pressed at that current time
if keys[pygame.K_DOWN]:
bally += bvel #Ball control (For debugging)
if keys[pygame.K_UP]:
bally -= bvel
if keys[pygame.K_w]:
y -= vel
if keys[pygame.K_a]:
x -= vel
if keys[pygame.K_s]: #Paddle controls
y += vel
if keys[pygame.K_d]:
x += vel
if x<0:
x += vel
if y<0:
y += vel
if x>80:
x -= vel #Stops the paddle from moving if it hits a boundary
if y>440:
#440 because window height - height of cube
y -= vel
win.fill((0,0,0))
pygame.draw.circle(win, (255, 255, 255), (ballx, bally), radius) #refreshes the screen
pygame.draw.rect(win,(255,255,255),(x, y, width, height))
pygame.display.update()
pygame.quit()
You are close, but you missed to declare the variables coordxGT, coordxLT, coordxLT, coordyLT to be global.
def setCoords():
global coordxGT, coordxLT, coordxLT, coordyLT
coordxGT = x
coordxLT = x + width
coordyGT = y
coordyLT = y + height
coordxLT += radius
coordyLT += radius
Note, if you want to write to a variable in global namespace in a function, then the varible has be interpreted as global. Otherwise an new variable in the scope of the function will be created and set. See global statement.

How do I detect the collision of two sprites using pygame?

This is my code so far, I can move and it places out a blip to pick up, I just need to know how to register that and move the blip to a new random spot!
I am very new to pygame and not 100% fluent in python either, but I'm decent. If there are pdf:s good for intermediate coders when it comes to python that would be wonderful!
import sys, pygame, os, math
from random import randint
pygame.init()
size = width, height = 800, 600
speed = [2, 2]
black = 1, 1, 1
screen = pygame.display.set_mode(size)
pygame.display.set_caption('Pick up the squares!')
UP='up'
DOWN='down'
LEFT='left'
RIGHT='right'
ball = pygame.image.load("ball.png")
ballrect = ball.get_rect()
ballx = 400
bally = 300
blip = pygame.image.load("blip.png")
bliprect = blip.get_rect()
blipx = randint(1,800)
blipy = randint(1,600)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
clock = pygame.time.Clock()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
ballx -= 5
if keys[pygame.K_RIGHT]:
ballx += 5
if keys[pygame.K_UP]:
bally -= 5
if keys[pygame.K_DOWN]:
bally +=5
screen.fill(black)
screen.blit(ball, (ballx,bally))
screen.blit(blip, (blipx, blipy))
pygame.display.update()
clock.tick(40)
Use the colliderect method of rectangles:
if ballrect.colliderect(bliprect):
print 'Do something here'
A basic collision detection works like this (assuming you are working with two rectangles):
def does_collide(rect1, rect2):
if rect1.x < rect2.x + rect2.width and rect1.x + rect1.width > rect2.x \
and rect1.y < rect2.y + rect2.height and rect1.height + rect1.y > rect2.y:
return True
return False
Fortunately Pygame is packed with such methods, so you should go with #MalikBrahimi's answer - using colliderect function call, which will do the math for you.
You can also try using pygame.sprite.spritecollide():
if pygame.sprite.spritecollide(a, b, False):
pass
#Do something
a here is your variable name for the class for one of the sprites in the collision. b here is the group that your second sprite will be. You can set the last one to True, which will remove the sprite from the group. Setting to False will keep it the way it is. Here is an example:
if pygame.sprite.spritecollide(my_ball, ballGroup, False):
Ball.speed[0] = -Ball.speed[0]
hit.play()
Ball.move()
The variable name for my sprite is my_ball. The group containing the other sprite(s) in this collision is ballGroup. I set the last one to False to keep all the sprites on the surface. If I set to True, the sprite from ballGroup will be removed from the group and screen. I hope this helps you!

how to shoot at shapes with pygame

I am reading this book for Python called "More Python Programming for the Absolute Beginner" and in chapter 4, you create this bomb catching game. I am trying to modify it so you shoot at the bomb or enemy.
I want to be able to draw the bullets using
pygame.draw.circle()
and so it shoots when you click on the right mouse button, then it hits the enemy and it adds on your score.
By the way - I already know how to add the score on I just need help with shooting the shape :)
if you want to have a look at the original game source file, go here -
http://www.delmarlearning.com/companions/content/1435459806/relatedfiles/index.asp?isbn=1435459806
and it's in chapter 4, "BombCatcher"
original source code-
# Bomb Catcher Game
# Chapter 4
import sys, random, time, pygame
from pygame.locals import *
def print_text(font, x, y, text, color=(255,255,255)):
imgText = font.render(text, True, color)
screen.blit(imgText, (x,y))
#main program begins
pygame.init()
screen = pygame.display.set_mode((600,500))
pygame.display.set_caption("Bomb Catching Game")
font1 = pygame.font.Font(None, 24)
pygame.mouse.set_visible(False)
white = 255,255,255
red = 220, 50, 50
yellow = 230,230,50
black = 0,0,0
lives = 3
score = 0
clock_start = 0
game_over = True
mouse_x = mouse_y = 0
pos_x = 300
pos_y = 460
bomb_x = random.randint(0,500)
bomb_y = -50
vel_y = 0.7
#repeating loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
elif event.type == MOUSEMOTION:
mouse_x,mouse_y = event.pos
move_x,move_y = event.rel
elif event.type == MOUSEBUTTONUP:
if game_over:
game_over = False
lives = 3
score = 0
keys = pygame.key.get_pressed()
if keys[K_ESCAPE]:
sys.exit()
screen.fill((0,0,100))
if game_over:
print_text(font1, 100, 200, "<CLICK TO PLAY>")
else:
#move the bomb
bomb_y += vel_y
#has the player missed the bomb?
if bomb_y > 500:
bomb_x = random.randint(0, 500)
bomb_y = -50
lives -= 1
if lives == 0:
game_over = True
#see if player has caught the bomb
elif bomb_y > pos_y:
if bomb_x > pos_x and bomb_x < pos_x + 120:
score += 10
bomb_x = random.randint(0, 500)
bomb_y = -50
#draw the bomb
pygame.draw.circle(screen, black, (bomb_x-4,int(bomb_y)-4), 30, 0)
pygame.draw.circle(screen, yellow, (bomb_x,int(bomb_y)), 30, 0)
#set basket position
pos_x = mouse_x
if pos_x < 0:
pos_x = 0
elif pos_x > 500:
pos_x = 500
#draw basket
pygame.draw.rect(screen, black, (pos_x-4,pos_y-4,120,40), 0)
pygame.draw.rect(screen, red, (pos_x,pos_y,120,40), 0)
#print # of lives
print_text(font1, 0, 0, "LIVES: " + str(lives))
#print score
print_text(font1, 500, 0, "SCORE: " + str(score))
pygame.display.update()
To check if a button was pressed, you should check for an event.type : MOUSEBUTTONDOWN,and then check for the RMB.
In order to shoot, you need some sort of a list, so that there will be many bullets possible.
You can create a simple Bullet class, that will have a draw and move functions.
An example:
class Bullet:
def __init__(self,position):
self.x,self.y = position
def move(self):
self.position += 5
def draw(self,screen):
pygame.draw.circle(screen, black, x, y, 30, 0)
This covers the drawing, and movement of your bullets. Apart from this, you should check if bullets do not go off screen, and if they do, remove them from the list.
Collision can be done in many ways. Pygame has a built in method called collide_rect, which checks if 2 rectangles are overlapping. But since you have 2 circles, the method is a bit different.
Since you have 2 circles, you have a distance between them. It's given by this equation:
D = sqrt((xa-xb)^2+(ya-yb)^2))
If the distance is smaller than R1 + R2, then the circles are overlapping. You can then proceed to remove the bullet as well as the bomb.
We can simplify this to:
(R1+R2)^2 > (xa-xb)^2+(ya-yb)^2)

Categories