I'm building a game with pygame and I have a problem with this game, I don't know how to make the background and the platform scroll slowly to the right as my player sprite moves to the right.
I want the scrolling to happen in def shift_world().
Maybe someone can teach me how to add an image to the background as well. It would be great if you can utilize the libraries I am using. I have three files: The first for the game, the second for sprites, and the third for the settings.
main.py:
# Sarada's Blaze! - side scrolling platform shooting game
import pygame as pg
import random
import os
from settings import *
from sprites import *
class Game:
def __init__(self):
# initialize game window, etc
pg.init()
pg.mixer.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption(TITLE)
self.clock = pg.time.Clock()
self.running = True
self.font_name = pg.font.match_font(FONT_NAME)
def new(self):
# start a new game
self.score = 0
self.all_sprites = pg.sprite.Group()
self.platforms = pg.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):
# Game loop
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()
self.platforms.update()
# check if player hits a platform - only if falling
if self.player.vel.y 0:
hits = pg.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.pos.y = hits[0].rect.top
self.player.vel.y = 0
def events(self):
# Game Loop - events
for event in pg.event.get():
# check for closing window
if event.type == pg.QUIT:
if self.playing:
self.playing = False
self.running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_UP:
self.player.jump()
def draw(self):
# Game Loop - draw
self.screen.fill(bgcolor) # I want to change the background color to an image
self.all_sprites.draw(self.screen)
self.draw_text(str(self.score), 22, white, WIDTH / 2, 15)
# after drawing everything, flip the display
pg.display.flip()
# How far the world has been scrolled right
# This is where i want the side scroller stuff to happen
def shift_world(self):
# when the user moves left/right, i want to scroll everything
self.world_shift += shift_x
# i want all my platforms and the background to scroll when
# my player sprite moves closer to the right
for plat in PLATFORM_LIST:
self.platform.rect.x += shift_x
if self.pos.x = 500:
diff = self.pos.x - 500
self.pos.x = 500
self.shift_world(- diff)
if self.pos.x <= 120:
diff = 120 - self.pos.x
self.pos.x -= 120
self.shift_world(diff)
def show_start_screen(self):
# game splash/start screen
self.screen.fill(greenblue)
self.draw_text(TITLE, 48, red, WIDTH / 2, HEIGHT / 4)
self.draw_text("left arrow to move left, right arrow to move right, up arrow to jump", 22, blue, WIDTH / 2, HEIGHT / 2)
self.draw_text("Press any key to begin", 22, green, WIDTH / 2, HEIGHT * 3 /4)
pg.display.flip()
self.wait_for_key()
def show_go_screen(self):
# game over/continue
if not self.running:
return
self.screen.fill(greenblue)
self.draw_text("Game Over!", 48, red, WIDTH / 2, HEIGHT / 4)
self.draw_text("Score: " + str(self.score), 22, blue, WIDTH / 2, HEIGHT / 2)
self.draw_text("Press any key to continue", 22, green, WIDTH / 2, HEIGHT * 3 /4)
pg.display.flip()
self.wait_for_key()
def wait_for_key(self):
waiting = True
while waiting:
self.clock.tick(FPS)
for event in pg.event.get():
if event.type == pg.QUIT:
waiting = False
self.running = False
if event.type == pg.KEYUP:
waiting = False
def draw_text(self, text, size, color, x, y):
font = pg.font.Font(self.font_name, size)
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)
self.screen.blit(text_surface, text_rect)
g = Game()
g.show_start_screen()
while g.running:
g.new()
g.show_go_screen()
pg.quit()
sprites.py:
# Sprite classes for platform shooting game
import pygame as pg
import random
from settings import *
import os
vec = pg.math.Vector2
game_folder = os.path.dirname(__file__)
img_folder = os.path.join(game_folder, "img")
class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.game = game
self.image = pg.image.load(os.path.join(img_folder, "sarada_shooting.png")).convert()
self.image.set_colorkey(black)
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 standing on a platform
self.rect.x += 1
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
self.rect.x -= 1
if hits:
self.vel.y = -PLAYER_JUMP
def update(self):
self.acc = vec(0, PLAYER_GRAV)
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
# equations of motion
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
# 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
# i want the platform graphic changed
class Platform(pg.sprite.Sprite):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((WIDTH, h))
self.image.fill(greenblue)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
settings.py:
# game options/settings
TITLE = "Sarada's Blaze"
WIDTH = 800
HEIGHT = 600
FPS = 60
FONT_NAME = 'times new roman'
# Player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.20
PLAYER_GRAV = 0.8
PLAYER_JUMP = 20
# starting platforms
PLATFORM_LIST = [(0, HEIGHT - 40, WIDTH, 40)]
# define colors
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
purple = (255, 0, 255) # ENEMY COLOR
greenblue = (0, 155, 155)
bgcolor = red
You need to iterate over the self.platforms list and subtract the player's vel.x from the x-position of every sprite in order to move them. Also, give your platform sprites a vector attribute for the position self.pos = vec(x, y) because the coordinates of pygame.Rects are truncated and turned into ints, so the movement will be inaccurate if your velocity vectors consist of floats.
def update(self):
# ...
self.shift_world()
def shift_world(self):
# Scroll when the player sprite moves closer to the right.
if self.player.pos.x >= 500:
self.player.pos.x = 500 # Stop at 500.
self.shift_platforms()
# Scroll when the player sprite moves closer to the left.
if self.player.pos.x <= 120:
self.player.pos.x = 120 # Stop at 120.
self.shift_platforms()
def shift_platforms(self):
for plat in self.platforms: # Iterate over the platform sprites.
plat.pos.x -= self.player.vel.x # Update the platform's pos vector.
plat.rect.x = plat.pos.x # Update the rect.
self.world_shift -= self.player.vel.x # For the background.
As for the background surface, you can use the self.world_shift attribute and subtract the self.player.vel.x in the shift_platforms method as well. Then blit it twice in the draw method and use the modulo operator to blit it at the correct x-positions.
I assume the background has the size of the screen and should be repeated.
# Load it once in the global scope.
BACKGROUND = pg.image.load(os.path.join(img_folder, "background.png")).convert()
def draw(self):
x = self.world_shift
self.screen.blit(BACKGROUND, (x % WIDTH, 0))
self.screen.blit(BACKGROUND, (x % WIDTH - WIDTH - 1, 0))
Related
EDIT: Reposted question so it is clearer.
My issue is that the player rectangles are not on the player because of the camera offset. As a result, the game looks like this (see image 1). It's not working because of the camera offset. I successfully repositioned the yellow rect over the player but I am having trouble repositioning the red rectangle.
I have added comments to my Camera class explaining what I tried and noticed. When I remove the offset to 0, the rects are positioned how I want (but obviously the camera doesn't work anymore). See image 2 for what I am trying to achieve.
This is image 1: https://i.stack.imgur.com/JnFPH.png
This is image 2: https://i.stack.imgur.com/NNw1e.png
Here is the link to the minimum code needed to reproduce my problem (I tried to make it as short as possible):
from sys import exit
import math
pygame.init()
# window and text
WIDTH = 1280
HEIGHT = 720
FPS = 60
screen = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption('Zombie Game')
clock = pygame.time.Clock()
# loads imgs
background = pygame.image.load("background/gamemap4.png").convert()
class Player(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.image.load("handgun/move/survivor-move_handgun_0.png").convert_alpha()
self.image = pygame.transform.rotozoom(self.image, 0, 0.35)
self.base_player_image = self.image
self.pos = pos
self.base_player_rect = self.base_player_image.get_rect(center = pos)
self.rect = self.base_player_rect.copy()
self.player_speed = 10
def player_turning(self):
self.mouse_coords = pygame.mouse.get_pos()
self.x_change_mouse_player = (self.mouse_coords[0] - (WIDTH // 2))
self.y_change_mouse_player = (self.mouse_coords[1] - (HEIGHT // 2))
self.angle = int(math.degrees(math.atan2(self.y_change_mouse_player, self.x_change_mouse_player)))
self.angle = (self.angle + 360) % 360
self.image = pygame.transform.rotate(self.base_player_image, -self.angle)
self.rect = self.image.get_rect(center=self.base_player_rect.center)
def player_input(self):
self.velocity_x = 0
self.velocity_y = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.velocity_y = -self.player_speed
if keys[pygame.K_s]:
self.velocity_y = self.player_speed
if keys[pygame.K_d]:
self.velocity_x = self.player_speed
if keys[pygame.K_a]:
self.velocity_x = -self.player_speed
if self.velocity_x != 0 and self.velocity_y != 0: # moving diagonally
self.velocity_x /= math.sqrt(2)
self.velocity_y /= math.sqrt(2)
if keys[pygame.K_SPACE]:
self.shoot = True
else:
self.shoot = False
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
self.shoot = False
def move(self):
self.base_player_rect.centerx += self.velocity_x
self.base_player_rect.centery += self.velocity_y
def update(self):
pygame.draw.rect(screen, "red", self.base_player_rect, width=2)
pygame.draw.rect(screen, "yellow", self.rect, width=2)
self.player_turning()
self.player_input()
self.move()
class Camera(pygame.sprite.Group):
def __init__(self):
super().__init__()
self.offset = pygame.math.Vector2()
self.floor_rect = background.get_rect(topleft = (0,0))
def custom_draw(self):
# self.offset.x = player.rect.centerx - (WIDTH // 2) # if i comment out these 2 lines, it works how I want.
# self.offset.y = player.rect.centery - (HEIGHT // 2)
#draw the floor
floor_offset_pos = self.floor_rect.topleft - self.offset
screen.blit(background, floor_offset_pos)
for sprite in all_sprites_group:
offset_pos = sprite.rect.topleft - self.offset
# sprite.rect.x -= self.offset.x # This sets the YELLOW rectangle over the player
# sprite.rect.y -= self.offset.y # This sets the YELLOW rectangle over the player
# player.base_player_rect.x -= self.offset.x # Attempting to draw red rectangle over the player - breaks the game
# player.base_player_rect.y -= self.offset.y # # Attempting to draw red rectangle over the player - breaks the game
screen.blit(sprite.image, offset_pos)
# Groups
all_sprites_group = pygame.sprite.Group()
obstacles_group = pygame.sprite.Group()
player = Player((900,900))
all_sprites_group.add(player)
camera = Camera()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
camera.custom_draw()
all_sprites_group.update()
pygame.display.update()
clock.tick(FPS)```
Since the player is always in the center of the screen, the rectangles are also always in the center of the screen:
pygame.draw.rect(screen, "red", self.base_player_rect, width=2)
pygame.draw.rect(screen, "yellow", self.rect, width=2)
base_rect = self.base_player_rect.copy()
base_rect.center = (WIDTH // 2), (HEIGHT // 2)
pygame.draw.rect(screen, "red", base_rect, width=2)
rect = self.rect.copy()
rect.center = (WIDTH // 2), (HEIGHT // 2)
pygame.draw.rect(screen, "yellow", rect, width=2)
You can also copy the rectangles, shift them by the offset and draw them in the Camera class:
class Camera(pygame.sprite.Group):
def __init__(self):
super().__init__()
self.offset = pygame.math.Vector2()
self.floor_rect = background.get_rect(topleft = (0,0))
def custom_draw(self):
self.offset.x = player.rect.centerx - (WIDTH // 2)
self.offset.y = player.rect.centery - (HEIGHT // 2)
#draw the floor
floor_offset_pos = self.floor_rect.topleft - self.offset
screen.blit(background, floor_offset_pos)
# draw the rectangles
base_rect = player.base_player_rect.copy().move(-self.offset.x, -self.offset.y)
pygame.draw.rect(screen, "red", base_rect, width=2)
rect = player.rect.copy().move(-self.offset.x, -self.offset.y)
pygame.draw.rect(screen, "yellow", rect, width=2)
for sprite in all_sprites_group:
offset_pos = sprite.rect.topleft - self.offset
screen.blit(sprite.image, offset_pos)
Also, you need to synchronize the center of the rectangles after moving the player:
class Player(pygame.sprite.Sprite):
# [...]
def move(self):
self.base_player_rect.centerx += self.velocity_x
self.base_player_rect.centery += self.velocity_y
self.rect.center = self.base_player_rect.center # <---
I am creating a platform game using pygame, in my game I have simulated gravity and platforms that can be jumped on but I'm having trouble getting the collision detection to work correctly. By collision detection I mean when my character sprite jumps I want him to bounce off of the bottom and sides of the platform that will be above him. Now my character sprite jumps through the platform and lands on top of it.
My code is as follows:
My main class:
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 a 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
# 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()
my sprite classes:
# 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 class
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 module:
# 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)]
# player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
P.S. added all my code for anyone that may need to see it.
Your question says "when you have simulated gravity" but i don't understand how that would affect the bouncing. The answer below should work with your applied gravity so i don't think gravity will be an issue. So first, to bounce, we will need to know which side of the player is colliding with a particular side of the platform. For this the following functions can be used.
#Checks if right side of the player is colliding with left side of platform
def rightCheck(rect1, rect2):
if rect1.x + rect1.width > rect2.x:
if (rect1.y > rect2.y and rect1.y < rect2.y + rect2.height) or (rect1.y + rect1.height < rect2.y + rect2.height and rect1.y + rect1.height> rect2.y):
if rect1.x < rect2.x:
return True
return False
#Checks if bottom side of the player is colliding with top side of platform
def botCheck(rect1, rect2):
if rect1.y + rect1.height > rect2.y:
if (rect1.x > rect2.x and rect1.x < rect2.x + rect2.width) or (rect1.x + rect1.width > rect2.x and rect1.x + rect1.width < rect2.x + rect2.width):
if rect1.y < rect2.y:
return True
return False
# NOTICE they take pygame.Rect as arguments
For your game you are probably going to need one for left of the player and right side of the platform well, but i don't have it since i copied these from my game. I am sure you can write one yourself :). So, moving on. The code below shows how right side of the player colliding with left side of the platform can be implemented. So you can do this with all the sides. Also, bounce function is probably the only one you are interested in, others i has to put there to make it work.
import pygame
win = pygame.display
D = win.set_mode((1200, 600))
def rightCheck(rect1, rect2):
if rect1.x + rect1.width > rect2.x:
if (rect1.y > rect2.y and rect1.y < rect2.y + rect2.height) or (rect1.y + rect1.height < rect2.y + rect2.height and rect1.y + rect1.height> rect2.y):
if rect1.x < rect2.x:
return True
return False
class Player:
def __init__(self, ):
self.pos = [10, 10]
self.surf = pygame.Surface((50, 50)).convert()
self.energy = 10 #how much the player bounces to the left by
self.trigger = False #Triggers the left push if it is true
self.rightCheck = rightCheck # this is the function to check collision
self.rvel = 0.5 # This is the movement speed
def move(self):
key = pygame.key.get_pressed()
if key[pygame.K_RIGHT]:
self.pos[0] += self.rvel
if key[pygame.K_LEFT]:
self.pos[0] -= 0.5
if key[pygame.K_UP]:
self.pos[1] -= 0.5
if key[pygame.K_DOWN]:
self.pos[1] += 0.5
def draw(self):
D.blit(self.surf, (self.pos[0], self.pos[1]))
def bounce(self, platRect):
selfRect = pygame.Rect(self.pos[0], self.pos[1], 50, 50)
if self.rightCheck(selfRect, platRect):
self.trigger = True
self.rvel = 0
else:
self.rvel = 0.5
if self.trigger:
self.pos[0] -= self.energy*0.1
self.rvel = 0.1
self.energy -= 0.05
if self.energy <= 0:
self.trigger = False
else:
self.energy = 10
p = Player()
while True:
pygame.event.get()
D.fill((255, 255, 255))
platformRect = pygame.Rect(300, 150, 600, 200)
pygame.draw.rect(D, (0, 0, 0), platformRect)
p.bounce(platformRect)
p.move()
p.draw()
p.bounce(platformRect)
win.flip()
It basically just checks if they are colliding and if they are pushes the player a little to the left.
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.
This question already has answers here:
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
How do I detect collision in pygame?
(5 answers)
Closed 2 years ago.
I have been trying to create a 2D platformer in pygame, and I have encountered a problem with the collision of the player sprite with the platform. I hope to implement full collision for the platforms, so that the player sprite will stop when it hits the platform from any direction. Currently, the sprite stops when it hits the top of the platforms, but if it collides with the bottom or sides of a platform it instantly jumps to the top of the platform. I have tried various things to solve this, but to no avail. Any help would be appreciated.
I have seperated my code into 3 files.
This is my main file:
import pygame
from settings import *
from sprites import *
#Game Class
class Game:
def __init__(self):
pygame.init()
pygame.mixer.init()
self.gameDisplay = pygame.display.set_mode((displayWidth, displayHeight))
self.clock = pygame.time.Clock()
self.gameRunning = True
#Stars the game
def new(self):
self.allSprites = pygame.sprite.Group()
self.platforms = pygame.sprite.Group()
self.player = Player(self)
self.allSprites.add(self.player)
floor = Platform(0, 680, displayWidth, 40)
plat2 = Platform( 500, 400, 100, 40)
self.allSprites.add(floor, plat2)
self.platforms.add(floor, plat2)
self.run()
#Game Loop
def run(self):
self.gameRunning = True
while self.gameRunning == True:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
#Updates the screen
def update(self):
self.allSprites.update()
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.pos.y = hits[0].rect.top
self.player.spd.y = 0
self.player.rect.midbottom = self.player.pos
if self.player.rect.left >= displayHeight - 200:
self.player.pos.x -= abs(self.player.spd.x)
for plat in self.platforms:
plat.rect.x -= abs(self.player.spd.x)
if self.player.rect.right <= displayHeight / 4:
self.player.pos.x += abs(self.player.spd.x)
for plat in self.platforms:
plat.rect.x += abs(self.player.spd.x)
#Events loop
def events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.player.jump()
#Draws things to the screen
def draw(self):
self.gameDisplay.fill(LIGHT_BLUE)
self.allSprites.draw(self.gameDisplay)
pygame.display.update()
def runGame():
game = Game()
game.new()
runGame()
Sprites file:
import pygame
from settings import *
vec = pygame.math.Vector2
#Player Class
class Player(pygame.sprite.Sprite):
def __init__(self, game):
pygame.sprite.Sprite.__init__(self)
self.game = game
self.image = pygame.Surface([40, 40])
self.image.fill(DARK_BLUE)
self.rect = self.image.get_rect()
self.rect.center = (displayWidth / 2, displayHeight / 2)
self.pos = vec(displayWidth / 2, displayHeight / 2)
self.spd = vec(0,0)
self.acc = vec(0,0)
def update(self):
self.acc = vec(0, playerGrav)
keysPressed = pygame.key.get_pressed()
if keysPressed[pygame.K_LEFT]:
self.acc.x = - playerAcc
if keysPressed[pygame.K_RIGHT]:
self.acc.x = playerAcc
self.acc.x += self.spd.x * playerFric
self.spd += self.acc
self.pos += self.spd + 0.5 * self.acc
self.rect.midbottom = self.pos
def jump(self):
self.rect.y +=1
hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
self.rect.y -= 1
if hits:
self.spd.y = -20
class Platform(pygame.sprite.Sprite):
def __init__(self, x, y, w, h):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((w, h))
self.image.fill((GREEN))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
Settings File:
import pygame
pygame.font.init()
#Setup
FPS = 60
displayWidth = 1280
displayHeight = 720
pygame.display.set_caption("2D Platformer")
gameDisplay = pygame.display.set_mode((displayWidth, displayHeight))
clock = pygame.time.Clock()
#Player
playerAcc = 0.5
playerFric = -0.1
playerGrav = 0.5
#Colour Pallette
BLACK = (0, 0, 0 )
WHITE = (255, 255, 255)
RED = (200, 0, 0 )
GREEN = (0, 200, 0 )
BLUE = (0, 0, 200)
LIGHT_BLUE = (0, 191, 255)
DARK_BLUE = (0, 50, 150)
#Score
score = 0
This is the code that is responsible for collision:
hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
if hits:
self.player.pos.y = hits[0].rect.top
self.player.spd.y = 0
self.player.rect.midbottom = self.player.pos
I understand that the reason why the sprite jumps to the top of the platform is due to these lines of code:
if hits:
self.player.pos.y = hits[0].rect.top
However, I don't know how to implement full collision.