2
\$\begingroup\$

I am currently making a 2d game in Pygame and have run into a roadblock trying to make a scrolling camera (follows the character). I have seen some other answers to similar questions, but they have helped very little. Also, I am purposely doing this without the use of Pygame's Sprite class, so that makes things a little more complex. How should I approach this?

display_width = 800
display_height = 640
size = (display_width, display_height)
half_width = int(display_width / 2)
half_height = int(display_height / 2)

black = (0,0,0)
white = (255,255,255)

clock = pygame.time.Clock()
pygame.display.set_caption("My game")
screen = pygame.display.set_mode(size)

background = Surface((32,32))
background.convert()
background.fill(Color('#783131'))

x = 0
y = 0

platformlst = []
players = []

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",]

total_level_width = len(level[0])*32
total_level_height = len(level)*32
map_size = (total_level_width, total_level_height)
virtualwindow = pygame.Surface.get_rect(screen) 


class Camera:
    def __init__(self, players):
        self.left_viewbox = display_width/2 - display_width/8
        self.right_viewbox = display_width/2 + display_width/10

    def follow(self, shift_x):
        virtualwindow.x += shift_x
        print virtualwindow.x
        for i in players:   
            i.rect.x += shift_x

    def viewbox(self):
        if player.x <= self.left_viewbox:
            view_difference = self.left_viewbox - player.x
            player.x = self.left_viewbox
            self.follow(view_difference)

        if player.x >= self.right_viewbox:
            view_difference = self.right_viewbox - player.x
            player.x = self.right_viewbox
            self.follow(view_difference)

Basically, the player objects are added to a list, which is called out in the follow() function, which is called by the viewbox() function. Then the follow() function should be adding the view_difference as shift_x to the virtualwindow.x. Shouldn't this therefore scroll the map along its x axis relative with the characters movement?

Here is the full code: https://github.com/tear727/Netse/blob/master/game.py

\$\endgroup\$

2 Answers 2

0
\$\begingroup\$

Code:

import pygame
pygame.init()
#
###
class Player:
    def __init__(self):
        self.image = pygame.Surface((16,16)) # Create Player Image
        self.image.fill(colors["RED"]) # Fill Player Red
        self.rect = pygame.Rect((50,50),(16,16)) # Create Player Rect
    def move(self,camera_pos):
        pos_x,pos_y = camera_pos # Split camara_pos
        #
        key = pygame.key.get_pressed() # Get Keyboard Input
        if key[pygame.K_w]: # Check Key
            self.rect.y -= 8 # Move Player Rect Coord
            pos_y += 8 # Move Camara Coord Against Player Rect
        if key[pygame.K_a]:
            self.rect.x -= 8
            pos_x += 8
        if key[pygame.K_s]:
            self.rect.y += 8
            pos_y -= 8
        if key[pygame.K_d]:
            self.rect.x += 8
            pos_x -= 8
        #
        if self.rect.x < 0: # Simple Sides Collision
            self.rect.x = 0 # Reset Player Rect Coord
            pos_x = camera_pos[0] #Reset Camera Pos Coord
        elif self.rect.x > 984:
            self.rect.x = 984
            pos_x = camera_pos[0]
        if self.rect.y < 0:
            self.rect.y = 0
            pos_y = camera_pos[1]
        elif self.rect.y > 984:
            self.rect.y = 984
            pos_y = camera_pos[1]
        #
        return (pos_x,pos_y) # Return New Camera Pos
    def render(self,display):
        display.blit(self.image,(self.rect.x,self.rect.y))
###
#
#
###
def Main(display,clock):
    world = pygame.Surface((1000,1000)) # Create Map Surface
    world.fill(colors["BLACK"]) # Fill Map Surface Black
    for x in range(10):
        pygame.draw.rect(world,colors["BLUE"],((x*100,x*100),(20,20))) # Put Blue Rectagles On Map Surface
    #
    player = Player() # Initialize Player Class
    camera_pos = (192,192) # Create Camara Starting Position
    #
    while True:
        clock.tick(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
        #
        camera_pos = player.move(camera_pos) # Run Player Move Function And Return New Camera Pos
        #
        display.fill(colors["WHITE"]) # Fill The Background White To Avoid Smearing
        world.fill(colors["BLACK"]) # Refresh The World So The Player Doesn't Smear
        for x in range(10):
            pygame.draw.rect(world,colors["BLUE"],((x*100,x*100),(20,20)))
        player.render(world) # Render The Player
        display.blit(world,camera_pos) # Render Map To The Display
        #
        pygame.display.flip()
###
#
if __name__ in "__main__":
    display = pygame.display.set_mode((500,500))
    pygame.display.set_caption("Scrolling Camera")
    clock = pygame.time.Clock()
    #
    global colors # Difign Colors
    colors = {
    "WHITE":(255,255,255),
    "RED"  :(255,0,0),
    "GREEN":(0,255,0),
    "BLUE" :(0,0,255),
    "BLACK":(0,0,0)
    }
    Main(display,clock) # Run Main Loop

I remember when I had this same issue. The above code is an example I wrote up quick showing how handled the issue. Feel free to take it and reverse engineer it to your hearts content. :)

The way I figured it out is that since the actual window is itself a surface, if all the games world assets, the player, scenery, goals, etc., were placed on a player made surface instead of the display surface. This surface can then be moved against the player movement to create the allusion that the player remains in the same place.

enter image description here

This is shown in my example code here within the players move function:

        pos_x,pos_y = camera_pos # Split camara_pos
        #
        key = pygame.key.get_pressed() # Get Keyboard Input
        if key[pygame.K_w]: # Check Key
            self.rect.y -= 8 # Move Player Rect Coord
            pos_y += 8 # Move Camara Coord Against Player Rect
        if key[pygame.K_a]:
            self.rect.x -= 8
            pos_x += 8
        if key[pygame.K_s]:
            self.rect.y += 8
            pos_y -= 8
        if key[pygame.K_d]:
            self.rect.x += 8
            pos_x -= 8

By moving the blitting coods of the camera the an opposite direction and at the same velocity of the player, it cancels out the momentum of the player object, and creates the illusion that the player is standing still while the game world moves around him/her/it. (I actually figured this all out in Physics class during a lecture on momentum)

Make sure to blit the player to the world surface and not the display surface as shown in the code:

        display.fill(colors["WHITE"]) # Fill The Background White To Avoid Smearing
        world.fill(colors["BLACK"]) # Refresh The World So The Player Doesn't Smear
        for x in range(10):
            pygame.draw.rect(world,colors["BLUE"],((x*100,x*100),(20,20)))
        player.render(world) # Render The Player
        display.blit(world,camera_pos) # Render Map To The Display

As if you mistakenly blit the player to the display instead of the world, they won't work off of each other and the player will instead have the illusion of moving twice as fast as the world. If you want, you can switch out:

        player.render(world) # Render The Player

with:

        player.render(display) # Render The Player

And see what I mean by that.

I hope all this helps, and best of luck with your future coding projects. :)

\$\endgroup\$
2
  • \$\begingroup\$ Wow! Thanks so much, this is awesome. This is exactly what I needed. I will implement this right away \$\endgroup\$ Commented Aug 17, 2015 at 20:20
  • \$\begingroup\$ @tear728 I'm glad I can help. If there is anything about my answer that you don't quite understand, feel free to comment, and I'll update my answer to help make everything more clear. :) \$\endgroup\$ Commented Aug 17, 2015 at 22:07
0
\$\begingroup\$

Take a look at this post. Basically you can keep the camera position around, and decide where to draw things based on that. I couldn't quite make sense of your game but here are some brief changes I made:

...
total_level_width = len(level[0])*32
total_level_height = len(level)*32
map_size = pygame.Rect(0, 0, total_level_width, total_level_height)
cameraPosition = [0, 0]
#################Objects################



class Camera:
    def __init__(self, players):
        self.left_viewbox = display_width/2 - display_width/8
        self.right_viewbox = display_width/2 + display_width/10

    def follow(self, shift_x):
        cameraPosition[0] += shift_x
        print cameraPosition
        for i in players:   
            i.rect.x += shift_x

...

Whenever you draw an object then you can call blit like this:

screen.blit(self.img, (self.x - cameraPosition[0], self.y - cameraPosition[1], self.w, self.h))
\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I'll give it a try. Don't worry, I can't even make sense of it anymore. I need to rewrite it. \$\endgroup\$ Commented Aug 13, 2015 at 6:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .