Question

I've been trying to develop a small shooting game and i'd like the window to shake eveytime the hero gets hit or looses a life, i was thinkingto maybe reposition the window 3 times in a very shot time but im not sure how to implement this and still keep the game running.

Here is my code for my main game loop:

    def game():      
        #set screen
        width = 800
        height = 600

        pygame.init()
        screen = pygame.display.set_mode((width, height))
        pygame.display.set_caption('UoN Invaders: Game')

        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((000, 000, 000))
        screen.blit(background, (0, 0))

        pygame.display.flip()

        #load music with help of helpers.py
        music = pygame.mixer.music.load ("data/spacequest.mp3")
        pygame.mixer.music.play(-1)

        global clock, player, playerSprite, enemySprites, enemiestocreat
        global enemiesset, lvl2, laserSprites, enemyExplosion, playerExplosion, explosionSprites
        clock = pygame.time.Clock()

        i = 0  #setting i that define if the game is just starting of in the middle of the game

        space1 = Space1(i)
        space2 = Space2()
        player = Player()

        space1 = pygame.sprite.RenderPlain((space1))
        space2 = pygame.sprite.RenderPlain((space2))


        playerSprite = pygame.sprite.RenderPlain((player))


        enemySprites = pygame.sprite.RenderPlain(())

        if lvl2 == False:
            enemiestocreat = int(float(enemiesset))
            while enemiestocreat != 0:
                enemySprites.add(Enemy(enemiestocreat*50))
                enemiestocreat -= 1
        elif lvl2 == True:
            enemiesset = int(float(enemiesset)) * 2
            enemiestocreat = enemiesset
            while enemiestocreat != 0:
                enemySprites.add(Enemy(enemiestocreat*50))
                enemiestocreat -= 1            

        laserSprites = pygame.sprite.RenderPlain(())   

        enemyExplosion = pygame.sprite.RenderPlain(())

        playerExplosion = pygame.sprite.RenderPlain(())

        explosionSprites = pygame.sprite.RenderPlain(())

    # Main Loop

        global gameover, enemydead

        while gameover == False:
            clock.tick(60)
            global lives
            font = pygame.font.Font(None,36)
            text1 = font.render("Lives:" + str(lives + 1), 1, (250,250,250))
            textpos1 = text1.get_rect()
            textpos1.centerx = 750
            textpos1.centery = 590

            global enemieskilled
            text2 = font.render("Score: " + str(enemieskilled), 1, (250,250,250))
            textpos2 = text2.get_rect()
            textpos2.centerx = 55
            textpos2.centery = 590


            if int(float(enemiesset)) == enemydead: 
                    lvl2 = True
                    enemydead = 0
                    lvl2src()
            for event in pygame.event.get():
                if event.type == KEYDOWN and event.key == K_ESCAPE:
                   main()

            # Update
            i += 1        
            space1.update(i)
            space2.update()
            player.update()
            enemySprites.update()
            laserSprites.update()
            enemyExplosion.update()
            playerExplosion.update()
            explosionSprites.update()        
            screen.blit(background, (0, 0))

            # Draw
            space1.draw(screen) 
            space2.draw(screen)        
            playerSprite.draw(screen)
            enemySprites.draw(screen)
            laserSprites.draw(screen)
            enemyExplosion.draw(screen)
            playerExplosion.draw(screen)
            explosionSprites.draw(screen)   

            screen.blit(text1, textpos1)
            screen.blit(text2, textpos2)          

            pygame.display.flip()

        while gameover == True:
                gameoversrc() 

And here is my function where i detect if the hero gets hit:

class Enemy(pygame.sprite.Sprite):
    def __init__(self, centerx):
        global level
        if level%2:                                                 #choosing which lecturer depending on the modulus of the level
            pygame.sprite.Sprite.__init__(self)
            self.image, self.rect = load_image("fse.png", -1)
            self.rect = self.image.get_rect()
            self.dy = 8
            self.reset()
        elif level%3:
            pygame.sprite.Sprite.__init__(self)
            self.image, self.rect = load_image("aps.png", -1)
            self.rect = self.image.get_rect()
            self.dy = 8
            self.reset()
        else:
            pygame.sprite.Sprite.__init__(self)
            self.image, self.rect = load_image("ust.png", -1)
            self.rect = self.image.get_rect()
            self.dy = 8
            self.reset()

    def update(self):
        self.rect.centerx += self.dx
        self.rect.centery += self.dy
        #checking if enemies are outside scrren
        if self.rect.top > 600:
            self.reset()

        # Laser Collisions    
        if pygame.sprite.groupcollide(enemySprites, laserSprites, 1, 1):
           explosionSprites.add(EnemyExplosion(self.rect.center))

        # Ship Collisions
        if pygame.sprite.groupcollide(playerSprite, enemySprites, 0, 1):
            global lives
            if lives == 0: 
               explosionSprites.add(PlayerExplosion(self.rect.center))
            elif lives != 0:

                explosionSprites.add(EnemyExplosion(self.rect.center))
                lives -= 1

    #randome movement of enemies
    def reset(self):
        self.rect.bottom = 0
        self.rect.centerx = random.randrange(0, 800)
        self.dy = random.randrange(5, 10)
        self.dx = random.randrange(-2, 2)
Was it helpful?

Solution

A simple solution is to not draw to the screen Surface directly, but to a temporary Surface. Then blit that temporary Surface to the screen Surface.

If you want to shake the screen, simply apply an offset to the position of the temporary Surface while blitting.

To generate that offset, you can make use of a generator function.

Here's a complete example. Note how the screen shakes if the player is hit by a falling rock.

import pygame
from random import randint
from itertools import repeat

pygame.init()
org_screen = pygame.display.set_mode((400, 400))
screen = org_screen.copy()
screen_rect = screen.get_rect()
player = pygame.Rect(180, 180, 20, 20)

def get_rock():
    return pygame.Rect(randint(0, 340), 0, 60, 60)

falling = get_rock()
clock = pygame.time.Clock()

# 'offset' will be our generator that produces the offset
# in the beginning, we start with a generator that 
# yields (0, 0) forever
offset = repeat((0, 0))

# this function creates our shake-generator
# it "moves" the screen to the left and right
# three times by yielding (-5, 0), (-10, 0),
# ... (-20, 0), (-15, 0) ... (20, 0) three times,
# then keeps yieling (0, 0)
def shake():
    s = -1
    for _ in xrange(0, 3):
        for x in range(0, 20, 5):
            yield (x*s, 0)
        for x in range(20, 0, 5):
            yield (x*s, 0)
        s *= -1
    while True:
        yield (0, 0)

while True:
    if pygame.event.get(pygame.QUIT): break
    pygame.event.pump()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_w]: player.move_ip(0, -2)
    if keys[pygame.K_a]: player.move_ip(-2, 0)
    if keys[pygame.K_s]: player.move_ip(0, 2)
    if keys[pygame.K_d]: player.move_ip(2, 0)
    player.clamp_ip(screen_rect) 

    falling.move_ip(0, 4)
    org_screen.fill((0, 0, 0))
    screen.fill((255,255,255))
    pygame.draw.rect(screen, (0,0,0), player)
    pygame.draw.rect(screen, (255,0,0), falling)

    if player.colliderect(falling):
        # if the player is hit by the rock,
        # we create a new shake-generator
        offset = shake()
        falling = get_rock()

    if not screen_rect.contains(falling):
        falling = get_rock()

    clock.tick(100)
    # here we draw our temporary surface to the
    # screen using the offsets created by the 
    # generators.
    org_screen.blit(screen, next(offset))
    pygame.display.flip()

OTHER TIPS

For anyone that is using the Pyscroll library (which enables scrolling maps and a few other things) you can achieve this by calling scroll on the BufferedRenderer with an offset of your choice.

Example:

map_layer.scroll(
    (
        random.randint(0, your_offset),
        random.randint(0, your_offset)
    )
)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top