I am making a game with Pygame. For this game I need to be able to detect not only that two rectangles have collided, but also the point of collision between them. I looked at the documentation but couldn't seem to find any answers.
Is something like this possible?
You can use Rect.clip:
crops a rectangle inside another
clip(Rect) -> Rect
Returns a new rectangle that is cropped to be completely inside the argument Rect. If the two rectangles do not overlap to begin with, a Rect with 0 size is returned.
Here's an example:
import pygame
import random
class Stuff(pygame.sprite.Sprite):
def __init__(self, pos, color, *args):
super().__init__(*args)
self.image = pygame.Surface((30, 30))
self.image.fill(color)
self.rect = self.image.get_rect(center=pos)
self.pos = pygame.Vector2(pos)
def update(self):
self.rect.center = self.pos
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
screen_rect = screen.get_rect()
font = pygame.font.SysFont(None, 26)
clock = pygame.time.Clock()
sprites = pygame.sprite.Group()
blocks = pygame.sprite.Group()
movement = {
pygame.K_UP: ( 0, -1),
pygame.K_DOWN: ( 0, 1),
pygame.K_LEFT: (-1, 0),
pygame.K_RIGHT: ( 1, 0)
}
for _ in range(15):
x, y = random.randint(0, 500), random.randint(0, 500)
color = random.choice(['green', 'yellow'])
Stuff((x, y), pygame.Color(color), sprites, blocks)
player = Stuff(screen_rect.center, pygame.Color('dodgerblue'))
sprites.add(player)
dt = 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
pressed = pygame.key.get_pressed()
move = pygame.Vector2()
for dir in (movement[key] for key in movement if pressed[key]):
move += dir
if move.length() > 0: move.normalize_ip()
player.pos += move * dt/5
sprites.update()
screen.fill(pygame.Color('black'))
sprites.draw(screen)
for block in pygame.sprite.spritecollide(player, blocks, False):
clip = player.rect.clip(block.rect)
pygame.draw.rect(screen, pygame.Color('red'), clip)
hits = [edge for edge in ['bottom', 'top', 'left', 'right'] if getattr(clip, edge) == getattr(player.rect, edge)]
text = font.render(f'Collision at {", ".join(hits)}', True, pygame.Color('white'))
screen.blit(text, (20, 20))
pygame.display.flip()
dt = clock.tick(60)
if __name__ == '__main__':
main()
Related
I am creating a game with pygame & python 3.10.0 using VSCode everything is working fine but this if statement stops working after sometime and the font doesn't draw but I can't pinpoint the problem for that , for the if statement it usually runs till 10 or 11 score but it can be quicker like 2:
if player.rect.colliderect(food):
pygame.sprite.Sprite.kill(food)
food.rect.x = random.randrange(20, 1700)
food.rect.y = random.randrange(20, 860)
all_sprites_list.add(food)
score += 1
print(score)
whole code:
import pygame
import sys
import time
import random
import ctypes
from ctypes import wintypes
from pygame import sprite
from pygame.draw import rect
from pygame.event import pump, wait
from pygame import font
pygame.font.init()
myappid = 'elementalstudios.snake.Alpha 0_1' # arbitrary string
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
#------ Initialize Variables------#
# Player
player_width = 20
player_height = 20
# Food
food_width = 10
food_height = 10
# Colours
seafoam_gr = (159, 226, 191)
black = (0, 0, 0)
blue = (0, 0, 255)
red = (255, 0, 0)
green = (0, 255, 0)
white = (255, 255, 255)
# Score
score = 0
#------ Score Font Initialize ------#
font = pygame.font.Font(None, 50)
text = font.render("Score:", False, white, None)
#------Sprites Class------#
class Sprite(pygame.sprite.Sprite):
def __init__(self, color, height, width):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(seafoam_gr)
self.image.set_colorkey(black)
pygame.draw.rect(self.image,
color,
pygame.Rect(0, 0, width, height))
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveUp(self, speed):
self.rect.y -= speed * speed / 5
def moveDown(self, speed):
self.rect.y += speed * speed / 5
#------ Font Class ------#
def create_font(t,s = 24, colour_font = white, bold = False, italic = False):
font = pygame.font.Font("prstart.ttf", s, bold, italic)
text = font.render(t, True, colour_font)
return text
#------ Initialize Pygame and Window------#
pygame.init()
icon = pygame.image.load('Icon.ico')
pygame.display.set_icon(icon)
gameDisplay = pygame.display.set_mode((1920,1080), pygame.FULLSCREEN)
#rect = pygame.Rect( * gameDisplay.get_rect().center, 0, 0).inflate(100, 100)
pygame.display.set_caption("Blocky")
gameDisplay.fill(black)
clock = pygame.time.Clock()
running = True
#------Initialize Sprites------#
all_sprites_list = pygame.sprite.Group()
player = Sprite(seafoam_gr, player_height, player_width)
food = Sprite(red, food_height, food_width)
player.rect.x = 960
player.rect.y = 540
food.rect.x = 20 #random.randrange(20, 1800)
food.rect.y = 30 #random.randrange(20, 1050)
#------Add Sprites to sprite list------#
all_sprites_list.add(player)
all_sprites_list.add(food)
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
#------Eating------#
if player.rect.colliderect(food):
pygame.sprite.Sprite.kill(food)
food.rect.x = random.randrange(20, 1700)
food.rect.y = random.randrange(20, 860)
all_sprites_list.add(food)
score += 1
print(score)
#------ Score Draw ------#
text_rect = text.get_rect()
text_rect.center = (100, 150)
gameDisplay.blit(text, text_rect)
#------Key Movement------#
keys = pygame.key.get_pressed()
# Move Left
if keys[pygame.K_a]:
player.moveLeft(3)
if keys[pygame.K_LCTRL]:
player.moveLeft(5)
if keys[pygame.K_LEFT]:
player.moveLeft(3)
if keys[pygame.K_LCTRL]:
player.moveLeft(5)
# Move Right
if keys[pygame.K_d]:
player.moveRight(5)
if keys[pygame.K_LCTRL]:
player.moveRight(5)
if keys[pygame.K_RIGHT]:
player.moveRight(3)
if keys[pygame.K_LCTRL]:
player.moveRight(5)
# Move Down
if keys[pygame.K_s]:
player.moveDown(3)
if keys[pygame.K_LCTRL]:
player.moveDown(5)
if keys[pygame.K_DOWN]:
player.moveDown(3)
if keys[pygame.K_LCTRL]:
player.moveDown(5)
# Move Up
if keys[pygame.K_w]:
player.moveUp(3)
if keys[pygame.K_LCTRL]:
player.moveUp(5)
if keys[pygame.K_UP]:
player.moveUp(3)
if keys[pygame.K_LCTRL]:
player.moveUp(5)
all_sprites_list.update()
gameDisplay.fill(black)
all_sprites_list.draw(gameDisplay)
pygame.display.flip()
clock.tick(60)
pygame.quit()
quit()
It is not necessary to kill the food object. It is sufficient to change the position of the food. pygame.sprite.Sprite.kill just removes the Sprite object from all Gorups. So there is no point in calling kill and then adding the object back to the Group.
The text does not show up, because you draw it before you clear the display. pygame.Surface.fill fills the Surface with a solid color. Everything that was previously drawn will be cleared. blit the text after fill.
You will have to render the text Surface again if the score has changed.
I recommend simplifying the code that moves the player. See How can I make a sprite move when key is held down.
class Sprite(pygame.sprite.Sprite):
def __init__(self, color, x, y, height, width):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.image.set_colorkey(black)
self.rect = self.image.get_rect(topleft = (x, y))
self.x, self.y = self.rect.x, self.rect.y
def move(self, move_x, move_y, speed_up):
if move_x or move_y:
direction = pygame.math.Vector2(move_x, move_y)
direction.scale_to_length(5 if speed_up else 3)
self.x += direction.x
self.y += direction.y
self.rect.x, self.rect.y = round(self.x), round(self.y)
player = Sprite(seafoam_gr, 400, 300, player_height, player_width)
food = Sprite(red, 20, 30, food_height, food_width)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if player.rect.colliderect(food):
food.rect.x = random.randrange(20, 780)
food.rect.y = random.randrange(20, 580)
score += 1
# render text
text = font.render("Score: " + str(score), False, white, None)
keys = pygame.key.get_pressed()
move_x = ((keys[pygame.K_d] or keys[pygame.K_RIGHT]) - (keys[pygame.K_a] or keys[pygame.K_LEFT]))
move_y = ((keys[pygame.K_s] or keys[pygame.K_DOWN]) - (keys[pygame.K_w] or keys[pygame.K_UP]))
player.move(move_x, move_y, keys[pygame.K_LCTRL])
all_sprites_list.update()
gameDisplay.fill(black)
# draw text
gameDisplay.blit(text, text.get_rect(center = (100, 150)))
all_sprites_list.draw(gameDisplay)
pygame.display.flip()
clock.tick(60)
pygame.quit()
quit()
I'm new to Pygame and trying to learn about sprites and collision detection. I found some code and tried to add some functionality to it.
The basic idea is that I have a polygon which can be rotated in place. When the user presses SPACEBAR, the polygon fires a projectile which can bounce around the frame. If the projectile hits the polygon again, I would like to quite the program
First of all, here is the code
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill((0, 0, 0))
self.image.set_colorkey((0, 0, 0))
self.fire_from = (36, 20)
pygame.draw.polygon(self.image, pygame.Color('dodgerblue'), ((0, 0), (32, 16), (0, 32)))
self.org_image = self.image.copy()
self.angle = 0
self.direction = pygame.Vector2(1, 0)
self.rect = self.image.get_rect(center=(200, 200))
self.pos = pygame.Vector2(self.rect.center)
def update(self, events, dt):
for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
bullet = Projectile(self.fire_from, self.direction.normalize())
self.groups()[0].add(bullet)
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]:
self.angle += 3
if pressed[pygame.K_RIGHT]:
self.angle -= 3
self.direction = pygame.Vector2(1, 0).rotate(-self.angle)
self.image = pygame.transform.rotate(self.org_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Projectile(pygame.sprite.Sprite):
def __init__(self, pos, direction):
super().__init__()
self.image = pygame.Surface((8, 8))
self.image.fill((0, 0, 0))
self.image.set_colorkey((0, 0, 0))
pygame.draw.circle(self.image, pygame.Color('orange'), (4, 4), 4)
self.rect = self.image.get_rect(center=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.center)
self.max_bounce = 10
def update(self, events, dt):
#print(self.pos)
# Bounding box of the screen
screen_r = pygame.display.get_surface().get_rect()
# where we would move next
next_pos = self.pos + self.direction * dt
# we hit a wall
if not screen_r.contains(self.rect):
# after 10 hits, destroy self
self.max_bounce -= 1
if self.max_bounce == 0:
return self.kill()
# horizontal reflection
if next_pos.x > screen_r.right or next_pos.x < screen_r.left:
self.direction.x *= -1
# vertical reflection
if next_pos.y > screen_r.bottom or next_pos.y < screen_r.top:
self.direction.y *= -1
# move after applying reflection
next_pos = self.pos + self.direction * dt
# set the new position
self.pos = next_pos
self.rect.center = self.pos
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
tank = Player()
sprites = pygame.sprite.Group()
sprites.add(tank)
#print(tank.groups()[0])
clock = pygame.time.Clock()
dt = 0
running = True
while running:
#print(tank.groups()[0])
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
## ALWAYS DETECTS A COLLISION, WHY?
if len(tank.groups()[0])>1:
if pygame.sprite.spritecollideany(tank, tank.groups()[0]):
running = False
dt = clock.tick(60)
if __name__ == '__main__':
main()
I gather here that the Player class (i.e. my tank) itself has a sprite groups, to which it adds each projectile as its fired. Furthermore, the projectile originates from within the rectangle for the polygon (hence it always initially collides)
I would like to only add the projectile to the sprite group if it has made at least 1 bounce, is that possible? Or... is there a better way to do this?
Any help or pointers please?
Thanks
Null
The tank collides with itself, because it is a member of tank.groups()[0]:
if pygame.sprite.spritecollideany(tank, tank.groups()[0]):
Add a Group that contains just the bullets:
class Player(pygame.sprite.Sprite):
def __init__(self):
# [...]
self.bullets = pygame.sprite.Group()
def update(self, events, dt):
for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
bullet = Projectile(self.fire_from, self.direction.normalize())
self.groups()[0].add(bullet)
self.bullets.add(bullet)
# [...]
Use this Group for the collision test:
def main():
# [...]
while running:
# [...]
pygame.display.update()
if pygame.sprite.spritecollideany(tank, tank.bullets):
running = False
dt = clock.tick(60)
I was wondering if i could use picture maps to create solid walls within my game, an example is below:
How would I go about making a program that generates a map based on the black wall locations on my picture
Iv been looking into doing this for a while although up to now iv rested on simply drawing in the lines using python.draw. Iv also looked into the use of turtles but i dont think thats what I desire for my program at this time.
You can read the color value of each pixel.
The easiest way is to use the get_at function to the Surface class, but for more features and performance, you can also use a PixelArray or the surfarray module.
Another way would be to create a Mask from the images and use that for collision detection.
Here's a simple, hackish but working example:
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self, image, pos, background):
super().__init__()
self.image = image
self.pos = pygame.Vector2(pos)
self.rect = self.image.get_rect(center=self.pos)
self.background = background
def update(self, events, dt):
pressed = pygame.key.get_pressed()
move = pygame.Vector2((0, 0))
if pressed[pygame.K_w]: move += (0, -1)
if pressed[pygame.K_a]: move += (-1, 0)
if pressed[pygame.K_s]: move += (0, 1)
if pressed[pygame.K_d]: move += (1, 0)
if move.length() > 0: move.normalize_ip()
new_pos = self.pos + move*(dt/5)
new_rect = self.rect.copy()
new_rect.center = new_pos
new_rect.clamp_ip(self.background.get_rect())
new_pos = new_rect.center
hit_box = self.background.subsurface(new_rect)
for x in range(new_rect.width):
for y in range(new_rect.height):
if sum(hit_box.get_at((x, y))) < 500:
return
self.pos = new_pos
self.rect.center = self.pos
def main():
pygame.init()
screen = pygame.display.set_mode((284, 384))
screen_rect = screen.get_rect()
clock = pygame.time.Clock()
sprites = pygame.sprite.Group()
background = pygame.transform.scale2x(pygame.image.load('maze.jpg'))
pimg = pygame.Surface((10, 10))
pimg.fill((200, 20, 20))
sprites.add(Player(pimg, (50, 50), background))
dt = 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill(pygame.Color('grey'))
screen.blit(background, (0, 0))
sprites.draw(screen)
pygame.display.flip()
dt = clock.tick(60)
if __name__ == '__main__':
main()
Of course this can be improved, for example by using an image with alpha values so you could easily create masks; but you'll get the idea: just "look" at the pixels of your Surface...
I am making a flappy bird clone game, using pygame. I want to draw pillars by using Sprite.draw. I made a Pillar class and initialized it with two rectangles p_upper and p_lower on the left side of the screen, coming towards the right side with the help of the update function of the sprite. But the screen is only showing the p_lower pillar. Can anyone help?
class Pillar(pygame.sprite.Sprite):
# the "h" parameter is height of upper pillar upto gap
# "w" is the width of the pillar
# pillar is coming from left to right
def __init__(self, w, h, gap):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((w, h))
self.image.fill(green)
self.p_upper = self.rect = self.image.get_rect()
self.p_upper.topleft = (-w, 0)
self.image = pygame.Surface((w, HEIGHT - (h + gap)))
self.image.fill(green)
self.p_lower = self.rect = self.image.get_rect()
self.p_lower.topleft = (-w, h + gap)
def update(self):
self.p_upper.x += 1
self.p_lower.x += 1
Because of the following two lines:
self.p_upper = self.rect = self.image.get_rect()
and...
self.p_lower = self.rect = self.image.get_rect()
These are both grabbing the same reference to the self.rect. The first line runs and assigns the rect reference to p_upper. Then the same reference is assigned to p_lower. Because it's the same reference, when you update the location of the lower rectangle, you're actually updating both.
Using a sprite that consists of two rects and images isn't a good solution for this problem. I suggest to create two separate sprites with their own image and rect. To create two sprite instances at the same time and to add them to a sprite group, you can write a short function as you can see in this example:
import pygame as pg
from pygame.math import Vector2
green = pg.Color('green')
HEIGHT = 480
class Pillar(pg.sprite.Sprite):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w, h))
self.image.fill(green)
self.rect = self.image.get_rect(topleft=(x, y))
def update(self):
self.rect.x += 1
def create_pillars(w, h, gap, sprite_group):
sprite_group.add(Pillar(0, 0, w, h-gap))
sprite_group.add(Pillar(0, HEIGHT-(h+gap), w, h+gap))
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
create_pillars(50, 170, 0, all_sprites)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_a:
create_pillars(50, 170, 15, all_sprites)
elif event.key == pg.K_s:
create_pillars(50, 170, 30, all_sprites)
elif event.key == pg.K_d:
create_pillars(50, 100, -60, all_sprites)
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
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)
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()
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)
)
)