Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame: Infinite scrolling camera?

Tags:

python

pygame

So I've programmed a small platform game in pygame, where you can place blocks and jump around on them in stuff.. but, the game is confined to the border of the window (obviously). So how could I add a method of scrolling a "camera" with the A and D keys?

Here is the code for the game:

    import pygame,random
    from pygame.locals import *
    from collections import namedtuple

    pygame.init()
    clock=pygame.time.Clock()
    screen=pygame.display.set_mode((640,480))
    pygame.display.set_caption("PiBlocks | By Sam Tubb")
    max_gravity = 100
    blocksel="texture\\dirt.png"
    curs = pygame.image.load("texture\\cursor.png").convert()
    curs.set_colorkey((0,255,0))

    class Block(object):
            def __init__(self,x,y,sprite):
                    self.sprite = pygame.image.load(sprite).convert_alpha()
                    self.rect = self.sprite.get_rect(centery=y, centerx=x)

    class Player(object):
        sprite = pygame.image.load("texture\\playr.png").convert()
        sprite.set_colorkey((0,255,0))
        def __init__(self, x, y):
            self.rect = self.sprite.get_rect(centery=y, centerx=x)
            # indicates that we are standing on the ground
            # and thus are "allowed" to jump
            self.on_ground = True
            self.xvel = 0
            self.yvel = 0
            self.jump_speed = 7
            self.move_speed = 3

        def update(self, move, blocks):

            # check if we can jump 
            if move.up and self.on_ground: 
                self.yvel -= self.jump_speed

            # simple left/right movement
            if move.left: self.xvel = -self.move_speed
            if move.right: self.xvel = self.move_speed

            # if in the air, fall down
            if not self.on_ground:
                self.yvel += 0.3
                # but not too fast
                if self.yvel > max_gravity: self.yvel = max_gravity

            # if no left/right movement, x speed is 0, of course
            if not (move.left or move.right):
                self.xvel = 0

            # move horizontal, and check for horizontal collisions
            self.rect.left += self.xvel
            self.collide(self.xvel, 0, blocks)

            # move vertically, and check for vertical collisions
            self.rect.top += self.yvel
            self.on_ground = False;
            self.collide(0, self.yvel, blocks)

        def collide(self, xvel, yvel, blocks):
            # all blocks that we collide with
            for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:

                # if xvel is > 0, we know our right side bumped 
                # into the left side of a block etc.
                if xvel > 0: self.rect.right = block.rect.left
                if xvel < 0: self.rect.left = block.rect.right

                # if yvel > 0, we are falling, so if a collision happpens 
                # we know we hit the ground (remember, we seperated checking for
                # horizontal and vertical collision, so if yvel != 0, xvel is 0)
                if yvel > 0:
                    self.rect.bottom = block.rect.top
                    self.on_ground = True
                    self.yvel = 0
                # if yvel < 0 and a collision occurs, we bumped our head
                # on a block above us
                if yvel < 0: self.rect.top = block.rect.bottom

    colliding = False
    Move = namedtuple('Move', ['up', 'left', 'right'])
    player=[]
    blocklist=[]
    font=pygame.font.Font(None,20)
    while True:
        screen.fill((25,30,90))
        mse = pygame.mouse.get_pos()
        key = pygame.key.get_pressed()
        if key[K_1]:
            blocksel="texture\\dirt.png"
        if key[K_2]:
            blocksel="texture\\stonetile.png"
        if key[K_3]:
            blocksel="texture\\sand.png"
        if key[K_ESCAPE]:
            exit()
        for event in pygame.event.get():
            if event.type == QUIT:
                exit()

            if key[K_LSHIFT]:
                if event.type==MOUSEMOTION:
                    if not any(block.rect.collidepoint(mse) for block in blocklist):
                        x=(int(mse[0]) / 32)*32
                        y=(int(mse[1]) / 32)*32
                        blocklist.append(Block(x+16,y+16,blocksel))
            else:
                if event.type == pygame.MOUSEBUTTONUP:
                    if event.button == 1:
                        to_remove = [b for b in blocklist if b.rect.collidepoint(mse)]
                        for b in to_remove:
                            blocklist.remove(b)

                        if not to_remove:
                            x=(int(mse[0]) / 32)*32
                            y=(int(mse[1]) / 32)*32
                            blocklist.append(Block(x+16,y+16,blocksel))

                    elif event.button == 3:
                        x=(int(mse[0]) / 32)*32
                        y=(int(mse[1]) / 32)*32
                        player=Player(x+16,y+16)

        move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])

        for b in blocklist:
                screen.blit(b.sprite, b.rect)

        if player:
            player.update(move, blocklist)
            screen.blit(player.sprite, player.rect)
        x=(int(mse[0]) / 32)*32
        y=(int(mse[1]) / 32)*32
        screen.blit(curs,(x,y))
        clock.tick(60)
        x=blocksel.replace('texture\\','')
        x=x.replace('.png','')
        words=font.render('Selected Block: '+str(x), True, (255,255,255))
        screen.blit(words,(1,1))
        pygame.display.flip()

Any help is appreciated :)

like image 294
Sam Tubb Avatar asked Feb 16 '23 03:02

Sam Tubb


2 Answers

In order to have a scrolling camera, you must make a distinction between screen coordinates and world coordinates.

In this figure the outline of the screen (and screen coordinates) are shown in red. World coordinates are in black. The dashed arrow shows the offset of the screen origin from the world origin (the camera vector or camera position).

World vs screen coordinates

You'll need to convert from world coordinates to screen coordinates (the camera transform) when drawing objects, and convert from screen coordinates back to world coordinates (the inverse camera transform) when working out which block the mouse is pointing at.

In 2-D, these transforms are straightforward: the camera transform is "world coordinates minus camera position", and the inverse camera transform is "screen coordinates plus camera position".


If you're interested in more general comments on your code, you might consider posting your code to Code Review (when you feel that it's as good as you can make it).

Some quick notes on issues I spotted:

  1. You load a texture and make a sprite each time you create a new block. This seems a bit wasteful: why not load each texture just once and use the sprite multiple times?

  2. The player has no control over the height of the jump. It's worth looking very carefully at classic platform games like Super Mario World to see how jumping works.

  3. Speeds are expressed in pixels per frame, which means that you can't vary the framerate. Consider expressing speeds in pixels per second and multiplying by the timestep.

  4. The variable max_gravity is poorly named: it's the player's terminal downward velocity. (Also, since it's constant, you might want to name it in uppercase.)

  5. There's lots of repetitious code that could be turned into functions or methods. For example, code like this:

    x=(int(mse[0]) / 32)*32
    y=(int(mse[1]) / 32)*32
    ... x+16,y+16 ...
    

    appears in four places. (This is going to become an inverse camera transform when you revise your code to add a camera position.)

  6. It would make the code easier to understand if you gave names to your constants, for example:

    BACKGROUND_COLOR = 25, 30, 90
    TEXT_COLOR = pygame.Color('white')
    BLOCK_SIZE = 32, 32
    SCREEN_SIZE = 640, 480
    FRAMES_PER_SECOND = 60
    
like image 112
Gareth Rees Avatar answered Feb 17 '23 16:02

Gareth Rees


The simplest way to do this that I know of is to set up game variables (or a separate class) containing camera coordinates. These act as offsets for drawing your sprites.

So, if camera_x and camera_y are said variables, you would change all of your screen.blit calls to include the offset - except for the cursor and UI text, as well as anything else that should be positioned relative to the screen origin and not the world.

In order to move the camera you can use much the same method as moving the character. Set your key processing to increase camera_x if the D key is pressed, and decrease it for A. Keep in mind that if you are using the positive x axis to represent that of screen-space, you should subtract this offset from your blit calls (if the camera moves right, x is increased, and sprites are shifted to the left on screen.)

like image 22
Kyle Travis Avatar answered Feb 17 '23 16:02

Kyle Travis