What is the most efficient way of drawing a grid with Pygame? - python-3.x

Is there a significant difference in term of performance drawing one rectangle for every cell of the grid compared to drawing the grid using horizontal and vertical lines ?

Yes, using lines drawing is one or more order of magnitude faster than drawing rectangles. The code below times both methods.
import pygame
import os
import time
TITLE = "Draw grid"
WINDOWS_LOCATION = '100,100'
WIDTH = 710
HEIGHT = 710
FPS = 1
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
CELL_SIZE = 15
GRID_COORD_MARGIN_SIZE = 20
class Grid():
def __init__(self, surface, cellSize, axisLabels):
self.surface = surface
self.colNb = surface.get_width() // cellSize
self.lineNb = surface.get_height() // cellSize
self.cellSize = cellSize
self.axisLabels = axisLabels
self.grid = [[0 for i in range(self.colNb)] for j in range(self.lineNb)]
self.font = pygame.font.SysFont('arial', 12, False)
def drawUseRect(self):
for li in range(self.lineNb):
liCoord = GRID_COORD_MARGIN_SIZE + li * CELL_SIZE
if self.axisLabels:
if li < 10:
ident = ' '
else:
ident = ' '
text = self.font.render(ident + str(li), 1, (0, 0, 0))
self.surface.blit(text, (0, liCoord))
for co in range(self.colNb):
colCoord = GRID_COORD_MARGIN_SIZE + co * CELL_SIZE
if self.axisLabels:
if co < 10:
ident = ' '
else:
ident = ' '
text = self.font.render(ident + str(co), 1, (0, 0, 0))
self.surface.blit(text, (colCoord, 1))
pygame.draw.rect(self.surface, BLACK, pygame.Rect(liCoord, colCoord, CELL_SIZE, CELL_SIZE), 1)
def drawUseLine(self):
for li in range(self.lineNb):
liCoord = GRID_COORD_MARGIN_SIZE + li * CELL_SIZE
if self.axisLabels:
if li < 10:
ident = ' '
else:
ident = ' '
text = self.font.render(ident + str(li), 1, (0, 0, 0))
self.surface.blit(text, (0, liCoord))
pygame.draw.line(self.surface, BLACK, (GRID_COORD_MARGIN_SIZE, liCoord), (self.surface.get_width(), liCoord))
for co in range(self.colNb):
colCoord = GRID_COORD_MARGIN_SIZE + co * CELL_SIZE
if self.axisLabels:
if co < 10:
ident = ' '
else:
ident = ' '
text = self.font.render(ident + str(co), 1, (0, 0, 0))
self.surface.blit(text, (colCoord, 1))
pygame.draw.line(self.surface, BLACK, (colCoord, GRID_COORD_MARGIN_SIZE), (colCoord,self.surface.get_height()))
# setting Pygame window position (ok on Windows ...)
os.environ['SDL_VIDEO_WINDOW_POS'] = WINDOWS_LOCATION
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
clock = pygame.time.Clock()
running = True
axisLabels = False
grid = Grid(surface=screen, cellSize=CELL_SIZE, axisLabels=axisLabels)
while running:
clock.tick(FPS)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
screen.fill(WHITE)
start = time.time()
grid.drawUseRect()
print("draw using rect: {}".format(time.time() - start))
start = time.time()
grid.drawUseLine()
print("draw using line: {}".format(time.time() - start))
# *after* drawing everything, flip the display
pygame.display.flip()
pygame.quit()
Here's a example of the timing output on a Windows 10 tablet pc:
draw using rect: 0.04449152946472168
draw using line: 0.0008492469787597656
draw using rect: 0.012907981872558594
draw using line: 0.0
draw using rect: 0.027329683303833008
draw using line: 0.002046346664428711
draw using rect: 0.019585847854614258
draw using line: 0.0009150505065917969
draw using rect: 0.022649049758911133
draw using line: 0.0009458065032958984
draw using rect: 0.022264480590820312
draw using line: 0.0017306804656982422
draw using rect: 0.022340059280395508
draw using line: 0.0009708404541015625
draw using rect: 0.04112124443054199
draw using line: 0.0019001960754394531
And here's a comparison of the visual result of drawing the grid (variable axisLabel set to True):

Related

too many values to unpack (expected 3) pygame click issue [duplicate]

This question already has answers here:
Pygame - Mouse clicks not getting detected
(2 answers)
Closed 2 years ago.
I am currently working on a CS50 project studying the AI required for tic-tac-toe. however I can't get the runner.py file to run, as I get the following error:
Traceback (most recent call last): File
"/Users/newtracksuit/Downloads/tictactoe/runner.py", line 57, in
click, _, _ = pygame.mouse.get_pressed() ValueError: too many values to unpack (expected 3)
here is the full code provided, I have yet to write any actual functionality to the tic-tac-toe program, Just need to understand why this is not working yet
import pygame
import sys
import time
import tictactoe as ttt
pygame.init()
size = width, height = 600, 400
# Colors
black = (0, 0, 0)
white = (255, 255, 255)
screen = pygame.display.set_mode(size)
mediumFont = pygame.font.Font("OpenSans-Regular.ttf", 28)
largeFont = pygame.font.Font("OpenSans-Regular.ttf", 40)
moveFont = pygame.font.Font("OpenSans-Regular.ttf", 60)
user = None
board = ttt.initial_state()
ai_turn = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(black)
# Let user choose a player.
if user is None:
# Draw title
title = largeFont.render("Play Tic-Tac-Toe", True, white)
titleRect = title.get_rect()
titleRect.center = ((width / 2), 50)
screen.blit(title, titleRect)
# Draw buttons
playXButton = pygame.Rect((width / 8), (height / 2), width / 4, 50)
playX = mediumFont.render("Play as X", True, black)
playXRect = playX.get_rect()
playXRect.center = playXButton.center
pygame.draw.rect(screen, white, playXButton)
screen.blit(playX, playXRect)
playOButton = pygame.Rect(5 * (width / 8), (height / 2), width / 4, 50)
playO = mediumFont.render("Play as O", True, black)
playORect = playO.get_rect()
playORect.center = playOButton.center
pygame.draw.rect(screen, white, playOButton)
screen.blit(playO, playORect)
# Check if button is clicked
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
if playXButton.collidepoint(mouse):
time.sleep(0.2)
user = ttt.X
elif playOButton.collidepoint(mouse):
time.sleep(0.2)
user = ttt.O
else:
# Draw game board
tile_size = 80
tile_origin = (width / 2 - (1.5 * tile_size),
height / 2 - (1.5 * tile_size))
tiles = []
for i in range(3):
row = []
for j in range(3):
rect = pygame.Rect(
tile_origin[0] + j * tile_size,
tile_origin[1] + i * tile_size,
tile_size, tile_size
)
pygame.draw.rect(screen, white, rect, 3)
if board[i][j] != ttt.EMPTY:
move = moveFont.render(board[i][j], True, white)
moveRect = move.get_rect()
moveRect.center = rect.center
screen.blit(move, moveRect)
row.append(rect)
tiles.append(row)
game_over = ttt.terminal(board)
player = ttt.player(board)
# Show title
if game_over:
winner = ttt.winner(board)
if winner is None:
title = f"Game Over: Tie."
else:
title = f"Game Over: {winner} wins."
elif user == player:
title = f"Play as {user}"
else:
title = f"Computer thinking..."
title = largeFont.render(title, True, white)
titleRect = title.get_rect()
titleRect.center = ((width / 2), 30)
screen.blit(title, titleRect)
# Check for AI move
if user != player and not game_over:
if ai_turn:
time.sleep(0.5)
move = ttt.minimax(board)
board = ttt.result(board, move)
ai_turn = False
else:
ai_turn = True
# Check for a user move
click, _, _ = pygame.mouse.get_pressed()
if click == 1 and user == player and not game_over:
mouse = pygame.mouse.get_pos()
for i in range(3):
for j in range(3):
if (board[i][j] == ttt.EMPTY and tiles[i][j].collidepoint(mouse)):
board = ttt.result(board, (i, j))
if game_over:
againButton = pygame.Rect(width / 3, height - 65, width / 3, 50)
again = mediumFont.render("Play Again", True, black)
againRect = again.get_rect()
againRect.center = againButton.center
pygame.draw.rect(screen, white, againButton)
screen.blit(again, againRect)
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
if againButton.collidepoint(mouse):
time.sleep(0.2)
user = None
board = ttt.initial_state()
ai_turn = False
pygame.display.flip()
pygame.mouse.get_pressed()
Returns a sequence of booleans representing the state of all the mouse buttons.
The number of buttons is either 4 or 6, that depends on the version of pygame. I recommend to get the state of a single button by subscription:
buttons = pygame.mouse.get_pressed()
if button[0] == 1:
# [...]
What is returned when you execute pygame.mouse.get_pressed()? I see (0, 0, 0) which is the three arguments your code expects.
You could work out what your code is returning and add the requisite number of underscores, but if you aren't interested in the remaining arguments, you can use * to handle the rest of the returned values, e.g.
click, *_ = pygame.mouse.get_pressed()

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()

My player sprite keeps on disappearing if it lands on the platform

I am trying to build my first Platformer Game. So far, I have make moving left and right but unfortunately, I have encountered a error that I have not been able to fix when I implemented collisions and gravity. My player keeps on dissolving like spider man if it lands on the platform. The character is still existent, and lands on the platform, but unfortunately, he becomes invisible. There is no error message, and I suspect is has to do with the collision check.
hits = pygame.sprite.spritecollide(object, allPlatforms, False)
if hits:
object.rect.y = hits[0].rect.top + 1
object.vy = 0
print(object.rect.midbottom)
It prints out the Players location in the code, and the player is still existent and movable, but it just doesn't show. Is there something that I did that makes the character vanish?
import pygame
import random
WIDTH = 500
HEIGHT = 400
FPS = 30
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
playerImage = "blockBandit/BlockBandit.png"
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50))
self.image = pygame.image.load(playerImage).convert()
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.vx = 0
self.vy = 0
class Platform(pygame.sprite.Sprite):
def __init__(self, x, y, w, h):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((w, h))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Block Bandit")
clock = pygame.time.Clock()
allPlatforms = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
p1 = Platform(0, HEIGHT - 40, WIDTH, 40)
all_sprites.add(p1)
allPlatforms.add(p1)
def moveCharacter(object):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
object.vx += -2
if keys[pygame.K_RIGHT]:
object.vx += 2
object.vx = object.vx * 0.9
if (abs(object.vx) < 1):
object.vx = 0
if (abs(object.vx) > 10):
if(object.vx < 0):
object.vx = -10
else:
object.vx = 10
object.vy = object.vy + 1
object.rect.x += object.vx
object.rect.y += object.vy
hits = pygame.sprite.spritecollide(object, allPlatforms, False)
if hits:
object.rect.y = hits[0].rect.top + 1
object.vy = 0
print(object.rect.midbottom)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
moveCharacter(player)
#Update State
all_sprites.update()
#Render
screen.fill(BLACK)
all_sprites.draw(screen)
#screen.blit(player.icon, (20, 40))
pygame.display.flip()
pygame.quit()
Am I doing something wrong? Thanks!
The y attribute of the rect is the same as the top coordinate, so you're setting the top of the player sprite to the top of the platform sprite here object.rect.y = hits[0].rect.top + 1. And if the platform comes later in the sprite group, it will be blitted after the player and the player won't be visible.
Just change that line to object.rect.bottom = hits[0].rect.top + 1.

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!

Collision between two circle in pygame [duplicate]

This question already has answers here:
Pygame how to let balls collide
(2 answers)
pygame Get the balls to bounce off each other
(1 answer)
Closed 2 years ago.
I am trying to create my first game, where I have successfully added a bouncing ball and another ball around the mouse-pointer. The collision between these two circles works fine except sometimes the circles intersects each other hugely and the collision does not occur in a single frame, and some strange behaviour thereafter. How do I fix this?
import pygame
from pygame.locals import *
import numpy as np
# Define the colors we will use in RGB format
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
width = 800
height = 600
radius = 40
mRadius = 60
base = 20
g = 0.2
pygame.init()
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption('Testing')
clock = pygame.time.Clock()
class Circle:
def __init__(self):
self.pos = np.array([100,100])
self.velocity = np.array([2,0])
def upgrade(self,mouse):
self.pos = np.add(self.velocity,self.pos)
if ((self.pos[0] > width - radius - 1) or (self.pos[0] < 0 + radius)):
self.velocity[0] *= -1
if self.pos[1] > height - radius - base - 1:
self.velocity[1] *= -1
#print(str(self.velocity[1])+"\t"+str(self.pos[1]))
else:
self.velocity = np.add(self.velocity,[0,g])
if ((radius + mRadius)**2 >= ((self.pos[0] - mouse[0])**2 +(self.pos[1] - mouse[1])**2)):
self.pos = np.subtract(self.pos,self.velocity)
lx = self.pos[0] - mouse[0]
ly = self.pos[1] - mouse[1]
A = (lx**2 - ly**2)/(lx**2 + ly**2)
B = 2*lx*ly/(lx**2 + ly**2)
M = np.array([[A,B],[B,(-1)*A]])
self.velocity = list((-1)*M.dot(self.velocity))
print(str(lx)+"\t"+str(ly)+"\t"+str(mouse))
pygame.draw.circle(screen, GREEN, [int(self.pos[0]),int(self.pos[1])], radius)
return True
def main():
mouse = [100,100]
running = True
ball = Circle()
while running:
(mouse[0],mouse[1]) = pygame.mouse.get_pos()
screen.fill(WHITE)
pygame.draw.circle(screen, BLUE, mouse, mRadius)
running = ball.upgrade(mouse)
pygame.draw.rect(screen,RED,(0,height-base,width,base))
pygame.display.update()
clock.tick(120)
for event in pygame.event.get():
if event.type == QUIT:
running = False
if event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
pygame.display.quit()
if __name__ == '__main__':
main()
Here is the github link

Resources