3
votes

I have a problem regarding a timed event in Python/PyGame. I am making a 2D side-scrolling platformer, where the goal is to touch all enemies, and after all enemies have been collected I want the camera center on the "Exit Block".

After the camera has been centered on the "Exit Block", I want to remove blocks surrounding it, and center the camera back on the player. So the player can see how the surrounding blocks are removed and the "Exit Block" can be reached.

I have gotten the code to center on the "Exit Block" after all enemies have been collected, but then the camera switches to the player before the surrounding blocks are removed. The blocks are removed after camera centers on the player.

I'm missing crucial parts here. I have tried pygame.time.wait() after the surrounding blocks have been removed. Any help? Please tell me if the code I have provided is not enough.

EDIT: I have broken down my main game loop into:

level.handle_events(pygame.event.get())
level.update()
level.render(screen)
pygame.display.flip()

Could the problem be, that the main loop handles the USEREVENT, after that the camera is updated and only after that drawing is done, which is why the removal of the surrounding blocks cannot be seen? But still I don't know how to proceed..

Relevant code:

Player's init:

self.EXITBLOCKCLEAR = pygame.USEREVENT + 1

Player's collision detection:

for e in enemies_group:
    if pygame.sprite.collide_rect(self, e)
        if len(enemies_group) == 0:
           self.exit_block_reveal = True
           pygame.time.set_timer(self.EXITBLOCKCLEAR, 3000)

Level's update:

if self.player.exit_block_reveal == False:
    self.camera.update(self.player)     # camera follows player
else:
    self.camera.update(self.exit_block) # camera centered on "exit block"

Level's event handling:

if event.type == self.player.EXITBLOCKCLEAR:
    if self.player.exit_block_reveal == True:
        for block in self.disappearing_block_group:
            block.kill()
            self.disappearing_block_group.remove(block) # remove the surrounding blocks

        if len(self.disappearing_block_group) == 0:     # when blocks are removed, center camera back to player
            self.player.exit_block_reveal = False

LATEST EDIT: Got the code to work

I played around and I got it working like I wanted. This works perfectly for me now.

UPDATED CODE (THESE ARE THE ONLY CHANGES TO THE ORIGINAL CODE)

With this code, when I collect last enemy, the camera moves to "Exit Block" for 3 seconds, the surrounding blocks are removed, and after another 3 seconds the camera centers back to the player.

Level's init:

self.i = 0

Level's event handling:

if event.type == self.player.EXITBLOCKCLEAR:
    for block in self.disappearing_block_group:
        block.kill()
        self.disappearing_block_group.remove(block)

    if len(self.disappearing_block_group) == 0 and self.i < 1:
        self.i += 1
        self.player.exit_block_reveal = True
    else:
        self.player.exit_block_reveal = False
        pygame.time.set_timer(self.player.EXITBLOCKCLEAR, 0) # stop calling USEREVENT
1
I got it working. I'm not sure if there would have been an easier way, but I tried EVERYTHING I could think of. - lefti

1 Answers

0
votes

The problem is that your block-clearing code in the level event handler sets, then resets the switch used to recenter the camera before the screen actually redraws. Once the the event handler sees that exit_block_reveal is True, it goes about clearing the exit blocks. The list disappearing_exit_group is emptied, and the function continues. The next instruction after the loop which clears all of the blocks checks if there are any blocks left-- if len(self.disappearing_block_group) == 0:. We know there won't be, because the iterator killed them all a moment ago. The if block fires, setting exit_block_reveal back to False before the updater ever knows it changed.

In short: the collision detector sets exit_block_reveal to True, but the event handler sets it right back to False before the updater ever gets a look at it.

Your solution works by adding a cycle to the block removal function, but I think you can do it without that additional attribute. Consider this modification of your event handler, where the check for disappearing_block_group is performed before exit_block_reveal is toggled, instead of after:

if event.type == self.player.EXITBLOCKCLEAR:
    if self.player.exit_block_reveal:
        if not self.disappearing_block_group:  # disappearing_block_group still has contents at this point, so player.exit_block_reveal won't toggle!
            self.player.exit_block_reveal = False

        for block in self.disappearing_block_group:
            block.kill()
        self.disappearing_block_group = list()  # ** SEE NOTE **

In this, all I've done is moved the exit_block_reveal toggle above the disappearing_block_group truth check. Because that list still has contents, if not self.disappearing_block_group: does not run until the next cycle. At that point, the list has been depopulated (or initialized, if you reassign it as shown), and the if block runs. Since there are no elements in disappearing_block_group anymore, the for block has nothing to do and runs zero iterations.

Note: You can also just clear the whole list in one go instead of removing every element. You do not need to to it this way at all! It's just one option for you, if you're at all interested.

Hope this helps!