Border collision only works once in pygame - python

I'm making a snake game in python, and I've almost finished, but I'm struggling to solve an issue. In the main class in the collision method, I wrote some code to detect the snake collision with the borders of the screen, and if the statement is true, the snake will return to its original state. However, when I run the program the collision detection only works once. Afterwards, the snake is able to simply move off the screen. Thanks in advance.
import pygame
import random
pygame.init()
clock = pygame.time.Clock()
# Screen setup
screen_width = 600
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Snake")
cellSize = 20
# Colors
black = (0, 0, 0)
white = (255, 255, 255)
blue = (255, 0, 0)
green = (0, 0, 255)
# Grid
def draw_grid():
# Vertical lines
for x in range(0, screen_height, cellSize):
pygame.draw.line(screen, white, (x, 0), (x, screen_height))
# Horizontal lines
for y in range(0, screen_width, cellSize):
pygame.draw.line(screen, white, (0, y), (screen_width, y))
# Maze
class MAZE:
def __init__(self):
self.rect_width = 20
self.rect_height = 20
self.rects = []
def draw_rects(self):
# Drawing Maze
pass
# Snake
class SNAKE(MAZE):
def __init__(self):
super().__init__()
self.width = 20
self.height = 20
self.speed = 20
self.initBody = [[3 * cellSize, 40], [4 * cellSize, 40], [5 * cellSize, 40]]
self.body = [[3 * cellSize, 40], [4 * cellSize, 40], [5 * cellSize, 40]]
self.rects = []
self.move = [0, 0]
self.grow = False
def draw_snake(self):
# Drawing snake body
for cor in self.body:
snake_rect = pygame.Rect(cor[0], cor[1], self.width, self.height)
self.rects.append(snake_rect)
pygame.draw.rect(screen, blue, snake_rect)
def move_snake(self):
# Key bindings
key = pygame.key.get_pressed()
if key[pygame.K_UP]:
self.move = [0, -1]
if key[pygame.K_DOWN]:
self.move = [0, 1]
if key[pygame.K_LEFT]:
self.move = [-1, 0]
if key[pygame.K_RIGHT]:
self.move = [1, 0]
if self.move[0] != 0 or self.move[1] != 0:
# Adding rects to the end of snake body
new_rect = self.body[-1][:]
new_rect[0] += self.move[0] * cellSize
new_rect[1] += self.move[1] * cellSize
self.body.append(new_rect)
# Removing rects from tail
if self.grow == False:
self.body.pop(0)
self.grow = False
# Food
class FOOD_1:
def __init__(self):
self.width = 20
self.height = 20
self.x = 4 * cellSize
self.y = 4 * cellSize
self.food_rect = pygame.Rect(self.x, self.y, self.width, self.height)
def draw_food_1(self):
pygame.draw.rect(screen, green, self.food_rect)
maze = MAZE()
snake = SNAKE()
food_1 = FOOD_1()
class MAIN():
def __init__(self):
self.maze = MAZE()
self.snake = SNAKE()
self.food_1 = FOOD_1()
def draw(self):
self.maze.draw_rects()
self.snake.draw_snake()
self.food_1.draw_food_1()
def move(self):
self.snake.move_snake()
def collision(self):
# Snake and food collision
for rect in self.snake.rects:
if rect.colliderect(self.food_1.food_rect):
self.food_1.x = random.randint(0, 29) * cellSize
self.food_1.y = random.randint(0, 29) * cellSize
self.food_1.food_rect.topleft = (self.food_1.x, self.food_1.y)
self.snake.grow = True
# Border collisions
if self.snake.body[-1][0] <= 0:
self.snake.body = self.snake.initBody
elif self.snake.body[-1][0] >= 600:
self.snake.body = self.snake.initBody
elif self.snake.body[-1][1] <= 0:
self.snake.body = self.snake.initBody
elif self.snake.body[-1][1] >= 600:
self.snake.body = self.snake.initBody
main = MAIN()
# Main loop
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# Drawing on screen
screen.fill(black)
main.draw()
draw_grid()
# Movement and Collisions
main.move()
main.collision()
pygame.display.flip()
clock.tick(10)
pygame.quit()

When you do self.snake.body = self.snake.initBody, you are not making a copy of initBody, you are defining body as being the same object as initBody, so when you modify one, the other is modified too...which happens while playing.
So after the first collision, initBody is modified at the same time as body (as they have become the same thing), so when another collision happens, the line self.snake.body = self.snake.initBody does nothing.
You need to replace it with self.snake.body = self.snake.initBody.copy() so that the original initBody remains intact.

Better you make a code like this in your loop.
if playerx = 0:
playerx = 0
if playerx = 600:
playerx = 600:
Use same method for also y axis.

Related

Drawings rects over the player

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 # <---

Pygame, restarting game results in program closing

I know this question has been asked before but I'v tried different approaches and I can't seem to be able to fix it.
I know I have to reset my important global variables in order to correctly restart the game.
The reset of those variables is done when lives == 0 -> I reset the global variables, main menu becomes True and Game False which should put me back in the main menu.
However when I run my game, as soon as the lives reach 0 the code finishes and exits -> Process finished with exit code 0.
The issue must be in my while game loop but I'm unsure what I'm doing wrong.
I'v also tried wrapping my game logic in a function, this seems to mess with my game since I only get the frozen state screen, no enemy spawning and not able to move.
Any ideas what might cause this issue?
Full code:
import pygame
import pygame_gui
import random
# Initialize game window here
pygame.init()
# Variables for the game window
window = pygame.display.set_mode((800, 800))
manager = pygame_gui.UIManager((800, 800))
pygame.display.set_caption('Sea Invaders')
clock = pygame.time.Clock() # Adds the clock for spawn timers
score_font = pygame.font.SysFont('Calibri', 36)
multiplier_font = pygame.font.SysFont('Calibri', 18)
# Sprites, background, UI and music
background = pygame.image.load('Background.jpg')
whale_sprite = pygame.image.load('Whale.png')
speedboat_sprite = pygame.image.load('Speedboat.png')
beam_sprite = pygame.image.load('Beam.png')
life_sprite = pygame.image.load('Life.png')
pirate_boss_sprite_right = pygame.image.load('Pirateboss_right.png')
game_title = pygame_gui.elements.UILabel(relative_rect=pygame.Rect((200, 100), (400, 100)), text='SEA INVADERS',
manager=manager)
description_box = pygame_gui.elements.UILabel(relative_rect=pygame.Rect((150, 250), (500, 100)),
text='Protect the ocean as long as possible by destroying ships',
manager=manager)
start_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((350, 400), (100, 50)), text='Start',
manager=manager)
quit_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((350, 450), (100, 50)), text='Quit',
manager=manager)
# Classes
class Player():
def __init__(self, x, y, width, height):
# Variables for the Whale go here
self.x = x
self.y = y
self.width = width
self.height = height
self.mov_speed = 5
self.hitbox = (self.x, self.y, self.width, self.height)
def draw(self, window):
# This function draws the whale using the sprite and the defined location in the class parameters
window.blit(whale_sprite, (self.x, self.y))
self.hitbox = (self.x, self.y, self.width, self.height)
# Whale hitbox check
# pygame.draw.rect(window, (255, 0, 0), self.hitbox, 2)
class Enemy(pygame.sprite.Sprite):
def __init__(self, width, height, mov_speed):
# Variables for the various enemies
pygame.sprite.Sprite.__init__(self)
self.width = width
self.height = height
self.mov_speed = mov_speed
self.image = speedboat_sprite
self.rect = self.image.get_rect()
self.rect.x = random.randint(64, 734)
self.rect.y = random.randrange(-150, -100)
self.hitbox = (self.rect.x + 22, self.rect.y + 15, 19, 45)
self.alive = True
def update(self):
# This function lets the enemy go forward until the end of the screen
max_distance = 800 - self.height
if self.rect.y < max_distance:
self.rect.y += self.mov_speed
def hit(self):
# This function deletes the sprite from the sprite group when hit by an attack from a player
self.kill()
self.alive = False
class Boss():
def __init__(self, x, y, width, height, mov_speed, hitpoints):
# Variables for the boss objects
self.x = x
self.y = y
self.width = width
self.height = height
self.mov_speed = mov_speed
self.hitpoints = hitpoints
self.hitbox = (self.x + 30, self.y + 50, 225, 160) # Dimensions of the hitbox to make it close to the model
self.alive = False
self.killed = False
self.end_reached = False
def draw(self, window):
if self.alive and not self.killed:
window.blit(pirate_boss_sprite_right, (self.x, self.y))
self.hitbox = (self.x + 30, self.y + 50, 225, 160)
pygame.draw.rect(window, (255, 0, 0),
(self.x + ((self.width / 2) - (self.hitpoints / 2)), self.y + 220, 50, 10))
pygame.draw.rect(window, (0, 255, 0),
(self.x + ((self.width / 2) - (self.hitpoints / 2)), self.y + 220, self.hitpoints, 10))
# Pirate Boss hitbox check
# pygame.draw.rect(window, (255, 0, 0), self.hitbox, 2)
def move(self):
# This function moves the boss forward. As soon as the boss reaches the end it spawns lower on the screen.
# If the end is reached the model is deleted and the end_reached parameter is set tot True.
max_distance = 800 - self.height
if self.x < (800 + self.width):
self.x += self.mov_speed
else:
self.y += 100
self.x = -51
if self.y >= max_distance:
self.alive = False
self.end_reached = True
def hit(self):
# As soon as the Whale hitpoints reaches 0, the model is overwritten on screen.
if self.hitpoints <= 0:
self.killed = True
return True
class Projectile():
def __init__(self, x, y, width, height, speed, damage, image):
# Variables for the player attacks (ranged)
self.x = x
self.y = y
self.width = width
self.height = height
self.speed = speed
self.damage = damage
self.image = image
self.rect = self.image.get_rect()
def draw(self, window):
window.blit(self.image, (self.x, self.y))
def checkCollision(self, enemy):
# This function checks collision with enemies that belong to a Sprite Group.
self.rect.x = int(self.x)
self.rect.y = int(self.y)
# noinspection PyTypeChecker
return any(pygame.sprite.spritecollide(self, enemy, True))
class Hud():
# class to place the HUDS on the screen (lives, score)
def __init__(self, x, y, sprite):
self.x = x
self.y = y
self.sprite = sprite
def draw(self, window):
window.blit(self.sprite, (self.x, self.y))
# Methods
def redrawUI():
window.blit(background, (0, 0)) # Loads in the background
whale.draw(window) # Draws the Whale in the game
window.blit(speedboat_sprite, (100, 100))
window.blit(speedboat_sprite, (620, 100))
manager.draw_ui(window)
if lives == 0:
text = f'Game over! Your score: {score}.'
game_over = score_font.render(text, True, (0, 0, 0))
window.blit(game_over, (200, 200))
pygame.display.update()
def redrawGameWindow():
window.blit(background, (0, 0)) # Loads in the background
whale.draw(window) # Draws the Whale in the game
scoreboard = score_font.render(str(score), True, (255, 255, 255))
multiplier_message = multiplier_font.render(f'multiplier: {multiplier}', True, (255, 255, 255))
window.blit(scoreboard, (700, 760))
window.blit(multiplier_message, (85, 766))
x_life = 0
# This for loop places the lives next to eachother on the HUD
for life in range(lives):
life = Hud(x_life, 760, life_sprite)
x_life += 25
life.draw(window)
for b in bosses:
b.alive = True
b.draw(window)
b.move()
speedboats.draw(window)
speedboats.update()
for fired_beam in beams:
fired_beam.draw(window)
pygame.display.update() # This updates the above changes to the game window
# Game logic here
main_menu = True
game = False
FPS = 100
whale = Player(334, 650, 128, 128) # Spawns the Player at the start of the game in the middle of the screen
speedboat_locations = (125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800)
speedboats = pygame.sprite.Group()
bosses = []
beams = []
cooldown = 0
lives = 3
score = 0
speedboat_hits = 0
multiplier = 1
start = pygame.time.get_ticks()
while main_menu:
time_delta = clock.tick(60) / 1000.0
for event in pygame.event.get(): # this for-loop makes sure the game exits when clicking x
if event.type == pygame.QUIT:
main_menu = False
if event.type == pygame.USEREVENT:
if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == start_button:
game = True
main_menu = False
if event.ui_element == quit_button:
main_menu = False
manager.process_events(event)
manager.update(time_delta)
redrawUI()
while game:
for event in pygame.event.get(): # this for-loop makes sure the game exits when clicking x
if event.type == pygame.QUIT:
game = False
# Life check
for speedboat in speedboats:
if speedboat.rect.y >= 734:
speedboat.kill()
lives -= 1
if not speedboat.alive:
speedboats.remove(speedboat)
for pirate_boss in bosses:
if pirate_boss.end_reached and not pirate_boss.killed:
lives = 0
if lives == 0:
whale = Player(334, 650, 128, 128) # Spawns the Player at the start of the game in the middle of the screen
speedboat_locations = (125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800)
speedboats = pygame.sprite.Group()
bosses = []
beams = []
cooldown = 0
lives = 3
score = 0
speedboat_hits = 0
multiplier = 1
main_menu = True
game = False
# Basic cooldown for the projectiles of the player
if cooldown > 0:
cooldown += 1
if cooldown > 50:
cooldown = 0
# Collision of attacks
for beam in beams:
if beam.checkCollision(speedboats):
speedboat_hits += 1
score += (25 * multiplier)
beams.pop(beams.index(beam))
for pirate_boss in bosses:
if pirate_boss.alive and not pirate_boss.killed:
if beam.y - beam.width < pirate_boss.hitbox[1] + pirate_boss.hitbox[3] and beam.y + beam.width > \
pirate_boss.hitbox[1]:
if beam.x + beam.height > pirate_boss.hitbox[0] and beam.x - beam.height < pirate_boss.hitbox[0] + \
pirate_boss.hitbox[2]:
pirate_boss.hitpoints -= beam.damage
beams.pop(beams.index(beam))
alive_check = pirate_boss.hit()
if alive_check:
multiplier += 1
bosses.remove(pirate_boss)
if beam.y > 0:
beam.y -= beam.speed # This makes sure the bullet moves forward as long as it is not of the screen
beam.rect.center = (beam.x, beam.y)
else:
beams.pop(beams.index(beam)) # If the bullet goes of the screen it gets removed from the list
# --- PLAYER CONTROLS ---
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and whale.x > whale.mov_speed:
# Makes sure the whale can move left
# and prevents the whale from exiting the screen
whale.x = whale.x - whale.mov_speed
if keys[pygame.K_RIGHT] and whale.x < 800 - whale.mov_speed - whale.width:
# Makes sure the whale can move right
# and prevents the whale from exiting the screen
whale.x = whale.x + whale.mov_speed
if keys[pygame.K_SPACE] and cooldown == 0:
# This block of code takes care of the beam-projectile the player can shoot
if len(beams) < 3:
beams.append(
Projectile(round(whale.x + (whale.width // 2) - (32 // 2)), round(whale.y - (32 // 2)), 32, 32, 2,
10, beam_sprite))
# The beam gets spawned at the whale X/Y Coordinate. To make the beam appear in the middle and at the
# nose we add half the sprites width - half the width of the projectile to the for the x coordinate
# and we use the y coordinate - half the length of the projectile to make the attack spawn at the top
cooldown = 1
# --- ENEMY SPAWNING ---
now = pygame.time.get_ticks()
spawn_time = 3000
speedboat = Enemy(64, 64, 1)
boss = Boss(1, 1, 256, 256, 1, 50)
# Game loop to increase difficulty
if score > 500:
spawn_time = 2000
if score > 1000:
spawn_time = 1500
if score > 5000:
speedboat = Enemy(64, 64, 2)
spawn_time = 2000
if score > 10000:
speedboat = Enemy(64, 64, 2)
spawn_time = 1500
if now - start > spawn_time:
start = now
speedboats.add(speedboat)
if speedboat_hits >= 10:
bosses.append(boss)
speedboat_hits = 0
redrawGameWindow()
clock.tick(FPS)
You are setting game = False, but using that in the while loop. As soon as game is set to False the while loop exits and the game ends. If you want to do it this way, use another variable in the while loop:
running = True
while running:
if game:
game_loop()
elif menu:
menu_loop()
Then use game and menu as switches to decide which thing to show.

Bullet not moving upwards because of OOP

I have a simple shooter game using pygame. I'm having some problems making the bullet's y coordinate slowly increasing up. I know this is something to do with the way I've programmed the Player class even though a bullet Rect is in it. I think I have to change the update function inside it. This is my code:
import pygame, random, sys, time
pygame.init()
#Constants
WIDTH = 800
HEIGHT = 500
BLACK = (0, 0, 0)
WHITE = (255, 255, 255) # Background Colour
RED = (255, 0, 0)
GREEN = (0, 255, 0)
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pygame Shooter Game")
clock = pygame.time.Clock()
fps = 60
run = True
class Player():
def __init__(self, width, colour, x, y):
self.width = width
self.colour = colour
self.x = x
self.y = y
self.vel = 5
self.shoot = False
self.player = pygame.Rect(self.x, self.y, self.width, self.width)
self.cartridge = pygame.Rect(0, 0, self.width/2, self.width/2)
self.bullet = pygame.Rect(0, 0, 10, 20)
self.shoot = False
def draw(self, win):
self.win = win
pygame.draw.rect(self.win, self.colour, self.player) # Draw player(rect)
pygame.draw.rect(self.win, GREEN, self.cartridge) #Draw cartridge
if self.shoot:
pygame.draw.rect(self.win, BLACK, self.bullet)
def move(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and self.x > 0: self.x -= self.vel # We don't do elif cuz we want them to be able to move diagonally
if keys[pygame.K_RIGHT] and self.x < WIDTH-self.width: self.x += self.vel
if keys[pygame.K_UP] and self.y > 0: self.y -= self.vel
if keys[pygame.K_DOWN] and self.y < HEIGHT-self.width: self.y += self.vel
if keys[pygame.K_SPACE]:
self.shoot = True
def update(self):
self.player = pygame.Rect(self.x, self.y, self.width, self.width)
self.cartridge.midbottom = self.player.midtop
self.bullet.midbottom = self.cartridge.midtop
if self.shoot:
while self.bullet.y > 0:
self.bullet.y -= 1
def main(win):
run = True
player = Player(50, RED, WIDTH/2, HEIGHT/2)
while run:
win.fill(WHITE)
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
player.move()
player.update()
player.draw(win)
pygame.display.update()
pygame.quit()
sys.exit()
main(window)
Also, how can I make the create classes for each individual Cartridge and Bullet, to make the whole code more efficient?
update is invoked continuously in the main application loop. Therefore, no additional animation loops are required for the update. Change the loop to a selection (change while to if):
while self.bullet.y > 0:
if self.bullet.y > 0:
self.bullet.y -= 1
The starting position of the bullet must be set when the bullet is shot, rather than continuously when the bullet is updated:
class Player():
# [...]
def move(self):
# [...]
if keys[pygame.K_SPACE]:
self.shoot = True
self.bullet.midbottom = self.cartridge.midtop # <--- INSERT
def update(self):
self.player = pygame.Rect(self.x, self.y, self.width, self.width)
self.cartridge.midbottom = self.player.midtop
# self.bullet.midbottom = self.cartridge.midtop <--- DELETE
if self.shoot:
if self.bullet.y > 0: # <--- if (not while)
self.bullet.y -= 1
See also:
How can i shoot a bullet with space bar?
How do I stop more than 1 bullet firing at once?

I am trying to make a platform around the bottom of the screen but it keeps giving me an error

I have a problem when I made my code and This is the error it shows
platforms_list.append[pl,Platform([200,20], 100, 450, White), Platform([200,20], 400, 250, White)]
TypeError: 'builtin_function_or_method' object is not subscriptable
I don't get what it means by subscriptable. I have a circle player that can jump move and can go around but falls off his platform because there is no bottom platform and I don't no how to make it
import math
import os
import sys
# It is importing everything
import pygame
from pygame.locals import *
class Platform:
def __init__(self, size, x, y, color):
#size is a list, this means it has width and height
self.size = size
self.x = x
self.y = y
self.color = color
# This is what the platform class has and what it does
def draw(self):
display = pygame.display.get_surface()
pygame.draw.rect(display, self.color, (int(self.x), int(self.y), self.size[0], self.size[1]))
# This is def draw function is showing that how I want my Platform to look like
def do(self):
self.draw()
# The def do function is running def draw function
class Player:
def __init__(self, velocity, maxJumpRange, x, y, size):
self.falling = True
self.jumpCounter = 0
self.xVelocity = 0
self.y = y
self.x = x
self.jumping = False
self.velocity = velocity
self.maxJumpRange = maxJumpRange
self.jump_offset = 0
self.size = size
self.TouchedGround = False
# The player class is making how the Player is going to look and what are his limits
def keys(self):
k = pygame.key.get_pressed()
# The def keys(self): is creating a variable for pygame.key.get_pressed() and underneath is a function to make the player move around
if k[K_LEFT]:
self.xVelocity = -self.velocity
elif k[K_RIGHT]:
self.xVelocity = self.velocity
else:
self.xVelocity = 0
if (k[K_SPACE] or k[K_UP]) and not self.jumping and self.TouchedGround:
self.jumping = True
self.jumpCounter = 0
self.TouchedGround = False
# The if k[K_Space] or k[K_UP] is making sure the player has a jump limit and can't continue jumping forever.
def move(self):
self.x += self.xVelocity
# if the player is jumping, change y value
if self.jumping:
self.y -= self.velocity
self.jumpCounter += 1
if self.jumpCounter == self.maxJumpRange:
self.jumping = False
self.falling = True
elif self.falling:
self.y += self.velocity
self.jumpCounter -= 1
def draw(self):
display = pygame.display.get_surface()
pygame.draw.circle(display, White, (int(self.x), int(self.y)), self.size)
def do(self):
self.keys()
self.move()
self.draw()
# This Function is doing all of the Functions self.keys(), self.move(), self.draw()
def events():
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
#window size
w = 576
h = 516
# The above is showing the width the height and Area
os.environ['SDL_VIDEO_WINDOW_POS'] = "50,50"
# the above is showing what the graphics are
#player
p = Player(1, 100, 290, 250, 30)
#start pygame
pygame.init()
Clock = pygame.time.Clock()
DS = pygame.display.set_mode((w, h)) # This is what the display size is
pygame.display.set_caption("Try to get point B")
#variables
FPS = 120
Black = (0, 0, 0, 255)
White = (255, 255, 255, 255)
Red = (255, 0, 0)
# Bkgd stands for background
bkgd = pygame.Surface((w,h)) # didnt have the image so i made it blue
bkgd.fill((0,0,255))
#platforms
pl = Platform([290,20], 250, 350, White)
#this is a list that holds all the platforms
platforms_list = [pl,Platform([200,20], 100, 450, White), Platform([200,20], 400, 250, White)]
platforms_list.append[pl,Platform([200,20], 100, 450, White), Platform([200,20], 400, 250, White)]
#this is how much to scroll the background by
background_scroll = 0
# What the while true loop is doing is to make sure that the background moves while the player moves
while True:
events()
#blit the background, since the image is same size as window blit twice so when scrolls, you dont have blackness
DS.blit(bkgd, (-background_scroll, 0))
DS.blit(bkgd, (w-background_scroll, 0))
#check for x button clicked
events()
#update the player
p.do()
#update platforms and check for collision with player
platform_color = Red
for platform in platforms_list:
platform.color = platform_color
if p.jumping == 0:
platform.color = White
platform.do()
#if bottom of player is in the platform, move the player on top of the platform
if p.y + p.size > platform.y and p.y + p.size < platform.y + platform.size[1]:
if p.x > platform.x and p.x < platform.x + platform.size[0]:
p.y = platform.y - p.size
p.TouchedGround = True
#if the player reaches the side of the screen, move the background and platforms to make it look like it is moving
if p.x + p.size >= w:
p.x = w - p.size
background_scroll += 1
for platform in platforms_list:
platform.x -= 1
if background_scroll == w:
background_scroll = 0
#same but for the left
if p.x - p.size <= 0:
p.x = 0 + p.size
background_scroll -= 1
for platform in platforms_list:
platform.x += 1
if background_scroll == 0:
background_scroll = w
#update screen
pygame.display.update()
Clock.tick(FPS)
DS.fill(Black)
platforms_list.append[pl,Platform([200,20], 100, 450, White), Platform([200,20], 400, 250, White)] this is the code that I added but it keeps giving me the code
I think append needs to be .append() not [].
Try this:
platforms_list.append(pl,Platform([200,20], 100, 450, White), Platform([200,20], 400, 250, White))

How can I make objects permanently stick in window without being refreshed in Pygame?

I am trying to recreate Atari Breakout using Pygame. I met with a problem, I put all three rows of the tiles in three lists and I want to print them and make them stay at the location before the ball hits them.
This is the code:
import pygame
import random
pygame.init()
screenWidth = 1200
screenHeight = 700
window = pygame.display.set_mode((screenWidth,screenHeight))
pygame.display.set_caption('Atari Breakout')
pygame.mouse.set_pos(-500,650)
class Plate():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
def draw_plate(self):
pygame.mouse.set_visible(True)
pos = pygame.mouse.get_pos()
self.x = pos[0]-100
pygame.draw.rect(window, (00,00,255), (self.x, self.y ,self.width, self.height))
class Circle():
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
self.vel_x = 5
self.vel_y = 5
class Tiles():
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self):
if self.color == 'red':
pygame.draw.rect(window, (255,0,0), (self.x, self.y, self.width, self.height))
elif self.color == 'green':
pygame.draw.rect(window, (44,176,55), (self.x, self.y, self.width, self.height))
elif self.color == 'blue':
pygame.draw.rect(window, (0,191,255), (self.x, self.y, self.width, self.height))
pygame.display.update()
def draw_titles():
first_row = []
second_row = []
third_row = []
preset_width1 = [70, 120, 200, 30, 240, 140, 130, 120, 80] # nine elements
preset_width2 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
preset_width3 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
random.shuffle(preset_width1)
random.shuffle(preset_width2)
random.shuffle(preset_width3)
#print(f'preset_width1 is: {preset_width1}')
put_width1 = []
put_width2 = []
put_width3 = []
for t in range(1,10):
if t==1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
if t==1:
x = 0 + 5
else:
add = sum(put_width1)
#print(f'add is: {add}')
x = t*5 + add
#print(f'x is: {x}')
if t>1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
y = 125
height = 35
first_row.append(Tiles(x,y,width,height,'red'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width2.pop(0)
put_width2.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width2)
x = t*5 + add
if t>1:
width = preset_width2.pop(0)
put_width2.append(width)
y = 170
height = 35
second_row.append(Tiles(x,y,width,height,'green'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width3.pop(0)
put_width3.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width3)
x = t*5 + add
if t>1:
width = preset_width3.pop(0)
put_width3.append(width)
y = 215
height = 35
third_row.append(Tiles(x,y,width,height,'blue'))
if t == 9:
break
for num in range(0,9):
first_row[num].draw()
for num in range(0,9):
second_row[num].draw()
for num in range(0,9):
third_row[num].draw()
keys = pygame.key.get_pressed()
if keys[pygame.K_BACKSPACE]:
run = False
# main loop
plate = Plate(10,650,200,40)
ball = Circle(600,300,10)
run = True
start = False
bounds = pygame.Rect(0, 0, 1200, 700)
while run:
pygame.time.Clock().tick(120)
for event in pygame.event.get():
if event == pygame.QUIT:
run = False
plate.draw_plate()
keys = pygame.key.get_pressed()
# bounce algorithem
if keys[pygame.K_SPACE]:
start = True
if start:
ball.y -= ball.vel_y
ball.x += ball.vel_x
if ball.x - ball.radius < bounds.left or ball.x + ball.radius > bounds.right:
ball.vel_x *= -1
if ball.y - ball.radius < bounds.top or ball.y + ball.radius > bounds.bottom:
ball.vel_y *= -1
pygame.draw.rect(window, (0, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (ball.x, ball.y), ball.radius)
#pygame.display.update()
draw_titles()
# close call
if keys[pygame.K_BACKSPACE]:
run = False
break
window.fill((0,0,0))
pygame.display.update()
pygame.quit()
This is the ideal situation:
But instead, it refreshes like crazy. I know the problem is I put the draw_titles() function inside of the main While loop. But I believe is the way I code the draw_tiles() function that made it not work. If I place draw_titles() before the loop, the tiles will appear and instantly disappear and both the ball and the plate will not display.
I did some research online and I see tutorial for text and images. For images, they use .blit() but I believe it is only for images.
I had tried many variations to fix this but to no avail. Please help.
Thank you.
Here's a quick fix:
import pygame
import random
pygame.init()
screenWidth = 1200
screenHeight = 700
window = pygame.display.set_mode((screenWidth,screenHeight))
pygame.display.set_caption('Atari Breakout')
pygame.mouse.set_pos(-500,650)
class Plate():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
def draw_plate(self):
pygame.mouse.set_visible(True)
pos = pygame.mouse.get_pos()
self.x = pos[0]-100
pygame.draw.rect(window, (00,00,255), (self.x, self.y ,self.width, self.height))
class Circle():
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
self.vel_x = 5
self.vel_y = 5
class Tiles():
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self):
if self.color == 'red':
pygame.draw.rect(window, (255,0,0), (self.x, self.y, self.width, self.height))
elif self.color == 'green':
pygame.draw.rect(window, (44,176,55), (self.x, self.y, self.width, self.height))
elif self.color == 'blue':
pygame.draw.rect(window, (0,191,255), (self.x, self.y, self.width, self.height))
first_row = []
second_row = []
third_row = []
def create_titles():
preset_width1 = [70, 120, 200, 30, 240, 140, 130, 120, 80] # nine elements
preset_width2 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
preset_width3 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
random.shuffle(preset_width1)
random.shuffle(preset_width2)
random.shuffle(preset_width3)
#print(f'preset_width1 is: {preset_width1}')
put_width1 = []
put_width2 = []
put_width3 = []
for t in range(1,10):
if t==1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
if t==1:
x = 0 + 5
else:
add = sum(put_width1)
#print(f'add is: {add}')
x = t*5 + add
#print(f'x is: {x}')
if t>1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
y = 125
height = 35
first_row.append(Tiles(x,y,width,height,'red'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width2.pop(0)
put_width2.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width2)
x = t*5 + add
if t>1:
width = preset_width2.pop(0)
put_width2.append(width)
y = 170
height = 35
second_row.append(Tiles(x,y,width,height,'green'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width3.pop(0)
put_width3.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width3)
x = t*5 + add
if t>1:
width = preset_width3.pop(0)
put_width3.append(width)
y = 215
height = 35
third_row.append(Tiles(x,y,width,height,'blue'))
if t == 9:
break
# main loop
plate = Plate(10,650,200,40)
ball = Circle(600,300,10)
run = True
start = False
bounds = pygame.Rect(0, 0, 1200, 700)
create_titles()
while run:
pygame.time.Clock().tick(120)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
# bounce algorithem
if keys[pygame.K_SPACE]:
start = True
# close call
if keys[pygame.K_BACKSPACE]:
run = False
break
if start:
ball.y -= ball.vel_y
ball.x += ball.vel_x
if ball.x - ball.radius < bounds.left or ball.x + ball.radius > bounds.right:
ball.vel_x *= -1
if ball.y - ball.radius < bounds.top or ball.y + ball.radius > bounds.bottom:
ball.vel_y *= -1
window.fill((0,0,0))
plate.draw_plate()
pygame.draw.rect(window, (0, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (ball.x, ball.y), ball.radius)
for tile in first_row:
tile.draw()
for tile in second_row:
tile.draw()
for tile in third_row:
tile.draw()
pygame.display.update()
pygame.quit()
When drawing to the screen, clear the screen surface first, then draw all objects, like this:
...
window.fill((0,0,0))
plate.draw_plate()
pygame.draw.rect(window, (0, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (ball.x, ball.y), ball.radius)
for tile in first_row:
tile.draw()
for tile in second_row:
tile.draw()
for tile in third_row:
tile.draw()
...
Note how all drawing related stuff is in one place. This way, it's clearer and less confusing.
You should make sure to only call pygame.display.flip() (or .update()) only once per frame (as Rabbid76 already said in a comment).
I also moved the lists outside the draw_titles function, which I renamed to create_titles. The function creates the tiles, so it should only be done once, not every frame.
You should also probably look into pygame's Sprite and Group class. Here's an example I hacked together which makes use of some pygame features:
import pygame
import random
class Paddle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, bounds, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width, height))
self.image.fill((0,0,255))
self.rect = self.image.get_rect(topleft=(x, y))
self.bounds = bounds
def update(self, dt):
pos = pygame.mouse.get_pos()
self.rect.centerx = pos[0]
self.rect.clamp_ip(self.bounds)
class Circle(pygame.sprite.Sprite):
def __init__(self, x, y, radius, bounds, *grps):
super().__init__(*grps)
self.image = pygame.Surface((radius, radius))
self.image.set_colorkey((1, 2, 3))
self.image.fill((1, 2, 3))
self.rect = self.image.get_rect(topleft=(x, y))
pygame.draw.circle(self.image, (44,176,55), (radius//2, radius//2), 5)
self.vel = pygame.Vector2((5, 5))
self.pos = self.rect.center
self.bounds = bounds
def update(self, dt):
self.pos += self.vel * min(dt/15, 10)
self.rect.center = self.pos
if self.rect.left < self.bounds.left or self.rect.right > self.bounds.right:
self.vel.x *= -1
if self.rect.top < self.bounds.top or self.rect.bottom > self.bounds.bottom:
self.vel.y *= -1
self.rect.clamp_ip(self.bounds)
def bounce(self, sprite):
if self.rect.top <= sprite.rect.top or sprite.rect.bottom >= sprite.rect.bottom:
self.vel.y *= -1
elif self.rect.left <= sprite.rect.left or sprite.rect.right >= sprite.rect.right:
self.vel.x *= -1
class Tiles(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(topleft=(x, y))
def hit(self):
self.kill()
def main():
pygame.init()
screen = pygame.display.set_mode((1200, 700))
screen_rect = screen.get_rect()
pygame.display.set_caption('Atari Breakout')
sprites = pygame.sprite.Group()
tiles = pygame.sprite.Group()
paddle = Paddle(10,650,200,40, screen_rect, sprites)
ball = Circle(600,300,10, screen_rect, sprites)
preset = [70, 120, 200, 30, 240, 140, 130, 120, 80]
y = 215
for color in ['blue', 'green', 'red']:
x = 5
line = preset[:]
random.shuffle(line)
for width in line:
Tiles(x, y, width, 35, pygame.Color(color), sprites, tiles)
x += width + 5
y -= 45
dt = 0
clock = pygame.time.Clock()
while True:
pygame.time.Clock().tick(120)
# events
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_BACKSPACE:
return
# game logic
tile = pygame.sprite.spritecollideany(ball, tiles)
if tile:
tile.hit()
ball.bounce(tile)
if pygame.sprite.collide_rect(paddle, ball):
ball.bounce(paddle)
sprites.update(dt)
# drawing
screen.fill((0,0,0))
sprites.draw(screen)
pygame.draw.rect(screen, (0, 0, 0), screen_rect, 1)
pygame.display.update()
dt = clock.tick(120)
pygame.quit()
if __name__ == '__main__':
main()

Categories