Reputation: 43
I'm making an platformer game where the camera follows the player. I'm trying to implement this by having a large surface surface with the whole map and only blitting a zoomed in section. however im only getting 30 fps (minimized) and 8 fps (full screen).
So my attempt to optimize it was to to crop it before blitting but i get ValueError: subsurface rectangle outside surface area
code
class screen_handler:
def __init__(self, screen=False, mapSize=[3, 3]):
if not screen: # if screen isn't open
init() # initialize pygame
user32 = ctypes.windll.user32 # set user32
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (user32.GetSystemMetrics(0) / 4, user32.GetSystemMetrics(1) / 4) # center future screen
screen = display.set_mode((640, 512), RESIZABLE) # create screen
self.screen = screen # save screen
self.blit_surf = Surface((640 * mapSize[0], 512 * mapSize[1])) # create blit_surf
self.clock = time.Clock() # create clock
self.neutralizerZoom = min(self.blit_surf.get_width() / 640, self.blit_surf.get_height() / 512) # reset zoom
self.zoom = 2
self.mousePos = [0, 0]
self.cameraPos = [0, 0]
self.fit_to_rect = self.blit_surf.get_rect().fit(self.screen.get_rect()) # fit the surface to the screen
self.fit_to_rect.size = self.fit_to_rect.width * self.neutralizerZoom * self.zoom, self.fit_to_rect.height * self.neutralizerZoom * self.zoom # add zoom
def video_resize(self):
self.fit_to_rect = self.blit_surf.get_rect().fit(self.screen.get_rect()) # fit the surface to the screen
self.fit_to_rect.size = self.fit_to_rect.width * self.neutralizerZoom * self.zoom, self.fit_to_rect.height * self.neutralizerZoom * self.zoom # add zoom
def update(self):
scaled = transform.scale(self.blit_surf, (self.fit_to_rect.width, self.fit_to_rect.height)) # scale surface to screen
self.fit_to_rect.topleft = self.screen.get_rect().top + self.cameraPos[0], self.screen.get_rect().left + self.cameraPos[1] # center surface & camera pos
self.mousePos[0] = (mouse.get_pos()[0] / (scaled.get_width() / self.blit_surf.get_width())) - (self.cameraPos[0] / (scaled.get_width() / self.blit_surf.get_width())) # scale x axis mouse pos
self.mousePos[1] = (mouse.get_pos()[1] / (scaled.get_height() / self.blit_surf.get_height())) # scale y axis mouse pos
scaled = scaled.subsurface(self.fit_to_rect.x, self.fit_to_rect.y, self.fit_to_rect.x + self.fit_to_rect.width, self.fit_to_rect.y + self.fit_to_rect.height)
self.screen.blit(scaled ,(0, 0)) # blit surface to screen
#self.screen.blit(scaled, self.fit_to_rect)
display.flip() # update screen
self.clock.tick(60)
print(self.clock.get_fps())
note: please tell me if there is a better way/ quicker way of implementing a camera
Upvotes: 1
Views: 398
Reputation: 652
Here is how i do my camera movement:
WINDOW_WIDTH, WINDOW_HEIGHT = ...
window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), RESIZABLE)
screen = pygame.Surface(your_resolution)
...
scroll_x, scroll_y = player_position # get the scroll
...
screen.blit(image, (x_pos + scroll_x, y_pos + scroll_y))
...
for event in pygame.event.get():
if event.type == VIDEORESIZE:
WINDOW_WIDTH, WINDOW_HEIGHT = event.size
...
window.blit(pygame.transform.scale(screen, (WINDOW_WIDTH, WINDOW_HEIGHT)), (0, 0))
pygame.display.update()
every time you want to show something you need to blit it onto screen instead of window.
if you want to have the same scale i would recommend the follwing class:
class Window:
def __init__(self, surf, width, height):
self.screen = pygame.display.set_mode((width, height), RESIZABLE)
self.surf = surf
self.orig_w, self.orig_h = surf.get_size()
self.set_sizes(width, height)
def set_sizes(self, width, height):
self.rate = min(width / self.orig_w, height / self.orig_h)
self.width = int(self.orig_w * self.rate)
self.x_off = int((width - self.width) / 2)
self.height = int(self.orig_h * self.rate)
self.y_off = int((height - self.height) / 2)
def get_mouse_pos(self):
mouse_x, mouse_y = pygame.mouse.get_pos()
return int((mouse_x - self.x_off) / self.rate), int((mouse_y - self.y_off) / self.rate)
def show(self):
self.screen.fill((50, 50, 50))
self.screen.blit(pygame.transform.scale(self.surf, (self.width, self.height)), (self.x_off, self.y_off))
pygame.display.flip()
EDIT: OPTIMTZING
the following code will replace the line that caused you problems:
instead of
scaled = scaled.subsurface(...)
self.screen.blit(scaled, (0, 0))
do
self.screen.blit(scaled, (0, 0), self.fit_to_rect)
this is more efficient because it doesn't need to create the subsurface but blits is directly onto the screen.
optimizing tips:
avoid recreating surfaces every frame.
your large surface does only need to be created when the map is loaded and never again. if you are rotating images you can simply create a list or dict of rotated images at the start of the program and just need to call it. same goes for changes in scale.
use img = img.convert()
this is a pretty simple optimizing trick.
Upvotes: 1