How to draw a list of sprites in Pygame - python

I am asking if it is possible to draw a list of sprites in Pygame, and if so how?
I am attempting to draw from a two dimensional list, that will draw the map
import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])
pygame.init()
image = pygame.image.load("Textures(Final)\Grass_Tile.bmp").convert()
imagerect = image.get_rect()
tilemap = [
[image, image, image],
[image, image, image]
]
done = False
clock = pygame.time.Clock()
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(BLACK)
screen.blit(tilemap, imagerect)
clock.tick(20)
pygame.display.flip()
pygame.quit()
I know I can do something much simpler, like drawing each image. But, I am wondering if I can do this, so in the future when I have more sprites I can just add to the list, and create the map.
Thank you!

I don't believe this is quite possible as you describe it, but if these images aren't going to change often I would suggest pre-blitting all your map images to a pygame Surface which can later be used, using something like:
map_surface = pygame.Surface(( len(tilemap[0])*TILE_WIDTH, len(tilemap)*TILE_HEIGHT ))
for y,row in enumerate(tilemap):
for x,tile_surface in enumerate(row):
map_surface.blit(tile_surface,(x*TILE_WIDTH,y*TILE_HEIGHT))
and then later you can simply use:
screen.blit(map_surface)
One thing to note is that even if your map does change, you will only have to blit the changed tiles onto the map_surface surface, and not have to recreate the whole thing.

Related

How do I make the whole scene invisible, pygame

I am trying to make a game where if someone press left-click, pygame draws a circle, then a line connecting the circle to the upper left corner. If he/she press right-click, it removes everything created from the screen. I thought about just covering everything with a big black square, but that is not really clearing the screen, just covering it. My code is as follow:
from pygame import *
init()
size = width, height = 650, 650
screen = display.set_mode(size)
button = 0
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
def drawScene(screen, button):
# Draw circle if the left mouse button is down.
if button==1:
draw.circle(screen,GREEN,(mx,my), 10)
draw.line(screen,GREEN, (0,0),(mx,my))
if button == 3:
mouse.set_visible(False)
display.flip()
running = True
myClock = time.Clock()
# Game Loop
while running:
for e in event.get(): # checks all events that happen
if e.type == QUIT:
running = False
if e.type == MOUSEBUTTONDOWN:
mx, my = e.pos
button = e.button
drawScene(screen, button)
myClock.tick(60) # waits long enough to have 60 fps
quit()
Kindly guide me in this situation. Thanks in advance.
I thought about just covering everything with a big black square, but that is not really clearing the screen, just covering it.
There is not any difference between cover and clear. The screen consist of pixels. Drawing an object just changes the color of the pixels. Drawing a black square changes the color of the pixel again.
Note, operations like pygame.draw.circle() and pygame.draw.line() do not create any object. They just change the colors of some pixels of the surface which is passed to the operation.
The easiest way to clear the entire window is pygame.Surface.fill():
screen.fill((0, 0, 0))
respectively
screen.fill(BLACK)
In computer graphics, there is no such thing as "clearing" a screen. you can only overwrite the previous drawing with new pixels. your idea of drawing a big black rectangle would work perfectly but I may be more efficient to use screen.fill(0)

Trying to make sections of sprite change colour, but whole sprite changes instead

I have a few sprites in my game that need specific parts to be able to change colour.
My process I am trying to to have a pure white sprite image that is transparent everywhere the colour does not need to be. I am blitting a coloured square on top of that, and then that on top of the main sprite, however the main sprite then changes colour everywhere, but while respecting the main sprite transparency. The part that confuses me most is that the masked colour image does look correct when I put it on the main screen.
# Load main sprite and mask sprite
self.image = pygame.image.load("Enemy.png").convert_alpha()
self.mask = pygame.image.load("EnemyMask.png").convert_alpha()
# Create coloured image the size of the entire sprite
self.coloured_image = pygame.Surface([self.width, self.height])
self.coloured_image.fill(self.colour)
# Mask off the coloured image with the transparency of the masked image, this part works
self.masked = self.mask.copy()
self.masked.blit(self.coloured_image, (0, 0), None, pygame.BLEND_RGBA_MULT)
# Put the masked image on top of the main sprite
self.image.blit(self.masked, (0, 0), None, pygame.BLEND_MULT)
Enemy.png
EnemyMask.png (It's white so can't be seen)
Masked colour Masked Colour
Final Failed Sprite Failed Sprite
Can't post images, not enough reputation
I get no error, but only the white part of the shield is supposed to be green
self.image is the loaded image, where you want to change specific regions by a certain color and self.mask is a mask which defines the regions.
And you create an image masked, which contains the regions which are specified in mask tinted in a specific color.
So all you've to do is to .blit the tinted mask (masked) on the image without any special_flags set:
self.image.blit(self.masked, (0, 0))
See the example, where the red rectangle is changed to a blue rectangle:
repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea
Minimal example: repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea-3
Sprite:
Mask:
import pygame
def changColor(image, maskImage, newColor):
colouredImage = pygame.Surface(image.get_size())
colouredImage.fill(newColor)
masked = maskImage.copy()
masked.set_colorkey((0, 0, 0))
masked.blit(colouredImage, (0, 0), None, pygame.BLEND_RGBA_MULT)
finalImage = image.copy()
finalImage.blit(masked, (0, 0), None)
return finalImage
pygame.init()
window = pygame.display.set_mode((404, 84))
image = pygame.image.load('avatar64.png').convert_alpha()
maskImage = pygame.image.load('avatar64mask.png').convert_alpha()
colors = []
for hue in range (0, 360, 60):
colors.append(pygame.Color(0))
colors[-1].hsla = (hue, 100, 50, 100)
images = [changColor(image, maskImage, c) for c in colors]
clock = pygame.time.Clock()
nextColorTime = 0
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill((255, 255, 255))
for i, image in enumerate(images):
window.blit(image, (10 + i * 64, 10))
pygame.display.flip()
pygame.quit()
exit()

Blitting a Surface onto another without combining alpha

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.

Make a rectangle bounce depending on the wall it touched in PYGAME

Im trying to make a rectangle bounce, without going off limits.
I want my rectangle to bounce depending on the wall it touched.
In this code im trying to bounce the rectangle in a 90º angle, but it isn't working.
Im using this to calculate each advance:
rect_x += rectxSpeed
rect_y += rectySpeed
When it reachs the limit
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rectx_y=rectxSpeed*-(math.pi/2)
Whole code here:
import pygame
import random
import math
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
rect_x= 50.0
rect_y = 50.0
rectxSpeed=5
rectySpeed=5
pygame.init()
# Set the width and height of the screen [width, height]
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
# --- Screen-clearing code goes here
# Here, we clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
# If you want a background image, replace this clear with blit'ing the
# background image.
screen.fill(BLACK)
string=str(rect_x)
string2=str(rect_y)
string3="["+string+"]"+"["+string2+"]"
font = pygame.font.SysFont('Calibri', 25, True, False)
text = font.render(string3,True,RED)
screen.blit(text, [0, 0])
#Main rectangle
pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])
#Second rectangle inside the rectangle 1
pygame.draw.rect(screen, RED, [rect_x+10, rect_y+10, 30, 30])
rect_x += rectxSpeed
rect_y+=rectySpeed
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rect_x=rectxSpeed*-(math.pi/2)
# --- Drawing code should go here
# --- Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(20)
# Close the window and quit.
pygame.quit()
¿How can i adjust the advance?
This code produce this:
By Changing the reach limit code with:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-(math.pi/2)
Produces this:
I think it's important to recognize here that the speed of the rectangle object here is a scalar value, not it's vector counterpart velocity. You are attempting to multiply the rectySpeed (a scalar value) by -(math/pi)/2, which is a value that will be returned in radians as #0x5453 mentioned. As far as the rectangle bouncing differently depending on the wall that it contacted, you have not specified the differing constraints that you intend to impose. I think you may want to consider why you want the rectangle to always bounce at a 90° angle. Note that a rectangle that always bounces at a 90° angle would yield some very odd functionality to the player/user.
The angle that the rectangle is approaching the wall measured from the horizontal will be equal to the angle that the rectangle will rebound off the wall measured from the vertical line of the wall to it's new path (in the case of an x-directional bounce).
In terms of an accurate physics engine, you may want to consider just simplifying your mechanics to the following in the case that the rectangle contacts a wall:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-1
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-1
The above ensures that the rectangle object simply changes direction, therefore the magnitude of the rectangle's speed does not change, however the velocity indeed does since it is a vector quantity.

Pygame Erasing Images on Backgrounds

You blit an image onto your surface to use as your background. Then you press button X to blit an image on the same surface, how do you erase the image? I have this so far, but then I end up with a white rectangle in the middle of my background.
screen = pygame.display.set_mode(1280, 512)
screen.blit(background, (0,0))
while True:
pygame.display.flip() #flip is same as update
for event in pygame.event.get():
if (event.type == pygame.KEYDOWN):
if event.key == pygame.K_SPACE:
screen.blit(player, (x, y))
if event.key == pygame.K_BACKSPACE:
pygame.draw.rect(screen, [255,255,255], (x, y, 62,62))
There are basically two things you can do here. You can go the simple route and just draw the background again...
if event.key == pygame.K_BACKSPACE:
screen.blit(background, (0,0))
Or, if you want it to be a little more efficient, you can have the blit method only draw the part of the background that the player image is covering up. You can do that like this...
screen.blit(background, (x, y), pygame.Rect(x, y, 62, 62))
The Rect in the third argument will cause the blit to only draw the 62x62 pixel section of 'background' located at position x,y on the image.
Of course this assumes that the player image is always inside the background. If the player image only partially overlaps the background I'm not entirely sure what will happen. It'll probably throw an exception, or it'll just wrap around to the other side of the image.
Really the first option should be just fine if this is a fairly small project, but if you're concerned about performance for some reason try the second option.
Normally the blit workflow is just to blit the original background to screen.
In your code you would use screen.blit(background, (0,0))
When you start combining lots of surfaces you will probably want either a variable or a list of variables to keep track of screen state.
E.g.
Initialise Background
objectsonscreen = []
objectsonscreen.append(background)
screen.blit(background, (0,0))
Add Sprite
objectsonscreen.append(player)
screen.blit(player, (x, y))
Clear Sprite
objectsonscreen = [background]
screen.blit(background, (0,0))
See here for more information on sprites in Pygame. Note that if your background isn't an image background and is just a solid color you could also use screen.fill([0, 0, 0]) instead.
I personally use
pygame.draw.rect(screen, (RgbColorHere), (x, y, width, height))

Categories

Resources