Creating a permanent background in pygame - python

I have a program where I am using mouse controls to move objects. When the objects move, the program resets the screen to solid white so that there are no trails of the object using:
screen.fill(255,255,255)
What I am trying to do now is have the background a created background I made in a method.
black = (0,0,0)
brown = (218,134,10)
white = (255,255,255)
pygame.draw.rect(screen,white,(0,0,1000,600))
first =pygame.draw.rect(screen,brown,(150,150,50,400),0)
second = pygame.draw.rect(screen,brown,(450,150,50,400),0)
third = pygame.draw.rect(screen,brown,(750,150,50,400),0)
first_out = pygame.draw.rect(screen,black,(150,150,50,400),2)
second_out = pygame.draw.rect(screen,black,(450,150,50,400),2)
third_out= pygame.draw.rect(screen,black,(750,150,50,400),2)
How can I make my background the background that refreshes every time an object is moved?

Rather than drawing directly on the screen surface, you should create a new surface, let's call it background_surface.
By doing so, you will only have to draw the background once (at the very beginning of your application) and any blitting afterwards will be done a lot quicker, since no pygame.draw.xxx is being done.
To fill the screen surface with background_surface, you'll just use screen.blit().
A quick snippet:
WIDTH, HEIGHT = 800, 600
background_surface = pygame.Surface((WIDTH, HEIGHT))
# do all the drawings on the background surface
background_surface.fill((255, 255, 255))
while application_running:
# processinput
# update objects
# draw everything
screen.blit(background_surface, (0, 0))
pygame.display.update()

Related

Is there a way to copy a certain section of a surface using Surface.copy()?

I am new to dirty rect animation and I am currently trying to store a snapshot of the main display surface window, however I would only like to store the area where my item is going to be blit so the next frame I can call this stored snapshot instead of re blitting the whole background.
I looked at the documentation for Surface.copy() but it doesn't take arguments and I couldn't find anything similar other than pygame.pixelcopy() which from what I understand is not what I am looking for. If Surface.copy() isn't what I am looking for, please let me know of alternatives.
import pygame, time
pygame.init()
screen = pygame.display.set_mode((500, 500))
screen.fill((128, 128, 128))
pygame.display.update()
#immagine a complex pattern being blit to the screen here
pygame.draw.rect(screen, (128, 0, 0), (0, 0, 50, 50))
pygame.draw.rect(screen, (0, 128, 0), (50, 0, 50, 50))
pygame.draw.rect(screen, (0, 0, 128), (200, 0, 50, 50))
#my complex background area that i want to save ()
area_to_save = pygame.Rect(0, 0, 100, 50)
rest_of_background = pygame.Rect(200, 0, 50, 50)
#updating for demo purposes
dirty_rects = [area_to_save, rest_of_background]
for rect in dirty_rects:
pygame.display.update(rect)
temp_screen = screen.copy()
time.sleep(3)
#after some events happen and I draw the item thats being animated onto the background
item_to_animate = pygame.Rect(35, 10, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
pygame.display.update(item_to_animate)
time.sleep(3)
item_to_animate = pygame.Rect(50, 60, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
#now that the item has moved, draw back old frame, which draws over the whole surface
screen.blit(temp_screen, (0, 0))
pygame.display.update()
#I understand swapping the drawing of the new item location to after temp_surface blit
#will provide me the desired outcome in this scenario but this is a compressed version of my problem
#so for simplicity sake, is there a way of not saving the whole surface, only those rects defined?
I expect the output of this code to be displaying my background for 3 seconds, then the black square overlaying the patter and then after another 3 seconds, the black square appearing below my pattern.
P.S.:I am new to this site, let me know if I did something wrong please!
Edit: For anyone wondering if this solution (of saving the background before blitting an item over it and then redrawing the saved background before the new item location is blit over) is more efficient than redrawing the whole background and then blitting the item, using a simple square animation over a chequered pattern with redrawing the whole background each time reduced my overall fps by around 50% from 1000 (before redrawing the background) to 500 average. While using dirty rects and this method above I get around 900 fps.
What you want to do can be achieved by pygame.Surface.blit().
Create a surface with the decided size and blit an area of the screen to this surface. Note, the 3rd argument to bilt is an optional parameter which selects a rectangular area of the source surface:
# create a surface with size 'area_to_save.size'
temp_screen = pygame.Surface(area_to_save.size)
# blit the rectangular area 'area_to_save' from 'screen' to 'temp_screen' at (0, 0)
temp_screen.blit(screen, (0, 0), area_to_save)
You could use subsurface to specify the area you want to copy, and then call copy on the returned Surface.
But note that it may happend that this will not increase the performance of your game at all, since copying a lot of Surfaces around does not come free. Just try and check yourself if it actually better then drawing a background surface every frame.
Also note that you should never use time.sleep in your game. While sleep is blocking your game's process, your game can't process events, so you e.g. can't quit the game in this time. Also, it may happen that your game's window will just not be redrawn if you don't process events by calling pygame.event.get; and once the event queue fills up because you never call pygame.event.get, your game will freeze.

pygame - trying to move a rectangle

I am codding a drum game (based on dtx-Mania).
I got an issue on how to draw a rectangle and move it. I can make a static one, but not a moving one. I currently used a line but it seams pygame draw it as a rectangle so far if it as an impact i will change back to rectangle.
My goal is to draw the rectangle and move it at a slow enough pace so that it takes about 1 second to get to a line.
I know I still have a lot to learn, this is what I got to test so far.
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#small exemple of a moving rectangle
import pygame, sys
pygame.init()
fpsClock = pygame.time.Clock()
windowsSurfaceObj = pygame.display.set_mode((640, 480))
pygame.display.set_caption('moving rectangle test')
white = pygame.Color(255, 255, 255)
black = pygame.Color(0, 0, 0)
step = pygame.draw.line(windowsSurfaceObj, white, (233, 0), (269, 0), 6)
step
while True:
windowsSurfaceObj.fill(black)
#the coordonate are moved but the rectangle is now drew
step.move(0, -1)
#this is the target line (where the moving object must go to)
pygame.draw.line(windowsSurfaceObj, white, (90, 420), (390, 420), 6)
pygame.display.update()
fpsClock.tick(30)
Thank you for your help.
It is always good to read the docs. From the pygame docs:
line(Surface, color, start_pos, end_pos, width=1) -> Rect
Draw a straight line segment on a Surface. There are no endcaps, the
ends are squared off for thick lines.
In the description text of the whole module:
The functions return a rectangle representing the bounding area of changed pixels.
So you draw your first line, and move a rectangle representing a bounding are of changed pixels. Since you do not redraw your line, your first line disappears.
To solve this, you need to draw the line in the while loop after moving.
step.move(0, -1)
pygame.draw.rect(windowsSurfaceObj, white,step, 6)

How to draw to an off-screen display in PyGame

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)

Drawn surface transparency in pygame?

I'm writing a predator-prey simulation using python and pygame for the graphical representation. I'm making it so you can actually "interact" with a creature (kill it, select it and follow it arround the world, etc). Right now, when you click a creature, a thick circle(made of various anti-aliased circles from the gfxdraw class) sorrounds it, meaning that you have succesfully selected it.
My goal is to make that circle transparent, but according to the documentation you can't set an alpha value for drawn surface. I have seen solutions for rectangles (by creating a separate semitransparent surface, blitting it, and then drawing the rectangle on it), but not for a semi-filled circle.
What do you suggest? Thank's :)
Have a look at the following example code:
import pygame
pygame.init()
screen = pygame.display.set_mode((300, 300))
ck = (127, 33, 33)
size = 25
while True:
if pygame.event.get(pygame.MOUSEBUTTONDOWN):
s = pygame.Surface((50, 50))
# first, "erase" the surface by filling it with a color and
# setting this color as colorkey, so the surface is empty
s.fill(ck)
s.set_colorkey(ck)
pygame.draw.circle(s, (255, 0, 0), (size, size), size, 2)
# after drawing the circle, we can set the
# alpha value (transparency) of the surface
s.set_alpha(75)
x, y = pygame.mouse.get_pos()
screen.blit(s, (x-size, y-size))
pygame.event.poll()
pygame.display.flip()

Is there any way to "clear" a surface?

Is there any way to clear a surface from anything that has been blitted to it?
If what you want is to make a pygame Surface object "clean", that is erase all images blited to it, to blit new images to it every game loop and keep the peer pixel alpha without creating a new surface, you can fill it, but instead of a solid color, use a transparent color
from pygame import Color, Surface
empty = Color(0,0,0,0) #The last 0 indicates 0 alpha, a transparent color
field = Surface((width, height), flags=SRCALPHA)
#Inside the game loop
field.fill(empty)
*Sorry is my english is bad, still learning
When I dont care about performance, I use:
mysurface.fill((0,0,0))
Which will draw the specified color (black in this case) across the entire surface. Is that what you meant by "clear"?
Oh, and you need, of course, to "flip" the surface after doing this for it to be visible on the screen.
I don't know what API you're using, so here's a vague answer:
In virtually all cases "clearing" a surface simply blits a coloured quad of the same size as the surface onto it. The colour used is whatever you want your clear colour to be.
If you know how do blit, just blit a white quad of the same size onto the surface.
You can't undo one graphic written over the top of another graphic any more than you can undo one chalk illustration drawn over the top of another chalk illustration on the same board.
What is typically done in graphics is what you'd do with the chalkboard - clear the whole lot, and next time only redraw what you want to keep.
I had this problem too
To create the surface:
mask=pygame.Surface((180,100),pygame.SRCALPHA)
To clear the surface:
mask.fill
mask.set_alpha(255)
Draw your lines/images etc
Then blit this surface onto your main surface using the Alpha Blend flag
screen.blit(mask,(0,0),special_flags=(pygame.BLEND_RGBA_ADD))
.fill((255,255,255,0)) appears to work for me anyway.
I used the following in a game I made:
self.image = 0 #to empty it
self.image = pygame.image.load("image")
For pygame you can use Surface.fill
If you want to clear a surface before blitting, you can first fill the surface with a unlikely random color, set the alpha (colorkey) with that color, and then blit the original sprite with the transparent background back onto the surface.
idle_image = pg.image.load("sprite.png") #sprite with transparent background
temp_color = pg.Color(132, 23, 213, 255) #random unlikely replacement color
def clear_surface(surface):
surface.fill(st.temp_color)
surface.set_colorkey(st.temp_color)
surface.blit(idle_image, (0,0))
You can clear a surface by making it transparent.
Below is how I used the empty color RGB to make text blink by setting the surface to switch to a transparent version.
from __future__ import absolute_import, division, print_function
from itertools import cycle
import pygame
VISITOR_TTF_FILENAME = 'pixelFont.ttf'
BLINK_EVENT = pygame.USEREVENT + 0
empty = (255,255,255,0)
def main():
pygame.init()
try:
screen = pygame.display.set_mode((800, 600))
screen.fill((255,255,255))
screen_rect = screen.get_rect()
clock = pygame.time.Clock()
font = pygame.font.Font(VISITOR_TTF_FILENAME, 50)
on_text_surface = font.render(
'Press Any Key To Start', True, pygame.Color('green3')
)
blink_rect = on_text_surface.get_rect()
blink_rect.center = screen_rect.center
off_text_surface = pygame.Surface(blink_rect.size)
off_text_surface.fill(empty)
blink_surfaces = cycle([on_text_surface, off_text_surface])
blink_surface = next(blink_surfaces)
pygame.time.set_timer(BLINK_EVENT, 1000)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == BLINK_EVENT:
blink_surface = next(blink_surfaces)
screen.blit(blink_surface, blink_rect)
pygame.display.update()
clock.tick(60)
finally:
pygame.quit()
if __name__ == '__main__':
main()
When I want to get rid of something I use
screen.fill("Gray")
every time I update! So it hides it behind the gray screen.
Gray is not the only color available, so here is a list of all of the available colors you can use for pygame
Hope it helped!
Fill the surface with fill, then make it transparent with set_alpha
surface.fill
surface.set_alpha(255)

Categories