Reputation: 92
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.
Upvotes: 2
Views: 708
Reputation: 20438
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.
Upvotes: 0
Reputation: 101042
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()
Upvotes: 2
Reputation: 605
You could create a function that tests for shadow collision and adjust the blend values of the shadows accordingly.
Upvotes: 0