Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add scrolling to a platformer in pygame

Ok so I included the code for my project below, I'm just doing some experimenting with pygame on making a platformer. I'm trying to figure out how to do some very simple scrolling that follows the player, so the player is the center of the camera and it bounces/follows him. Can anyone help me?

import pygame from pygame import *  WIN_WIDTH = 800 WIN_HEIGHT = 640 HALF_WIDTH = int(WIN_WIDTH / 2) HALF_HEIGHT = int(WIN_HEIGHT / 2)  DISPLAY = (WIN_WIDTH, WIN_HEIGHT) DEPTH = 32 FLAGS = 0 CAMERA_SLACK = 30  def main():     global cameraX, cameraY     pygame.init()     screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)     pygame.display.set_caption("Use arrows to move!")     timer = pygame.time.Clock()      up = down = left = right = running = False     bg = Surface((32,32))     bg.convert()     bg.fill(Color("#000000"))     entities = pygame.sprite.Group()     player = Player(32, 32)     platforms = []      x = y = 0     level = [         "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",         "P                                P",         "P                                P",         "P                                P",         "P                                P",         "P                                P",         "P                                P",         "P                                P",         "P       PPPPPPPPPPP              P",         "P                                P",         "P                                P",         "P                                P",         "P                                P",         "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]     # build the level     for row in level:         for col in row:             if col == "P":                 p = Platform(x, y)                 platforms.append(p)                 entities.add(p)             if col == "E":                 e = ExitBlock(x, y)                 platforms.append(e)                 entities.add(e)             x += 32         y += 32         x = 0      entities.add(player)      while 1:         timer.tick(60)          for e in pygame.event.get():             if e.type == QUIT: raise SystemExit, "QUIT"             if e.type == KEYDOWN and e.key == K_ESCAPE:                 raise SystemExit, "ESCAPE"             if e.type == KEYDOWN and e.key == K_UP:                 up = True             if e.type == KEYDOWN and e.key == K_DOWN:                 down = True             if e.type == KEYDOWN and e.key == K_LEFT:                 left = True             if e.type == KEYDOWN and e.key == K_RIGHT:                 right = True             if e.type == KEYDOWN and e.key == K_SPACE:                 running = True              if e.type == KEYUP and e.key == K_UP:                 up = False             if e.type == KEYUP and e.key == K_DOWN:                 down = False             if e.type == KEYUP and e.key == K_RIGHT:                 right = False             if e.type == KEYUP and e.key == K_LEFT:                 left = False             if e.type == KEYUP and e.key == K_RIGHT:                 right = False          # draw background         for y in range(32):             for x in range(32):                 screen.blit(bg, (x * 32, y * 32))          # update player, draw everything else         player.update(up, down, left, right, running, platforms)         entities.draw(screen)          pygame.display.update()  class Entity(pygame.sprite.Sprite):     def __init__(self):         pygame.sprite.Sprite.__init__(self)  class Player(Entity):     def __init__(self, x, y):         Entity.__init__(self)         self.xvel = 0         self.yvel = 0         self.onGround = False         self.image = Surface((32,32))         self.image.fill(Color("#0000FF"))         self.image.convert()         self.rect = Rect(x, y, 32, 32)      def update(self, up, down, left, right, running, platforms):         if up:             # only jump if on the ground             if self.onGround: self.yvel -= 10         if down:             pass         if running:             self.xvel = 12         if left:             self.xvel = -8         if right:             self.xvel = 8         if not self.onGround:             # only accelerate with gravity if in the air             self.yvel += 0.3             # max falling speed             if self.yvel > 100: self.yvel = 100         if not(left or right):             self.xvel = 0         # increment in x direction         self.rect.left += self.xvel         # do x-axis collisions         self.collide(self.xvel, 0, platforms)         # increment in y direction         self.rect.top += self.yvel         # assuming we're in the air         self.onGround = False;         # do y-axis collisions         self.collide(0, self.yvel, platforms)      def collide(self, xvel, yvel, platforms):         for p in platforms:             if pygame.sprite.collide_rect(self, p):                 if isinstance(p, ExitBlock):                     pygame.event.post(pygame.event.Event(QUIT))                 if xvel > 0:                     self.rect.right = p.rect.left                     print "collide right"                 if xvel < 0:                     self.rect.left = p.rect.right                     print "collide left"                 if yvel > 0:                     self.rect.bottom = p.rect.top                     self.onGround = True                     self.yvel = 0                 if yvel < 0:                     self.rect.top = p.rect.bottom   class Platform(Entity):     def __init__(self, x, y):         Entity.__init__(self)         self.image = Surface((32, 32))         self.image.convert()         self.image.fill(Color("#DDDDDD"))         self.rect = Rect(x, y, 32, 32)      def update(self):         pass  class ExitBlock(Platform):     def __init__(self, x, y):         Platform.__init__(self, x, y)         self.image.fill(Color("#0033FF"))  if __name__ == "__main__":     main() 
like image 559
user1758231 Avatar asked Jan 16 '13 08:01

user1758231


People also ask

What does Flip () do in Pygame?

flip() for software displays. It allows only a portion of the screen to updated, instead of the entire area. If no argument is passed it updates the entire Surface area like pygame.

How do you make a sprite move continuously in Pygame?

If you want to achieve a continuously movement, you have to use pygame. key. get_pressed() .


1 Answers

You need to apply an offset to the position of your entities when drawing them. Let's call that offset a camera, since this is the effect we want to achieve with this.

First of all, we can't use the draw function of the sprite group, since the sprites don't need to know that their position (rect) is not the position they are going to be drawn on the screen (At the end, we'll subclass the Group class and reimplement the it's draw to be aware of the camera, but let's start slow).


Let's start by creating a Camera class to hold the state of the offset we want to apply to the position of our entities:

class Camera(object):     def __init__(self, camera_func, width, height):         self.camera_func = camera_func         self.state = Rect(0, 0, width, height)              def apply(self, target):         return target.rect.move(self.state.topleft)              def update(self, target):         self.state = self.camera_func(self.state, target.rect) 

some things to note here:

We need to store the position of the camera, and the width and height of the level in pixels (since we want to stop scrolling at the edges of the level). I used a Rect to store all these informations, but you could easily just use some fields.

Using Rect comes in handy in the apply function. This is where we re-calculate the position of an entity on the screen to apply the scrolling.

Once per iteration of the main loop, we need to update the position of the camera, hence there's the update function. It just alters the state by calling the camera_func function, which will do all the hard work for us. We implement it later.

Let's create an instace of the camera:

for row in level:     ...  total_level_width  = len(level[0])*32 # calculate size of level in pixels total_level_height = len(level)*32    # maybe make 32 an constant camera = Camera(*to_be_implemented*, total_level_width, total_level_height)  entities.add(player) ...  

and alter our main loop:

# draw background for y in range(32):     ...  camera.update(player) # camera follows player. Note that we could also follow any other sprite  # update player, draw everything else player.update(up, down, left, right, running, platforms) for e in entities:     # apply the offset to each entity.     # call this for everything that should scroll,     # which is basically everything other than GUI/HUD/UI     screen.blit(e.image, camera.apply(e))   pygame.display.update() 

Our camera class is already very flexible and yet dead simple. It can use different kinds of scrolling (by providing different camera_func functions), and it can follow any arbitary sprite, not just the player. You even can change this at runtime.

Now for the implementation of camera_func. A simple approach is to just center the player (or whichever entity we want to follow) at the screen, and the implementation is straight forward:

def simple_camera(camera, target_rect):     l, t, _, _ = target_rect # l = left,  t = top     _, _, w, h = camera      # w = width, h = height     return Rect(-l+HALF_WIDTH, -t+HALF_HEIGHT, w, h) 

We just take the position of our target, and add the half total screen size. You can try it by creating your camera like this:

camera = Camera(simple_camera, total_level_width, total_level_height) 

So far, so good. But maybe we don't want to see the black background outside the level? How about:

def complex_camera(camera, target_rect):     # we want to center target_rect     x = -target_rect.center[0] + WIN_WIDTH/2      y = -target_rect.center[1] + WIN_HEIGHT/2     # move the camera. Let's use some vectors so we can easily substract/multiply     camera.topleft += (pygame.Vector2((x, y)) - pygame.Vector2(camera.topleft)) * 0.06 # add some smoothness coolnes     # set max/min x/y so we don't see stuff outside the world     camera.x = max(-(camera.width-WIN_WIDTH), min(0, camera.x))     camera.y = max(-(camera.height-WIN_HEIGHT), min(0, camera.y))          return camera 

Here we simply use the min/max functions to ensure we don't scroll outside out level.

Try it by creating your camera like this:

camera = Camera(complex_camera, total_level_width, total_level_height) 

There's a little animation of our final scrolling in action:

enter image description here

Here's the complete code again. Note I changed some things:

  • the level is bigger and to have some more platforms
  • use python 3
  • use a sprite group to handle the camera
  • refactored some duplicate code
  • since Vector2/3 is now stable, use them for easier math
  • get rid of that ugly event handling code and use pygame.key.get_pressed instead

 #! /usr/bin/python  import pygame from pygame import *  SCREEN_SIZE = pygame.Rect((0, 0, 800, 640)) TILE_SIZE = 32  GRAVITY = pygame.Vector2((0, 0.3))  class CameraAwareLayeredUpdates(pygame.sprite.LayeredUpdates):     def __init__(self, target, world_size):         super().__init__()         self.target = target         self.cam = pygame.Vector2(0, 0)         self.world_size = world_size         if self.target:             self.add(target)      def update(self, *args):         super().update(*args)         if self.target:             x = -self.target.rect.center[0] + SCREEN_SIZE.width/2             y = -self.target.rect.center[1] + SCREEN_SIZE.height/2             self.cam += (pygame.Vector2((x, y)) - self.cam) * 0.05             self.cam.x = max(-(self.world_size.width-SCREEN_SIZE.width), min(0, self.cam.x))             self.cam.y = max(-(self.world_size.height-SCREEN_SIZE.height), min(0, self.cam.y))      def draw(self, surface):         spritedict = self.spritedict         surface_blit = surface.blit         dirty = self.lostsprites         self.lostsprites = []         dirty_append = dirty.append         init_rect = self._init_rect         for spr in self.sprites():             rec = spritedict[spr]             newrect = surface_blit(spr.image, spr.rect.move(self.cam))             if rec is init_rect:                 dirty_append(newrect)             else:                 if newrect.colliderect(rec):                     dirty_append(newrect.union(rec))                 else:                     dirty_append(newrect)                     dirty_append(rec)             spritedict[spr] = newrect         return dirty                          def main():     pygame.init()     screen = pygame.display.set_mode(SCREEN_SIZE.size)     pygame.display.set_caption("Use arrows to move!")     timer = pygame.time.Clock()      level = [         "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",         "P                                          P",         "P                                          P",         "P                                          P",         "P                    PPPPPPPPPPP           P",         "P                                          P",         "P                                          P",         "P                                          P",         "P    PPPPPPPP                              P",         "P                                          P",         "P                          PPPPPPP         P",         "P                 PPPPPP                   P",         "P                                          P",         "P         PPPPPPP                          P",         "P                                          P",         "P                     PPPPPP               P",         "P                                          P",         "P   PPPPPPPPPPP                            P",         "P                                          P",         "P                 PPPPPPPPPPP              P",         "P                                          P",         "P                                          P",         "P                                          P",         "P                                          P",         "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]           platforms = pygame.sprite.Group()     player = Player(platforms, (TILE_SIZE, TILE_SIZE))     level_width  = len(level[0])*TILE_SIZE     level_height = len(level)*TILE_SIZE     entities = CameraAwareLayeredUpdates(player, pygame.Rect(0, 0, level_width, level_height))          # build the level     x = y = 0     for row in level:         for col in row:             if col == "P":                 Platform((x, y), platforms, entities)             if col == "E":                 ExitBlock((x, y), platforms, entities)             x += TILE_SIZE         y += TILE_SIZE         x = 0          while 1:          for e in pygame.event.get():             if e.type == QUIT:                  return             if e.type == KEYDOWN and e.key == K_ESCAPE:                 return          entities.update()          screen.fill((0, 0, 0))         entities.draw(screen)         pygame.display.update()         timer.tick(60)  class Entity(pygame.sprite.Sprite):     def __init__(self, color, pos, *groups):         super().__init__(*groups)         self.image = Surface((TILE_SIZE, TILE_SIZE))         self.image.fill(color)         self.rect = self.image.get_rect(topleft=pos)  class Player(Entity):     def __init__(self, platforms, pos, *groups):         super().__init__(Color("#0000FF"), pos)         self.vel = pygame.Vector2((0, 0))         self.onGround = False         self.platforms = platforms         self.speed = 8         self.jump_strength = 10              def update(self):         pressed = pygame.key.get_pressed()         up = pressed[K_UP]         left = pressed[K_LEFT]         right = pressed[K_RIGHT]         running = pressed[K_SPACE]                  if up:             # only jump if on the ground             if self.onGround: self.vel.y = -self.jump_strength         if left:             self.vel.x = -self.speed         if right:             self.vel.x = self.speed         if running:             self.vel.x *= 1.5         if not self.onGround:             # only accelerate with gravity if in the air             self.vel += GRAVITY             # max falling speed             if self.vel.y > 100: self.vel.y = 100         print(self.vel.y)         if not(left or right):             self.vel.x = 0         # increment in x direction         self.rect.left += self.vel.x         # do x-axis collisions         self.collide(self.vel.x, 0, self.platforms)         # increment in y direction         self.rect.top += self.vel.y         # assuming we're in the air         self.onGround = False;         # do y-axis collisions         self.collide(0, self.vel.y, self.platforms)      def collide(self, xvel, yvel, platforms):         for p in platforms:             if pygame.sprite.collide_rect(self, p):                 if isinstance(p, ExitBlock):                     pygame.event.post(pygame.event.Event(QUIT))                 if xvel > 0:                     self.rect.right = p.rect.left                 if xvel < 0:                     self.rect.left = p.rect.right                 if yvel > 0:                     self.rect.bottom = p.rect.top                     self.onGround = True                     self.vel.y = 0                 if yvel < 0:                     self.rect.top = p.rect.bottom  class Platform(Entity):     def __init__(self, pos, *groups):         super().__init__(Color("#DDDDDD"), pos, *groups)  class ExitBlock(Entity):     def __init__(self, pos, *groups):         super().__init__(Color("#0033FF"), pos, *groups)  if __name__ == "__main__":     main() 
like image 93
sloth Avatar answered Oct 02 '22 15:10

sloth