Reputation: 192
Say we have a game made in pygame, and in this game, we have "scroll" objects which basically boxes the camera scrolls within, stopping at the edges. When the player leaves a scroll and enters a new one, the camera moves from one scroll to the other. In this situation, to achieve a parallax effect, we would need to divide the amount the camera scrolls by a value. However, if we just do this alone, when we move into a new "scroll", the background will appear to be shifted by an incorrect amount relative to how much it scrolled in the first "scroll", how would one go about fixing this? Here is my camera code, and the scroll code,
import pygame
from pygame.locals import *
class scroll_handler(list):
def __init__(self):
self.primary_scroll = None
self.append(scroll_obj((50+1)*16,(2)*16,65*16,30*16))
self.append(scroll_obj((51+65)*16,(2)*16,65*16,30*16))
def update(self,player):
for i in self:
if i.entity_in_scroll(player):
self.primary_scroll = i
class scroll_obj(pygame.Rect):
def __init__(self,x,y,sx,sy,enable_backgrounds = True):
super().__init__((x,y),(sx,sy))
self.enable_backgrounds = enable_backgrounds
def entity_in_scroll(self,entity):
return self.contains(entity)
class camera(object):
def __init__(self, camera_func, widthy, heighty):
self.state = pygame.Rect((16,16),(widthy,heighty))
self.camera_func = camera_func
self.scroll = scroll_obj(16,16,widthy,heighty)
def update_scroll(self, scroll):
self.state = pygame.Rect(scroll.x, scroll.y, scroll.width, scroll.height)
def apply(self, target, state = 0, parallax = False):
if not parallax:
return target.move(self.state.topleft)
if parallax:
return target.move(state.topleft)
def update(self, target, parallax_x = 1, parallax_y = 1):
self.update_scroll(self.scroll)
self.state = self.camera_func( self.state, target, parallax_x, parallax_y)
def complex_camera(camera, target_rect, parallax_x=1, parallax_y=1):
l, t, _, _ = target_rect
_, _, w, h = camera
l, t, _, _ = -l+(320), -t+(180), w, h
l = min(-camera.x, l) # stop scrolling at the left edge
l = max(-(camera.x + camera.width-640), l) # stop scrolling at the right edge
l = round(l/(parallax_x),2) # apply parallax in x direction
t = max(-(camera.y + camera.height-360), t) # stop scrolling at the bottom
t = min(-camera.y, t) # stop scrolling at the top
t = round(t/(parallax_y),2) # apply parallax in y direction
return pygame.Rect(l, t, w, h)
here is my code for backgrounds and applying the parallax effect to them
import pygame
class background(pygame.Rect):
def __init__(self,x,y,image):
super().__init__(x*16,y*16,1024,512)
self.image = image
self.CAX = self.x
self.CAY = self.y
self.rect = pygame.Rect(self.CAX,self.CAY,1024,512) # an additional rect object used elsewhere in another code file, ignore this
def apply_camera(self, state, GAME):
cam = GAME.camera.apply(self, state, True)
self.CAX = cam[0]
self.CAY = cam[1]
self.rect.x = self.CAX
self.rect.y = self.CAY
class backgrounds(dict):
def __init__(self,GAME):
self.GAME = GAME
BH = self.GAME.background_handler
self["background_0"] = [background(0,0,BH[0])]
self["background_1"] = [background(0,1,BH[3])]
self["background_2"] = [background(0,2,BH[2])]
self["background_3"] = [background(0,3,BH[1])]
def get_background(self):
self.background_0 = []
self.background_1 = []
self.background_2 = []
self.background_3 = []
original = self.GAME.camera.state
half = pygame.Rect(int(original[0]/2), int(original[1]/2), self.GAME.camera.state.width, self.GAME.camera.state.height)
quater = pygame.Rect(int(original[0]/4), int(original[1]/4), self.GAME.camera.state.width, self.GAME.camera.state.height)
eighth = pygame.Rect(int(original[0]/8), int(original[1]/8), self.GAME.camera.state.width, self.GAME.camera.state.height)
fortieth = pygame.Rect(int(original[0]/40), int(original[1]/40), self.GAME.camera.state.width, self.GAME.camera.state.height)
for i in self["background_0"]:
i.apply_camera(fortieth,self.GAME)
self.background_0.append([(i.CAX,i.CAY),(i.image)])
for i in self["background_1"]:
i.apply_camera(eighth,self.GAME)
self.background_1.append([(i.CAX,i.CAY),(i.image)])
for i in self["background_2"]:
i.apply_camera(quater,self.GAME)
self.background_2.append([(i.CAX,i.CAY),(i.image)])
for i in self["background_3"]:
i.apply_camera(half,self.GAME)
self.background_3.append([(i.CAX,i.CAY),(i.image)])
return (self.background_0), (self.background_1), (self.background_2), (self.background_3)
what do I need to change in order to make it such that when entering a new scroll, the background will be in the appropriate position and not moved significantly to the right?
NOTE: The camera and scroll code are in separate modules, don't worry about this if it seems odd
Upvotes: 4
Views: 1277
Reputation: 211166
Scrolling the background is achieved by moving the background image and drawing the background image multiple times with an offset on the display:
def draw_background(surf, bg_image, bg_x, bg_y):
surf.blit(bg_image, (bg_x - surf.get_width(), bg_y))
surf.blit(bg_image, (bg_x, bg_y))
For a background parallax effect, you need several background layers that you must move at different speeds. The back layers must be moved more slowly than the front layers. e.g.:
bg_layer_1_x = (bg_layer_1_x - move_x) % 600
bg_layer_2_x = (bg_layer_2_x - move_x*0.75) % 600
bg_layer_3_x = (bg_layer_3_x - move_x*0.5) % 600
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((600, 400))
clock = pygame.time.Clock()
tree1_image = pygame.Surface((100, 100), pygame.SRCALPHA)
pygame.draw.rect(tree1_image, (64, 32, 0), (45, 75, 10, 25))
pygame.draw.polygon(tree1_image, (16, 64, 0), [(50, 0), (70, 35), (65, 35), (90, 75), (10, 75), (35, 35), (30, 35)])
tree2_image = pygame.Surface((100, 100), pygame.SRCALPHA)
pygame.draw.rect(tree2_image, (128, 64, 16), (45, 75, 10, 25))
pygame.draw.polygon(tree2_image, (64, 128, 0), [(50, 0), (70, 35), (65, 35), (90, 75), (10, 75), (35, 35), (30, 35)])
tree2_image = pygame.transform.smoothscale(tree2_image, (75, 75))
tree3_image = pygame.Surface((100, 100), pygame.SRCALPHA)
pygame.draw.rect(tree3_image, (164, 128, 96), (45, 75, 10, 25))
pygame.draw.polygon(tree3_image, (128, 164, 64), [(50, 0), (70, 35), (65, 35), (90, 75), (10, 75), (35, 35), (30, 35)])
tree3_image = pygame.transform.smoothscale(tree3_image, (50, 50))
bg_layer_1_image = pygame.Surface((600, 100), pygame.SRCALPHA)
for i in range(6):
bg_layer_1_image.blit(tree1_image, (i*100, 0))
bg_layer_2_image = pygame.Surface((600, 75), pygame.SRCALPHA)
for i in range(8):
bg_layer_2_image.blit(tree2_image, (i*75, 0))
bg_layer_3_image = pygame.Surface((600, 50), pygame.SRCALPHA)
for i in range(12):
bg_layer_3_image.blit(tree3_image, (i*50, 0))
bg_layer_1_x = 0
bg_layer_2_x = 0
bg_layer_3_x = 0
move_x = 1
def draw_background(surf, bg_image, bg_x, bg_y):
surf.blit(bg_image, (bg_x - surf.get_width(), bg_y))
surf.blit(bg_image, (bg_x, bg_y))
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
bg_layer_1_x = (bg_layer_1_x - move_x) % 600
bg_layer_2_x = (bg_layer_2_x - move_x*0.75) % 600
bg_layer_3_x = (bg_layer_3_x - move_x*0.5) % 600
window.fill((192, 192, 255))
pygame.draw.rect(window, (64, 128, 64), (0, 250, 600, 150))
pygame.draw.rect(window, (128, 164, 192), (0, 170, 600, 80))
draw_background(window, bg_layer_3_image, bg_layer_3_x, 120)
draw_background(window, bg_layer_2_image, bg_layer_2_x, 135)
draw_background(window, bg_layer_1_image, bg_layer_1_x, 150)
pygame.display.flip()
pygame.quit()
exit()
Also see PyGameExamplesAndAnswers - Background
Upvotes: 0