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 # <---
Related
I am trying to:
draw a circle onto my window
let it move AND rotate at the same time to simulate a rolling ball.
Appreciate anyone's solutions! Attached below is my code.
import pygame
#create win
W, H = 700, 700
win = pygame.display.set_mode((W, H))
pygame.display.set_caption("Rotating Ball Simul")
#color lib
BG = (20,20,20)
BLUE = (0,0,255)
#draw circle
ballX, ballY = 0, 0
ballW = 20
ballH = ballW
ball = pygame.draw.circle(win, BLUE, (ballX, ballY), ballW, 1)
def redraw_window():
win.fill(BG)
win.blit(ball, (ballX, ballY))
pygame.display.update()
def main():
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
redraw_window()
if __name__ == '__main__':
main()
A circle always looks the same when you rotate it. You must use a sphere image to see the rotation. Read about How do I rotate an image around its center using PyGame? and move the ball image and change the rotation of the image depending on the movement. The angle of rotation depends on the movement and the diameter of the ball:
angle -= (self.x - prev_x) / self.diameter * 180 / math.pi
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = self.rect.center)
Minimal example:
repl.it/#Rabbid76/PyGame-FollowInGrid
import os
import math
import pygame
class MarbelSprite(pygame.sprite.Sprite):
def __init__(self, x, ground, diameter, velocity, filename):
pygame.sprite.Sprite.__init__(self)
try:
self.image = pygame.transform.smoothscale(pygame.image.load(filename).convert_alpha(), (diameter, diameter))
except:
self.image = pygame.Surface((diameter, diameter), pygame.SRCALPHA)
pygame.draw.circle(self.image, (255, 128, 0), (diameter // 2, diameter // 2), diameter // 2)
self.original_image = self.image
self.rect = self.image.get_rect(midbottom = (x, ground))
self.diameter = diameter
self.x = x
self.velocity = velocity
self.move_x = 0
self.follow = None
self.angle = 0
def update(self, time, restriction):
move_x = 0
prev_x = self.x
if self.move_x != 0:
move_x = self.move_x * self.velocity * time
elif self.follow:
dx = self.follow.rect.centerx - self.x
move_x = (-1 if dx < 0 else 1) * min(self.velocity * time, abs(dx))
self.x += move_x
self.x = max(restriction.left + self.diameter // 2, min(restriction.right - self.diameter // 2, self.x))
self.rect.centerx = round(self.x)
self.angle -= (self.x - prev_x) / self.diameter * 180 / math.pi
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = self.rect.center)
pygame.init()
window = pygame.display.set_mode((500, 300))
clock = pygame.time.Clock()
ground_level = 220
object = MarbelSprite(window.get_rect().centerx, ground_level, 100, 0.4, 'BaskteBall.png')
follower = MarbelSprite(window.get_width() // 4, ground_level, 50, 0.2, 'TennisBall.png')
all_sprites = pygame.sprite.Group([object, follower])
run = True
while run:
time = clock.tick(60)
for events in pygame.event.get():
if events.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
object.move_x = keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]
follower.follow = object
all_sprites.update(time, window.get_rect())
window.fill((32, 64, 224))
pygame.draw.rect(window, (80, 64, 64), (0, ground_level, window.get_width(), window.get_height()-ground_level))
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
exit()
I am creating an Asteroids type game using Pygame and am having trouble firing projectiles. I have a sprite group for the projectile object and some code to add the projectile.
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
Problem is when I press space in game to fire the projectile I get the error "add() argument after * must be an iterable, not int".
I have a very similar statement for adding asteroids when the game first starts without any issues so I'm not really sure what the problem is. I'll leave the rest of the code below. The add statement giving issues is in the main function near the bottom. Any help is appreciated.
#Import Modules
import pygame
import math
import random
#Movement Function
def calculate_new_xy(old_xy,speed,direction):
new_x = old_xy[0] + (speed*math.cos(direction))
new_y = old_xy[1] + (speed*math.sin(direction))
return new_x, new_y
#Collision Function
def isColliding(x, y, xTo, yTo, size):
if x > xTo - size and x < xTo + size and y > yTo - size and y < yTo + size:
return True
return False
#Draw Text Function
def drawText(msg, color, x, y, s, center=True):
screen_text = pygame.font.SysFont("Impact", s).render(msg, True, color)
if center:
rect = screen_text.get_rect()
rect.center = (x, y-50)
else:
rect = (x, y)
display.blit(screen_text, rect)
#Initialize Variables
#Colors
white = (255, 255, 255)
black = (0, 0, 0)
#Display Height/Width
display_width = 800
display_height = 600
#Asteroid Class
class Asteroid(pygame.sprite.Sprite):
#Initialize values
def __init__(self, pos=(0, 0)):
#Initialize sprite class
pygame.sprite.Sprite.__init__(self)
#Asteroid sprite
self.asteroid = pygame.image.load("asteroid.png").convert()
self.image = self.asteroid
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize random starting angle
self.angle = random.randint(0, 360)
#Asteroid random Speed
self.speed = random.randint(2, 3)
#Asteroid random direction
self.direction = math.radians(random.randrange(0, 360, 3))
#Update asteroid object
def update(self):
#Constantly rotate asteroid
self.angle -= 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.asteroid, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Move Asteroid
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the asteroid to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -20
elif self.rect.x < -20:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -20
elif self.rect.y < -20:
self.rect.y = display_height
#Projectile Class
class Projectile(pygame.sprite.Sprite):
#Initialize values
def _init_(self,x,y,direction):
self.x = x
self.y = y
self.dir = direction
self.ttl = 30
#Update projectile object
def update(self):
#Changing direction
self.x += projectilespd * math.cos(self.direction)
self.y += projectilespd * math.sin(self.direction)
#Draw projectile
pygame.draw.circle(display, white, (self.x,self.y),1)
#Screen Border
if self.x > display_width:
self.x = 0
elif self.x < 0:
self.x = display_width
elif self.y > display_height:
self.y = 0
elif self.y < 0:
self.y = display_height
self.ttl -= 1
#Player Class
class Player(pygame.sprite.Sprite):
#Initialize ship sprite, angle lines, and rectangle
def __init__(self, pos=(0, 0), size=(200, 200)):
#Player sprite
self.ship = pygame.image.load("ship.png").convert()
self.image = self.ship
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize angle
self.angle = 0
#Initialize direction
self.direction = 0
#Update player object
def update(self):
#Rotation
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]: self.angle -= 3 % 360
if pressed[pygame.K_RIGHT]: self.angle += 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.ship, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Convert angle to radians
self.direction = math.radians(self.angle-90)
#Increase speed if Up is pressed
if pressed[pygame.K_UP]: self.speed = 5
else: self.speed = 0
#Move Ship
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the player to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -50
elif self.rect.x < -50:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -50
elif self.rect.y < -50:
self.rect.y = display_height
#Main Function
def main(gameState):
#Player starting position
player = Player(pos=(400, 300))
#Asteroid group
asteroids = pygame.sprite.Group()
#Projectile group
projectiles = pygame.sprite.Group()
#Create asteroids
for x in range(8):
asteroids.add(Asteroid(pos=(100 + (x*120), 100 + (x*20))))
while True:
for event in pygame.event.get():
#closes game
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
#Game Menu
while gameState == "Menu":
#Fill background
display.fill((0,0,0))
#Display menu text
drawText("ASTEROIDS", white, display_width / 2, display_height / 2, 150)
drawText("Press any key to START", white, display_width / 2, display_height / 2 + 120, 40)
#Check game start or end
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
gameState = "Playing"
pygame.display.update()
#Low frame rate for menu
clock.tick(5)
#Get key inputs
pressed = pygame.key.get_pressed()
#Fill background
display.fill(black)
#Check for player collision with asteroid
for asteroid in asteroids:
if gameState != "Game Over":
if isColliding(player.rect.x, player.rect.y, asteroid.rect.x, asteroid.rect.y, 30):
gameState = "Game Over"
#Update and draw player if not game over
if gameState != "Game Over":
#Update player
player.update()
#Draw player
display.blit(player.image, player.rect)
#Update asteroids
asteroids.update()
#Draw asteroids
asteroids.draw(display)
#Fire Projectiles
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
#Update projectiles
projectiles.update()
#Draw projectiles
projectiles.draw(display)
#Display Game Over and restart option
if gameState == "Game Over":
drawText("GAME OVER", white, display_width / 2, display_height / 2, 150)
drawText("Press R to restart", white, display_width / 2, display_height / 2 + 120, 40)
if pressed[pygame.K_r]:
main(gameState = "Playing")
#Makes updates to the game screen
pygame.display.update()
#Frame rate
clock.tick(60)
#Initialize Game
if __name__ == '__main__':
#initialize pygame
pygame.init()
#initialize display settings
display = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Asteroids')
#initialize game clock
clock = pygame.time.Clock()
#start main function
main(gameState = "Menu")
The issue is not the pygame.sprite.Group.add operation, but the obejct you want to add is not a pygame.sprite.Sprite object, because the object is not constructed at all.
You missed to the super call in the constructor of Projectile. Furthermore the name of the constructor has to be __init__ rather _init_:
class Projectile(pygame.sprite.Sprite):
#Initialize values
def __init__(self,x,y,direction):
super.__init__()
self.x = x
self.y = y
self.dir = direction
self.ttl = 30
I am writing a simple invaders game. To add damage to the bases I figured I could blit a small, black surface on the base at bullet impact, and use a mask to check if the bullet was on the damage or the base, but it isn't working and I feel I am misunderstanding the mask. The first collision is detected but after that it also detects a collision but doesn't put any more damage on the base. I thought because the surface was black the base mask wouldn't include it, but it isn't working. Here is a short test to demo this. Press space (or any key) to fire a bullet at the base. I thought maybe I should generate a new mask for the base but that doesn't work. The mask collide is from the pygame sprite code on github.
import sys, pygame, random
from pygame.locals import *
screenwidth = 600
screenheight = 400
pygame.init()
screen = pygame.display.set_mode((screenwidth, screenheight))
pygame.display.set_caption("shoot 'em up")
screenrect = screen.get_rect()
black = (0, 0, 0)
blue = (10, 10, 255)
yellow = (238, 238, 0)
base_width = 80
base_height = 40
bullet_width = 3
bullet_height = 10
class Bullet(pygame.Surface):
def __init__(self, point):
super().__init__((bullet_width, bullet_height), pygame.SRCALPHA)
self.rect = self.get_rect()
self.rect.midbottom = point
self.fill(yellow)
self.velocity = -5
self.alive = True
self.mask = pygame.mask.from_surface(self)
def update(self):
self.rect.top += self.velocity
def draw(self, surf):
surf.blit(self, self.rect)
class Base(pygame.Surface):
def __init__(self, x, y, colour):
super().__init__((base_width, base_height), pygame.SRCALPHA)
self.rect = self.get_rect()
self.rect.x = x
self.rect.y = y
self.fill(colour)
self.alive = True
def add_damage(self, bullet):
width = random.randint(3, 6)
height = random.randint(8, 12)
damage = pygame.Surface((width, height), pygame.SRCALPHA)
damage.fill(black)
rect = damage.get_rect()
rect.x = bullet.rect.x - self.rect.x
rect.y = bullet.rect.top - self.rect.top
self.blit(damage, rect)
#self.mask = pygame.mask.from_surface(self)
def draw(self, surf):
surf.blit(self, self.rect)
class Test(pygame.Surface):
def __init__(self):
super().__init__((600, 400))
self. base = Base(50, 300, blue)
self.bullets = []
def run(self):
while 1:
self.get_events()
self.update()
self.draw()
def get_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
bullet = Bullet((60, 380))
self.bullets.append(bullet)
def update(self):
if self.bullets:
for bullet in self.bullets:
bullet.update()
self.collision_check(bullet)
for bullet in self.bullets:
if not bullet.alive:
self.bullets.remove(bullet)
def collision_check(self, bullet):
if bullet.rect.colliderect(self.base):
if self.collide_mask(bullet, self.base):
print("collide")
self.base.add_damage(bullet)
bullet.alive = False
def collide_mask(self, left, right):
xoffset = right.rect[0] - left.rect[0]
yoffset = right.rect[1] - left.rect[1]
try:
leftmask = left.mask
except AttributeError:
leftmask = pygame.mask.from_surface(left)
try:
rightmask = right.mask
except AttributeError:
rightmask = pygame.mask.from_surface(right)
return leftmask.overlap(rightmask, (xoffset, yoffset))
def draw(self):
self.fill(black)
self.base.draw(self)
for bullet in self.bullets:
bullet.draw(self)
screen.blit(self, (0,0))
pygame.display.flip()
if __name__=="__main__":
t = Test()
t.run()
As you can see this is not using pygame sprites.
if the pygame.Surface object is changed you need to recreate the mask with pygame.mask.from_surface. However, the mask is generated form the Surface's alpha channel. Therefore, you need to make the damaged area transparent. Create a completely transparent rectangle (RGBA = 0, 0, 0, 0) and blit the rectangle using the special flag BLEND_RGBA_MULT (or BLEND_RGBA_MIN). Finally recreate the mask:
damage = pygame.Surface((width, height), pygame.SRCALPHA)
self.blit(damage, rect, special_flags=pygame.BLEND_RGBA_MULT)
self.mask = pygame.mask.from_surface(self)
add_damage Mehtod:
class Base(pygame.Surface):
# [...]
def add_damage(self, bullet):
width = random.randint(3, 6)
height = random.randint(8, 12)
damage = pygame.Surface((width, height), pygame.SRCALPHA)
rect = damage.get_rect()
rect.x = bullet.rect.x - self.rect.x
rect.y = bullet.rect.top - self.rect.top
self.blit(damage, rect, special_flags=pygame.BLEND_RGBA_MULT)
self.mask = pygame.mask.from_surface(self)
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.
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))