Pygame drawing a rectangle with OOP - python

I'm trying to learn OOP with pygame and make a simple game, I'm loosely following a tutorial, but have tried to modify it to fit my own needs and now it's not working. I'm trying to draw a white rectangle onto a black window, the tutorial draws a blue circle on a black window and when I replace the circle to a rectangle it doesn't work.
My code is sepereated into 2 different files heres the first file:
import pygame
import LanderHandler
black = (0, 0, 0)
white = (255, 255, 255)
green = (0, 255, 0)
red = (255, 0, 0)
class MainLoop(object):
def __init__(self, width=640, height=400):
pygame.init()
pygame.display.set_caption("Lander Game")
self.width = width
self.height = height
self.screen = pygame.display.set_mode((self.width, self.height), pygame.DOUBLEBUF)
self.background = pygame.Surface(self.screen.get_size()).convert()
def paint(self):
lander = LanderHandler.Lander()
lander.blit(self.background)
def run(self):
self.paint()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
# call with width of window and fps
MainLoop().run()
And my second file:
import pygame
black = (0, 0, 0)
white = (255, 255, 255)
green = (0, 255, 0)
red = (255, 0, 0)
class Lander(object):
def __init__(self, height=10, width=10, color=white, x=320, y=240):
self.x = x
self.y = y
self.height = height
self.width = width
self.surface = pygame.Surface((2 * self.height, 2 * self.width))
self.color = color
pygame.draw.rect(self.surface, white, (self.height, self.height, self.width, self.width))
def blit(self, background):
"""blit the Ball on the background"""
background.blit(self.surface, (self.x, self.y))
def move(self, change_x, change_y):
self.change_x = change_x
self.change_y = change_y
self.x += self.change_x
self.y += self.change_y
if self.x > 300 or self.x < 0:
self.change_x = -self.change_x
if self.y > 300 or self.y < 0:
self.change_y = -self.change_y
Any help or pointing me in the right direction would be amazing thank you.
P.S. I get no running errors and a black window does pop up, but without a white rectangle.

Problem is because you draw rectangle on surface self.background
lander.blit(self.background)
but you never blit self.background on self.screen which is main buffer and which is send on monitor when you do
pygame.display.flip()
So you can draw directly on self.screen
lander.blit(self.screen)
or you have to blit self.background on self.screen
lander.blit(self.background)
self.screen.blit(self.background, (0,0))

You shouldn't create a function with the name blit because it might get in the way of the actual blit function. Also right here in the second code:
pygame.draw.rect(self.surface, white, (self.height, self.height, self.width, self.width))
you should use surface

Related

Pygame mask collision only putting damage on base on first collision

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)

drawing objects on pygame

I am restarting some code for a covid simulation as I cant use the collide function in my current one. I have been able to draw the basic background, and draw one cell. However, when i try create the cell in a different place on my screen it does not appear for some reason.
My code is as seen below:
import random
import pygame
# import numpy
import time
pygame.init()
GREEN1 = (0, 255, 0) # Healthy cells
RED = (255, 0, 0) # Infected cells
GREEN2 = (0, 100, 0) # Healthy cells not susecptible
BLACK = (0, 0, 0) # Dead cells
WHITE = (255, 255, 255)
Bgcolor = (225, 198, 153)
ScreenSize = (800, 800)
Screen = pygame.display.set_mode(ScreenSize)
pygame.display.set_caption("Covid-19 Simualtion")
clock = pygame.time.Clock()
speed = [0.5, -0.5]
class Cells(pygame.sprite.Sprite):
def __init__(self, color, speed, width, height):
super().__init__()
self.color = color
self.x_cord = random.randint(0, 400)
self.y_cord = random.randint(50, 700)
self.radius = 5
self.speed = speed
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
pygame.draw.circle(self.image, self.color, [30, 70], self.radius, width = 0)
self.rect = self.image.get_rect()
self.radius = 5
#x_number = random.randint(0, 1)
#self.xSpeed = speed[x_number]
#y_number = random.randint(0, 1)
#self.ySpeed = speed[y_number]
allCellsList = pygame.sprite.Group()
Cell1 = Cells(GREEN1, 5, 50, 50)
allCellsList.add(Cell1)
End = False
while not End:
for event in pygame.event.get():
if event.type == pygame.QUIT:
End = True
Screen.fill(Bgcolor)
pygame.draw.rect(Screen, BLACK, (0, 50, 400, 700), 3)
allCellsList.update()
allCellsList.draw(Screen)
pygame.display.flip()
clock.tick(60)
Thanks for any help in advance
The main problem is that your cell has size (50,50) and you try to draw on position (20,70) so it draws outside rectangle (50, 50) and you can't see it. You have to draw inside rectangle (50, 50) - for example in center (25,25). And later you should use self.rect to move it on screen.
Second problem is that you keep position in self.x_coord, self.x_coord but you should use self.rect.x self.rect.y because Sprite use self.image and self.rect to draw it on screen.
And it show third problem - in Cell you need method update which will change values in self.rect to move object.
Minimal working example which move 5 cells in random directions.
I organize code in different way and try to use PEP 8 - Style Guide for Python Code
import random
import pygame
# --- constants --- (UPPER_CASE_NAMES)
GREEN1 = (0, 255, 0) # Healthy cells
RED = (255, 0, 0) # Infected cells
GREEN2 = (0, 100, 0) # Healthy cells not susecptible
BLACK = (0, 0, 0) # Dead cells
WHITE = (255, 255, 255)
BACKGROUND_COLOR = (225, 198, 153)
SCREEN_SIZE = (800, 800)
# --- classes --- (CamelCaseNames)
# class keeep only one cell so it should has name `Cell` instead of `Cells`
class Cell(pygame.sprite.Sprite):
def __init__(self, color, speed, width, height):
super().__init__()
self.color = color
self.speed = speed
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.radius = width//2 # 25
center = [width//2, height//2]
pygame.draw.circle(self.image, self.color, center, self.radius, width=0)
self.rect = self.image.get_rect()
self.rect.x = random.randint(0, 400)
self.rect.y = random.randint(50, 700)
def update(self):
self.rect.x += random.randint(-10, 10)
self.rect.y += random.randint(-10, 10)
# --- functions --- (lower_case_names)
# empty
# --- main --- (lower_case_names)
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("Covid-19 Simualtion")
speed = [0.5, -0.5]
# - objects -
all_cells = pygame.sprite.Group() # PEP8: lower_case_name
for _ in range(5):
cell = Cell(GREEN1, 5, 50, 50) # PEP8: lower_case_name
all_cells.add(cell)
# - loop -
clock = pygame.time.Clock()
end = False
while not end:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
end = True
# - upadates (without draws) -
all_cells.update()
# - draws (without updates) -
screen.fill(BACKGROUND_COLOR)
pygame.draw.rect(screen, BLACK, (0, 50, 400, 700), 3)
all_cells.draw(screen)
pygame.display.flip()
clock.tick(30) # to use less CPU
# - end
pygame.quit() # some system may need it to close window

Collision in Pygame game

I need help with my programming assignment. I need to make it so the blocks in the game bounce off of each other when the collide. The code below uses Pygame. I've been trying to do this for a couple hours now and keep running into walls.
import pygame
from pygame.locals import *
import time
class Block:
def __init__(self,win,left,top,
width,height,color,velocity):
self.win = win
self.rect = pygame.Rect(left,top,
width,height)
self.color = color
self.velocity = velocity
def move(self):
self.rect = self.rect.move(
self.velocity[0],self.velocity[1])
if ((self.rect.top < 0) or
(self.rect.bottom > self.win.height)):
self.velocity[1] = -self.velocity[1]
if ((self.rect.left < 0) or
(self.rect.right > self.win.width)):
self.velocity[0] = -self.velocity[0]
def tupleRep(block):
return ((block.rect.left, block.rect.top),(block.rect.right, block.rect.bottom))
colliderect()
def draw(self):
pygame.draw.rect(self.win.surface,
self.color, self.rect,0)
class BlockWindow:
def __init__(self,width,height,caption):
self.surface = pygame.display.set_mode((width,height))
self.caption = caption
pygame.display.set_caption(self.caption)
self.height = height
self.width = width
self.blocks = [ ]
self.blocks.append(Block(self,300,80,50,
100,RED,[BASESPEED,-BASESPEED]))
self.blocks.append(Block(self,200,200,20,
20,GREEN,[-BASESPEED,-BASESPEED]))
self.blocks.append(Block(self,100,150,60,
60,BLUE,[-BASESPEED,BASESPEED]))
self.blocks.append(Block(self,100,100,70,
200,PURPLE,[BASESPEED,BASESPEED]))
self.blocks.append(Block(self,300,70,50,
60,TEAL,[-BASESPEED,BASESPEED]))
Quit = False
while not Quit:
self.surface.fill(BLACK)
for b in self.blocks:
b.move()
b.draw()
pygame.display.update()
time.sleep(0.02) #import what for this?
for event in pygame.event.get():
if event.type == QUIT:
Quit = True
# set up the colors
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PURPLE= (200,0,200)
TEAL = (0,200,200)
BASESPEED = 2
# set up pygame
pygame.init()
win = BlockWindow(800,800,'Animation with Objects')
pygame.quit()
Here is the most I can help you without preventing you from learning anything:
http://www.pygame.org/docs/ref/sprite.html
http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollide
http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.collide_rect
http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.collide_circle
Also try the book "Hello World!" by Carter and Warren Sande.

pygame - blitting a moving background image causes extreme fps drop?

I'm making a Flappy Bird clone in PyGame and I'm trying to add a moving background image. I'm using a 800x600 image right now, and for some reason, when I blit the image, the performance suffers terribly. Is there something I'm doing wrong that is taking up a lot of resources?
import pygame, sys
import random
from pygame import *
pygame.init()
red = (255, 0, 0)
white = (255, 255, 255)
black = (0, 0, 0)
blue = (0, 0, 255)
green = (0, 255, 0)
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("flap it up")
clock = pygame.time.Clock()
MAX_VEL = 5
GAP = 175
WALLSPEED = -2
WALLEVENT = pygame.USEREVENT+1
SCOREEVENT = pygame.USEREVENT+2
pygame.time.set_timer(WALLEVENT, 5000)
pygame.time.set_timer(SCOREEVENT, 2500)
myfont = pygame.font.SysFont("monospace", 18)
class Player(object):
def __init__(self):
self.rect = pygame.Rect(384, 284, 32, 32)
self.xvel = 0
self.yvel = MAX_VEL
self.xcel = 0
self.ycel = 1
def move(self):
self.rect.top += self.yvel
if self.yvel < MAX_VEL:
self.yvel += self.ycel
else:
self.yvel = MAX_VEL
def jump(self):
self.yvel = -15
def draw(self):
pygame.draw.rect(screen, red, self.rect)
class Wall(object):
def __init__(self, y):
self.rect1 = pygame.Rect(800, y, 32, 600-y)
self.rect2 = pygame.Rect(800, 0, 32, y-GAP)
self.xvel = WALLSPEED
def move(self, walls):
self.rect1.x += self.xvel
self.rect2.x += self.xvel
if self.rect1.x < -31:
walls.remove(self)
def draw(self):
pygame.draw.rect(screen, green, self.rect1)
pygame.draw.rect(screen, green, self.rect2)
class BG(object):
def __init__(self, x):
self.x = x
self.xvel = WALLSPEED
self.img = pygame.image.load("bg.jpg")
def move(self, bg):
self.x += self.xvel
if self.x < -799:
bg.remove(self)
bg.append(BG(self.x+1600))
screen.blit(self.img, (self.x, 0))
def lose():
pygame.quit()
quit()
def main(player, walls, bg, score, playing):
while playing:
for event in pygame.event.get():
if event.type == KEYDOWN:
player.jump()
if event.type == WALLEVENT:
numbo = random.randrange(GAP, 600, 25)
walls.append(Wall(numbo))
if event.type == SCOREEVENT:
score += 1
for b in bg:
b.move(bg)
label = myfont.render("Score: " + str(score), 1, (0, 0, 0))
screen.blit(label, (30, 20))
player.move()
player.draw()
for w in walls:
w.move(walls)
w.draw()
if player.rect.colliderect(w.rect1) or player.rect.colliderect(w.rect2):
lose()
playing = False
clock.tick(60)
pygame.display.update()
player = Player()
walls = []
bg = []
score = 0
walls.append(Wall(300))
bg.append(BG(0))
bg.append(BG(800))
playing = True
main(player, walls, bg, score, playing)
Typically, this should not be a problem. You should try to set the FPS manually and see if there is a difference
# Create a clock
clock = pygame.time.Clock()
# Set FPS (frames per second)
clock.tick(50)

Move polygon with move_ip in pygame

I can't get to move a polygon with the function move_ip() in pygame, the code works good if I draw a rectangle, but not with polygon. It doesn't respond when I press any arrow key.
I think I don't need spaceship rectangle because polygons are rects with pygame, but it seems not right, I don't know, i've got a mess.
This is my pygame code:
import sys, os
import pygame
from pygame.locals import *
import numpy as np
if sys.platform in ["win32","win64"]: os.environ["SDL_VIDEO_CENTERED"]='1'
width_screen = 600
height_screen = 480
screen_size = (width_screen, height_screen)
positionx_screen = 0
positiony_screen = 32
title_window = 'ASTEROIDS'
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
spaceship = pygame.Rect(200, 475, 120, 20)
def quit_screen(event):
if event.type == QUIT:
pygame.quit()
sys.exit()
class Spaceship():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.colour = WHITE
self.thickness = 0
self.movex = 20
def draw_nave(self):
vertices = np.array([[screen_size[0]/2, screen_size[1] /2 - 30],[screen_size[0]/2 + 15, screen_size[1]/2],
[screen_size[0]/2-15, screen_size[1] / 2]])
pygame.draw.polygon(screen_game, self.colour, vertices, self.thickness)
def move_spaceship_right(self):
spaceship.move_ip(self.movex, 0)
def move_spaceship_left(self):
spaceship.move_ip(- self.movex, 0)
def game():
pygame.init()
running = True
global screen_game
screen_game = pygame.display.set_mode(screen_size,
positionx_screen, positiony_screen)
pygame.display.set_caption(title_window)
clock = pygame.time.Clock()
my_spaceship = Spaceship(200, 475, 120, 20)
screen_game.fill(BLACK)
while running:
screen_game.fill(BLACK)
for event in pygame.event.get():
quit_screen(event)
if event.type == pygame.KEYDOWN:
if event.key == K_RIGHT:
my_spaceship.move_spaceship_right()
if event.key == K_LEFT:
my_spaceship.move_spaceship_left()
my_spaceship.draw_nave()
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
try:
game()
except:
pygame.quit()
Thanks
The problem is that your Rect is in no way related to the polygon you draw. You simply draw it at the same position every time.
Better create a Surface once, draw your polygon on that Surface once, and use spaceship as position when drawing that new Surface.
I recommend subclassing from Sprite, create that Surface in the __init__ function, and save the Rect in self.rect instead of the spaceship variable.
Then either use the draw function of Sprite or put your Sprite into a Group.

Categories