I am trying to create a function that will fade in and out for a game that I'm making. the problem is that the first part works fine but the second pard doesn't work.
WIDTH = screen width, HEIGHT = screen height, WINDOW = the name of the window, and I imported pygame as pg
def fade():
fade = pg.Surface((WIDTH, HEIGHT))
fade.fill((0,0,0))
opacity = 0
for r in range(0, 100):
opacity += 1
fade.set_alpha(opacity)
WINDOW.blit(fade, (0,0))
pg.display.update()
pg.time.delay(10)
for r in range(0, 100):
opacity -= 1
fade.set_alpha(opacity)
WINDOW.blit(fade, (0,0))
pg.display.update()
pg.time.delay(10)
From looking at the code everything should work fine but it doesn't. I didn't paste the entire code because its 300 lines.
What they actually do is draw a transparent image over the background over and over again until the background is completely covered. You can't make the background visible again by blending the image with less transparency over it. You have to completely redraw the whole scene in every frame, blending an ever more agile transparent image over it.
I suggest to write a blitFadeIn and blitFadeOut function and call it in the application loop.
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *background.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
[pygame.draw.rect(background, color, rect) for rect, color in tiles]
font = pygame.font.SysFont(None, 100)
text = font.render("image", True, (255, 255, 0))
image = pygame.Surface(window.get_size(), pygame.SRCALPHA)
pygame.draw.ellipse(image, "red", window.get_rect().inflate(-20, -20))
image.blit(text, text.get_rect(center = window.get_rect().center))
image.set_alpha(0)
def blitFadeIn(target, image, pos, step=2):
alpha = image.get_alpha()
alpha = min(255, alpha + step)
image.set_alpha(alpha)
target.blit(image, pos)
return alpha == 255
def blitFadeOut(target, image, pos, step=2):
alpha = image.get_alpha()
alpha = max(0, alpha - step)
image.set_alpha(alpha)
target.blit(image, pos)
return alpha == 0
fade_in = False
fade_out = False
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
if not fade_in and not fade_out:
fade_in = True
window.blit(background, (0, 0))
if fade_in:
done = blitFadeIn(window, image, (0, 0))
if done:
fade_in, fade_out = False, True
if fade_out:
done = blitFadeOut(window, image, (0, 0))
if done:
fade_out = False
pygame.display.flip()
pygame.quit()
exit()
Also see:
How to fade from one colour to another in pygame?
How to fade in a text or an image with PyGame
Pygame: Frame ghosting?
Related
I am making a small simulation where at the current stage i can click somewhere on the screen and color will spread out like a wave. currently it does that, but my system is that it checks each and every squares up, down, left, and right square to see if its red. if it is, make that square red. Then it saves that x,y and does it a bunch more, then it prints all x,y values to the screen all at once. the problem is, every time it does this it keeps all the values in it and has to do more if checks and slows down a lot. is there a better system I can implement that would be better?
if I want to move each pixel out progressively then i cant thing of any other way than checking pixel by pixel. I also cant just hardcode the pixels because I could click anywhere on the screen. Is there a way to make a wave simulation that does not have to check each and every pixel?
import pygame
import sys
width=300
height=300
pygame.init()
surface = pygame.display.set_mode( (500, 500) )
size=1
tfx=1
tfy=1
increment=1
red=[]
surface.fill( (255,255,255) )
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
pygame.draw.rect( surface, (255,0,0), (pos[0],pos[1], size, size) )
#pygame.draw.rect( surface, (255,0,0), (width/2-tfx/2, height/2-tfy/2, size, size) )
#!!!!!! this block of code checks every single pixel on the screen to see if the pixel
#to the right is red and so on. Alternative method in progress that checks in a 1x1 3x3 5x5 square
for x in range(width):
for y in range(height):
if x+size <=width-1 :
color = surface.get_at((x+size,y))
if color[1]==0:
if surface.get_at((x,y))[1] !=0:
red.append((x,y))
#pygame.display.update()
if x-1 >=0:
color = surface.get_at((x-1,y))
if color[1]==0:
if surface.get_at((x,y))[1] !=0:
red.append((x,y))
if y-1 >=0:
color = surface.get_at((x,y-1))
if color[1]==0:
if surface.get_at((x,y))[1] !=0:
red.append((x,y))
if y+1 <=height:
color = surface.get_at((x,y+1))
if color[1]==0:
if surface.get_at((x,y))[1] !=0:
red.append((x,y))
for i in range(len(red)):
pygame.draw.rect( surface, (255,0,0), (red[i][0], red[i][1], size, size) )
pygame.display.update()
#print( surface.get_at((97,99)))
pygame.quit()
Probably the only way to get acceptable performance (with pygame) is to use pygame.mask.Mask and convolve(). Create a mask size of the screen:
mask = pygame.mask.Mask(screen.get_size())
Create a convolution mask with the following pattern:
[False, True, False]
[True, True, True ]
[False, True, False]
convolution_mask = pygame.mask.Mask((3, 3))
convolution_mask.set_at(p) for p in [(0, 1), (1, 1), (2, 1), (1, 0), (1, 2)]]
Set a bit in the mask on mouse click:
if event.type == pygame.MOUSEBUTTONDOWN:
mask.set_at(event.pos)
Create a convolution with the convolution mask in each frame:
mask = mask.convolve(convolution_mask , offset=(-1, -1))
Convert the mask into a surface and blit it on the screen:
surface = mask.to_surface(setcolor = (255, 0, 0), unsetcolor = (255, 255, 255))
screen.blit(surface, (0, 0))
import pygame
import sys
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
mask = pygame.mask.Mask(screen.get_size())
convolution_mask = pygame.mask.Mask((3, 3))
[convolution_mask.set_at(p) for p in [(0, 1), (1, 1), (2, 1), (1, 0), (1, 2)]]
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
mask.set_at(event.pos)
mask = mask.convolve(convolution_mask, offset=(-1, -1))
surface = mask.to_surface(setcolor = (255, 0, 0), unsetcolor = (255, 255, 255))
screen.blit(surface, (0, 0))
pygame.display.update()
pygame.quit()
sys.exit()
Based on #Kingsley's suggestion of remembering storing the wave origin, here's an example that draws circles of increasing radius as time goes by. It will support about five-hundred waves before the frame rate drops on my PC. It isn't drawing filled circles so you can see all the waves, but that should be an easy modification.
import pygame
import pygame.gfxdraw
import time
import math
pygame.init()
width, height = 500, 500
# don't draw circles too large for the screen
max_radius = max(width, height) / math.sin(math.pi / 4)
screen = pygame.display.set_mode((width, height))
class Wave:
"""Class to store the origin position and time of a wave"""
def __init__(self, position, color=pygame.Color("red")):
self.pos = position
self.start_time = time.time()
self.color = color
def get_radius(self):
"""Returns the number of deci-seconds since creation"""
return round(10 * (time.time() - self.start_time))
def draw(self, screen):
radius = self.get_radius()
if radius < max_radius:
x, y = self.pos
pygame.gfxdraw.aacircle(screen, x, y, radius, self.color)
waves = []
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
wave = Wave(event.pos)
waves.append(wave)
## Clear screen
screen.fill("white")
## draw waves
for wave in waves:
wave.draw(screen)
## Update display
pygame.display.update()
## Set window title
pygame.display.set_caption(f"Waves: {len(waves)}, FPS: {clock.get_fps():.1f}")
## Limit frame rate
clock.tick(60)
pygame.quit()
Example:
Here is the board I drew using pygame: https://i.stack.imgur.com/Hne6A.png
I'm facing a bug with the thickness of the last two lines as I marked them on the image. I believe there is something wrong with the if statement in my code but I can't quite figure it out, it just won't take effect.
here is the code that has drawn the board above:
import pygame, sys
pygame.init()
width, height = 750, 750
rows, cols = 9, 9
BLACK = (0,0,0)
screen = pygame.display.set_mode((width, height))
screen.fill((255, 255, 255, 255))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
sys.exit()
surf = pygame.Surface((600, 600))
surf.fill((255,255,255))
padding = surf.get_width()/9
for i in range(rows+1):
if i % 3 == 0:
thick = 4
else:
thick = 1
pygame.draw.line(surf, BLACK, (0, i*padding), (width, i*padding), width=thick)
pygame.draw.line(surf, BLACK, (i*padding, 0), (i*padding, height), width=thick)
surf_center = (
(width-surf.get_width())/2,
(height-surf.get_height())/2
)
screen.blit(surf, surf_center)
pygame.display.update()
pygame.display.flip()
To draw a thick line, half the thickness is applied to both sides of the line. The simplest solution is to make the target surface a little larger and add a small offset to the coordinates of the lines.
For performance reasons, I also recommend creating the surface before the application loop and continuously blit it in the application loop:
import pygame, sys
pygame.init()
width, height = 750, 750
rows, cols = 9, 9
BLACK = (0,0,0)
screen = pygame.display.set_mode((width, height))
surf_size = 600
surf = pygame.Surface((surf_size+4, surf_size+4))
surf.fill((255,255,255))
padding = surf_size/9
for i in range(rows+1):
thick = 4 if i % 3 == 0 else 1
offset = i * padding + 1
pygame.draw.line(surf, BLACK, (0, offset), (width, offset), width=thick)
pygame.draw.line(surf, BLACK, (offset, 0), (offset, height), width=thick)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255, 255))
screen.blit(surf, surf.get_rect(center = screen.get_rect().center))
pygame.display.update()
pygame.display.flip()
pygame.quit()
sys.exit()
The code below should do the trick. The thing is that the width in pygame.draw.line is drawn in the middle of the line. So, the border lines are actually cropped in half because the half on one side and another half on the other side are not taken into account. But the catch is that if the thickness is even there is an offset of 1 on the borders as the width is larger on one of the sides.
Even if the thickness (bold_think) is raised or lowered the algorithm below will work flawlessly without any gaps outside the grid.
Thickness of 4
Thickness of 8
Thinkness of 15
import pygame
import sys
pygame.init()
width, height = 750, 750
rows, cols = 9, 9
BLACK = (0, 0, 0)
screen = pygame.display.set_mode((width, height))
screen.fill((255, 255, 255, 255))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
sys.exit()
surf = pygame.Surface((600, 600))
surf.fill((255, 255, 255))
bold_thick = 4
# Calculates the offset of 1 for the even widths
thick_offset = (bold_thick - 1) % 2
offset = (bold_thick / 2) - thick_offset
# The padding should take into account the borders
padding = (surf.get_width() - bold_thick) / 9
for i in range(rows+1):
if i % 3 == 0:
thick = bold_thick
else:
thick = 1
pygame.draw.line(surf, BLACK, (0, i*padding + offset),
(width, i*padding + offset), width=thick)
pygame.draw.line(surf, BLACK, (i*padding + offset, 0),
(i*padding + offset, height), width=thick)
surf_center = (
(width-surf.get_width())/2,
(height-surf.get_height())/2
)
screen.blit(surf, surf_center)
pygame.display.update()
pygame.display.flip()
I know that there are several similar questions online, but none of them really helped me. I simply want to draw a grid and give the user the option to click into those grid cells. Every time the user clicks, the color/fill of the cell should change from black to white.
What I'm doing at the moment is the following:
BLACK = (0, 0, 0)
WHITE = (200, 200, 200)
def drawGrid(h, w, blocksize):
for x in range(w):
for y in range(h):
rect = pygame.Rect(x*blocksize, y*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, WHITE, rect, 1)
def handle_events():
col = WHITE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
col = WHITE
# determine mouse position
mpos_x, mpos_y = event.pos
# determine cell number
coord = mpos_x // blocksize, mpos_y // blocksize
rect = pygame.Rect(coord[0]*blocksize, coord[1]*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, col, rect, 1)
pygame.display.update()
def main():
global SCREEN, CLOCK, blocksize
w = int( sys.argv[1] )
h = int( sys.argv[2] )
blocksize = int( sys.argv[3] )
pygame.init()
SCREEN = pygame.display.set_mode((h, w))
CLOCK = pygame.time.Clock()
SCREEN.fill(BLACK)
drawGrid(h,w,blocksize)
handle_events()
if __name__ == "__main__":
main()
The program is printing the grid. However, when I click somewhere nothing happens. I know this is not the best code, so I would appreciate for any suggestion.
I changed the code a little and it worked properly, pygame.draw.rect(SCREEN, col, rect, 1) you draw same thing and you can't see the change. You should use pygame.draw.rect(SCREEN, col, rect):
import pygame
import sys
BLACK = (0, 0, 0)
WHITE = (200, 200, 200)
# WINDOW_HEIGHT = 400
# WINDOW_WIDTH = 400
def drawGrid(h, w, blocksize):
#blockSize = 20 #Set the size of the grid block
for x in range(w):
for y in range(h):
rect = pygame.Rect(x*blocksize, y*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, WHITE, rect, 1)
def handle_events():
#coords_list = []
col = WHITE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
col = WHITE
# determine mouse position
mpos_x, mpos_y = event.pos
# determine cell number
coord = mpos_x // 32, mpos_y // 32
rect = pygame.Rect(coord[0]*32, coord[1]*32,
32, 32)
pygame.draw.rect(SCREEN, col, rect)
#coords_list.append(coord)
pygame.display.update()
#return coords_list
def main():
global SCREEN, CLOCK, blocksize
pygame.init()
SCREEN = pygame.display.set_mode((480, 640))
CLOCK = pygame.time.Clock()
SCREEN.fill(BLACK)
drawGrid(480,640,32)
handle_events()
if __name__ == "__main__":
main()
As a suggestion, I think you should use sprite for every cell. For example like this:
class Cell(pygame.sprite.Sprite):
def __init__(self, sprite_group, x, y, cell_dimension, color=BLACK):
self.groups = sprite_group
pygame.sprite.Sprite.__init__(self, self.groups)
self.image = pygame.Surface((cell_dimension, cell_dimension))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x * cell_dimension
self.rect.y = y * cell_dimension
self.clicked = False
def update(self):
if self.clicked:
self.image.fill(WHITE)
I created a window with a width and height of 800 pixels using pygame then drew rectangles with size 32 to make the window a 25x25 grid. What I want to do is change the color of the rectangle I click to change.
My Code:
def createGrid():
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
BLOCK_SIZE = 32
WHITE = (255,255,255)
pygame.init()
frame = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("PathFinder")
frame.fill(WHITE)
for y in range(SCREEN_HEIGHT):
for x in range(SCREEN_WIDTH):
rect = pygame.Rect(x*BLOCK_SIZE, y*BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1)
pygame.draw.rect(frame, (0,250,0), rect)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
First you should create list with all rects and they colors.
Next you should draw all rect inside while loop.
And finally you have to use event.MOUSEBUTTONDOWN to get mouse click and compare mouse position with every rect on list and change color on list.
import pygame
import sys
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
BLOCK_SIZE = 32
WHITE = (255,255,255)
pygame.init()
frame = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("PathFinder")
# create list with all rects
all_rects = []
for y in range(0, SCREEN_HEIGHT, BLOCK_SIZE):
row = []
for x in range(0, SCREEN_WIDTH, BLOCK_SIZE):
rect = pygame.Rect(x, y, BLOCK_SIZE-1, BLOCK_SIZE-1)
row.append([rect, (0, 255, 0)])
all_rects.append(row)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# check which rect was clicked and change its color on list
for row in all_rects:
for item in row:
rect, color = item
if rect.collidepoint(event.pos):
if color == (0, 255, 0):
item[1] = (255, 0, 0)
else:
item[1] = (0, 255, 0)
# draw all in every loop
frame.fill(WHITE)
for row in all_rects:
for item in row:
rect, color = item
pygame.draw.rect(frame, color, rect)
pygame.display.flip()
Eventually you could draw all rects before loop and use event to draw new rect on clicked rect. But you still need list with all rects to check which rect was click and what is its color.
It depends on what your code is intended to do? This example shows how to paint to the primary display surface but doesn't keep track of which colour is where.
import pygame
white = (255, 255, 255)
red = (255, 0, 0)
size = 32
pygame.init()
s = pygame.display.set_mode((800, 800))
s.fill(white)
# press escape to exit example
while True:
e = pygame.event.get()
if pygame.key.get_pressed()[pygame.K_ESCAPE]: break
x = int(pygame.mouse.get_pos()[0] / size) * size
y = int(pygame.mouse.get_pos()[1] / size) * size
if pygame.mouse.get_pressed()[0]:
pygame.draw.rect(s, red, (x, y, size, size), 0)
pygame.display.update()
pygame.time.Clock().tick(60)
pygame.quit()
With pygame, I created a 20x20 pixel window and added a 2x2 pixel rectangle.
When I run the program, the window size is super small and I can barely see the rectangle. How can I increase the window size whilst keeping the number of pixels constant, i.e. increase the pixel size? I am aware of this similar question, but there a somewhat more complicated case is discussed.
import pygame
screen_width, screen_height = 20, 20
x, y = 10, 10
rect_width, rect_height = 2, 2
vel = 2
black = (0, 0, 0)
white = (255, 255, 255)
pygame.init()
win = pygame.display.set_mode((screen_width, screen_height))
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(black)
pygame.draw.rect(win, white, (x, y, rect_width, rect_height))
pygame.display.update()
pygame.quit()
Don't draw directly to the screen, but to another Surface.
Then scale that new Surface to the size of the screen and blit it onto the real screen surface.
Here's an example:
import pygame
screen_width, screen_height = 20, 20
scaling_factor = 6
x, y = 10, 10
rect_width, rect_height = 2, 2
vel = 2
black = (0, 0, 0)
white = (255, 255, 255)
pygame.init()
win = pygame.display.set_mode((screen_width*scaling_factor, screen_height*scaling_factor))
screen = pygame.Surface((screen_width, screen_height))
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill(black)
pygame.draw.rect(screen, white, (x, y, rect_width, rect_height))
win.blit(pygame.transform.scale(screen, win.get_rect().size), (0, 0))
pygame.display.update()
pygame.quit()