I am using python 2.7.1 with pygame 1.9.1 on 64-bit win7. I am using the gradient code from http://www.pygame.org/wiki/GradientCode to draw my background. I then display text like so:
countText = font.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.blit(countText, countRect)
pygame.display.flip()
I use this to display a countdown timer, but the problem is the numbers draw on top of one another. I can specify a background color in the font.render() call that will solve this, but then I get another problem where the solid background doesn't match the gradient background.
I think this can be solved by saving a copy of the gradient background in another surface, and then drawing the relevant portion of the saved surface onto the background before drawing the next number, but I am not sure how to do this.
I can save a copy of the gradient background like this:
# save a surface with same size and gradient as background
bg_image = pygame.Surface(screen.get_size())
fill_gradient(bg_image, BG_COLOR, GRADIENT_COLOR)
But how do I select the relevant portion of bg_image and draw it to my main screen background? I tried something like this, doing a screen.blit() to try and erase the current countdown number before blitting the new number, but it doesn't work:
countText = usefont.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.blit(bg_image, (0,0), countRect)
screen.blit(countText, countRect)
pygame.display.flip()
Would this be the best approach (with code that works)? Or is there a better way to do this?
Thanks for your help.
use pygame.display.set_mode(size, 0, 32) to get your screen surface, it supports transparent.
Use a surface to save your background, we name it bg_surface
You get a new text surface every time you do font.render, name it txt_surface
When each time before pygame.display.flip(), blit the bg_surface and then txt_surface to screen.
It's that what you need? If you are rendering a counter, it's enougth to blit the whole background every frame.
I can use Surface.set_clip() to restrict the area being updated to just the rectangle containing the countdown text:
countText = usefont.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.set_clip(countRect) # Allow updates only to area in countRect
screen.blit(bg_image, (0,0)) # bg_img will only be drawn within countRect
screen.blit(countText, countRect)
pygame.display.flip()
Found this post while researching a minor variation of the same question. Here is a running generic solution created after combining information from various places. In this case, the bg_image can be used directly, since the title that is (also) placed on top does not overlap with the count. I used a different method of generating the gradient, but that is not important. Especially when using screen.copy() to create a reference surface to use during the restore.
#!python3
import os
import numpy as np
import pygame
from pygame.locals import *
size = 640, 480
pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'
screen = pygame.display.set_mode(size, NOFRAME, 0)
width, height = screen.get_size()
pygame.event.set_blocked(MOUSEMOTION) # keep our queue cleaner
# build gradient background
colors = np.random.randint(0, 255, (2, 3))
topcolor = np.array(colors[0], copy=0)
bottomcolor = np.array(colors[1], copy=0)
diff = bottomcolor - topcolor
column = np.arange(height, dtype=np.float32) / height # create array from 0.0 to 1.0 triplets
column = np.repeat(column[:, np.newaxis], [3], 1)
column = topcolor + (diff * column).astype(np.int) # create a single column of gradient
column = column.astype(np.uint8)[np.newaxis, :, :] # make column a 3d image column by adding X
column = pygame.surfarray.map_array(screen, column) # 3d array into 2d array
gradient = np.resize(column, (width, height)) # stretch the column into a full image
bg_image = pygame.surfarray.make_surface(gradient)
screen.blit(bg_image, (0, 0))
usefont = pygame.font.Font(None, 144)
# add content that does not get erased with the count down value
title_surf = usefont.render('Counting…', True, (200, 100, 50))
title_rect = title_surf.get_rect()
title_rect.topleft = (20, 5)
screen.blit(title_surf, title_rect)
pygame.display.flip()
savedSurface = screen.copy() # when no convenient surface to restore from
pygame.time.set_timer(USEREVENT, 1000)
screen_center = int(width / 2), int(height / 2)
savedRect = screen_center, (0, 0) # First time, nothing to restore
secs_left = 11
while secs_left > 0:
event = pygame.event.wait()
if event.type in (QUIT, KEYDOWN, MOUSEBUTTONDOWN):
break
if event.type == USEREVENT:
# screen.blit(bg_image, savedRect, savedRect) # restore background
screen.blit(savedSurface, savedRect, savedRect) # restore background
secs_left -= 1
countText = usefont.render('%d' % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.center = screen_center
savedRect = screen.blit(countText, countRect)
pygame.display.flip()
Related
This question already has an answer here:
Nested for loop chess board coloring not working Python
(1 answer)
Closed 1 year ago.
I made a chess board, but I need help with coloring the different squares. I only have white squares but I need black squares. Here is my code.
import pygame
def board():
width=480 # measurements for the window
height=480
block_size=59
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
for y in range(height):
for x in range(width):
rect = pygame.Rect(x*(block_size+1), y*(block_size+1), block_size, block_size)
pygame.draw.rect(window, (255,255,255), rect) # Leaves space for lines to be visible.
pygame.display.flip()
board()
# It's just the board.
I already know I violated PEP 8.
You can do it like: I changed your coord-manipulation to benefit directly from the given range values not using a mult inside it. Color is flipped on each rect drawn and also on line-change to get alternating row colorings:
import pygame
def board():
def flipColor(color):
white = (240,240,240)
black = (30,30,30)
if not color or color == white:
color = black
else:
color = white
return color
width=480 # measurements for the window
height=480
block_size= 60
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
c = None
pygame.draw.rect(window,(255,0,0),pygame.Rect(0,0,width,height)) # red background
for y in range(0,height,block_size):
c = flipColor(c)
for x in range(0,width,block_size):
c = flipColor(c)
rect = pygame.Rect(x , y , x+block_size , y+block_size )
pygame.draw.rect(window, c , rect, 0) # Leaves space for lines to be visible.
for i in range(0,height+1,block_size):
pygame.draw.line(window,(233,33,187),(i,0),(i,width),2)
pygame.draw.line(window,(233,33,187),(0,i),(height,i),2)
pygame.draw.line(window,(233,33,187),(height-2,0),(height-2,width),2) # fix for out of window line
pygame.draw.line(window,(233,33,187),(0,width-2),(height,width-2),2) # fix for out of wondow line
pygame.display.flip()
board()
You can also use itertools.cycle, pass an iterable with the colors and then just call next to cycle through them. I'd create the background surface when the program starts and then just blit it in the while loop. If you need a background with extra lines, just draw them on the same background surface as well or create a copy.
import itertools
import pygame as pg
pg.init()
screen = pg.display.set_mode((480, 480))
clock = pg.time.Clock()
width, height = screen.get_size()
block_size = 60
# Create a surface onto which we'll blit the rectangles.
background = pg.Surface((width, height))
colors = itertools.cycle((pg.Color('white'), pg.Color('black')))
for y in range(0, height, block_size):
for x in range(0, width, block_size):
rect = (x, y, block_size, block_size)
pg.draw.rect(background, next(colors), rect)
next(colors) # Skip the next color.
# Then you can just blit the background in the while loop.
screen.blit(background, (0, 0))
pg.display.flip()
You can also calculate the color.
If the index of the column and row are both equal or not equal the color is white, else black.
For example field A8:
Index of A is 0 --> equal
8 --> equal
==> Field is white
Example field A1:
Index of A is 0 --> equal
1 --> unequal
==> Field is black
Is there a way to create a smooth transition between one image and another?
Say I have Image X and Image Y
ImageX = pygame.image.load('foo.png')
ImageY = pygame.image.load('oof.png')
Image X transforms into image Y when W = 5
Normally, I would just do this in the draw code:
if w == 5:
screen.blit(ImageY, (100, 100))
else:
screen.blit(ImageX, (100, 100))
But that just makes image X become replaced with ImageY, one frame its imageX, another frame its imageY
How would I make ImageX transition into ImageY smoothly? Like the transition attribute in css.
You can assign transparency (0-255) to surface with set_alpha
ImageX.set_alpha(128)
and when you blit ImageX after ImageY then you should see both images.
blit(ImageY, ...)
blit(ImageX, ...)
You have to only change set_alpha() (from 255 to 0) in loop to get smooth efect.
if transparency > 0:
transparency -= 1
ImageX.set_alpha(transparency)
blit(ImageY, ...)
blit(ImageX, ...)
BTW: probably to use set_alpha() image has to use convert(), not convert_alpha().
See my examples on GitHub: pygame - transparency
To set the alpha/transparency of images with per-pixel alpha, you need to use a little trick. You can create an intermediate surface, fill it with white and the desired alpha value and then blit it onto the other surface with the pygame.BLEND_RGBA_MULT flag. That will leave fully transparent pixels untouched and just change the transparency of the visible parts.
So if you want to fade one surface in and the other out, you just have to store their alpha values, change them every frame and then call the change_alpha function to get new surfaces with the right transparency. (I'm using text surfaces in this example, but it works with other surfaces/loaded images as well).
import pygame as pg
def change_alpha(orig_surf, alpha):
"""Create a copy of orig_surf with the desired alpha value.
This function creates another surface with the desired alpha
value and then blits it onto the copy of the original surface
with the `BLEND_RGBA_MULT` flag to change the transparency."""
surf = orig_surf.copy()
# This surface is used to adjust the alpha of the txt_surf.
alpha_surf = pg.Surface(surf.get_size(), pg.SRCALPHA)
alpha_surf.fill((255, 255, 255, alpha)) # Set the alpha value.
surf.blit(alpha_surf, (0, 0), special_flags=pg.BLEND_RGBA_MULT)
return surf
def main():
clock = pg.time.Clock()
screen = pg.display.set_mode((640, 480))
font = pg.font.Font(None, 64)
# The original surface which will never be modified.
orig_surf = font.render('Enter your text', True, pg.Color('dodgerblue'))
alpha = 255 # The current alpha value of the surface.
# Surface 2
orig_surf2 = font.render('Another text surface', True, pg.Color('sienna1'))
alpha2 = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if alpha > 0:
# Reduce alpha each frame.
alpha -= 4
alpha = max(0, alpha) # Make sure it doesn't go below 0.
surf = change_alpha(orig_surf, alpha)
if alpha2 < 255:
alpha2 += 4
alpha2 = min(255, alpha2)
surf2 = change_alpha(orig_surf2, alpha2)
screen.fill((30, 30, 30))
screen.blit(surf, (30, 60))
screen.blit(surf2, (30, 60))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
I have been trying to make an image rotate in pygame, using python 3.6, however when I do it either distorts the image into an unrecognizable image, or when it rotates it bumps all over the place
Just using pygame.transform.rotate(image, angle) makes the distorted mess.
And using something like:
pygame.draw.rect(gameDisplay, self.color, [self.x, self.y, self.width, self.height]) makes the image bump all over the place.
I have looked at many questions on this site and others and so far none of them have worked perfectly.
To anyone who is interested here is the link to my code so far.
https://pastebin.com/UQJJFNTy
My image is 64x64.
Thanks in advance!
Per the docs (http://www.pygame.org/docs/ref/transform.html):
Some of the transforms are considered destructive. These means every time they are performed they lose pixel data. Common examples of this are resizing and rotating. For this reason, it is better to re-transform the original surface than to keep transforming an image multiple times.
Each time you call transform.rotate you need to do it on the original image, not on the previously rotated one. For example, if I want the image rotated 10 degrees each frame:
image = pygame.image.load("myimage.png").convert()
image_clean = image.copy()
rot = 0
Then in your game loop (or object's update):
rot += 10
image = pygame.transform.rotate(image_clean, rot)
Here's a complete example. Don't modify the original image and in the while loop use pygame.transform.rotate or rotozoom to get a new rotated surface and assign it to another name. Use a rect to keep the center.
import sys
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
BG_COLOR = pg.Color('darkslategray')
# Here I just create an image with per-pixel alpha and draw
# some shapes on it so that we can better see the rotation effects.
ORIG_IMAGE = pg.Surface((240, 180), pg.SRCALPHA)
pg.draw.rect(ORIG_IMAGE, pg.Color('aquamarine3'), (80, 0, 80, 180))
pg.draw.rect(ORIG_IMAGE, pg.Color('gray16'), (60, 0, 120, 40))
pg.draw.circle(ORIG_IMAGE, pg.Color('gray16'), (120, 180), 50)
def main():
clock = pg.time.Clock()
# The rect where we'll blit the image.
rect = ORIG_IMAGE.get_rect(center=(300, 220))
angle = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
# Increment the angle, then rotate the image.
angle += 2
# image = pg.transform.rotate(ORIG_IMAGE, angle) # rotate often looks ugly.
image = pg.transform.rotozoom(ORIG_IMAGE, angle, 1) # rotozoom is smoother.
# The center of the new rect is the center of the old rect.
rect = image.get_rect(center=rect.center)
screen.fill(BG_COLOR)
screen.blit(image, rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
sys.exit()
I'm writing a class in pygame to create a sprite object, and I'd like to be able to rotate it. It works fine with an image, and rotates without issue. But when rotating a surface with a plain colour, the box appears to grow and shrink. I know that this is a result of the surface changing size to fit the vertices of the rectangle inside, but how do I stop it? I'd like to see a visual rotation.
I've created some sample code to show the problem that I'm facing, running it causes the box to simply change in size.
import sys, pygame
from pygame.locals import *
pygame.init()
SCREEN = pygame.display.set_mode((200, 200))
CLOCK = pygame.time.Clock()
surface = pygame.Surface((50 , 50))
surface.fill((0, 0, 0))
rotated_surface = surface
rect = surface.get_rect()
angle = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
SCREEN.fill((255, 255, 255))
angle += 5
rotated_surface = pygame.transform.rotate(surface, angle)
rect = rotated_surface.get_rect(center = (100, 100))
SCREEN.blit(rotated_surface, (rect.x, rect.y))
pygame.display.update()
CLOCK.tick(30)
How do I fix this issue, to make the surface rotate how I want?
Any help would be appreciated!
You have to create the Surface objects you create to stamp on the display surface in a way they use transparency information (That is - they have to have an alpha channel).
To do that, is just a question of passing the appropriate flag when creating your surface objects - simply replace this:
surface = pygame.Surface((50 , 50))
with:
surface = pygame.Surface((50 , 50), pygame.SRCALPHA)
and it should work.
I'm doing a graphics test using PyGame to simulate the Dragon Curve being unfolded. I already made one successful version that keeps track of all the points as they rotate around each other, but obviously, this begins to slow down pretty substantially after a few iterations. In order to speed this up, I want to simply store the drawn segments into an image variable, and continually save a segment of the screen to a variable and draw those moving rather than keeping track of a lot of points. How can I do either of the following?
Draw to an off-screen image variable that then gets drawn to the screen in the correct place
Save a section of the visible display into an image variable
I tried reading through some of the PyGame documentation, but I didn't have any success.
Thanks!
Creating an additional surface object, and drawing to it is the solution. This surface object can then be drawn onto the display's surface object as shown below.
More information on the PyGame Surface object can be found here
import pygame, sys
SCREEN_SIZE = (600, 400)
BG_COLOR = (0, 0, 0)
LINE_COLOR = (0, 255, 0)
pygame.init()
clock = pygame.time.Clock() # to keep the framerate down
image1 = pygame.Surface((50, 50))
image2 = pygame.Surface((50, 50))
image1.set_colorkey((0, 0, 0)) # The default background color is black
image2.set_colorkey((0, 0, 0)) # and I want drawings with transparency
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
screen.fill(BG_COLOR)
# Draw to two different images off-screen
pygame.draw.line(image1, LINE_COLOR, (0, 0), (49, 49))
pygame.draw.line(image2, LINE_COLOR, (49, 0), (0, 49))
# Optimize the images after they're drawn
image1.convert()
image2.convert()
# Get the area in the middle of the visible screen where our images would fit
draw_area = image1.get_rect().move(SCREEN_SIZE[0] / 2 - 25,
SCREEN_SIZE[1] / 2 - 25)
# Draw our two off-screen images to the visible screen
screen.blit(image1, draw_area)
screen.blit(image2, draw_area)
# Display changes to the visible screen
pygame.display.flip()
# Keep the window from closing as soon as it's finished drawing
# Close the window gracefully upon hitting the close button
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
clock.tick(30)