Python/Pygame sprites using same class not updating properly - python

import pygame, sys, random
pygame.init()
class Ship(pygame.sprite.Sprite):
movepersec = 0
dx = 0
dy = 0
direction = ""
imgarray = {}
def __init__(self, imgarr, rect, speed, xpos, ypos, direct):
pygame.sprite.Sprite.__init__(self)
self.imgarray = imgarr
self.rect = rect
self.movepersec = speed
self.dx = xpos
self.dy = ypos
self.direction = direct
self.image = self.imgarray[self.direction]
def setDirection(self, direct):
self.direction = direct
def setSpeed(self, speed):
self.movepersec = speed
def update(self, secs):
movePix = self.movepersec*secs
if self.direction == "N": self.dy -= movePix
if self.direction == "S": self.dy += movePix
if self.direction == "E": self.dx += movePix
if self.direction == "W": self.dx -= movePix
self.rect.centerx = self.dx
self.rect.centery = self.dy
self.image = self.imgarray[self.direction]
background = pygame.image.load("sea.jpg")
backgroundWidth = background.get_width()
backgroundHeight = background.get_height()
size = background.get_size()
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
imgarray = {}
imgarray["N"] = pygame.image.load("shipNorth.png")
imgarray["S"] = pygame.image.load("shipSouth.png")
imgarray["E"] = pygame.image.load("shipEast.png")
imgarray["W"] = pygame.image.load("shipWest.png")
imgrect = imgarray["N"].get_rect()
movepersec = 150
keydownflag = False
allSpriteGroup = pygame.sprite.Group()
shipX = Ship(imgarray, imgrect, movepersec, 100, 100, "E")
allSpriteGroup.add(shipX)
shipY = Ship(imgarray, imgrect, movepersec, 100, 100, "S")
allSpriteGroup.add(shipY)
screen.blit(background,(0,0))
while True:
secs = clock.tick(30)/1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT : sys.exit(0)
if event.type == pygame.KEYDOWN:
keydownflag = True
if event.key == pygame.K_LEFT:
shipX.setDirection("W")
if event.key == pygame.K_RIGHT:
shipX.setDirection("E")
if event.key == pygame.K_UP:
shipX.setDirection("N")
if event.key == pygame.K_DOWN:
shipX.setDirection("S")
if event.type == pygame.KEYUP:
keydownflag = False
if keydownflag:
shipX.update(secs)
shipY.update(secs)
#shipX collision
if shipX.rect.right + 65 >= backgroundWidth:
shipX.setDirection("W")
if shipX.rect.bottom >= backgroundHeight:
shipX.setDirection("N")
if shipX.rect.left <= 0:
shipX.setDirection("E")
if shipX.rect.top <= 0:
shipX.setDirection("S")
#Ship Y collision
if shipY.rect.top <= 0:
shipY.setDirection("S")
if shipY.rect.bottom >= backgroundHeight:
shipY.setDirection("N")
print(shipX.dx)
allSpriteGroup.clear(screen,background)
allSpriteGroup.draw(screen)
pygame.display.flip()
Quick rundown, shipX should move when the arrow keys are pressed and shipY moves up and down by itself. Im having an issue where the sprites update only to shipY meaning they will only move up and down. the ShipX sprite will rotate properly and the x and y locations change in the log, but the sprites seem to constantly be attached. I'm stumped and cannot figure out a way to fix this issue. Any help would be great. thank you

To solve the problem change the __init__() method of your Ship sprite class to:
def __init__(self, imgarr, speed, xpos, ypos, direct):
pygame.sprite.Sprite.__init__(self)
self.imgarray = imgarr
self.movepersec = speed
self.dx = xpos
self.dy = ypos
self.direction = direct
self.image = self.imgarray[self.direction]
self.rect = self.image.get_rect() #create a *new* rect-object
The important thing is that every time you create a new instance (i.e. a new sprite) you must also create a new pygem.rect object (see above code).
In your code every Ship object (shipX and shipY) used the same pygem.rect object which was passed to it via the __init__() method (rect argument).
When you called the update() method, one of the two ships changed the rect.centerx and rect.centery attributes of the same rect. That´s the reason why PyGame drew both sprites on the same (rect)-position (allSpriteGroup.draw(screen)).
When you create now a new ship, be aware that the constructor (__init__() method) does not need the rect argument anymore:
shipX = Ship(imgarray, movepersec, 100, 100, "E")
By default shipX appears in the top left corner, because the .get_rect() method returns a pygame.rect which location is set to (0, 0). To set the position of shipX according to the passed xpos and ypos arguments in the constructor, add these two lines after the craetion of the self.rect object:
self.rect.centerx = self.dx
self.rect.centery = self.dy
I hope this helps :)

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)
# [...]

Spawn moving objects from border

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.

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.

All the sprites have to move in pygame

I am using Vector2 for my game to make the camera. But when I collided with the wall all the sprites started moving so I fixed it with my walls and floor, but I can't figure out how to fix my enemy. Any ideas?
this is my code:
import sys
import pygame as pg
from pygame.math import Vector2
width = 1280
height = 720
x1 = 200
y1 = 100
x2 = 500
y2 = 400
x3 = 100
y3 = 300
x = 0
y = 0
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load("character.png")
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0, 0)
self.pos = Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
#enemy class
class Enemy(pg.sprite.Sprite):
def __init__(self, pos, waypoints, *groups):
super().__init__(*groups)
self.image = pg.image.load("enemy.png")
self.image = pg.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0,0)
self.max_speed = 5
self.pos = Vector2(pos)
self.waypoints = waypoints
self.waypoint_index = 0
self.target = self.waypoints[self.waypoint_index]
self.target_radius = 50
self.rect.x = width / 2
self.rect.y = height / 2
def update(self):
# A vector pointing from self to the target.
heading = self.target - self.pos
distance = heading.length() # Distance to the target.
heading.normalize_ip()
if distance <= 2: # We're closer than 2 pixels.
# Increment the waypoint index to swtich the target.
# The modulo sets the index back to 0 if it's equal to the length.
self.waypoint_index = (self.waypoint_index + 1) % len(self.waypoints)
self.target = self.waypoints[self.waypoint_index]
if distance <= self.target_radius:
# If we're approaching the target, we slow down.
self.vel = heading
else: # Otherwise move with max_speed.
self.vel = heading * self.max_speed
self.pos += self.vel
self.rect.center = self.pos
#Enemy waypoints
waypoints = [[x1, y1], [x2, y2], [x3, y3]]
class Floor(pg.sprite.Sprite):
def __init__(self, x, y, *groups):
super().__init__(*groups)
self.image = pg.image.load("floor.png")
self.rect = self.image.get_rect(topleft=(x, y))
self.rect.x = x
self.rect.y = y
class SideWall(pg.sprite.Sprite):
def __init__(self, x, y, *groups):
super().__init__(*groups)
self.image = pg.image.load("sidewall.png")
self.rect = self.image.get_rect(topleft=(x, y))
self.rect.x = x
self.rect.y = y
class TopAndBottomWall(pg.sprite.Sprite):
def __init__(self, x, y, *groups):
super().__init__(*groups)
self.image = pg.image.load("topandbottomwall.png")
self.rect = self.image.get_rect(topleft=(x, y))
self.rect.x = x
self.rect.y = y
def main():
screen = pg.display.set_mode((1280, 720))
clock = pg.time.Clock()
#all the sprites group
all_sprites = pg.sprite.Group()
#the floor
floor = Floor(540, -620, all_sprites)
#player
player = Player(((width / 2), (height / 2)), all_sprites)
#walls group
walls = pg.sprite.Group()
#all walls
walltop = TopAndBottomWall(540, -620, all_sprites, walls)
wallbottom = TopAndBottomWall(540, 410, all_sprites, walls)
wallleft = SideWall((width / 2) - 100, (height / 2) - 930, all_sprites, walls)
wallright = SideWall((wallleft.rect.x + (1920 - 50)), (height / 2) - 930, all_sprites, walls)
#all enemy's
enemy = Enemy((100, 300), waypoints, all_sprites)
camera = Vector2(0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
#player movement
elif event.type == pg.KEYDOWN:
if event.key == pg.K_d:
player.vel.x = 5
elif event.key == pg.K_a:
player.vel.x = -5
elif event.key == pg.K_w:
player.vel.y = -5
elif event.key == pg.K_s:
player.vel.y = 5
elif event.type == pg.KEYUP:
if event.key == pg.K_d and player.vel.x > 0:
player.vel.x = 0
elif event.key == pg.K_a and player.vel.x < 0:
player.vel.x = 0
elif event.key == pg.K_w:
player.vel.y = 0
elif event.key == pg.K_s:
player.vel.y = 0
camera -= player.vel
all_sprites.update()
if pg.sprite.spritecollide(player, walls, False):
#stop the left wall from moving
wallleft.rect.x = wallleft.rect.x + player.vel.x
wallleft.rect.y = wallleft.rect.y + player.vel.y
#stop the top wall from moving
walltop.rect.y = walltop.rect.y + player.vel.y
walltop.rect.x = walltop.rect.x + player.vel.x
#stop the right wall from moving
wallright.rect.x = wallright.rect.x + player.vel.x
wallright.rect.y = wallright.rect.y + player.vel.y
#stop the bottom wall from moving
wallbottom.rect.x = wallbottom.rect.x + player.vel.x
wallbottom.rect.y = wallbottom.rect.y + player.vel.y
#stop the floor from moving
floor.rect.x = floor.rect.x + player.vel.x
floor.rect.y = floor.rect.y + player.vel.y
screen.fill((0, 0, 0))
for sprite in all_sprites:
screen.blit(sprite.image, sprite.rect.topleft+camera)
pg.display.flip()
clock.tick(30)
main()
pg.quit()
This is a link with the files if you want to run it.
https://geordyd.stackstorage.com/s/hZZ1RWcjal6ecZM
You are working with screen coordinates. You only move the walls while checking for keypresses; you should move the enemies at the same time.
But there is a far better way; more expandable if you add more classes, and less confusing.
Remove the keypresses check out of Wall and put them into Player. This will change the player's position in world coordinates, the same coordinates as the walls are (static) and the enemies move in (dynamic).
Then draw both walls and enemies at world_position - players_position, adjusted for the player's relative position in the center. Technically even the player itself is drawn at that position – the calculation for him amounts to zero movement relative to the screen.
For even more flexibility you could consider a separate Camera class, which is set up to follow the player by default. That allows you to let the camera move 'elsewhere', or if the player is near the edge of your world, let him walk off the screen center.

Python (pygame) collision detection - sprite and group

I am new to python and am trying to write a game that launches a character and when he interacts with a sprite on the ground, something will change, for example speed. My apologies for the disorganization in my code. I have taken samples from a few tutorials and I can't make them work together.
How do I make the player's collision with the bomb detectable?
import pygame
import random
import math
drag = 1
gravity = (math.pi, .4)
elasticity = .75
# Colors
BLACK = ( 0, 0, 0)
WHITE = ( 255, 255, 255)
BLUE = ( 0, 0, 255)
RED = ( 255, 0, 0)
GREEN = ( 0, 255, 0)
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
def addVectors((angle1, length1), (angle2, length2)):
x = math.sin(angle1) * length1 + math.sin(angle2) * length2
y = math.cos(angle1) * length1 + math.cos(angle2) * length2
angle = 0.5 * math.pi - math.atan2(y, x)
length = math.hypot(x, y)
return (angle, length)
class Player(pygame.sprite.Sprite):
change_x = 0
change_y = 0
level = None
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('player.png')
image_rect = self.image.get_rect()
self.rect = pygame.rect.Rect(x, y, image_rect.width, image_rect.height)
def update(self):
pass
def move(self):
(self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)
self.rect.x += math.sin(self.angle) * self.speed
self.rect.y -= math.cos(self.angle) * self.speed
self.speed *= drag
def bounce(self):
if self.rect.x > 800 - self.rect.width:
self.rect.x = 2*(800 - self.rect.width) - self.rect.x
self.angle = - self.angle
self.speed *= elasticity
elif self.rect.x < 0:
self.rect.x = 2*self.rect.width - self.rect.x
self.angle = - self.angle
self.speed *= elasticity
if self.rect.y > SCREEN_HEIGHT - self.rect.height:
self.rect.y = 2*(SCREEN_HEIGHT - self.rect.height) - self.rect.y
self.angle = math.pi - self.angle
self.speed *= elasticity
class Bomb(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('cherry.png')
image_rect = self.image.get_rect()
self.rect = pygame.rect.Rect(x, y, image_rect.width, image_rect.height)
class Level():
bomb_list = None
world_shift = 0
def __init__(self, player):
self.bomb_list = pygame.sprite.Group()
self.player = player
def update(self):
self.bomb_list.update()
def draw(self, screen):
screen.fill(BLACK)
self.bomb_list.draw(screen)
def shift_world(self, shift_x):
self.world_shift += shift_x
for bomb in self.bomb_list:
bomb.rect.x += shift_x
if bomb.rect.x < 0:
self.bomb_list.remove(bomb)
self.bomb_list.add(Bomb(random.randint(SCREEN_WIDTH, 2*SCREEN_WIDTH), 580))
I am not sure if this Level_01 class is even necessary:
class Level_01(Level):
def __init__(self, player):
Level.__init__(self, player)
bombs = 0
for n in range(10):
self.bomb_list.add(Bomb(random.randint(0, SCREEN_WIDTH), 580))
This was my attempt at a collision detection method. I'm not sure if it should be in a class, in main, or seperate. I can't seem to get the list of bombs, and the player at the same time.
def detectCollisions(sprite1, sprite_group):
if pygame.sprite.spritecollideany(sprite1, sprite_group):
sprite_group.remove(pygame.sprite.spritecollideany(sprite1, sprite_group))
print True
else:
print False
def main():
pygame.init()
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
active_sprite_list = pygame.sprite.Group()
enemy_list = pygame.sprite.Group()
player = Player(0, 0)
player.speed = 30
player.angle = math.radians(45)
player.rect.x = 500
player.rect.y = SCREEN_HEIGHT - player.rect.height
active_sprite_list.add(player)
done = False
clock = pygame.time.Clock()
current_level = Level_01(player)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.go_left()
if event.key == pygame.K_RIGHT:
player.go_right()
active_sprite_list.update()
enemy_list.update()
player.level = current_level
player.move()
player.bounce()
if player.rect.x >= 500:
diff = player.rect.x - 500
player.rect.x = 500
current_level.shift_world(-diff)
if player.rect.x <= 120:
diff = 120 - player.rect.x
player.rect.x = 120
current_level.shift_world(diff)
current_level.draw(screen)
active_sprite_list.draw(screen)
enemy_list.draw(screen)
clock.tick(60)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
Thanks for helping me out!
Probably the easiest thing to do is to draw out pixels (or virtual pixels) and if in drawing bomb/person pixels you find an overlap then you know a collision occurred.
You can however get way more complicated (and efficient) in your collision detection if you need a higher performance solution. See Wikipedia Collision Detection for a reference.
I suggest creating sprite groups using pygame.sprite.Group() for each class; Bomb and Player. Then, use pygame.sprite.spritecollide().
For example:
def Main()
...
player_list = pygame.sprite.Group()
bomb_list = pygame.sprite.Group()
...
Then in your logic handling loop, after creating a Player and Bomb instance, you could do something like this:
for bomb in bomb_list:
# See if the bombs has collided with the player.
bomb_hit_list = pygame.sprite.spritecollide(bomb, player_list, True)
# For each bomb hit, remove bomb
for bomb in bomb_hit_list:
bomb_list.remove(bomb)
# --- Put code here that will trigger with each collision ---

Categories