Skip to content

Instantly share code, notes, and snippets.

@Mega-JC
Last active November 16, 2021 11:04
Show Gist options
  • Save Mega-JC/6b67083d0f94d4bfc4a83f063d8724f5 to your computer and use it in GitHub Desktop.
Save Mega-JC/6b67083d0f94d4bfc4a83f063d8724f5 to your computer and use it in GitHub Desktop.
Basic 2D Camera System for pygame using LayeredUpdates and a Camera class.
# Two user-friendly classes for integrating a 2D camera system for pygame.
#
# The first class is Camera2, which is essentially a 2D camera with rect-like behaviour. To setup something like player tracking, one can simply do:
### Cam2 = Camera2(area=(0, 0, WIDTH, HEIGHT), view_distance=1000)
### Cam2.center = player.rect.center
# for even more precise control, one can do:
### Cam2.anchor = (Cam2.w*0.3, Cam2.h*0.7) # some coordinate on the screen
### Cam2.goto(player.rect.center) # moves the camera's world coordinate to the player's center
# The second class is CameraView, which is a subclass of pygame.sprite.LayeredUpdates .
#
# This is where all the scrolling is implemented, without ever messing with the actual sprite coordinates.
# For something to be visible in the camera's view, one can simply add those sprites into the camera's CameraView group:
### Cam2.view.add(Background1, Background2, player, monster1, monster2, ...)
# Note however, that each sprite needs to have a .distance attribute that is set to a positive real number. This is required for parallax effects to work.
# Each Camera2 object has a .view_distance property, which also is set to a positive real number.
# To finally draw sprites with the right displacement, one writes:
# Cam2.draw(surface, parallax=1, return_dirty_rects=1)
# This subsequently calls the draw() method of the CameraView object:
# with parallax set to 1:
#All sprites are displaced, with the strength based on their distance relative to the camera's view distance: clamp( (spr.distance/Cam2.view_distance) , 0.0, 1.0)
# with parallax set to 0:
#All sprites are displaced, at maximum strength.
# if a sprite's rect (after displacement) is still visible in the camera's area on the screen (using Rect.colliderect()), its image will be blitted unto the camera's view unto the destination surface, otherwise it won't.
# Multi Camera system:
#This system also supports multiple cameras on the screen, as long as their areas do not overlap:
### Cam = Camera2(area=(0, 0, WIDTH//2, HEIGHT), view_distance=1000)
### Cam2 = Camera2(area=(WIDTH//2, 0, WIDTH//2, HEIGHT), view=Cam.view, view_distance=1000) # CameraView objects can be shared
###
### Cam.view.add(Background1, Background2, player, monster1, monster2, ...)
### Cam.draw(surface, parallax=1, return_dirty_rects=1)
### Cam2.draw(surface, parallax=1, return_dirty_rects=1)
from pygame.sprite import LayeredUpdates, Sprite
from pygame import Surface, Rect
import pygame
RECT_INT_MIN = -2**15
RECT_INT_MAX = 2**15-1
max2, min2 = (lambda a, b: a if a > b else b), (lambda a, b: b if a > b else a) # faster than standard min() and max()
def clamp(a, mn=0.0, mx=1.0):
"""limits the input number a to the range ( mn < a < mx ).
"""
return mn if a < mn else mx if a > mx else a
class CameraView(LayeredUpdates):
"""subclass of LayeredUpdates specialized for use in Camera2 objects."""
def add_internal(self, sprite, layer=None):
"""Do not use this method directly.
It is used by the group to add a sprite internally.
"""
self.spritedict[sprite] = self._init_rect
if layer is None:
try:
layer = sprite._layer
except AttributeError:
layer = sprite._layer = self._default_layer
elif hasattr(sprite, '_layer'):
sprite._layer = layer
try:
sprite.distance += 0
except AttributeError as a:
a.args = ("each sprite passed to a CameraView group must have a (.distance) attribute with a positive real number as a value.",)
raise
except TypeError as t:
t.args = ("each sprite passed to a CameraView group must have a (.distance) attribute with a positive real number as a value.",)
raise
sprites = self._spritelist # speedup
sprites_layers = self._spritelayers
sprites_layers[sprite] = layer
# add the sprite at the right position
# bisect algorithmus
leng = len(sprites)
low = mid = 0
high = leng - 1
while low <= high:
mid = low + (high - low) // 2
if sprites_layers[sprites[mid]] <= layer:
low = mid + 1
else:
high = mid - 1
# linear search to find final position
while mid < leng and sprites_layers[sprites[mid]] <= layer:
mid += 1
sprites.insert(mid, sprite)
def add(self, *sprites, **kwargs):
"""add a sprite or sequence of sprites to a group
LayeredUpdates.add(*sprites, **kwargs): return None
Each sprite must have a (.distance) attribute set to a real number above or equal to zero.
If the sprite you add has an attribute _layer, then that layer will be
used. If **kwarg contains 'layer', then the passed sprites will be
added to that layer (overriding the sprite._layer attribute). If
neither the sprite nor **kwarg has a 'layer', then the default layer is
used to add the sprites.
"""
if not sprites:
return
if 'layer' in kwargs:
layer = kwargs['layer']
else:
layer = None
for sprite in sprites:
# It's possible that some sprite is also an iterator.
# If this is the case, we should add the sprite itself,
# and not the iterator object.
if isinstance(sprite, Sprite):
if not self.has_internal(sprite):
self.add_internal(sprite, layer)
sprite.add_internal(self)
else:
try:
# See if sprite is an iterator, like a list or sprite
# group.
self.add(*sprite, **kwargs)
except (TypeError, AttributeError):
# Not iterable. This is probably a sprite that is not an
# instance of the Sprite class or is not an instance of a
# subclass of the Sprite class. Alternately, it could be an
# old-style sprite group.
if hasattr(sprite, '_spritegroup'):
for spr in sprite.sprites():
if not self.has_internal(spr):
self.add_internal(spr, layer)
spr.add_internal(self)
elif not self.has_internal(sprite):
self.add_internal(sprite, layer)
sprite.add_internal(self)
def draw(self, camera_object, surface, parallax=0, return_dirty_rects=1):
"""Do not use this method in production.
It is used by Camera2 objects to draw sprites.
"""
set_clip = surface.set_clip
spritelist = self._spritelist
screenrect = camera_object._screenrect
set_clip(screenrect) # clipping to restrict blitting to Camera2 screenrect area
colliderect = Rect.colliderect # speedup
spritedict = self.spritedict
surface_blit = surface.blit
union = Rect.union # speedup
dirty = self.lostsprites
self.lostsprites = []
dirty_append = dirty.append
init_rect = self._init_rect
X = camera_object._xfloat # float Camera2 world-coordinates
Y = camera_object._yfloat
VIEW_X, VIEW_Y = screenrect.topleft # integer Camera2 screen coordinates (used as an offset for blitting)
view_distance = camera_object._view_distance # Camera2 maximum view distance
if parallax:
if return_dirty_rects:
for spr in spritelist:
spr_rect = spr.rect
spr_x, spr_y = spr_rect.topleft
parallax_ratio = 1.0-abs(spr.distance/view_distance) # percentage to multiply sprite displacement by, the higher the sprite distance, the lower the displacement.
blitpos = (VIEW_X+(clamp(spr_x-(parallax_ratio)*(X), RECT_INT_MIN, RECT_INT_MAX)), # sprite displacement formula
VIEW_Y+(clamp(spr_y-(parallax_ratio)*(Y), RECT_INT_MIN, RECT_INT_MAX))) # clamping necessary to prevent potential errors with blitting coordinates (they stop at 2**16, 16 bit ints)
if colliderect(screenrect, (blitpos, spr_rect.size)): # check if displaced sprite is contained in the Camera2's screen view, to prevent excessive blit calls.
rec = spritedict[spr]
newrect = surface_blit(spr.image, blitpos)
if rec is init_rect:
dirty_append(newrect)
else:
if colliderect(newrect, rec):
dirty_append(union(newrect, rec))
else:
dirty_append(newrect)
dirty_append(rec)
spritedict[spr] = newrect
else:
for spr in spritelist:
spr_rect = spr.rect
spr_x, spr_y = spr_rect.topleft
parallax_ratio = 1.0-abs(spr.distance/view_distance)
blitpos = (VIEW_X+(clamp(spr_x-(parallax_ratio)*(X), RECT_INT_MIN, RECT_INT_MAX)), VIEW_Y+(clamp(spr_y-(parallax_ratio)*(Y), RECT_INT_MIN, RECT_INT_MAX)))
if colliderect(screenrect, (blitpos, spr_rect.size)):
spritedict[spr] = surface_blit(spr.image, blitpos)
elif return_dirty_rects:
for spr in spritelist:
spr_rect = spr.rect
spr_x, spr_y = spr_rect.topleft
blitpos = (VIEW_X+spr_x-X, VIEW_Y+spr_y-Y) # simplified displacement formula, without parallax
if colliderect(screenrect, (blitpos, spr_rect.size)):
rec = spritedict[spr]
newrect = surface_blit(spr.image, blitpos)
if rec is init_rect:
dirty_append(newrect)
else:
if colliderect(newrect, rec):
dirty_append(union(newrect, rec))
else:
dirty_append(newrect)
dirty_append(rec)
spritedict[spr] = newrect
else: # no need to return dirty rects if not needed
for spr in spritelist:
spr_rect = spr.rect
spr_x, spr_y = spr_rect.topleft
blitpos = (VIEW_X+spr_x-X, VIEW_Y+spr_y-Y)
if colliderect(screenrect, (blitpos, spr_rect.size)):
spritedict[spr] = surface_blit(spr.image, blitpos)
set_clip(None) # remove clipping
return dirty if return_dirty_rects else None
class Camera2():
def __init__(self, area, view=None, view_distance=1000):
"""2D sprite camera class, with parallax support and rect-like behavior."""
self.RETURN_POSITION_AS_FLOATS = 0 # for returning float-coordinates when preferred
self._anchor_point = (0,0)
self._view_distance = view_distance
self.view = CameraView() if view is None else view
if not isinstance(self.view, CameraView):
t = TypeError("view parameter must be a CameraView object or None")
raise t
self._area = list(area) # Camera2 view area in 2D world space
self._xfloat = self._yfloat = 0.0 # floating point coordinates, for more precise movement
self._screenrect = Rect(area) # area on the screen that the camera blits to
self.dx = 0
self.dy = 0
def draw(self, surface, parallax=False, return_dirty_rects=True):
return self.view.draw(self, surface, parallax=parallax, return_dirty_rects=return_dirty_rects)
def _updatepos(self):
self._xfloat += self.dx
self._yfloat += self.dy
self._area[:2] = int(self._xfloat), int(self._yfloat)
@property
def screenrect(self):
""" Camera 2 area on the screen (as a rect copy) """
return self._screenrect.copy()
@screenrect.setter
def screenrect(self, rect):
self._screenrect = r = Rect(rect)
self._area[2:] = r.size
def slide_to(self, destination, dt, speed_factor=0.5, anchor_point=None):
"""slide a Camera2 object to a certain destination smoothly."""
if anchor_point is None:
anchor_point = (0,0)
else:
anchor_point = tuple(anchor_point)
fac = clamp(speed_factor*dt)
self.dx = (destination[0]-self._xfloat-anchor_point[0])*fac # Modified Lerp formula
self.dy = (destination[1]-self._yfloat-anchor_point[1])*fac
self._updatepos()
def _g_anchor_p(self):
return self._anchor_point
def _s_anchor_p(self, v):
w, h = self._screenrect.size
try:
self._anchor_point = (clamp(v[0]+0, 0, w), clamp(v[0]+0, 0, h))
return
except (TypeError, ValueError, IndexError):
pass
p = pygame.error("invalid assignment for Camera2 anchor point")
raise p
def _g_w(self):
return self._area[2]
def _s_w(self, v):
try:
self._area[2] = self._screenrect.w = int(v)
return
except ValueError:
pass
p = pygame.error("invalid assignment for Camera2 width")
raise p
def _g_h(self):
return self._area[3]
def _s_h(self, v):
try:
self._area[3] = self._screenrect.h = int(v)
return
except ValueError:
pass
p = pygame.error("invalid assignment for Camera2 height")
raise p
def _g_sz(self):
area = self._area
return (area[2], area[3])
def _s_sz(self, v):
try:
self._area[2:] = self._screenrect.size = ( int(v[0]), int(v[1]) )
return
except (ValueError, TypeError):
pass
p = pygame.error("invalid assignment for Camera2 size")
raise p
def _g_x(self):
return self._xfloat if self.RETURN_POSITION_AS_FLOATS else self._area[0]
def _s_x(self, v):
try:
self.dx = v-self._xfloat
self.dy = 0
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_y(self):
return self._yfloat if self.RETURN_POSITION_AS_FLOATS else self._area[1]
def _s_y(self, v):
try:
self.dy = v-self._yfloat
self.dx = 0
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_xy(self):
return (self._xfloat, self._yfloat) if self.RETURN_POSITION_AS_FLOATS else (self._area[0], self._area[1])
def _g_yx(self):
return (self._yfloat, self._xfloat) if self.RETURN_POSITION_AS_FLOATS else (self._area[1], self._area[0])
def _s_yx(self, v):
try:
self.dx = v[1]-self._xfloat
self.dy = v[0]-self._yfloat
self._updatepos()
return
except (TypeError, ValueError, IndexError):
pass
p = pygame.error("invalid assignment for Camera2 coordinates")
raise p
def _s_xy(self, v):
try:
self.dx = v[0]-self._xfloat
self.dy = v[1]-self._yfloat
self._updatepos()
return
except (TypeError, ValueError, IndexError):
pass
p = pygame.error("invalid assignment for Camera2 coordinates")
raise p
def _g_ct(self):
area = self._area
return area[0]+area[2]//2, area[1]+area[3]//2
def _s_ct(self, v):
try:
area = self._area
#ct = (area[0]+area[2]//2, area[1]+area[3]//2)
self.dx = v[0]-(area[0]+area[2]//2)
self.dy = v[1]-(area[1]+area[3]//2)
self._updatepos()
return
except (TypeError, ValueError, IndexError):
pass
p = pygame.error("invalid assignment for Camera2 center")
raise p
def _g_r(self):
area = self._area
return area[0]+area[2]
def _s_r(self, v):
try:
area = self._area
self.dx = v-(area[0]+area[2])
self.dy = 0
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_b(self):
area = self._area
return area[1]+area[3]
def _s_b(self, v):
try:
area = self._area
self.dy = v-(area[1]+area[3])
self.dx = 0
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_tr(self):
area = self._area
return area[0]+area[2], area[1]
def _s_tr(self, v):
try:
area = self._area
self.dx = v[0]-(area[0]+area[2])
self.dy = v[1]-(area[1])
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_bl(self):
area = self._area
return area[0], area[1]+area[3]
def _s_bl(self, v):
try:
area= self._area
self.dx = v[0]-(area[0])
self.dy = v[1]-(area[1]+area[3])
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_br(self):
area = self._area
return area[0]+area[2], area[1]+area[3]
def _s_br(self, v):
try:
area = self._area
self.dx = v[0]-(area[0]+area[2])
self.dy = v[1]-(area[1]+area[3])
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_mt(self):
area = self._area
return area[0]+area[2]//2, area[1]
def _s_mt(self, v):
try:
area = self._area
self.dx = v[0]-(area[0]+area[2]//2)
self.dy = v[1]-(area[1])
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_ml(self):
area = self._area
return area[0], area[1]+area[3]//2
def _s_ml(self, v):
try:
area = self._area
self.dx = v[0]-(area[0])
self.dy = v[1]-(area[1]+area[3]//2)
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_mb(self):
area = self._area
return area[0]+area[2]//2, area[1]+area[3]
def _s_mb(self, v):
try:
#mb = self._g_mb()
area = self._area
self.dx = v[0]-(area[0]+area[2]//2)
self.dy = v[1]-(area[1]+area[3])
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
def _g_mr(self):
area = self._area
return area[0]+area[2], area[1]+area[3]//2
def _s_mr(self, v):
try:
#mr = self._g_mr()
area = self._area
self.dx = v[0]-(area[0]+area[2])
self.dy = v[1]-(area[1]+area[3]//2)
self._updatepos()
return
except (TypeError, ValueError):
pass
p = pygame.error("invalid assignment for Camera2 coordinate")
raise p
anchor = property(_g_anchor_p, _s_anchor_p) # special anchor point for (.goto()) method
topleft = xy = pos = property(_g_xy, _s_xy)
topright = property(_g_tr, _s_tr)
midtop = property(_g_mt, _s_mt)
midleft = property(_g_ml, _s_ml)
midbottom = property(_g_mb, _s_mb)
midright = property(_g_mr, _s_mr)
bottomleft = property(_g_bl, _s_bl)
bottomright = property(_g_br, _s_br)
center = middle = property(_g_ct, _s_ct)
yx = property(_g_yx, _s_yx)
x = left = property(_g_x, _s_x)
y = top = property(_g_x, _s_x)
right = property(_g_r, _s_r)
bottom = property(_g_b, _s_b)
width = w = property(_g_w, _s_w)
height = h = property(_g_h, _s_h)
size = property(_g_sz, _s_sz)
def move(self, dx_or_dy=None, dy=None): # (dx_or_dy=None, dy=None) this allows the method to support iterables by indexing, as well as two numbers as input.
if dx_or_dy is not None:
if dy is not None:
try:
self.dx=dx_or_dy+0.0; self.dy=dy+0.0
return self._updatepos()
except TypeError:
pass
else:
try:
self.dx = dx_or_dy[0]+0.0; self.dy = dx_or_dy[1]+0.0
return self._updatepos()
except TypeError:
try:
self.dx = self.dy = dx_or_dy+0.0
return self._updatepos()
except TypeError:
pass
except IndexError:
pass
else:
return self._updatepos()
t = TypeError("invalid coordinates given as input: ("+repr(dx_or_dy)+", "+repr(dy)+"). Inputs must be two real numbers, or a tuple, list or vector object with 2 coordinates.")
raise t
def goto(self, x_or_xy=None, y=None):
"""Move this Camera2 object to a certain location, based on its anchor."""
if x_or_xy is not None:
if y is not None:
try:
self.dx=(x_or_xy+0.0)-self._xfloat-self._anchor_point[0]; self.dy=(y+0.0)-self._yfloat-self._anchor_point[1]
return self._updatepos()
except TypeError:
pass
else:
try:
self.dx=(x_or_xy[0]+0.0)-self._xfloat-self._anchor_point[0]; self.dy=(x_or_xy[1]+0.0)-self._yfloat-self._anchor_point[1]
return self._updatepos()
except TypeError:
try:
self.dx=(x_or_xy+0.0)-self._xfloat-self._anchor_point[0]; self.dy=(x_or_xy+0.0)-self._yfloat-self._anchor_point[1]
return self._updatepos()
except TypeError:
pass
except IndexError:
pass
else:
return
t = TypeError("invalid coordinates given as input: ("+repr(x_or_xy)+", "+repr(y)+"). Inputs must be two real numbers, or a tuple, list or vector object with 2 coordinates.")
raise t
def teleport(self, x_or_xy=None, y=None):
old_dx, old_dy = self.dx, self.dy
passed = False
if x_or_xy is not None:
if y is not None:
try:
self.dx=(x_or_xy+0.0)-self._xfloat-self._anchor_point[0]; self.dy=(y+0.0)-self._yfloat-self._anchor_point[1]
self._updatepos()
passed = True
except TypeError:
pass
else:
try:
self.dx=(x_or_xy[0]+0.0)-self._xfloat-self._anchor_point[0]; self.dy=(x_or_xy[1]+0.0)-self._yfloat-self._anchor_point[1]
self._updatepos()
passed = True
except TypeError:
try:
self.dx=(x_or_xy+0.0)-self._xfloat-self._anchor_point[0]; self.dy=(x_or_xy+0.0)-self._yfloat-self._anchor_point[1]
self._updatepos()
passed = True
except TypeError:
pass
except IndexError:
pass
else:
return
self.dx, self.dy = old_dx, old_dy
if not passed:
t = TypeError("invalid coordinates given as input: ("+repr(x_or_xy)+", "+repr(y)+"). Inputs must be two real numbers, or a tuple, list or vector object with 2 coordinates.")
raise t
tp = teleport
def get_pos(self, precise=False):
"""Get the world postion of this Camera2 object."""
area = self._area
return (self._xfloat, self._yfloat) if precise else (area[0], area[1])
def reset_pos(self):
"""Reset the world postion of this Camera2 object."""
area = self._area
area[:] = (0.0, 0.0, area[3], area[4])
self._xfloat = self._yfloat = 0.0
def get_area(self):
"""Return the area of this Camera2 object in world-space."""
area = self._area
return (area[0], area[1], area[2], area[3])
def set_size(self, x_or_xy=None, y=None):
"""Set the view size of this Camera2 object."""
passed = False
if x_or_xy is not None:
if y is not None:
try:
self._area[2:] = self._screenrect.size = x_or_xy+0, y+0
passed = True
except TypeError:
pass
else:
try:
self._area[2:] = self._screenrect.size = x_or_xy[0]+0, x_or_xy[1]+0
passed = True
except TypeError:
try:
self._area[2:] = self._screenrect.size = x_or_xy.x+0, x_or_xy.y+0
passed = True
except AttributeError:
try:
self._area[2:] = self._screenrect.size = (x_or_xy+0, x_or_xy+0)
passed = True
except Exception:
pass
except IndexError:
pass
else:
return
if not passed:
t = TypeError("invalid coordinates given as input: ("+repr(x_or_xy)+", "+repr(y)+"). Inputs must be two real numbers, or a tuple, list or vector object with 2 coordinates.")
raise t
@property
def view_distance(self):
return self._view_distance
@view_distance.setter
def view_distance(self, d):
try:
self._view_distance = max2(abs(d+0.0), 1)
except TypeError as t:
t.args = ("invalid input for 'view_distance' parameter, it must be a positive real number",)
raise t from None
def __str__(self):
return "<(Camera2 | area: {0}, at [{1:.5f}, {2:.5f}])>".format(tuple(self._screenrect), self._xfloat, self._yfloat)
import pygame as pg
from pygame.locals import*
from CameraLib import Camera2
from pygame import Surface
from sys import exit
from random import randrange
pg.init()
RED = (255,0,0)
BLUE = (0, 0, 255)
GREY = (127, 127, 127)
class GameObj(pg.sprite.Sprite):
def __init__(self, img, pos=(0,0), distance=0):
pg.sprite.Sprite.__init__(self)
self.image = img
self.rect = img.get_rect()
self.rect.topleft = pos
self.distance = distance
self.dx = self.dy = 0
def update(self, *args, **kwargs):
self.rect.move_ip(self.dx, self.dy)
WIN = pg.display.set_mode((1600, 900))
Cam = Camera2((0, 0, 1600, 900), view_distance=1000)
player = GameObj(Surface((300, 300)))
Cam.center = player.rect.center = (0,0)
player.image.fill(BLUE)
circle_surfs = [Surface((randrange(50, 500),)*2, SRCALPHA) for i in range(100)]
print(circle_surfs)
circle_sprites = []
circle_surfs.sort(key=lambda x: x.get_size()) # sort surfaces by size
circle_surfs_len = 100
for i in range(100):
surf = circle_surfs[i]
pg.draw.circle(surf, (randrange(50, 200), randrange(50, 200), randrange(50, 200)), center=(surf.get_width()//2, surf.get_height()//2), radius=surf.get_width()//2)
obj = GameObj(surf, distance=((100-i)/100)*1000) # this places the sprites randomly, with their (.distance) depending on the index of their surface in the list
obj.rect.center = (randrange(0, 501), randrange(0, 501))
circle_sprites.append( obj )
Cam.view.add(circle_sprites, player)
clock = pg.time.Clock()
running = 1
while running:
for event in pg.event.get():
if event.type == QUIT:
running = 0
elif event.type == KEYDOWN:
if event.key == K_d:
player.dx = 10
elif event.key == K_a:
player.dx = -10
if event.key == K_s:
player.dy = 10
elif event.key == K_w:
player.dy = -10
elif event.type == KEYUP:
if event.key == K_d:
player.dx = 0
elif event.key == K_a:
player.dx = 0
if event.key == K_s:
player.dy = 0
elif event.key == K_w:
player.dy = 0
WIN.fill(GREY)
player.update()
Cam.center = player.rect.center
#Cam.slide_to(player.rect.center, 1/(clock.get_fps()+1e-6), speed_factor=10, anchor_point=Cam.screenrect.center)
Cam.draw(WIN, parallax=1, return_dirty_rects=1)
clock.tick(60)
pg.display.update()
pg.quit()
exit()
import pygame as pg
from pygame.locals import*
from CameraLib import Camera2
from pygame import Surface
from sys import exit
from random import randrange
pg.init()
RED = (255,0,0)
BLUE = (0, 0, 255)
GREY = (127, 127, 127)
class GameObj(pg.sprite.Sprite):
def __init__(self, img, pos=(0,0), distance=0):
pg.sprite.Sprite.__init__(self)
self.image = img
self.rect = img.get_rect()
self.rect.topleft = pos
self.distance = distance
self.dx = self.dy = 0
def update(self, *args, **kwargs):
self.rect.move_ip(self.dx, self.dy)
WIN = pg.display.set_mode((1600, 900))
Cam = Camera2((0, 0, 800, 900), view_distance=1000)
Cam2 = Camera2((800, 0, 800, 900), view=Cam.view, view_distance=1000)
player = GameObj(Surface((300, 300)))
Cam2.center = Cam.center = player.rect.center = (0,0)
player.image.fill(BLUE)
circle_surfs = [Surface((randrange(50, 500),)*2, SRCALPHA) for i in range(50)]
print(circle_surfs)
circle_sprites = []
circle_surfs.sort(key=lambda x: x.get_size()) # sort surfaces by size
circle_surfs_len = 50
for i in range(50):
surf = circle_surfs[i]
pg.draw.circle(surf, (randrange(50, 200), randrange(50, 200), randrange(50, 200)), center=(surf.get_width()//2, surf.get_height()//2), radius=surf.get_width()//2)
obj = GameObj(surf, distance=((50-i)/50)*1000) # this places the sprites randomly, with their (.distance) depending on the index of their surface in the list
obj.rect.center = (randrange(0, 501), randrange(0, 501))
circle_sprites.append( obj )
Cam.view.add(circle_sprites, player)
Cam2.view = Cam.view
clock = pg.time.Clock()
running = 1
while running:
for event in pg.event.get():
if event.type == QUIT:
running = 0
elif event.type == KEYDOWN:
if event.key == K_d:
player.dx = 10
elif event.key == K_a:
player.dx = -10
if event.key == K_s:
player.dy = 10
elif event.key == K_w:
player.dy = -10
elif event.type == KEYUP:
if event.key == K_d:
player.dx = 0
elif event.key == K_a:
player.dx = 0
if event.key == K_s:
player.dy = 0
elif event.key == K_w:
player.dy = 0
WIN.fill(GREY)
player.update()
Cam.center = player.rect.center
Cam2.slide_to(player.rect.center, 1/(clock.get_fps()+1e-6), speed_factor=10, anchor_point=(Cam2.w//2, Cam2.h//2)) # Note: for Cam2, the origin is at the topleft corner of its area (800, 0)
Cam.draw(WIN, parallax=1, return_dirty_rects=0)
Cam2.draw(WIN, parallax=1, return_dirty_rects=0)
clock.tick(60)
pg.display.update(Rect(0,0,500,500))
pg.quit()
exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment