Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>You need to apply an <em>offset</em> to the position of your entities when drawing them. Let's call that <em>offset</em> a <code>camera</code>, since this is the effect we want to achieve with this.</p> <p>First of all, we can't (and should not) use the <code>draw</code> function of the sprite group, since the sprites don't need to know that their position (<code>rect</code>) is not the position they are going to be drawn on the screen (of course we could subclass the <code>Group</code> class and reimplement the it's <code>draw</code> to be aware of the camera, but for the sake of learning let's be explicit here).</p> <hr> <p>Let's start by creating a <code>Camera</code> class to hold the state of the offset we want to apply to the position of our entities:</p> <pre><code>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) </code></pre> <p>some things to note here:</p> <p>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 <code>Rect</code> to store all these informations, but you could easily just use some fields. </p> <p>Using <code>Rect</code> comes in handy in the <code>apply</code> function. This is where we re-calculate the position of an entity on the screen to <em>apply</em> the scrolling.</p> <p>Once per iteration of the main loop, we need to update the position of the camera, hence there's the <code>update</code> function. It just alters the state by calling the <code>camera_func</code> function, which will do <em>all the hard work</em> for us. We implement it later.</p> <p>Let's create an instace of the camera:</p> <pre><code>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) ... </code></pre> <p>and alter our main loop:</p> <pre><code># 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() </code></pre> <p>Our camera class is already very flexible and yet dead simple. It can use different kinds of scrolling (by providing different <code>camera_func</code> functions), and it can follow any arbitary sprite, not just the player. You even can change this at runtime.</p> <p>Now for the implementation of <code>camera_func</code>. 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:</p> <pre><code>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) </code></pre> <p>We just take the position of our <code>target</code>, and add the half total screen size. You can try it by creating your camera like this:</p> <pre><code>camera = Camera(simple_camera, total_level_width, total_level_height) </code></pre> <p>So far, so good. But maybe we don't want to see the black background <em>outside</em> the level? How about:</p> <pre><code>def complex_camera(camera, target_rect): l, t, _, _ = target_rect _, _, w, h = camera l, t, _, _ = -l+HALF_WIDTH, -t+HALF_HEIGHT, w, h # center player l = min(0, l) # stop scrolling at the left edge l = max(-(camera.width-WIN_WIDTH), l) # stop scrolling at the right edge t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling at the bottom t = min(0, t) # stop scrolling at the top return Rect(l, t, w, h) </code></pre> <p>Here we simply use the <code>min</code>/<code>max</code> functions to ensure we don't scroll <em>outside</em> out level. </p> <p>Try it by creating your camera like this:</p> <pre><code>camera = Camera(complex_camera, total_level_width, total_level_height) </code></pre> <p>There's a little animation of our new scrolling in action:</p> <p><img src="https://i.stack.imgur.com/uZ5Qt.gif" alt="enter image description here"></p> <p>Here's the complete code again (note I changed your level a little bit to be bigger and to have some more platforms):</p> <pre><code>#! /usr/bin/python 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 = [ "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",] # 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 total_level_width = len(level[0])*32 total_level_height = len(level)*32 camera = Camera(complex_camera, total_level_width, total_level_height) 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 # draw background for y in range(32): for x in range(32): screen.blit(bg, (x * 32, y * 32)) camera.update(player) # update player, draw everything else player.update(up, down, left, right, running, platforms) for e in entities: screen.blit(e.image, camera.apply(e)) pygame.display.update() 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) def simple_camera(camera, target_rect): l, t, _, _ = target_rect _, _, w, h = camera return Rect(-l+HALF_WIDTH, -t+HALF_HEIGHT, w, h) def complex_camera(camera, target_rect): l, t, _, _ = target_rect _, _, w, h = camera l, t, _, _ = -l+HALF_WIDTH, -t+HALF_HEIGHT, w, h l = min(0, l) # stop scrolling at the left edge l = max(-(camera.width-WIN_WIDTH), l) # stop scrolling at the right edge t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling at the bottom t = min(0, t) # stop scrolling at the top return Rect(l, t, w, h) 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 &gt; 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 &gt; 0: self.rect.right = p.rect.left print "collide right" if xvel &lt; 0: self.rect.left = p.rect.right print "collide left" if yvel &gt; 0: self.rect.bottom = p.rect.top self.onGround = True self.yvel = 0 if yvel &lt; 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() </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload