I've completed a small game project of the breakout game, but my main problem is that the ball seems to get stuck going side to side much more often than i would be okay with it happening. It's definitely a problem with the ball velocity and I've tried messing around with the numbers but I can't seem to make it stop getting stuck. Any ideas on how to fix it ,or even improve any of the code, would be greatly appreciated.
import sys
import pygame as pg
from random import randint
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
score = 0
lives = 3
def run_game():
pg.init()
screen = pg.display.set_mode((800,600))
pg.display.set_caption("Brick Breaker")
bg_color = (0, 0, 0)
screen.fill(bg_color)
class Paddle(pg.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pg.Surface([width, height])
self.image.fill(black)
self.image.set_colorkey(black)
pg.draw.rect(self.image, color, [0, 0, width, height])
self.rect = self.image.get_rect()
def moveLeft(self,pixels):
self.rect.x -= pixels
if self.rect.x < 0:
self.rect.x = 0
def moveRight(self,pixels):
self.rect.x += pixels
if self.rect.x > 700:
self.rect.x = 700
class Ball(pg.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pg.Surface([width, height])
self.image.fill(black)
self.image.set_colorkey(black)
pg.draw.rect(self.image, color, [0, 0, width, height])
self.velocity = [randint(4,8),randint(-8,8)]
self.rect = self.image.get_rect()
def update(self):
self.rect.x += self.velocity[0]
self.rect.y += self.velocity[1]
def bounce(self):
self.velocity[0] = -self.velocity[0]
self.velocity[1] = randint(-8,10)
class Brick(pg.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pg.Surface([width, height])
self.image.fill(black)
self.image.set_colorkey(black)
pg.draw.rect(self.image, color, [0, 0, width, height])
self.rect = self.image.get_rect()
clock = pg.time.Clock()
all_sprites_list = pg.sprite.Group()
paddle = Paddle(white,100,10)
paddle.rect.x = 350
paddle.rect.y = 560
ball = Ball(white,10,10)
ball.rect.x = 345
ball.rect.y = 195
all_bricks = pg.sprite.Group()
for i in range(7):
brick = Brick(red,80,30)
brick.rect.x = 60 + i* 100
brick.rect.y = 60
all_sprites_list.add(brick)
all_bricks.add(brick)
for i in range(7):
brick = Brick(blue,80,30)
brick.rect.x = 60 + i* 100
brick.rect.y = 100
all_sprites_list.add(brick)
all_bricks.add(brick)
for i in range(7):
brick = Brick(green,80,30)
brick.rect.x = 60 + i* 100
brick.rect.y = 140
all_sprites_list.add(brick)
all_bricks.add(brick)
all_sprites_list.add(paddle)
all_sprites_list.add(ball)
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
sys.exit()
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
paddle.moveLeft(10)
if keys[pg.K_RIGHT]:
paddle.moveRight(10)
all_sprites_list.update()
if ball.rect.x>=790:
ball.velocity[0] = -ball.velocity[0]
if ball.rect.x<=0:
ball.velocity[0] = -ball.velocity[0]
if ball.rect.y>590:
ball.velocity[1] = -ball.velocity[1]
global lives
lives-=1
if lives == 0:
font = pg.font.Font(None, 74)
text = font.render("YOU LOSE", 1, red)
screen.blit(text,(280,250))
pg.display.flip()
sys.exit()
if ball.rect.y<40:
ball.velocity[1] = -ball.velocity[1]
if pg.sprite.collide_mask(ball, paddle):
ball.rect.x -= ball.velocity[0]
ball.rect.y -= ball.velocity[1]
ball.bounce()
brick_collision_list = pg.sprite.spritecollide(ball,all_bricks,False)
for brick in brick_collision_list:
ball.bounce()
brick.kill()
if len(all_bricks)==0:
font = pg.font.Font(None, 74)
text = font.render("YOU WIN", 1, green)
screen.blit(text,(280,250))
pg.display.flip()
sys.exit()
screen.fill(black)
font = pg.font.Font(None, 40)
text = font.render("lives: " + str(lives), 1, white)
screen.blit(text, (10,10))
all_sprites_list.draw(screen)
pg.display.flip()
clock.tick(50)
run_game()
When the ball hits the paddle, then set the bottom of the ball to the top of the paddle. It is not necessary to change the direction of the ball, because it's direction is changed in ball.bounce()
if pg.sprite.collide_mask(ball, paddle):
ball.rect.bottom = paddle.rect.top
#ball.rect.x -= ball.velocity[0]
#ball.rect.y -= ball.velocity[1]
ball.bounce()
In Ball.bounce invert the y direction of the ball and compute a new random x direction:
class Ball(pg.sprite.Sprite):
def __init__(self, color, width, height):
# [...]
self.velocity = [randint(-8,8), randint(4,8)]
def bounce(self):
# invert y
self.velocity[1] = -self.velocity[1]
# either random x
# self.velocity[0] = randint(-8, 8)
# or slightly changed x
self.velocity[0] = max(-8, min(8, self.velocity[0] + randint(-3, 3)))
Related
Here is my code
import pygame as pg
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
WIDTH = 1920
HEIGHT = 1080
pg.init()
FPS = 50
fpsClock = pg.time.Clock()
screen = pg.display.set_mode((WIDTH, HEIGHT), 0, 32)
ball1Img = pg.image.load('test/sferoid.png')
x1 = y1 = 200 # координаты
xv1 = yv1 = 4 # скорость
check = parity = False
class Ball(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load('test/sferoid.png')
self.rect = self.image.get_rect(centerx=200, bottom=200)
def update(self) -> None:
global xv1
global yv1
xv1 = -xv1 if (self.rect.x > 1920 - 49 or self.rect.x < 0) else xv1 # отскок
yv1 = -yv1 if (self.rect.y > 1080 - 49 or self.rect.y < 0) else yv1 # от стенок
self.rect.x += xv1
self.rect.y += yv1
class Basket(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
# self.image = pg.image.load('test/desk.png')\
self.image = pg.image.load('test/desk.png')
self.image = pg.transform.scale(self.image, (100, 25))
self.rect = self.image.get_rect(centerx=WIDTH // 2, bottom=HEIGHT - 50)
def update(self) -> None:
klavisha = pg.key.get_pressed()
if klavisha[pg.K_a] and self.rect.x >= 0:
self.rect.x -= 20
if klavisha[pg.K_d] and self.rect.right <= WIDTH:
self.rect.x += 20
basket = Basket()
ball = Ball()
while True:
fpsClock.tick(FPS)
screen.fill(BLACK)
basket.update()
ball.update()
screen.blit(basket.image, basket.rect)
screen.blit(ball.image, ball.rect)
pg.display.update()
if pg.sprite.spritecollide(basket, ball, dokill=False):
xv1 = -xv1
yv1 = -yv1
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
I apologize for such a crooked code, but please understand and forgive.
sferoid
desk
When I try to run the program, I get a TypeError: 'Ball' object is not iterable at the line:
if pg.sprite.spritecollide(basket, ball, dokill=False):
xv1 = -xv1
yv1 = -yv1
But if you remove this line, then the code will work.
I don't get why is is doing this but if someone can help me that would be greatly appreciated.
pypame.sprite.spritecollide is used to detect the collision between a pygame.sprite.Sprite and the sprites in a pygame.sprite.Group. However, basket and ball are both pygame.sprite.Sprite objects. You have to use pygame.sprite.collide_rect:
if pg.sprite.spritecollide(basket, ball, dokill=False):
if pygame.sprite.collide_rect(basket, ball):
I am currently creating a platform game using python pygame but have gotten stuck with the collisions. I have gotten the bottom and top collisions to work with my character sprite but as of now my character won't stop or bounce off the sides of a platform. when doing collisions, I am using the method sprite.spritecollide() which I would like to do in the same way if anyone can help. I have done the collision check correctly but my code for handling the collision I cannot seem to get it done correctly. My code where I do the collision detection is as follows in the main.py in the game update function:
import pygame
import random
from settings import *
from sprites import *
from camera import *
from os import path
class Game:
def __init__(self):
pygame.init() # initialises pygame
pygame.mixer.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) # sets the width and height of the pygame window
pygame.display.set_caption(TITLE)
self.clock = pygame.time.Clock()
self.running = True
self.font_name = pygame.font.match_font(FONT_NAME)
self.load_data()
def load_data(self):
pass
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.camera = Camera(WIDTH, HEIGHT) # creates the camera with WIDTH and HEIGHT of the screen
self.run()
def run(self): # Game Loop - runs the game
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self): # Game loop - update
self.all_sprites.update()
# collision with top of platform
if self.player.vel.y > 0:
hits = pygame.sprite.spritecollide(self.player, self.platforms, False) # returns a list of platform sprites that hit the player
if hits:
self.player.pos.y = hits[0].rect.top
self.player.vel.y = 0
# collision with the bottom of a platform
if self.player.vel.y < 0:
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.top = hits[0].rect.bottom
self.player.vel.y = -self.player.vel.y
# collision with the right side of a platform (moving left), here is the code for the right side of the platform
if self.player.acc.x < 0:
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.left = hits[0].rect.right
self.player.acc.x = 0
# screen moves with player
self.camera.update(self.player) # is the camera that tracks players movement
def events(self): # Game loop - events
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): # Game loop - draw
self.screen.fill(RED)
#self.all_sprites.draw(self.screen)
for sprite in self.all_sprites:
self.screen.blit(sprite.image, self.camera.apply(sprite)) # loops through the all_sprites group and blit's each sprite onto the screen
pygame.display.flip()
def start_screen(self):
pass
def game_over_screen(self):
pass
def wait_for_key(self):
pass
def draw_text(self,text, size, colour, x, y):
pass
g = Game()
g.start_screen()
while g.running:
g.new()
g.game_over_screen()
pygame.quit()
I have so far only tried doing collisions for the right side of the platforms, once I've done one side I can replicate for the other side.
P.S. if you need more of my code I will add it to the question if asked to.
EDIT
sprites.py
# will hold the sprite classes
import pygame
from settings import *
import random
vec = pygame.math.Vector2
class Player(pygame.sprite.Sprite):
def __init__(self, game):
pygame.sprite.Sprite.__init__(self)
self.game = game
self.image = pygame.Surface((30, 40))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
def jump(self):
# jump only if on a platform
self.rect.x += 1
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
self.rect.x -= 1
if hits:
self.vel.y = -20
def update(self):
self.acc = vec(0, PLAYER_GRAV)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.acc.x = -PLAYER_ACC
if keys[pygame.K_RIGHT]:
self.acc.x = PLAYER_ACC
# apply friction
self.acc.x += self.vel.x * PLAYER_FRICTION
# equations of motion
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
# stop from running of the left side of the screen
if self.pos.x < 0:
self.pos.x = 0
self.rect.midbottom = self.pos
class Platform(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((width, height))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
camera.py
import pygame
from settings import *
# A camera that keeps track of an offset that will be, how far we want to draw the screen which will include all objects on the screen. We are just shifting the drawing of our screen according to the offset. Camera needs to do two things, apply the offset and then update the movement of where the player is on the screen.
class Camera:
def __init__(self, width, height): # we will need to tell the camera how wide and high we want it to be
self.camera = pygame.Rect(0, 0, width, height) # is the rectangle we set to keep track of the screen/be the camera
self.width = width
self.height = height
def apply(self, entity): # method to apply the offset to the screen, by shifting the screen according to the movement of the entity within the camera screen
return entity.rect.move(self.camera.topleft)
def update(self, target): # method to update where the player/target has moved to, updates are done according to last known position of the target
# as the target moves the camera moves in the opposite direction of the target and stays within the center of the screen
x = -target.rect.x + int(WIDTH/2) # left to right
y = -target.rect.y + int(HEIGHT/2) # up and down
# limit scrolling to map size, keeps the 'camera' from going over the edges
x = min(0, x) # left
y = min(0, y) # top
y = max(-(self.height - HEIGHT), y) # bottom
self.camera = pygame.Rect(x, y, self.width, self.height) # adjusts the camera's rectangle with the new x and y
settings.py
# Game options/settings
TITLE = 'Platformer'
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'arial'
HS_FILE = 'highscore.txt'
SPRITESHEET = 'spritesheet_jumper.png'
# Game colours
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
# Starting Platforms:
PLATFORM_LIST = [(0, HEIGHT - 50, WIDTH, 50), (WIDTH / 2, HEIGHT * 1 / 2, 200, 30), (WIDTH + 150, HEIGHT - 50, WIDTH, 50), (WIDTH / 2, HEIGHT * 4 / 5, 200, 30)]
# player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
I can't test your code but usually problem is that spritecollide doesn't inform if you collide on x or y or both.
When you move x and y at once and check collision then you don't know if you collided on x or y or both. If you collided only on y and you will check vel.x and move player then you get wrong result. The same if you collided only on x and you will check vel.y and move player then you get also wrong result.
You have to do it separatelly:
first move only x, check collisions and check only vel.x,
next move only y, check collisions again and check only vel.y,
Something like this:
def update(self):
#self.all_sprites.update()
# collision with top and bottom of platform
# update only y
self.player.pos.y += self.player.vel.y + 0.5 * self.player.acc.y
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
if self.player.vel.y > 0:
self.player.pos.y = hits[0].rect.top
self.player.vel.y = 0
elif self.player.vel.y < 0:
self.player.top = hits[0].rect.bottom
self.player.vel.y = -self.player.vel.y
# collision with left and right of platform
# update only x
self.player.pos.x += self.player.vel.x + 0.5 * self.player.acc.x
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
if self.player.acc.x < 0:
self.player.left = hits[0].rect.right
self.player.acc.x = 0
elif self.player.acc.x > 0:
self.player.right = hits[0].rect.left
self.player.acc.x = 0
You should see working example in platform examples on page Program Arcade Games With Python And Pygame
EDIT:
Full code
main.py
#import random
#from os import path
import pygame
from settings import *
from sprites import *
from camera import *
class Game:
def __init__(self):
# initialises pygame
pygame.init()
#pygame.mixer.init() # `pygame.init()` should aready runs `pygame.mixer.init()`
# sets the width and height of the pygame window
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
self.font_name = pygame.font.match_font(FONT_NAME)
self.load_data()
# main loop elements
self.clock = pygame.time.Clock()
self.running = True
def load_data(self):
pass
def new(self):
"""Run game"""
self.reset()
self.run()
def reset(self):
"""Reset data"""
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)
# creates the camera with WIDTH and HEIGHT of the screen
self.camera = Camera(WIDTH, HEIGHT)
def run(self):
"""Game Loop - runs the game"""
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
"""Game loop - update"""
self.all_sprites.update()
# screen moves with player
self.camera.update(self.player) # is the camera that tracks players movement
def events(self):
"""Game loop - events"""
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:
# reset game but not exit
if event.key == pygame.K_ESCAPE:
if self.playing:
self.playing = False
# send event(s) to sprite(s)
self.player.events(event)
def draw(self):
"""Game loop - draw"""
self.screen.fill(RED)
# loops through the all_sprites group and blit's each sprite onto the screen
for sprite in self.all_sprites:
sprite.draw(self.screen, self.camera)
pygame.display.flip()
def start_screen(self):
pass
def game_over_screen(self):
pass
def wait_for_key(self):
pass
def draw_text(self,text, size, colour, x, y):
pass
# --- main ---
g = Game()
g.start_screen()
while g.running:
g.new()
g.game_over_screen()
#g.exit_screen()
pygame.quit()
sprites.py
# will hold the sprite classes
import random
import pygame
from settings import *
vec = pygame.math.Vector2
class BaseSprite(pygame.sprite.Sprite):
"""Base class with functions for all sprites"""
def draw(self, screen, camera):
screen.blit(self.image, camera.apply(self))
class Player(BaseSprite):
def __init__(self, game):
#pygame.sprite.Sprite.__init__(self)
super().__init__()
self.game = game
self.image = pygame.Surface((30, 40))
self.image.fill(BLUE)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
self.rect = self.image.get_rect()
self.rect.center = self.pos
self.on_ground = True
def jump(self):
if self.on_ground:
self.vel.y = -20
self.on_ground = False
def events(self, event):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.jump()
def update(self):
self.acc = vec(0, PLAYER_GRAV)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.acc.x = -PLAYER_ACC
if keys[pygame.K_RIGHT]:
self.acc.x = PLAYER_ACC
# apply friction
self.acc.x += self.vel.x * PLAYER_FRICTION
# equations of motion
self.vel += self.acc
# --- horizontal collision ---
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.centerx = self.pos.x
# stop from running of the left side of the screen
if self.rect.left < 0:
self.rect.left = 0
self.pos.x = self.rect.centerx
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
if hits:
if self.vel.x > 0:
self.rect.right = hits[0].rect.left
self.pos.x = self.rect.centerx
self.vel.x = 0
elif self.vel.x < 0:
self.rect.left = hits[0].rect.right
self.pos.x = self.rect.centerx
self.vel.x = 0
# --- vertical collision ---
self.pos.y += self.vel.y + 0.5 * self.acc.y
self.rect.centery = self.pos.y
# game over when left screen
if self.rect.top > HEIGHT:
self.game.playing = False
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
if hits:
if self.vel.y > 0:
self.rect.bottom = hits[0].rect.top
self.pos.y = self.rect.centery
self.vel.y = 0
self.on_ground = True
elif self.vel.y < 0:
self.rect.top = hits[0].rect.bottom
self.pos.y = self.rect.centery
self.vel.y = 0
class Platform(BaseSprite):
def __init__(self, x, y, width, height, color):
#pygame.sprite.Sprite.__init__(self)
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill(color)
#self.rect = self.image.get_rect()
#self.rect.x = x
#self.rect.y = y
# shorter
self.rect = self.image.get_rect(x=x, y=y)
camera.py
without changes
settings.py
I added few platforms
# Game options/settings
TITLE = 'Platformer'
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'arial'
HS_FILE = 'highscore.txt'
SPRITESHEET = 'spritesheet_jumper.png'
# Game colours
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
# Starting Platforms:
PLATFORM_LIST = [
# grounds
(0, HEIGHT - 50, WIDTH, 50, GREEN),
(WIDTH + 150, HEIGHT - 50, WIDTH, 50, GREEN),
# platforms
(WIDTH / 2, HEIGHT * 1 / 2, 200, 30, YELLOW),
(WIDTH / 2, HEIGHT * 4 / 5, 200, 30, YELLOW),
# walls
(WIDTH - 30, HEIGHT - 250, 30, 200, WHITE),
(WIDTH + 150, HEIGHT - 250, 30, 200, WHITE),
]
# player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
I was thinging to moves some code to class Screen and then I would use this class to create GameScreen, StartScreen, GameOverScreen, etc.
sprites.py:
import pygame as pg
from settings import *
vec = pg.math.Vector2
# for movement
class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.game = game
self.image = pg.Surface((30, 40))
self.image.fill((255, 255, 153))
self.rect = self.image.get_rect()
self.rect.center = WIDTH / 2, HEIGHT / 2
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
def jump(self):
self.rect.x += 1
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
self.rect.x -= 1
if hits:
self.vel.y = -20
def update(self):
self.acc = vec(0, PLAYER_GRAV)
# pouze zryhlení
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
self.acc.x = - PLAYER_ACC
if keys[pg.K_RIGHT]:
self.acc.x = PLAYER_ACC
# apply friction
self.acc.x += self.vel.x * PLAYER_FRICTION
# pohyb tělesa
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
if self.pos.x > WIDTH:
self.pos.x = 0
if self.pos.x < 0:
self.pos.x = WIDTH
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()
self.rect.x = x
self.rect.y = y
main.py:
import random
import pygame as pg
from settings import *
from sprites import *
class Game:
def __init__(self):
# initialize the game window
self.running = True
pg.init()
pg.mixer.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption('My game')
self.clock = pg.time.Clock()
def new(self):
# to start a new game, reset the pieces
self.all_sprites = pg.sprite.Group()
self.platforms = pg.sprite.Group()
self.player = Player(self)
self.all_sprites.add(self.player)
p1 = Platform(0, HEIGHT - 40, WIDTH, 40)
p2 = Platform(WIDTH / 2 - 50, HEIGHT * 3 / 4, 100, 20)
self.platforms.add(p2)
self.platforms.add(p1)
self.all_sprites.add(p1)
self.all_sprites.add(p2)
self.run()
def run(self):
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
self.all_sprites.update()
hits = pg.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.pos.y = hits[0].rect.top
self.player.rect.midbottom = self.player.pos
self.player.vel.y = 0
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
if self.playing:
self.playing = False
self.running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump()
def draw(self):
self.screen.fill(BLACK)
self.all_sprites.draw(self.screen)
pg.display.flip()
def show_start_screen(self):
pass
def show_go_screen(self):
pass
game = Game()
game.show_start_screen()
while game.running:
game.new()
game.show_go_screen()
pg.quit()
settings.py:
# game settings
WIDTH = 480
HEIGHT = 600
FPS = 60
# player settings
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = ((255, 255, 153))
I have a few questions:
Question n. 1:
Why doesn't the jump method work? And why is it using velocity -20 to jump?
Question n. 2:
I'm not quite sure if I understand the vector coordinates, can someone please try to explain it to me?
I think that this is exactly why I have problems understanding the code.
In pygame, the screen coordinates start at the top left (0,0). Positive Y goes down. Jump is -20 because the player is going up (toward zero). It looks like the player bounces up when it hits an object.
Concerning the jump issue, the game does not start for (Platform not defined) so I can't help there.
Hey I have a problem with my game code. Is there a way to check if, for example, the right side of my player sprite (hero) is colliding with the group obstacle_sprite?
I can only figure out how to check for collision in general but I want only a true output if the right side of the player is colliding wit the obstacle.
In my code I am using a invisible sprite as hitbox and draw the player inside this invisible sprite to check collisions but make it look like the obstacle denies moving of the player.
import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PINK = (230, 77, 149)
FPS = 60
window_width = 900
window_height = 600
window_color = WHITE
window = pygame.display.set_mode((window_width, window_height))
window.fill(window_color)
pygame.display.set_caption("Bub - the game")
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, velocity, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((width + 2*velocity, height + 2*velocity))
self.rect = self.image.get_rect()
self.rect.topleft = (x - velocity, y - velocity)
self.velocity = velocity
self.is_jump = False
self.jump_count = 15
self.fall_count = 1
self.ground = self.rect.bottom
self.body = pygame.Rect(x, y, width, height)
pygame.draw.rect(window, color, self.body)
def move(self):
key = pygame.key.get_pressed()
if key[pygame.K_a] and self.rect.left >= 0:
self.rect.x -= self.velocity
if key[pygame.K_d] and self.rect.right <= window_width:
self.rect.x += self.velocity
if not self.is_jump:
if key[pygame.K_SPACE] and self.rect.bottom == self.ground:
self.is_jump = True
else:
if self.jump_count >= 0:
self.rect.y -= round((self.jump_count ** 2) * 0.1)
self.jump_count -= 1
else:
self.jump_count = 15
self.is_jump = False
def gravity(self):
if not self.is_jump and self.rect.bottom != self.ground:
self.rect.y += round((self.fall_count ** 2) * 0.1)
if self.fall_count <= 15:
self.fall_count += 1
else:
self.fall_count = 1
def update(self):
self.move()
self.gravity()
pygame.draw.rect(window, BLACK, self.body)
self.body.topleft = (self.rect.x + self.velocity, self.rect.y + self.velocity)
class Obstacle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
player_sprite = pygame.sprite.Group()
obstacle_sprite = pygame.sprite.Group()
hero = Player(0, 570, 30, 30, 5, BLACK)
player_sprite.add(hero)
obstacle_1 = Obstacle(100, 585, 120, 30, PINK)
obstacle_sprite.add(obstacle_1)
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(window_color)
player_sprite.update()
player_sprite.draw(window)
obstacle_sprite.update()
obstacle_sprite.draw(window)
pygame.display.update()
pygame.quit()
It's easier if you keep track of your player's velocity; this will make handling jumping also less complex.
A common technique AFAIK is to do two collision detection runs: one for the x-axis and one for the y-axis.
Here's how I changed your code. As you can see, checking for the side of a collision is as easy as checking xvel < 0 or xvel > 0:
import pygame
pygame.init()
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PINK = (230, 77, 149)
FPS = 60
window_width = 900
window_height = 600
window_color = WHITE
window = pygame.display.set_mode((window_width, window_height))
window.fill(window_color)
pygame.display.set_caption("Bub - the game")
clock = pygame.time.Clock()
class Toast(pygame.sprite.Sprite):
def __init__(self, *grps):
super().__init__(*grps)
self.image = pygame.Surface((900, 200))
self.image.fill(WHITE)
self.rect = self.image.get_rect(topleft=(10, 10))
self.font = pygame.font.SysFont(None, 32)
def shout(self, message):
self.text = self.font.render(message, True, BLACK)
self.image.fill(WHITE)
self.image.blit(self.text, (0, 0))
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, velocity, color, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width + 2*velocity, height + 2*velocity))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.topleft = (x - velocity, y - velocity)
self.velocity = velocity
self.on_ground = True
self.xvel = 0
self.yvel = 0
def collide(self, xvel, yvel, obstacle_sprites):
global toast
for p in pygame.sprite.spritecollide(self, obstacle_sprites, False):
if xvel > 0:
self.rect.right = p.rect.left
toast.shout('collide RIGHT')
if xvel < 0:
self.rect.left = p.rect.right
toast.shout('collide LEFT')
if yvel > 0:
self.rect.bottom = p.rect.top
self.on_ground = True
self.yvel = 0
toast.shout('collide BOTTOM')
if yvel < 0:
self.rect.top = p.rect.bottom
self.yvel = 0
toast.shout('collide TOP')
def update(self, obstacle_sprites):
global toast
key = pygame.key.get_pressed()
self.xvel = 0
if key[pygame.K_a]:
self.xvel =- self.velocity
if key[pygame.K_d]:
self.xvel = self.velocity
if self.on_ground:
if key[pygame.K_SPACE]:
toast.shout('JUMPING')
self.on_ground = False
self.yvel = -20
else:
self.yvel += 1
self.rect.left += self.xvel
self.collide(self.xvel, 0, obstacle_sprites)
self.rect.top += self.yvel
self.on_ground = False
self.collide(0, self.yvel, obstacle_sprites)
self.rect.clamp_ip(pygame.display.get_surface().get_rect())
if self.rect.bottom == pygame.display.get_surface().get_rect().bottom:
self.on_ground = True
class Obstacle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect(center=(x, y))
all_sprites = pygame.sprite.Group()
player_sprite = pygame.sprite.Group()
obstacle_sprites = pygame.sprite.Group()
toast = Toast(all_sprites)
hero = Player(0, 570, 30, 30, 5, GREEN, all_sprites, player_sprite)
for x in [
(100, 585, 120, 30),
(300, 535, 120, 30),
(500, 485, 120, 30)
]:
Obstacle(*x, PINK, all_sprites, obstacle_sprites)
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(window_color)
all_sprites.update(obstacle_sprites)
all_sprites.draw(window)
pygame.display.update()
pygame.quit()
After about of hour of disaster and modification on my code, I can't seem to get my enemy sprite to move back and forth on a platform. So any help would be greatly appreciated :D
Test File, Crafted Super Fast....
import pygame, sys, os
GAME_TITLE = "GAME"
WINDOW_WIDTH, WINDOW_HEIGHT = 1280, 720
BLACK = (0, 0, 0)
BLUE = (0, 255, 0)
RED = (255, 0, 0)
FPS = 60
ENEMY_ACC = 0.3
ENEMY_FRICTION = -0.12
ENEMY_GRAVITY = 0.5
vec = pygame.math.Vector2
class Platform(pygame.sprite.Sprite):
def __init__(self, game, x, y, w, h):
pygame.sprite.Sprite.__init__(self)
self.game = game
self.group = self.game.platform_list
self.image = pygame.Surface((1280, 100))
self.rect = self.image.get_rect()
pygame.draw.rect(self.image, RED, (x, y, w, h))
class Enemy(pygame.sprite.Sprite):
def __init__(self, game, pos):
pygame.sprite.Sprite.__init__(self)
self.game = game
self.group = self.game.enemy_list
self.image = pygame.Surface((100, 100))
pygame.draw.rect(self.image, BLUE, (200, 200, 100, 100))
self.rect = self.image.get_rect()
self.pos = vec(pos)
self.rect.center = self.pos
self.vel = vec(0, 0)
self.acc = vec(0, 0)
self.direction = "R"
self.engage = False
def update(self):
hits = pygame.sprite.spritecollide(self, self.game.platform_list, False)
if hits:
plat_right = hits[0].rect.right
plat_left = hits[0].rect.left
self.pos.y = hits[0].rect.top
self.vel.y = 0
if self.direction == "R" and not self.engage:
if self.rect.right >= plat_right:
self.direction = "L"
self.acc.x = -ENEMY_ACC
if self.direction == "L" and not self.engage:
if self.rect.left <= plat_left:
self.direction = "R"
self.acc.x = -ENEMY_ACC
self.acc.x += self.vel.x * ENEMY_FRICTION
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
self.acc = vec(0, ENEMY_GRAVITY)
class Game:
def __init__(self):
pygame.init()
pygame.mixer.init()
pygame.font.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.display.set_caption(GAME_TITLE)
self.window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
self.clock = pygame.time.Clock()
self.platform_list = pygame.sprite.Group()
self.enemy_list = pygame.sprite.Group()
def run(self):
enemy = Enemy(self, (200, 500))
self.enemy_list.add(enemy)
platform = Platform(self, 0, 620, 1280, 100)
self.platform_list.add(platform)
while True:
self.events()
self.update()
self.draw()
def events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
def update(self):
self.enemy_list.update()
def draw(self):
self.window.fill(BLACK)
self.platform_list.draw(self.window)
self.enemy_list.draw(self.window)
pygame.display.flip()
def main():
g = Game()
g.run()
if __name__ == "__main__":
main()
Here's an example without the acc attribute. Just accelerate the self.vel by adding the ENEMY_GRAVITY, update the self.pos and self.rect, then if it collides with the platform see if the right or left edge is reached and then just invert the velocity.
I've set the ENEMY_GRAVITY to 3 and call clock.tick(30) to limit the frame rate. The direction attribute doesn't seem to be necessary anymore.
class Enemy(pygame.sprite.Sprite):
def __init__(self, game, pos):
pygame.sprite.Sprite.__init__(self)
self.game = game
self.group = self.game.enemy_list
self.image = pygame.Surface((60, 60))
self.image.fill((30, 90, 200))
self.rect = self.image.get_rect(midbottom=pos)
self.pos = vec(pos)
self.vel = vec(3, 0)
self.engage = False
def update(self):
self.vel.y += ENEMY_GRAVITY
self.pos += self.vel
self.rect.midbottom = self.pos
hits = pygame.sprite.spritecollide(self, self.game.platform_list, False)
for plat in hits:
self.pos.y = plat.rect.top
self.rect.bottom = plat.rect.top
self.vel.y = 0
if self.vel.x > 0 and not self.engage:
if self.rect.right >= plat.rect.right:
self.vel = vec(-3, 0) # Reverse the horizontal velocity.
elif self.vel.x < 0 and not self.engage:
if self.rect.left <= plat.rect.left:
self.vel = vec(3, 0) # Reverse the horizontal velocity.