pygame Drawing multiple rectangles - python

I'm learning python. The idea of the app is that there are 8 rectangles which when clicked turn off or on representing binary. Each rectangle representing a bit, and the decimal equivalent will be displayed.
I'm having difficulty translating that into a well written app without hard coding everything. I hard coded the 8 rects using .fill but I would prefer to use a function which does that automatically. I don't want the full code I'd rather have someone point me in the right direction in regards to the structure of the code I should follow. Should I use a class to initiate a new rect then use a render method with a for loop to run through an array of the rectangles, if so, how would I display the rectangles in a row?
To draw the rectangles I am sticking to display.fill()
I considered to hardcore the properties of each rectangle into a tuple of tuples then render each with a for loop, is this a good approach?

Here is a template you can use:
import pygame
import sys
def terminate():
pygame.quit()
sys.exit()
pygame.init()
black = (0,0,0)
white = (0,0,0)
clock = pygame.time.Clock()
fps=30
screen_height = 520
screen_width = 650
screen = pygame.display.set_mode((screen_width,screen_height)
class Rect(pygame.sprite.Sprite):
def __init__(self,x,y,width,height,color,value):
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
self.value = value
def change_value(self,color,value):
self.image.fill(color)
self.value=value
rects = pygame.sprite.Group()
rect = Rect(50,50,100,100,black)
rects.add(rect)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
terminate()
screen.fill(white)
rect.draw(screen)
pygame.display.flip()
clock.tick(fps)
Using a group will allow you to draw all the rectangles at once. You can change the rectangles using the change_value function.

Related

How to clear "trails" in sprite animation in pygame? [duplicate]

I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

i tried implementing a zoom function for my window that doesnt work [duplicate]

So I'm making a 2D pixel art game in pygame and as you could assume, all my sprite textures appear very small. I'm wondering if there's a way I can globally scale everything up in my game without either having to scale each sprite up individually or messing up the coordinates. Every sprite will move on a grid: one unit is 16x16 pixels and when my player sprite moves, for example, it will just move over in a direction 16 pixels.
Here's my main script:
import sys
from pygame.locals import *
import pygame
from game.sprites import Ghost
pygame.init()
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 640
DES_WIDTH = 64
DES_HEIGHT = 64
COL_BG = (46, 48, 55)
COL_FG = (235, 229, 206)
X = 1000
Y = 1000
win = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Through The Doors")
running = True
paused = False
# INITIALIZE SPRITES
player = Ghost()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)
clock = pygame.time.Clock()
while running:
clock.tick(30)
if not paused:
win.fill(COL_BG)
all_sprites.update()
all_sprites.draw(win)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
player.go_right()
elif keys[pygame.K_LEFT]:
player.go_left()
elif keys[pygame.K_UP]:
player.go_up()
elif keys[pygame.K_DOWN]:
player.go_down()
pygame.display.flip()
pygame.quit()
I do have more sprites I am going to load in, but I would like to resolve the scaling issue first.
I'm wondering if there's a way I can globally scale everything up in my game without either having to scale each sprite up individually [...]"
No there is no way. You have to scale each coordinate, each size and each surface individually. PyGame is made for images (Surfaces) and shapes in pixel units. Anyway up scaling an image will result in either a fuzzy, blurred or jagged (Minecraft) appearance.
Is there a way I could make a separate surface and just put that on top of the base window surface, and just scale that?
Yes of course.
Create a Surface to draw on (win). Use pygame.transform.scale() or pygame.transform.smoothscale() to scale it to the size of the window and blit it to the actual display Surface (display_win):
display_win = pygame.display.set_mode((WINDOW_WIDTH*2, WINDOW_HEIGHT*2))
win = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT))
while running:
# [...]
if not paused:
win.fill(COL_BG)
all_sprites.update()
all_sprites.draw(win)
# [...]
scaled_win = pygame.transform.smoothscale(win, display_win.get_size())
# or scaled_win = pygame.transform.scale(win, display_win.get_size())
display_win.blit(scaled_win, (0, 0))
pygame.display.flip()
Minimal example: repl.it/#Rabbid76/PyGame-UpScaleDisplay

My sprite is leaving a black trail when it moves in Pygame. Can I get help identifying my error? [duplicate]

I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

Python-pygame How to make an aura collision between sprites?

so, Im trying to get a reaction when the sprite player pass around (not over) another sprite.
So far my idea is something like:
if (sprite.x +100) > player.x > (sprite.x -100) and (sprite.y +100) > player.y > (sprite.y -100):
print("hit")
# Sprite.x and y are the position of the rect, not the whole image
This works but there is some way to simplify it? Also Im thinking how to get rid of the 100 pixels
If you want to use a scaled rect for the collision detection, you can inflate your original rect (or create a new one) and assign it as a separate attribute to your sprite (I call it hitbox here). The original rect will only be used to store the position.
Create a custom collision detection function which has to be passed as the collided argument to pygame.sprite.spritecollide or groupcollide. In this function you can use the colliderect method of the hitbox rect to check if it collides with the rect of the other sprite.
def hitbox_collision(sprite1, sprite2):
"""Check if the hitbox of the first sprite collides with the
rect of the second sprite.
`spritecollide` will pass the player object as `sprite1`
and the sprites in the enemies group as `sprite2`.
"""
return sprite1.hitbox.colliderect(sprite2.rect)
Then call spritecollide in this way:
collided_sprites = pg.sprite.spritecollide(
player, enemies, False, collided=hitbox_collision)
Here's a complete example (the red rect is the hitbox and the green rect the self.rect which is used as the blit position):
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
# Give the sprite another rect for the collision detection.
# Scale it to the desired size.
self.hitbox = self.rect.inflate(100, 100)
def update(self):
# Update the position of the hitbox.
self.hitbox.center = self.rect.center
class Enemy(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('sienna1'))
self.rect = self.image.get_rect(center=pos)
def hitbox_collision(sprite1, sprite2):
"""Check if the hitbox of the first sprite collides with the
rect of the second sprite.
`spritecollide` will pass the player object as `sprite1`
and the sprites in the enemies group as `sprite2`.
"""
return sprite1.hitbox.colliderect(sprite2.rect)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
enemies = pg.sprite.Group()
player = Player((100, 300), all_sprites)
enemy = Enemy((320, 240), all_sprites, enemies)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
player.rect.center = event.pos
all_sprites.update()
collided_sprites = pg.sprite.spritecollide(
player, enemies, False, collided=hitbox_collision)
for enemy_sprite in collided_sprites:
print(enemy_sprite)
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.draw.rect(screen, (0, 255, 0), player.rect, 1)
pg.draw.rect(screen, (255, 0, 0), player.hitbox, 1)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
If you want a circular collision area, you can just pass the pygame.sprite.collide_circle function as the collided argument.
Give your sprites a self.radius attribute,
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.radius = 100
and in the main loop pass collide_circle to spritecollide.
collided_sprites = pg.sprite.spritecollide(
player, enemies, False, collided=pg.sprite.collide_circle)
So, there's a couple things here. Starting with your second question (as I understand it from how you wrote it), you're correct to want to get rid of the 100 pixels. Having "100" randomly in your code is what's known as a Magic Number/Constant and is bad programming style for a few reasons:
There is no indication anywhere what that 100 is supposed to represent
In many cases (and yours specifically) Magic Numbers get replicated in multiple places. When that number changes (9-out-of-10 times, it does), you'll have to dig through all the code as well as related scripts to update the number and hope you didn't miss it anywhere
Related to 2, you may find out that you want that number (in this case, the hitbox/aura radius) to change often. Hardcoding it makes that difficult or impossible.
Chances are, this section of code is better off as its own function anyway, so you can simply have "hitradius" as a keyword parameter and then substitute "hitradius" for 100.
Either alongside or in place of this, you can track hitradius as an attribute of another object (i.e., the player or other sprite) and then substitute it in the same manner (e.g.- sprite.x - player.hitradius > player.x [etc]). If you're sure that it's never going to change, then have it as an accessible variable somewhere and use the same technique.
def check_aura_collision(player,sprite,hitradius = 100):
""" Checks if the player sprite is within hitradius (default 100)
pixels of another sprite.
"""
if (sprite.x + hitradius > player.x > sprite.x - hitradius )\
and (sprite.y + hitradius > player.y > sprite.y - hitradius ):
print("hit")
return True
return False
As for simplifying the logic, it's simple enough already; I might have used absolute value instead, but that's not really a meaningful change. Conversely, however, you may be interested in making it slightly more complex by using some basic trig to check a circular aura instead of the square aura you have right now.

Is it possible to use virtual width and height in pygame? [duplicate]

So I'm making a 2D pixel art game in pygame and as you could assume, all my sprite textures appear very small. I'm wondering if there's a way I can globally scale everything up in my game without either having to scale each sprite up individually or messing up the coordinates. Every sprite will move on a grid: one unit is 16x16 pixels and when my player sprite moves, for example, it will just move over in a direction 16 pixels.
Here's my main script:
import sys
from pygame.locals import *
import pygame
from game.sprites import Ghost
pygame.init()
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 640
DES_WIDTH = 64
DES_HEIGHT = 64
COL_BG = (46, 48, 55)
COL_FG = (235, 229, 206)
X = 1000
Y = 1000
win = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Through The Doors")
running = True
paused = False
# INITIALIZE SPRITES
player = Ghost()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)
clock = pygame.time.Clock()
while running:
clock.tick(30)
if not paused:
win.fill(COL_BG)
all_sprites.update()
all_sprites.draw(win)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
player.go_right()
elif keys[pygame.K_LEFT]:
player.go_left()
elif keys[pygame.K_UP]:
player.go_up()
elif keys[pygame.K_DOWN]:
player.go_down()
pygame.display.flip()
pygame.quit()
I do have more sprites I am going to load in, but I would like to resolve the scaling issue first.
I'm wondering if there's a way I can globally scale everything up in my game without either having to scale each sprite up individually [...]"
No there is no way. You have to scale each coordinate, each size and each surface individually. PyGame is made for images (Surfaces) and shapes in pixel units. Anyway up scaling an image will result in either a fuzzy, blurred or jagged (Minecraft) appearance.
Is there a way I could make a separate surface and just put that on top of the base window surface, and just scale that?
Yes of course.
Create a Surface to draw on (win). Use pygame.transform.scale() or pygame.transform.smoothscale() to scale it to the size of the window and blit it to the actual display Surface (display_win):
display_win = pygame.display.set_mode((WINDOW_WIDTH*2, WINDOW_HEIGHT*2))
win = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT))
while running:
# [...]
if not paused:
win.fill(COL_BG)
all_sprites.update()
all_sprites.draw(win)
# [...]
scaled_win = pygame.transform.smoothscale(win, display_win.get_size())
# or scaled_win = pygame.transform.scale(win, display_win.get_size())
display_win.blit(scaled_win, (0, 0))
pygame.display.flip()
Minimal example: repl.it/#Rabbid76/PyGame-UpScaleDisplay

Categories

Resources