Luphaestus
Luphaestus

Reputation: 43

ValueError: subsurface rectangle outside surface area

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

Answers (1)

Kesslwovv
Kesslwovv

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

Related Questions