Pygame key.get_pressed acted on unintended object? - python-3.x

I am writing a simple class Block which holds four Rect from pygame. It has a key_press function which would move the four Rect when according keys are pressed. It also has a draw function which draws the four Rect on the screen.
The problem comes within the while loop below. Within the while loop, I want to move the block around with keys. Whenever the current block reaches the bottom of the screen, it stops there and a new block gets created. The key press functionality should now ONLY act on the new block.
However, the problem I have with the codes below is that, after the new block is created, the key press acts on BOTH of the blocks (i.e. pressing right would move both to the right).
so I guess my problem could be summed up into one question:
Why didn't the key press function act on the current block only, but both?
Thank you very much in advance!
import pygame
from pygame.rect import Rect
pygame.init()
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
# Set the height and width of the screen
block_size = 20
row, col = 20, 20
color = RED
size = [block_size * col, block_size * row]
screen = pygame.display.set_mode(size)
class Block(Rect):
shape = [] # hold blocks which are part of the shape
def __init__(self):
self.block1 = Rect(0, block_size, block_size, block_size)
self.block2 = Rect(0, block_size * 2, block_size, block_size)
self.block3 = Rect(0, block_size * 3, block_size, block_size)
self.block4 = Rect(0, block_size * 4, block_size, block_size)
self.shape.append(self.block1)
self.shape.append(self.block2)
self.shape.append(self.block3)
self.shape.append(self.block4)
def key_press(self):
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
for item in self.shape:
item.move_ip(-block_size, 0)
if key[pygame.K_RIGHT]:
for item in self.shape:
item.move_ip(block_size, 0)
if key[pygame.K_UP]:
for item in self.shape:
item.move_ip(0, -block_size)
if key[pygame.K_DOWN]:
for item in self.shape:
item.move_ip(0, block_size)
def draw(self, surface):
for item in self.shape:
pygame.draw.rect(surface, color, item)
def get_bottom(self):
return self.block4.bottom
done = False
clock = pygame.time.Clock()
curr_block = Block()
while not done:
clock.tick(10)
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done = True # Flag that we are done so we exit this loop
screen.fill(WHITE)
curr_block.key_press()
if curr_block.get_bottom() >= block_size * row:
curr_block = Block()
curr_block.draw(screen) # draw the falling block
pygame.display.flip()
pygame.quit()

Every time when a new block is created by curr_block = Block(), then 4 new elements are append at the end of the list self.shape. If you only want to move the most resent blocks, then then you have to move the last 4 elements of self.shape. The last 4 elements can be accessed by self.shape[-4:] (See Subscriptions). For instance:
class Block(Rect):
# [...]
def key_press(self):
key = pygame.key.get_pressed()
for item in self.shape[-4:]:
if key[pygame.K_LEFT]:
item.move_ip(-block_size, 0)
if key[pygame.K_RIGHT]:
item.move_ip(block_size, 0)
if key[pygame.K_UP]:
item.move_ip(0, -block_size)
if key[pygame.K_DOWN]:
item.move_ip(0, block_size)

Related

efficient way to draw pixel art in pygame

I'm making a very simple pixel art software in pygame. My logic was creating a grid class, which has a 2D list, containing 0's. When I click, the grid approximates the row and column selected, and mark the cell with a number, corresponding to the color. For simplicity, let's say '1'.
The code works correctly, but It's slow. If the number of rows and columns is less or equal than 10, It works perfectly, but if It's more, it's very laggy.
I think the problem is that I'm updating the entire screen everytime, and, since the program has to check EVERY cell, It can't handle a bigger list
import pygame
from grid import Grid
from pincel import Pincel
from debugger import Debugger
from display import Display
from pygame.locals import *
pygame.init()
pygame.mixer_music.load("musica/musica1.wav")
pygame.mixer_music.play(-1)
width = 1300
height = 1300
screen = pygame.display.set_mode((1366, 768), pygame.RESIZABLE)
pygame.display.set_caption("SquareDraw")
#Grid Creator
numberOfRows = 25
numberOfColumns = 25
grid = Grid(numberOfRows, numberOfColumns)
# Medidas
basicX = width / numberOfColumns
basicY = height / numberOfRows
#Tool Creator
pincel = Pincel(2)
#variáveis de controle
running = 1
#Initial values
grid.equipTool(pincel)
#variáveis de controle de desenho
clicking = 0
def drawScreen(screen, grid, rows, columns, basicX, basicY):
for i in range(rows):
for j in range(columns):
if grid[i][j]:
print('yes')
print(i, j)
pygame.draw.rect(screen, (0, 0, 0), (j * basicX, i * basicY, basicX, basicY))
while running:
screen.fill((255, 255, 255))
Display.drawScreen(screen, grid.board, grid.getRows(), grid.getColumns(), basicX, basicY)
pygame.display.flip()
events = pygame.event.get()
for event in events:
if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
running = 0
if event.type == pygame.MOUSEBUTTONDOWN or clicking:
clicking = 1
x, y = pygame.mouse.get_pos()
Debugger.printArray2D(grid.board)
print('')
xInGrid = int(x / basicX)
yInGrid = int(y / basicY)
grid.ferramenta.draw(grid.board, xInGrid, yInGrid)
Debugger.printArray2D(grid.board)
print('')
if event.type == pygame.MOUSEBUTTONUP:
clicking = 0
if event.type == pygame.VIDEORESIZE:
width = event.w
height = event.h
basicX = width / numberOfColumns
basicY = height / numberOfRows
print(width, height)
pygame.quit()
The class grid contains the 2D list. The class "Pincel" marks the cells and The class "Debugger" is just for printing lists or anything related to debugging.
Is there a way to update only the part of the screen that was changed? If so, how can I apply that in my logic?
Thanks in advance :)
A few things:
Use the grid array to store the on\off blocks of the screen. It only gets read when the screen is resized and needs a full redraw
When a new rectangle is turned on, draw the rectangle directly in the event handler and update the grid array. There is no need to redraw the entire screen here.
In the resize event, reset the screen mode to the new size then redraw the entire screen using the grid array. This is the only time you need to do a full redraw.
Here is the updated code:
import pygame
#from grid import Grid
#from pincel import Pincel
#from debugger import Debugger
#from display import Display
from pygame.locals import *
pygame.init()
#pygame.mixer_music.load("musica/musica1.wav")
#pygame.mixer_music.play(-1)
width = 1000
height = 1000
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
pygame.display.set_caption("SquareDraw")
#Grid Creator
numberOfRows = 250
numberOfColumns = 250
#grid = Grid(numberOfRows, numberOfColumns)
grid = [[0 for x in range(numberOfRows)] for y in range(numberOfColumns)] # use array for grid: 0=white, 1=black
# Medidas
basicX = width / numberOfColumns
basicY = height / numberOfRows
#Tool Creator
#pincel = Pincel(2)
#xx
running = 1
#Initial values
#grid.equipTool(pincel)
#xx
clicking = 0
def drawScreen(screen, grid, basicX, basicY): # draw rectangles from grid array
for i in range(numberOfColumns):
for j in range(numberOfRows):
if grid[i][j]:
#print('yes')
#print(i, j)
pygame.draw.rect(screen, (0, 0, 0), (j * basicX, i * basicY, basicX, basicY))
screen.fill((255, 255, 255)) # start screen
while running:
events = pygame.event.get()
for event in events:
if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
running = 0
if event.type == pygame.MOUSEBUTTONDOWN or clicking: # mouse button down
clicking = 1
x, y = pygame.mouse.get_pos()
#Debugger.printArray2D(grid.board)
#print('')
xInGrid = int(x / basicX)
yInGrid = int(y / basicY)
grid[yInGrid][xInGrid] = 1 # save this point = 1, for screen redraw (if resize)
pygame.draw.rect(screen, (0, 0, 0), (xInGrid * basicX, yInGrid * basicY, basicX, basicY)) # draw rectangle
#grid.ferramenta.draw(grid.board, xInGrid, yInGrid)
#Debugger.printArray2D(grid.board)
#print('')
pygame.display.flip() # update screen
if event.type == pygame.MOUSEBUTTONUP:
clicking = 0
if event.type == pygame.VIDEORESIZE: # screen resized, must adjust grid height, width
width = event.w
height = event.h
basicX = width / numberOfColumns
basicY = height / numberOfRows
#print(width, height)
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE) # reset screen with new height, width
screen.fill((255, 255, 255)) # clear screen
drawScreen(screen, grid, basicX, basicY) # redraw rectangles
pygame.display.flip() # update screen
pygame.quit()

moving a square in a grid

making a square move in a grid
import pygame
from pygame.locals import *
pygame.init()
clock = pygame.time.Clock()
w = 1008
h = 640
left = 16
top = 16
width = 16
height = 16
YELLOW = (255, 255, 55)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
bg = (255, 255, 255)
x = 0
y = 0
screen = pygame.display.set_mode((w, h))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == KEYDOWN:
if event.key == K_DOWN:
top += 16
if event.key == K_UP:
top -= 16
if event.key == K_RIGHT:
left += 16
if event.key == K_LEFT:
left -= 16
x += 16
y += 16
screen.fill(BLACK)
pygame.draw.rect(screen, [255, 255, 55], [left, top, width, height], 0)
pygame.draw.line(screen, bg, (w - 16, y), (16, y), 1)
pygame.draw.line(screen, bg, (x, h - 16), (y, 16), 1)
pygame.display.flip()
pygame.quit()
at the moment if I # out screen.fill() grid appears on the screen and the rect moves with a tail behind, if I take away the # from the screen.fill() grid disappears but rect moves correct with no tail I want both to happen.
The issue with your code is that it isn't doing what you might expect. Every time you iterate through your while loop, you increment the x and y values by 16 and draw a single horizontal and vertical line. This appears to draw a grid, because the screen is NOT cleared automatically between your while loop iterations. However, as a result, after a while, your x and y values increase to the point that you start drawing more and more grid lines that are off the screen (try to print out the start and end points for these lines in your loop if this is unclear)!
When you add the screen.fill(BLACK) call before you start drawing grid lines, you basically clear the entire screen. Since you do not redraw your old grid lines, only the next pair of grid lines are drawn and the rest are lost. Since the new pair of grid lines eventually goes off the screen (as described in the first paragraph), your code does not appear to draw any grid lines.
When you comment out screen.fill(BLACK) instead, previous drawings are not cleared. All of the grid is drawn over multiple while loop iterations (in addition to wastefully drawing new grid lines off screen), as is all of the previously positions your player is drawn at.
To solve this, your game drawing loop should be structured like this:
Clear the screen.
Draw the entire grid, not just two lines at a time. Do this in a function (e.g. draw_grid).
Draw the rectangle at the new player position. Do this in a function as well (e.g. draw_player).
Rinse and repeat next iteration!
Implementing these small changes, your code could look like this:
import pygame
from pygame.locals import *
def draw_grid():
screen.fill(BLACK)
for y in range(height, h, height):#horizontal lines
pygame.draw.line(screen, bg, (width, y), (w - width, y), 1)
for x in range(width, w, width):#vertical lines
pygame.draw.line(screen, bg, (x, height), (x, h - height), 1)
def draw_player():
pygame.draw.rect(screen, [255, 255, 55], [left, top, width, height], 0)
pygame.init()
clock = pygame.time.Clock()
w = 1008
h = 640
left = 16
top = 16
width = 16
height = 16
YELLOW = (255, 255, 55)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
bg = (255, 255, 255)
x = 0
y = 0
screen = pygame.display.set_mode((w, h))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == KEYDOWN:
if event.key == K_DOWN:
top += 16
if event.key == K_UP:
top -= 16
if event.key == K_RIGHT:
left += 16
if event.key == K_LEFT:
left -= 16
draw_grid()
draw_player()
pygame.display.flip()
pygame.quit()
That being said, there is plenty of room for improvement for this code, which I will leave up to you. The big issue is that you are using a bunch of global variables. These are basically all of the variables listed before your while loop. Use of such global variables is "evil" when they are mutable. As your code gets bigger, it will become more difficult to reason what functions are changing these variables, since every function could potentially have access to these variables.

PyGame Color Adjustment only working for one of three Sliders

I am working on an example from an older book, so far I have the program working but the color adjustment only works for the third "blue" slider. I tried troubleshooting by changing the range() found after get_pressed(). I changed it from 3 to 2 to 1 and they all behaved the same. I don't understand, I was thinking it would move with the changes.
import pygame as pg
from pygame.locals import *
from sys import exit
pg.init()
WIDTH, HEIGHT = 800, 600
screen = pg.display.set_mode((WIDTH, HEIGHT), 0, 32)
color = [127, 127, 127]
# Creates an image with smooth gradients
def createScales(height):
redScaleSurface = pg.surface.Surface((WIDTH, height))
greenScaleSurface = pg.surface.Surface((WIDTH,height))
blueScaleSurface = pg.surface.Surface((WIDTH,height))
for x in range(WIDTH):
c =int((x/(WIDTH-1.))*255.)
red = (c, 0, 0)
green = (0, c, 0)
blue = (0, 0, c)
line_rect = Rect(x, 0, 1, height)
pg.draw.rect(redScaleSurface, red, line_rect)
pg.draw.rect(greenScaleSurface, green, line_rect)
pg.draw.rect(blueScaleSurface, blue, line_rect)
return redScaleSurface, greenScaleSurface, blueScaleSurface
redScale, greenScale, blueScale = createScales(80)
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
exit()
screen.fill((0, 0, 0))
# Draw the scales to the screen
screen.blit(redScale, (0, 00))
screen.blit(greenScale, (0, 80))
screen.blit(blueScale, (0, 160))
x, y = pg.mouse.get_pos()
# If the mouse was pressed on one of the sliders, adjust the color component
if pg.mouse.get_pressed()[0]:
for compononent in range(3):
if y > component*80 and y < (component+1)*80:
color[component] = int((x/(WIDTH-1.))*255.)
pg.display.set_caption("PyGame Color Test - " + str(tuple(color)))
# Draw a circle for each slider to represent the curret setting
for component in range(3):
pos = ( int((color[component]/255.)*(WIDTH-1)), component*80+40 )
pg.draw.circle(screen, (255, 255, 255), pos, 20)
pg.draw.rect(screen, tuple(color), (0, 240, WIDTH, HEIGHT/2))
pg.display.update()
On line 49, I made a typo and wrote component as compononent. Once fixed, I now have a neat interactive program for demonstrating computers' additive color model!

How to blit multiple images while looping over a list of filenames?

I am trying to blit multiple images of a moving car, using a for loop over the filenames of the images. However, it would just draw the screen but not actually show/blit the images. I am using python3.6 .
Here is my code.
import pandas as pd
import pygame
# BLACK = ( 0, 0, 0)
# WHITE = (255, 255, 255)
# BLUE = ( 0, 0, 255)
# GREEN = ( 0, 255, 0)
# RED = (255, 0, 0)
df = pd.read_csv('./result.csv')
preds = df['Predicted Angles']
true = df['Actual Angles']
filenames = df['File']
pygame.init()
size = (640, 320)
pygame.display.set_caption("Data viewer")
screen = pygame.display.set_mode(size, pygame.DOUBLEBUF)
myfont = pygame.font.SysFont("monospace", 15)
for i in range(len(list(filenames))):
img = pygame.image.load(filenames.iloc[i])
screen.blit(img, (0, 0))
pygame.display.flip()
View of the results.csv
First load all images and put them into a list or other data structure, then assign the current image to a variable and change it after the desired time interval (you can use one of these timers).
I'm just using some colored pygame.Surfaces in the example below and change the current image/surface with the help of a custom event and the pygame.time.set_timer function which adds the event to the event queue when the specified time has passed.
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
images = []
# Three differently colored surfaces for demonstration purposes.
for color in ((0, 100, 200), (200, 100, 50), (100, 200, 0)):
surface = pg.Surface((200, 100))
surface.fill(color)
images.append(surface)
index = 0
image = images[index]
# Define a new event type.
CHANGE_IMAGE_EVENT = pg.USEREVENT + 1
# Add the event to the event queue every 1000 ms.
pg.time.set_timer(CHANGE_IMAGE_EVENT, 1000)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == CHANGE_IMAGE_EVENT:
# Increment the index, use modulo len(images)
# to keep it in the correct range and change
# the image.
index += 1
index %= len(images)
image = images[index] # Alternatively load the next image here.
screen.fill(BG_COLOR)
# Blit the current image.
screen.blit(image, (200, 200))
pg.display.flip()
clock.tick(30)
pg.quit()

Bullet to Enemy Collision Detection

After a few hours of looking for code to reference. I failed i tried to re-write the collision code but it failed. Now my Question is
How can i fix my code to make it delete the Enemy ship when the bullet hits him
Note: Im not asking for you to do it, i just need it further explained what the problem or where im making a mistake
The Code Segment
for bullet in Bullets:
Hit_list = pygame.sprite.Group()
if pygame.sprite.collide_rect(bullet, Enemy_list):
Hit_list.add(hit)
for hit in Hit_list:
Bullets.remove(bullet)
All.remove(bullet)
All.remove(hit)
Enemy_list.remove(hit)
# If the bullet goes off the screen it deletes
if bullet.rect.y < 0:
Bullets.remove(bullet)
All.remove(bullet)
The Error
Traceback (most recent call last):
File "C:\Users\jb127996\Desktop\Invaders in Space\Space Shooter.py", line 92, in <module>
if pygame.sprite.collide_rect(bullet, Enemy_list):
File "C:\Python32\lib\site-packages\pygame\sprite.py", line 1290, in collide_rect
return left.rect.colliderect(right.rect)
AttributeError: 'Group' object has no attribute 'rect'
Entire Code
import pygame
import Player_func
import AI
from pygame.locals import *
#Background
Background = pygame.image.load('Tempback.png')
Background_size = Background.get_size()
Background_rect = Background.get_rect()
# Screens Resoultion
Width = 640
Height = 480
Scr = pygame.display.set_mode((Width,Height))
# Fps Clock Vars( Clock.tick(FPS) )
Clock = pygame.time.Clock()
FPS = 60
# Colors
White = (255, 255, 255)
Black = (0, 0, 0)
Green = (0, 255, 0)
Red = (255, 0, 0)
Blue = (0, 0, 255)
Orange = (255, 140, 0)
Default = White
# Sprite Lists
Bullets = pygame.sprite.Group()
Enemy_list = pygame.sprite.Group()
All = pygame.sprite.Group()
# Shorting the Class files
AI = AI.Enemy()
Ply = Player_func.Player()
# CORDS for Player
AI.rect.x, AI.rect.y = 100, 50
AI_pos = (AI.rect.x, AI.rect.y)
Ply.rect.x, Ply.rect.y = 580, 425
P_pos = (Ply.rect.x, Ply.rect.y)
# True false statements and Vars
running = True
Gun_Overload = 0
Fire = 20
while running:
Enemy_list.add(AI)
All.add(AI)
All.add(Ply)
# Collision Detection for walls on the spaceship
if Ply.rect.x <= 0:
Ply.rect.x += 5.5
elif Ply.rect.x >= 590:
Ply.rect.x -= 5.5
# Events
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN:
if event.key == K_SPACE and Gun_Overload == Fire:
bullet = Player_func.Bullet()
bullet.rect.x = (Ply.rect.x +15)
bullet.rect.y = Ply.rect.y
Bullets.add(bullet)
All.add(bullet)
Gun_Overload = 0
#Cool Down
if Gun_Overload < Fire:
Gun_Overload += 1
AI.movement()
Ply.Controls()
# Updates the Sprite lists so they can all be drawn
All.update()
# Checks if a bullet hits the enemy on the list
for bullet in Bullets:
Hit_list = pygame.sprite.Group()
if pygame.sprite.collide_rect(bullet, Enemy_list):
Hit_list.add(hit)
for hit in Hit_list:
Bullets.remove(bullet)
All.remove(bullet)
All.remove(hit)
Enemy_list.remove(hit)
# If the bullet goes off the screen it deletes
if bullet.rect.y < 0:
Bullets.remove(bullet)
All.remove(bullet)
Scr.blit(Background, Background_rect)
All.draw(Scr)
pygame.display.flip()
# FPS Clock Loaded in
Clock.tick(FPS)
pygame.quit()
Your If condition uses collide_rect:
pygame.sprite.collide_rect()
Collision detection between two sprites, using rects.
collide_rect(left, right) -> bool
You pass 2 parameters - a Bullet, and a SpriteGroup. The error is telling you that a SpriteGroup does not have a Rect attribute.
I think you wanted to use:
pygame.sprite.spritecollide()
Find sprites in a group that intersect another sprite.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
Apart from this, I would change all the variable names to lowercase. I have also noticed that your if bullet.rect.y < 0: is under the collide test. I think you want to remove a Bullet from the screen even if there is no collision.

Resources