pythonpygamefade

How to fade the screen out and back in using PyGame?


So I am trying to fade my screen out and back in after completing a level using PyGame. My problem is that only the fadeout() works and not the fadein(). When calling the fadein() the screen turns black for a few seconds then suddenly shows the next level. I can't find the problem, any ideas?

def fadeout():
    fadeout = pg.Surface((screen_width, screen_height))
    fadeout = fadeout.convert()
    fadeout.fill(black)
    for i in range(255):
        fadeout.set_alpha(i)
        screen.blit(fadeout, (0, 0))
        pg.display.update()


def fadein():
    fadein = pg.Surface((screen_width, screen_height))
    fadein = fadein.convert()
    fadein.fill(black)
    for i in range(255):
        fadein.set_alpha(255-i)
        screen.blit(fadein, (0, 0))
        pg.display.update()

Solution

  • Your problem is that you fade in to a black screen, so you don't see any effect. A black screen with a black half-translucent Surface drawn on top is still a black Surface.

    You should render the first frame of your level, and blit that Surface to the screen before blitting the fadein surface onto the screen.


    Here's a simple example that I hacked together. Press a key to switch from one scene to the next.

    import pygame
    import random
    from itertools import cycle
    
    class Cloud(pygame.sprite.Sprite):
        def __init__(self, x, y):
            super().__init__()
            self.image = pygame.Surface((50, 20))
            self.image.set_colorkey((11, 12, 13))
            self.image.fill((11, 12, 13))
            pygame.draw.ellipse(self.image, pygame.Color('white'), self.image.get_rect())
            self.rect = self.image.get_rect(topleft=(x,y))
    
        def update(self, dt, events):
            self.rect.move_ip(dt/10, 0)
            if self.rect.left >= pygame.display.get_surface().get_rect().width:
                self.rect.right = 0
    
    class DayScene:
        def __init__(self):
            self.clouds = pygame.sprite.Group(Cloud(0, 30), Cloud(100, 40), Cloud(400, 50))
    
        def draw(self, screen):
            screen.fill(pygame.Color('lightblue'))
            self.clouds.draw(screen)
    
        def update(self, dt, events):
            self.clouds.update(dt, events)
    
    class NightScene:
        def __init__(self):
            sr = pygame.display.get_surface().get_rect()
            self.sky = pygame.Surface(sr.size)
            self.sky.fill((50,0,50))
            for x in random.sample(range(sr.width), 50):
                pygame.draw.circle(self.sky, (200, 200, 0), (x, random.randint(0, sr.height)), 1)
            self.clouds = pygame.sprite.Group(Cloud(70, 70), Cloud(60, 40), Cloud(0, 50), Cloud(140, 10), Cloud(100, 20))
    
        def draw(self, screen):
            screen.blit(self.sky, (0, 0))
            self.clouds.draw(screen)
    
        def update(self, dt, events):
            self.clouds.update(dt, events)
    
    class Fader:
    
        def __init__(self, scenes):
            self.scenes = cycle(scenes)
            self.scene = next(self.scenes)
            self.fading = None
            self.alpha = 0
            sr = pygame.display.get_surface().get_rect()
            self.veil = pygame.Surface(sr.size)
            self.veil.fill((0, 0, 0))
    
        def next(self):
            if not self.fading:
                self.fading = 'OUT'
                self.alpha = 0
    
        def draw(self, screen):
            self.scene.draw(screen)
            if self.fading:
                self.veil.set_alpha(self.alpha)
                screen.blit(self.veil, (0, 0))
    
        def update(self, dt, events):
            self.scene.update(dt, events)
    
            if self.fading == 'OUT':
                self.alpha += 8
                if self.alpha >= 255:
                    self.fading = 'IN'
                    self.scene = next(self.scenes)
            else:
                self.alpha -= 8
                if self.alpha <= 0:
                    self.fading = None
    
    def main():
        screen_width, screen_height = 300, 300
        screen = pygame.display.set_mode((screen_width, screen_height))
        clock = pygame.time.Clock()
        dt = 0
        fader = Fader([DayScene(), NightScene()])
    
        while True:
            events = pygame.event.get()
            for e in events:
                if e.type == pygame.QUIT:
                    return
                if e.type == pygame.KEYDOWN:
                    fader.next()
    
            fader.draw(screen)
            fader.update(dt, events)
    
            pygame.display.flip()
            dt = clock.tick(30)
    
    main()
    

    enter image description here

    By abstracting each scene into it's own class and delegating the scene change to the Fader class, we're able to let the scenes continue (or add a simple if statement to prevent that) and to handle events while fading.