1

I'm trying to add a scrolling "camera" that follows the player when it moves but can't figure out how to do this. I know that you can just move the level in the opposite direction when you press one of the movement keys but I'd rather not do that as I plan on adding enemies later on and don't want have to keep update their coordinates as the player moves.

I've added my code with a sample level below.

Code:

import pygame, sys, time, random, math
from pygame.locals import *

BACKGROUNDCOLOR = (255, 255, 255)

WINDOWW = 800 
WINDOWH = 600
PLAYERW = 66
PLAYERH = 22
FPS = 60
MOVESPEED = 3
YACCEL = 0.13
GRAVITY = 2
BLOCKSIZE = 30

pygame.init()
screen = pygame.display.set_mode((WINDOWW, WINDOWH), 0, 32)
mainClock = pygame.time.Clock()

testLevel = [
            (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,),
            (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,)]

def createblock(length, height, color):
    tmpblock = pygame.Surface((length, height))
    tmpblock.fill(color)
    tmpblock.convert()
    return tmpblock

def terminate(): # Used to shut down the software
    pygame.quit()
    sys.exit()

def add_level(lvl, bSize): # Creates the level based on a map (lvl) and the size of blocks
    bList = [] # List of every block
    bListDisp = [] # List of every block to display    
    bTypeList = [] # List with corresponding type of block(wall, air, etc.)

    for y in range(len(lvl)): 
        for x in range(len(lvl[0])):

            if lvl[y][x] == 0: # If the block type on lvl[y][x] is '0', write "air" down in the type list
                bTypeList.append("air")
            elif lvl[y][x] == 1: # If the block type on lvl[y][x] is '1', write "wall" down in the type list
                bTypeList.append("solid")

            bList.append(pygame.Rect((bSize * x), (bSize * y), bSize, bSize)) #Append every block that is registered
            bListDisp.append(pygame.Rect((bSize * x), (bSize * y), bSize, bSize)) #Append every block to display that is registered

    return bList, bListDisp, bTypeList

player = pygame.Rect((WINDOWW/2), (WINDOWH - BLOCKSIZE*3), PLAYERW, PLAYERH)
wallblock = createblock(BLOCKSIZE, BLOCKSIZE,(20,0,50))

lastTime = pygame.time.get_ticks()
isGrounded = False

vx = 0
vy = 0

allLevels = [testLevel] # A list containing all lvls(only one for now)
maxLevel = len(allLevels) # Checks which level is the last
currLevel = allLevels[0] # Current level(start with the first lvl)
blockList, blockListDisp, blockTypeList = add_level(currLevel, BLOCKSIZE) # A list with every block and another list with the blocks types

thrusters = True
jumping = False
falling = True
while True:
    """COLLISION"""
    collision = False
    for i in range(len(blockTypeList)):
        if blockTypeList[i] == "solid":
            if player.colliderect(blockList[i]): 
                collision = True
                if vx > 0 and not falling:
                    player.right = blockListDisp[i].left
                    vx = 0
                    print('Collide Right')
                if vx < 0 and not falling:
                    player.left = blockListDisp[i].right
                    vx = 0
                    print('Collide Left')
                if vy > 0:
                    player.bottom = blockListDisp[i].top
                    isGrounded = True
                    falling = False
                    vy = 0
                    print('Collide Bottom')
                if vy < 0:
                    player.top = blockListDisp[i].bottom
                    vy = 0
                    print('Collide Top')
            else:
                player.bottom += 1
                if player.colliderect(blockList[i]):
                    collision = True
                    #isGrounded = True
                    #falling = False
                player.bottom -= 1
    if not collision:
        falling = True
        isGrounded = False

    # Input
    pressedKeys = pygame.key.get_pressed() # Checks which keys are being pressed
    timeDiff = pygame.time.get_ticks() - lastTime # Calculates time difference 
    lastTime +=  timeDiff # Last time checked reset to current time

    # Shut-down if the ESC-key is pressed or the window is "crossed down"
    for event in pygame.event.get():
        if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE:
            terminate()    

    """X-axis control"""
    if pressedKeys[ord('a')]:
        vx = -MOVESPEED
    if pressedKeys[ord('d')]:
        vx = MOVESPEED
    if not pressedKeys[ord('d')] and not pressedKeys[ord('a')]:
        vx = 0

    """Y-axis control"""
    # Controls for jumping
    if pressedKeys[ord('w')] and thrusters == True:
            vy -= YACCEL * timeDiff; # Accelerate along the y-xis when "jumping", but not above/below max speed
            if vy <= -4:
                vy = -4
            isGrounded = False # You are airborne
            jumping = True # You are jumping

    if event.type == KEYUP: # If you let go of the "jump"-button, stop jumping
        if event.key == ord('w') and vy < 0 and not isGrounded:
            jumping = False
            falling = True

    player.x += vx
    player.y += vy

    # Gravity
    if not isGrounded or falling:
        vy += 0.3
        if vy > 80:
            vy = 80

    screen.fill(BACKGROUNDCOLOR)

    for i in range(len(blockTypeList)):
        if blockTypeList[i] == "solid":
            screen.blit(wallblock, (blockListDisp[i].x, blockListDisp[i].y)) #blit the wall-block graphics

    pygame.draw.rect(screen, (0, 0, 0), player)

    pygame.display.update()
    mainClock.tick(FPS)
glglgl
  • 89,107
  • 13
  • 149
  • 217
user2274889
  • 371
  • 1
  • 4
  • 7
  • You may want to take a look at [this question](http://stackoverflow.com/questions/14354171/how-to-add-scrolling-to-a-platformer-in-pygame). IMO, the accepted answer deserves much more upvotes. – A. Rodas Apr 22 '13 at 18:15
  • Yes, I've seen that question before but since I'm relatively new to python and pygame I still have some trouble understanding classes so that answer doesn't really work for me. – user2274889 Apr 22 '13 at 19:14

1 Answers1

1

The trick is to keep track of camera coordinates and use these as an offset in your rendering code. It looks like you're doing you're rendering right at the end of the code you've posted, drawing each block with coord x,y to pixel x,y on the screen.

As you say, shifting the level around isn't great. Instead, have your key inputs (or other camera moving device) change cameraX and cameraY variables, and then add (or subtract, depending which direction you want to go) these values from the block x and y values to change which pixels map to which blocks. I.e. change your rendering to:

screen.blit(wallblock, (blockListDisp[i].x + cameraX, blockListDisp[i].y + cameraY))

This means if your camera moves to (10, 20) then you map your block at (5, 5) to (15, 25) on the screen, shifting your whole level across while your underlying model of the level stays the same. Make sense?

You can also take this slightly further; if your camera is only being moved to follow your character you can make swap cameraX and cameraY in the above for some function of the character position, and have the whole thing just managed directly there.

Tim Perry
  • 11,766
  • 1
  • 57
  • 85
  • I'm not quite sure I understand what you mean with the whole change the cameraX and cameraY variables thing. Could you maybe post an example code? – user2274889 Apr 22 '13 at 18:22