Tuesday, April 12, 2016

python - Pygame - implementing a scrolling camera


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



Answer




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. :)


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...