1

I tried to make a simple level with Pygame, which changes the background, when the player moves to a specific position (a simple level/scene change).

At startup, my first background is drawn correctly. When I change the background (move to the top left corner), the second background gets drawn correctly. But when I move to specific positions on my screen, behind the transparent sprite (set transparent with set_keycolor) not my second (correct) background is drawn, but the initial first one.

Do I heave to explicitly clear the background from the screen object? Do I have to delete the screen object, and create a complete new one?

program.py

#!/usr/bin/env python
import pygame

from player import Character

# see if we can load more than standard BMP
if not pygame.image.get_extended():
    raise SystemExit("Sorry, extended image module required")

# globals
id = 1

# main
def main(winstyle=0):
    # Initialize pygame
    pygame.init()

    SCREENRECT = pygame.Rect(0, 0, 630, 450)
    winstyle = 0  # |FULLSCREEN
    bestdepth = pygame.display.mode_ok(SCREENRECT.size, winstyle, 32)
    screen = pygame.display.set_mode(SCREENRECT.size, winstyle, bestdepth)

    BG_TILE_SIZE = 90

    # decorate the game window
    pygame.display.set_caption("Pygame TEST")
    pygame.mouse.set_visible(0)

    # create the background, tile the bgd image
    background = pygame.Surface(SCREENRECT.size)
    background.fill((0,0,255))
    screen.blit(background, (0, 0))
    pygame.display.flip()

    # Initialize Game Groups
    all = pygame.sprite.RenderUpdates()

    # assign default groups to each sprite class
    Character.containers = all

    # Create Some Starting Values
    clock = pygame.time.Clock()

    # initialize our starting sprites
    player = Character(SCREENRECT)

    # Run our main loop whilst the player is alive.
    while player.alive():

        # get input
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                return

        # update all the sprites
        all.update()

        # handle player input
        player.move(pygame.key.get_pressed(), SCREENRECT)

        # change background
        indexX, indexY = player.rect.center
        if checkPlayerPosition(indexY, screen, background):
            player.rect = player.image.get_rect(center=SCREENRECT.center)

        # clear/erase the last drawn sprites
        all.clear(screen, background)

        # draw the scene
        dirty = all.draw(screen)
        pygame.display.update(dirty)

        # cap the framerate at 60fps. Also called 60HZ or 60 times per second.
        clock.tick(60)

    pygame.time.wait(1000)

# change the background
def checkPlayerPosition(indexY, screen, background):
    global id
    if id == 1:
        if indexY < 90:
            screen.fill((0,0,0))
            pygame.display.flip()
            background.fill((0,0,255))
            id = -1
            return True
    else:
        if indexY > 360:
            screen.fill((0,0,255))
            pygame.display.flip()
            background.fill((0,0,0))
            id = 1
            return True
    return False

# call the "main" function if running this script
if __name__ == "__main__":
    main()
    pygame.quit()

Here's the code for my sprite.

player.py:

import os
import pygame

class Character(pygame.sprite.Sprite):
    # fields
    speed = 4
    run = 1
    images = []

    # constructor
    def __init__(self, SCREENRECT):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.setCharImages()
        self.image = self.images[0]
        self.rect = self.image.get_rect(center=SCREENRECT.center)
        self.reloading = 0
        self.origtop = self.rect.top
        self.facing = -1

    # sets the character image array
    def setCharImages(self):
        img = self.load_image("char.png")
        img.set_colorkey((0,0,0))
        self.images = [img, 
                       pygame.transform.flip(img, 1, 1),
                       pygame.transform.rotate(img, 270),
                       pygame.transform.rotate(img, 90)]

    # loads an image from disk
    def load_image(self, file):
        try:
            MAIN_DIR = os.path.split(os.path.abspath(__file__))[0]
            surface = pygame.image.load(os.path.join(MAIN_DIR, "img", file))
        except pygame.error:
            raise SystemExit('Could not load image "%s" %s' % (file, pygame.get_error()))
        return surface.convert()

    # move operation and animation
    def move(self, keystate, SCREENRECT):
        if keystate[pygame.K_LSHIFT]:
            self.run = 2
        else:
            self.run = 1

        movX = (keystate[pygame.K_RIGHT] - keystate[pygame.K_LEFT]) * self.speed * self.run
        movY = (keystate[pygame.K_DOWN] - keystate[pygame.K_UP]) * self.speed * self.run

        self.rect.move_ip(movX, movY)
        self.rect = self.rect.clamp(SCREENRECT)

        if movX < 0:
            self.image = self.images[0]
        elif movX > 0:
            self.image = self.images[1]
        if movY < 0:
            self.image = self.images[2]
        elif movY > 0:
            self.image = self.images[3]

Here's the first level, when starting the game.

Level 1

Here's the glitched version, after I change the level and the background gets black. As you can see, the transparent background of the sprite get's replaced with the first added background layer, not the second one.

Level 2

So basicaly I'd say, when moving my sprite the wrong background get's redrawn, when it leaves it position. This only happens on the second screen as well! If I move back to the first one, I don't experience this glitch.

So basically the two lines in my short code example above are actually calling this function here. Maybe this causes the problem?!

Mulambo
  • 33
  • 4

2 Answers2

0

You have to change the color of the Surface that is used to clear the screen:

all.clear(screen, background)

You have to fill the background with a new color and clear the screen with the same color:

def checkPlayerPosition(self, indexX, indexY, screen):
        if self.id == 1:
            if indexX <= 2 and indexY == 0:
                backgrund.fill((0, 0, 0)) 
                screen.blit(backgrund, (0, 0))
                pygame.display.flip()
                return True
        else:
            if indexX < 3 and indexY == 4:
                backgrund.fill((0, 0, 255)) 
                screen.blit(backgrund, (0, 0))
                pygame.display.flip()
                return True
        return False

Furthermore, call all.clear(screen, background) after checkPlayerPosition:

while player.alive():

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            return
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            return

    all.update()
    player.move(pygame.key.get_pressed(), map.matrix)

    indexX, indexY = player.mapPosition()
    if map.checkPlayerPosition(indexX, indexY, screen):
        player.rect = player.image.get_rect(center=SCREENRECT.center)

    # draw the scene
    all.clear(screen, background)
    dirty = all.draw(screen)
    pygame.display.update(dirty)

    # cap the framerate at 60fps. Also called 60HZ or 60 times per second.
    clock.tick(60)
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
0

I finally found the problem, and your code was very helpful so thanks!

When I tested the answer it all worked fine, but then I tried to implement more of my logic and it failed again. I changed the line:

background.fill((0,0,255))
screen.blit(background, (0, 0))

to the following:

bgGreen = load_image("background_green.png")
background = pygame.Surface(SCREENRECT.size) # <- this was wrong
for height in range(0, 450, 90):
   for width in range(0, 630, 90):
      background.blit(bgGreen, (width, height))
screen.blit(background, (0, 0))

I then discovered, that the "glitch" disappeared, when I removed the second line (the Surface constructor call).

So I'd say I totally misunderstood passing of the arguments in Python. The background was passed to the function by value not by reference? And so, I made a new Surface on my background, but when the function returned to my main game loop, it continued with the original background object.

I hope I got my explanation right, and this seems like a really stupid mistake by myself. But I managed to change my initial program, and now everything works fine and as I expected it to do.

@Rabbid76: Thanks for your patience. Should I mark your answer as correct/helpful as well, as it helped me getting the game loop arranged right? And of course it helped me a lot in finding my own bug...

Mulambo
  • 33
  • 4