Blitting a Surface onto another without combining alpha - python

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.

Related

Selectively reset the color of the surface after blitting PyGame

I have two green surfaces
s1 = Surface((100, 100))
s2 = Surface((100, 100))
s1.fill((0, 255, 0))
s2.fill((0, 255, 0))
Then i blit them on main surface with beautiful background
screen.blit(image.load("ocean.png").convert_alpha(), (0, 0))
screen.blit(s1, (0, 100))
screen.blit(s2, (200, 100))
Result:
Question: How can i selectively reset the color of one of the green surfaces after i have already blitted this surface on another surface? As if this surface never existed.
Desired result:
Note:
PyGame methods set_alpha and set_colorkey don't change anything. In addition, they will not allow to reset the surface selectively.
screen.set_alpha(0) # no changes
Or
sreen.set_colorkey((0, 255, 0)) # no changes
How can i selectively reset the color of one of the green surfaces...?
This is impossible
Each object in the scene is drawn to the pygame.Surface object associated with the display. screen is a pygame.Surface object and blit is short for block image transfer.
You cannot "reset" the color to a previous state. Each pixel of a surface knows only one color, the current color. However, you can blit a part of the original background on the rectangular area:
background = image.load("ocean.png").convert_alpha()
screen.blit(background, (200, 100), area=pygame.Rect(200, 100, 100, 100))

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()

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.

In pygame, how do you make the background continuously change color between red, green, blue, black , and white in a loop

I have the general outline code. But i need a background which can change color once you run the program at an reasonable speed between the colors red, green, blue, black and white. I know it has to do with the while loop, i just do not know how to incorporate it.
from __future__ import division
import pygame
pygame.init()
width = 640
height = 480
size = (width, height)
screen = pygame.display.set_mode(size)
background = (0,0,0)
fps = 60
clock = pygame.time.Clock()
while True:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
screen.blit(background)
pygame.display.flip()
clock.tick(1)
If you already know that colors are composite of red, green and blue that will help you already, those are called color channels and change between 0 and 255. I would first take a paper and draw how would change each of the channels with time. So you'll have 3 functions on the same time scale. Then I would brake the time axis in sections, where the functions are linear (or some other standard function), and define the function depending on in which section I am now. I gave just a general approach, since it's quite a big amount of job to do.
Another approach is to set up the table will the sequence of all colors that are to be shown and just cycle through them in a loop and filling the display.

Merging two images in to one in pygame

I have a program that takes two different images - one a tile texture (32x32) that has no blank/transparent spots and the second one is an effect (cracks effect) that has transparent pixels and it is also 32x32 .
From those two images i want to make one.
I tried placing the second image on the first one but it did not work (i thing i don't let the transparency work in it)
First of all - how do i get the transparency to work on an image in pygame(any color key) and different opacity's.
and Second thing is - How do i merge them?
To "merge" two images, just blit the second one onto the first one.
Given these two images (1.png, 2.png)
here's an example:
import pygame
pygame.init()
screen = pygame.display.set_mode((200, 50))
image = pygame.image.load("1.png")
shadow = pygame.image.load("2.png")
merged = image.copy()
merged.blit(shadow, (0, 0))
while True:
screen.fill(pygame.color.Color('white'))
for event in pygame.event.get():
if event.type == pygame.QUIT:
raise
screen.blit(image, (0, 0))
screen.blit(shadow, (50, 0))
screen.blit(image, (100, 0))
screen.blit(shadow, (100, 0))
screen.blit(merged, (150, 0))
pygame.display.flip()
Result:
If that does not work for you, your surfaces may have different pixel formats. Fix that by calling convert_alpha on each surface.
If your problem is about mixing surfaces with a color key and surfaces with per-pixel transparency, take a look at this answer.

Categories

Resources