I have an application written in python that's basically an etch-a-sketch, you move pixels around with WASD and arrow keys and it leaves a trail. However, I want to add a counter for the amount of pixels on the screen. How do I have the counter update without updating the entire surface and pwning the pixel drawings?
Alternatively, can I make a surface that's completely transparent except for the text so you can see the drawing surface underneath?
To solve this problem, you want to have a separate surface for your Etch-a-Sketch pixels, so that they do not get clobbered when you go to refresh the screen. Unfortunately, with Rigo's scheme, the font will continue to render on top of itself, which will get messy for more than two pixel count changes.
So, here's some sample rendering code:
# Fill background
screen.fill((0xcc, 0xcc, 0xcc))
# Blit Etch-a-Sketch surface (with the drawing)
# etch_surf should be the same size as the screen
screen.blit(etch_surf, (0, 0))
# Render the pixel count
arial = pygame.font.SysFont('Arial', 20)
counter_surf = arial.render(str(pixel_count), True, (0, 0, 0))
screen.blit(counter_surf, (16, 16))
# Refresh entire screen
pygame.display.update()
Now, admittedly, updating the entire screen is rather inefficient. For this, you have two options: only refresh the screen when the drawing changes or track the location of drawing changes and refresh individual locations (see the update documentation). If you choose the second option, you will have to refresh the text and where it was previously; I would recommend having a Sprite manage this.
What you need is pygame.font module
#define a font surface
spamSurface = pygame.font.SysFont('Arial', 20)
#then, in your infinite cycle...
eggsPixels = spamSurface.render(str(pixelsOnScreen), True, (255, 255, 255))
hamDisplay.blit(eggsPixels, (10, 10))
Where spamSurface is a new font surface, eggsPixels is the value that spamSurface will render (display/show) and hamDisplay is your main surface display.
Related
I have a project where i have to create a 4 way split screen using pygame. On this screen i have to draw the same image on each of the screen just have different view of the image. I just can not figure out how to create this 4 way split screen using pygame.
I need my screen to be divided like above so i can draw my points onto each section.
I have been looking around and I can not find anything like this so any help would be great
thanks
In addition to the surface you have that gets rendered to the display, likely called something like screen, you should create another surface which all of the "action" gets drawn to. You can then use a Rect object for each quadrant of the screen which will represent the "camera" (assuming each quadrant doesn't necessarily need to show exactly the same image). When you draw back to screen, you use each camera Rect object to select a portion of the game space to draw to a specific quadrant.
# canvas will be a surface that captures the entirety of the "action"
canvas = pygame.Surface((800, 600))
# the following are your "camera" objects
# right now they are taking up discrete and even portions of the canvas,
# but the idea is that they can move and possibly cover overlapping sections
# of the canvas
p1_camera = pygame.Rect(0,0,400,300)
p2_camera = pygame.Rect(400,0,400,300)
p3_camera = pygame.Rect(0,300,400,300)
p4_camera = pygame.Rect(400,300,400,300)
On each update, you would then use these "camera" objects to blit various portions of the canvas back to the screen surface.
# draw player 1's view to the top left corner
screen.blit(canvas, (0,0), p1_camera)
# player 2's view is in the top right corner
screen.blit(canvas, (400, 0), p2_camera)
# player 3's view is in the bottom left corner
screen.blit(canvas, (0, 300), p3_camera)
# player 4's view is in the bottom right corner
screen.blit(canvas, (400, 300), p4_camera)
# then you update the display
# this can be done with either display.flip() or display.update(), the
# uses of each are beyond this question
display.flip()
There is no functions to split screen. But you can draw 4 views directly on screen or you can draw on 4 surfaces (pygame.Surface) and than blit surfaces on screen.
Since you were looking for a way to split the screen in to 4 sections and draw some points on to them I'd suggest creating 4 subsurface surfaces of the original "canvas" image for convenience.
These surfaces would act as your player(split screen) canvasses which can easily be modified.
This will enable the usage of normalized coordinates for player specific drawing purposes.
Assuming you have a screen surface set up
# Image(Surface) which will be refrenced
canvas = pygame.Surface((800, 600))
# Camera rectangles for sections of the canvas
p1_camera = pygame.Rect(0,0,400,300)
p2_camera = pygame.Rect(400,0,400,300)
p3_camera = pygame.Rect(0,300,400,300)
p4_camera = pygame.Rect(400,300,400,300)
# subsurfaces of canvas
# Note that subx needs refreshing when px_camera changes.
sub1 = canvas.subsurface(p1_camera)
sub2 = canvas.subsurface(p2_camera)
sub3 = canvas.subsurface(p3_camera)
sub4 = canvas.subsurface(p4_camera)
Now drawing on any of of the subsurfaces with these normalized coordinates
# Drawing a line on each split "screen"
pygame.draw.line(sub2, (255,255,255), (0,0), (0,300), 10)
pygame.draw.line(sub4, (255,255,255), (0,0), (0,300), 10)
pygame.draw.line(sub3, (255,255,255), (0,0), (400,0), 10)
pygame.draw.line(sub4, (255,255,255), (0,0), (400,0), 10)
# draw player 1's view to the top left corner
screen.blit(sub1, (0,0))
# player 2's view is in the top right corner
screen.blit(sub2, (400, 0))
# player 3's view is in the bottom left corner
screen.blit(sub3, (0, 300))
# player 4's view is in the bottom right corner
screen.blit(sub4, (400, 300))
# Update the screen
pygame.display.update()
Note that modifications to the subsurface pixels will affect the canvas as well. I'd recommend reading the full documentation on subsurfaces.
I'm fairly new to pygame and ive hit my first stump which I cannot find an answer for..
After blitting text, then changing the string for the same variable, the game instead of replacing the original text with the new, overlaps the two texts..?
You have to erase the old text first. Surfaces created by Font.render are ordinary surfaces. Once a Surface is blit, its contents become part of the destination surface, and you have to manipulate the destination surface to erase whatever was blit from the source surface.
One way to erase the destination surface is to blit a background surface onto it. The background surface is what the destination surface would look like without anything like text or sprites on it. Another way is to fill the surface with a solid color:
# pygame initialization goes here
screen = pygame.display.get_surface()
font = pygame.font.Font(None, 40)
font_surface = font.render("original", True, pygame.Color("white"));
screen.blit(surface, (0, 0))
screen.fill(pygame.Color("black")) # erases the entire screen surface
font_surface = font.render("edited", True, pygame.Color("white"));
screen.blit(surface, (0, 0))
You could also overwrite your text.
Like this:
label = myfont.render("Text", 0, (255,255,0))
screen.blit(label, (100, 100))
if x: //Parameter you check before overwrite
label = myfont.render("Text", 0, BACKGROUND_COLOR)
screen.blit(label, (100, 100))
There can be an other solution, even if it's not very different.
The previous answer erases all the screen, but you can erase just your text.
If it's written on an image, you will replace just a part of the image, by getting the text size and blitting the corresponding image part (pygame.surface.subsurface function).
Or if it's not, you can just fill a part of the screen.
In this case you will just erase your text.
In a pygame project I'm working on, sprites of characters and objects cast a shadow onto the terrain. Both the shadow and the terrain are normal pygame surfaces so, to show them, the shadow is blitted onto the terrain. When there's no other shadow (only one shadow and the terrain) everything works fine, but when the character walks into the area of a shadow, while casting its own shadow, both shadows combine their alpha values, obscuring the terrain even more.
What I want is to avoid this behaviour, keeping the alpha value stable. Is there any way to do it?
EDIT: This is an image, that I made in Photoshop, to show the issue
EDIT2: #sloth's answer is ok, but I neglected to comment that my project is more complicated than that. The shadows are not whole squares, but more akin to “stencils”. Like real shadows, they are silhouettes of the objects they are cast from, and therefore they need per pixel alphas which are not compatible with colorkey and whole alpha values.
Here is a YouTube video that shows the issue a bit more clearly.
An easy way to solve this is to blit your shadows on another Surface first which has an alpha value, but no per pixel alpha. Then blit that Surface to your screen instead.
Here's a simple example showing the result:
from pygame import *
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
# we create two "shadow" surfaces, a.k.a. black with alpha channel set to something
# we use these to illustrate the problem
shadow = pygame.Surface((128, 128), pygame.SRCALPHA)
shadow.fill((0, 0, 0, 100))
shadow2 = shadow.copy()
# a helper surface we use later for the fixed shadows
shadow_surf = pygame.Surface((800, 600))
# we set a colorkey to easily make this surface transparent
colorkey_color = (2,3,4)
shadow_surf.set_colorkey(colorkey_color)
# the alpha value of our shadow
shadow_surf.set_alpha(100)
# just something to see the shadow effect
test_surface = pygame.Surface((800, 100))
test_surface.fill(pygame.Color('cyan'))
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
screen.fill(pygame.Color('white'))
screen.blit(test_surface, (0, 150))
# first we blit the alpha channel shadows directly to the screen
screen.blit(shadow, (100, 100))
screen.blit(shadow2, (164, 164))
# here we draw the shadows to the helper surface first
# since the helper surface has no per-pixel alpha, the shadows
# will be fully black, but the alpha value for the full Surface image
# is set to 100, so we still have transparent shadows
shadow_surf.fill(colorkey_color)
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow2, (164, 164))
screen.blit(shadow_surf, (400, 0))
pygame.display.update()
You could create a function that tests for shadow collision and adjust the blend values of the shadows accordingly.
You can combine per-pixel alpha shadows by blitting them onto a helper surface and then fill this surface with a transparent white and pass the pygame.BLEND_RGBA_MIN flag as the special_flags argument. The alpha value of the fill color should be equal or lower than the alphas of the shadows. Passing the pygame.BLEND_RGBA_MIN flag means that for each pixel the lower value of each color channel will be taken, so it will reduce the increased alpha of the overlapping shadows to the fill color alpha.
import pygame as pg
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
shadow = pg.image.load('shadow.png').convert_alpha()
# Shadows will be blitted onto this surface.
shadow_surf = pg.Surface((800, 600), pg.SRCALPHA)
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
screen.fill((130, 130, 130))
screen.blit(shadow, (100, 100))
screen.blit(shadow, (154, 154))
shadow_surf.fill((0, 0, 0, 0)) # Clear the shadow_surf each frame.
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow, (154, 154))
# Now adjust the alpha values of each pixel by filling the `shadow_surf` with a
# transparent white and passing the pygame.BLEND_RGBA_MIN flag. This will take
# the lower value of each channel, therefore the alpha should be lower than
# the shadow alphas.
shadow_surf.fill((255, 255, 255, 120), special_flags=pg.BLEND_RGBA_MIN)
# Finally, blit the shadow_surf onto the screen.
screen.blit(shadow_surf, (300, 0))
pg.display.update()
clock.tick(60)
Here's the shadow.png.
In pygame, when I draw a rectangle, corners are not filled like I want them to be. It looks like:
The problem is the same even with thick lines so I don't think it is the problem.
In another drawing library there were line styles like square or round. I searched everywhere and I couldn't find something similar. Anyone knows how this can be done without filling the corners manually?
import pygame
pygame.init()
while True:
window = pygame.display.set_mode((500, 500))
pygame.draw.rect(window, (255, 255, 255), (100, 100, 100, 100), 10)
pygame.display.flip()
draw.rect is really just a shortcut for draw.polygon using the four corners of the Rect. For each edge of the polygon, it draws a line of the specified width from one vertex to the next. It doesn't go beyond the vertex when you increase the line width. This will make it look like there are gaps in the corners for widths > 1. If you notice there are no gaps for widths of 0 and 1. Also notice that when increasing the line width, the lines grow on both sides (inside and outside of the specified rect). In fact if you increase your width to half the size of the rect, you get something that looks like a cross. If you are wanting better thick rectangles where you can specify the exact inside and outside, I'd use Surface.fill() for the outer Rect and the inside Rect.
Add example of Surface.fill()
window.fill((255, 255, 255), Rect(100, 100, 100, 100)) # outer rect
window.fill((0, 0, 0), Rect(110, 110, 80, 80)) # inner rect
I am learning pygame and want a graphic for a button with the three states: normal, hover, and pressed. I have an image like this one ...
... and I want to get a new Surface using a portion of it.
I'm loading the image with this code:
buttonStates = pygame.image.load(os.path.join('image','button.png'))
How can I make a new surface using just a portion of that graphic?
cropped = pygame.Surface((80, 80))
cropped.blit(buttonStates, (0, 0), (30, 30, 80, 80))
The blit method on a surface 'pastes' another surface on to it. The first argument to blit is the source surface. The second is the location to paste to (in this case, the top left corner). The third (optional) argument is the area of the source image to paste from -- in this case an 80x80 square 30px from the top and 30px from the left.
You can also use the pygame.Surface.subsurface method to create subsurfaces that share their pixels with their parent surface. However, you have to make sure that the rect is inside of the image area or a ValueError: subsurface rectangle outside surface area will be raised.
subsurface = a_surface.subsurface((x, y, width, height))
There are 2 possibilities.
The blit method allows to specify a rectangular sub-area of the source _Surface:
[...] An optional area rectangle can be passed as well. This represents a smaller portion of the source Surface to draw. [...]
In this way you can blit an area of the source surface directly onto a target:
cropped_region = (x, y, width, height)
target.blit(source_surf, (posx, posy), cropped_region)
Alternatively, you can define a subsurface that is directly linked to the source surface with the subsurface method:
Returns a new Surface that shares its pixels with its new parent. The new Surface is considered a child of the original. Modifications to either Surface pixels will effect each other.
As soon as a subsurface has been created, it can be used as a normal surface at any time:
cropped_region = (x, y, width, height)
cropped_subsurf = source_surf.subsurface(cropped_region)
target.blit(cropped_subsurf, (posx, posy))
I think the best way to do it is crop the image of these 3 kind of buttons in a external program and load in different surface instead use pygame to crop it