我的 pygame 平台游戏的基本相机系统

Basic camera system for my pygame platformer

您好,我目前正在尝试创建一个平台游戏,并尝试为其创建一个相机系统。但是经过多次尝试,即使尝试了我在网上看到的所有内容,我仍然找不到好的方法。我真的希望相机的工作方式有点像我发现的这个动画 here 关于我应该如何处理这个问题有什么建议吗?

import pygame,sys

pygame.init()

# Settings
tile_size = 64
fov = 10
screen_width = 1200
screen_height = tile_size * fov

level_data = [
'                       ',
'                       ',
'                  XX   ',
'XX    XXX              ',
'XX                   XX',
'XXXX        XX         ',
'XXXX  P   XX           ',
'XX    X  XXX    XX  X  ',
'      X  XXX    XX  XX ',
'   XXXX  XXXXX  XX  XXX',
'XXXXXXX  XXXXX  XX  XXX']

# Classes
class Game():
    def __init__(self):
        self.level = Level(screen)
        self.status = 'level'

    def run(self):
        if self.status == 'level':
            self.level.run()

class Level:
    def __init__(self,display_surface):
        # Basic setup
        self.setup_level()
        self.display_surface = display_surface

        # Movement
        self.x_shift = 0
        self.y_shift = 0

    def setup_level(self):
        self.tiles = pygame.sprite.Group()
        self.player = pygame.sprite.GroupSingle()
        for row_index,row in enumerate(level_data):
            for col_index,col in enumerate(row):
                x = col_index * tile_size
                y = (row_index * tile_size) - ((len(level_data) - fov) * tile_size)
                if col == 'X':
                    tile = Tile((x,y))
                    self.tiles.add(tile)
                if col == 'P':
                    player = Player((x,y))
                    self.player.add(player)

    def vertical_movement_collision(self):
        player = self.player.sprite
        player.apply_gravity()

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.y > 0:
                    player.rect.bottom = sprite.rect.top
                    player.on_ground = True
                elif player.direction.y < 0:
                    player.rect.top = sprite.rect.bottom
                player.direction.y = 0

        if player.on_ground and player.direction.y > 1 or player.direction.y < 0:
            player.on_ground = False

    def horizontal_movement_collision(self):
        player = self.player.sprite
        player.rect.x += player.direction.x * player.speed

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.x > 0:
                    player.rect.right = sprite.rect.left
                elif player.direction.x < 0:
                    player.rect.left = sprite.rect.right

    def run(self):
        self.vertical_movement_collision()
        self.horizontal_movement_collision()

        # Tiles
        self.tiles.update(self.x_shift,self.y_shift)
        self.tiles.draw(self.display_surface)

        # Player
        self.player.update()
        self.player.draw(self.display_surface)

class Tile(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        self.image = pygame.Surface((tile_size,tile_size))
        self.image.fill('grey')
        self.rect = self.image.get_rect(topleft = pos)
        self.x = self.rect.x
        self.y = self.rect.y

    def update(self,x_shift,y_shift):
        self.x += x_shift
        self.y -= y_shift
        self.rect.topleft = round(self.x), round(self.y)

class Player(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        # Image
        self.image = pygame.Surface((32,64))
        self.image.fill('red')
        self.rect = self.image.get_rect(topleft = pos)

        # Movement
        self.direction = pygame.math.Vector2(0,0)
        self.speed = 8
        self.gravity = 0.8
        self.jump_speed = -20

        # Status
        self.on_ground = False

    def get_inputs(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_SPACE]:
            self.jump()
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            self.direction.x = 1
        elif keys[pygame.K_a] or keys[pygame.K_LEFT]:
            self.direction.x = -1
        else:
            self.direction.x = 0

    def apply_gravity(self):
        self.direction.y += self.gravity
        self.rect.y += self.direction.y

    def jump(self):
        if self.on_ground:
            self.direction.y = self.jump_speed

    def update(self):
        self.get_inputs()

# Game setup
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption('Platformer')
clock = pygame.time.Clock()
game = Game()

# Main
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    
    screen.fill('black')
    game.run()

    pygame.display.update()
    clock.tick(60)

在基本的相机系统中,你让玩家处于中心位置。所以相机取决于玩家位置和屏幕尺寸

camera_x = -self.player.sprite.rect.centerx + self.display_surface.get_rect().centerx
camera_y = -self.player.sprite.rect.centery + self.display_surface.get_rect().centery

camera = (camera_x, camera_y) 

以后你必须在 blit 时将 camera 添加到所有精灵 - 但不要将其添加到精灵位置。

    def draw(self, screen, camera):
        screen.blit(self.image, self.rect.move(camera))

但它需要为每个精灵分别 运行 draw() 因为 group.draw() 运行 直接 blit(self.image, self.rect)


对于更复杂的相机系统,您必须编写更复杂的代码来检查玩家位置并以不同方式更改它(使用 if/else


最小工作代码:

import pygame,sys

# Classes
class Game():
    def __init__(self):
        self.level = Level(screen)
        self.status = 'level'

    def run(self):
        if self.status == 'level':
            self.level.run()

class Level:
    def __init__(self,display_surface):
        # Basic setup
        self.setup_level()
        self.display_surface = display_surface

        # Movement
        self.x_shift = 0
        self.y_shift = 0

    def setup_level(self):
        self.tiles = pygame.sprite.Group()
        self.player = pygame.sprite.GroupSingle()
        for row_index,row in enumerate(level_data):
            for col_index,col in enumerate(row):
                x = col_index * tile_size
                y = (row_index * tile_size) - ((len(level_data) - fov) * tile_size)
                if col == 'X':
                    tile = Tile((x,y))
                    self.tiles.add(tile)
                if col == 'P':
                    player = Player((x,y))
                    self.player.add(player)

    def vertical_movement_collision(self):
        player = self.player.sprite
        player.apply_gravity()

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.y > 0:
                    player.rect.bottom = sprite.rect.top
                    player.on_ground = True
                elif player.direction.y < 0:
                    player.rect.top = sprite.rect.bottom
                player.direction.y = 0

        if player.on_ground and player.direction.y > 1 or player.direction.y < 0:
            player.on_ground = False

    def horizontal_movement_collision(self):
        player = self.player.sprite
        player.rect.x += player.direction.x * player.speed

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.x > 0:
                    player.rect.right = sprite.rect.left
                elif player.direction.x < 0:
                    player.rect.left = sprite.rect.right

    def run(self):
        self.vertical_movement_collision()
        self.horizontal_movement_collision()

        # Tiles
        self.tiles.update(self.x_shift,self.y_shift)
        self.player.update()
        
        camera_x = -self.player.sprite.rect.centerx + self.display_surface.get_rect().centerx
        camera_y = -self.player.sprite.rect.centery + self.display_surface.get_rect().centery
        camera = (camera_x, camera_y) 
        print(camera)
        
        for item in self.tiles:
            item.draw(self.display_surface, camera)

        self.player.sprite.draw(self.display_surface, camera)

class Tile(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        self.image = pygame.Surface((tile_size,tile_size))
        self.image.fill('grey')
        self.rect = self.image.get_rect(topleft = pos)
        self.x = self.rect.x
        self.y = self.rect.y

    def update(self,x_shift,y_shift):
        self.x += x_shift
        self.y -= y_shift
        self.rect.topleft = round(self.x), round(self.y)

    def draw(self, screen, camera):
        screen.blit(self.image, self.rect.move(camera))
    
class Player(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        # Image
        self.image = pygame.Surface((32,64))
        self.image.fill('red')
        self.rect = self.image.get_rect(topleft = pos)

        # Movement
        self.direction = pygame.math.Vector2(0,0)
        self.speed = 8
        self.gravity = 0.8
        self.jump_speed = -20

        # Status
        self.on_ground = False

    def get_inputs(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_SPACE]:
            self.jump()
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            self.direction.x = 1
        elif keys[pygame.K_a] or keys[pygame.K_LEFT]:
            self.direction.x = -1
        else:
            self.direction.x = 0

    def apply_gravity(self):
        self.direction.y += self.gravity
        self.rect.y += self.direction.y

    def jump(self):
        if self.on_ground:
            self.direction.y = self.jump_speed

    def update(self):
        self.get_inputs()

    def draw(self, screen, camera):
        screen.blit(self.image, self.rect.move(camera))

# --- main ---

pygame.init()

# Settings
tile_size = 64
fov = 10
screen_width = 1200
screen_height = tile_size * fov

level_data = [
'                       ',
'                       ',
'                  XX   ',
'XX    XXX              ',
'XX                   XX',
'XXXX        XX         ',
'XXXX  P   XX           ',
'XX    X  XXX    XX  X  ',
'      X  XXX    XX  XX ',
'   XXXX  XXXXX  XX  XXX',
'XXXXXXX  XXXXX  XX  XXX']



# Game setup
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption('Platformer')
clock = pygame.time.Clock()
game = Game()

# Main
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    
    screen.fill('black')
    game.run()

    pygame.display.update()
    clock.tick(60)

编辑:

更复杂的相机系统 - 当玩家靠近地图边界时它不会移动地图。

    def run(self):
        self.vertical_movement_collision()
        self.horizontal_movement_collision()

        # Tiles
        self.tiles.update(self.x_shift, self.y_shift)
        self.player.update()
        
        map_left   = 0
        map_right  = len(level_data[0])*tile_size
        map_top    = 0
        map_bottom = len(level_data)*tile_size
        
        camera_x = -self.player.sprite.rect.centerx + self.display_surface.get_rect().centerx
        
        if self.player.sprite.rect.centerx < map_left + self.display_surface.get_rect().centerx:
            camera_x = -map_left
        if self.player.sprite.rect.centerx > map_right - self.display_surface.get_rect().centerx:
            camera_x = -(map_right - self.display_surface.get_rect().width)

        camera_y = -self.player.sprite.rect.centery + self.display_surface.get_rect().centery

        if self.player.sprite.rect.centery < map_top + self.display_surface.get_rect().centery:
            camera_y = -map_top
        if self.player.sprite.rect.centery > map_bottom - self.display_surface.get_rect().centery:
            camera_y = -(map_bottom - self.display_surface.get_rect().height)
        
        camera = (camera_x, camera_y) 
        
        for item in self.tiles:
            item.draw(self.display_surface, camera)

        self.player.sprite.draw(self.display_surface, camera)

但在 setup_level() 中它需要 y 而没有 - ((len(level_data) - fov) * tile_size)

y = (row_index * tile_size) # - ((len(level_data) - fov) * tile_size)

完整代码:

import pygame,sys

# Classes
class Game():
    def __init__(self):
        self.level = Level(screen)
        self.status = 'level'

    def run(self):
        if self.status == 'level':
            self.level.run()

class Level:
    def __init__(self,display_surface):
        # Basic setup
        self.setup_level()
        self.display_surface = display_surface

        # Movement
        self.x_shift = 0
        self.y_shift = 0

    def setup_level(self):
        self.tiles = pygame.sprite.Group()
        self.player = pygame.sprite.GroupSingle()
        for row_index,row in enumerate(level_data):
            for col_index,col in enumerate(row):
                x = col_index * tile_size
                y = (row_index * tile_size)# - ((len(level_data) - fov) * tile_size)
                if col == 'X':
                    tile = Tile((x,y))
                    self.tiles.add(tile)
                if col == 'P':
                    player = Player((x,y))
                    self.player.add(player)

    def vertical_movement_collision(self):
        player = self.player.sprite
        player.apply_gravity()

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.y > 0:
                    player.rect.bottom = sprite.rect.top
                    player.on_ground = True
                elif player.direction.y < 0:
                    player.rect.top = sprite.rect.bottom
                player.direction.y = 0

        if player.on_ground and player.direction.y > 1 or player.direction.y < 0:
            player.on_ground = False

    def horizontal_movement_collision(self):
        player = self.player.sprite
        player.rect.x += player.direction.x * player.speed

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.x > 0:
                    player.rect.right = sprite.rect.left
                elif player.direction.x < 0:
                    player.rect.left = sprite.rect.right

    def run(self):
        self.vertical_movement_collision()
        self.horizontal_movement_collision()

        # Tiles
        self.tiles.update(self.x_shift, self.y_shift)
        self.player.update()
        
        map_left   = 0
        map_right  = len(level_data[0])*tile_size
        map_top    = 0
        map_bottom = len(level_data)*tile_size
        
        camera_x = -self.player.sprite.rect.centerx + self.display_surface.get_rect().centerx
        
        if self.player.sprite.rect.centerx < map_left + self.display_surface.get_rect().centerx:
            camera_x = -map_left
        if self.player.sprite.rect.centerx > map_right - self.display_surface.get_rect().centerx:
            camera_x = -(map_right - self.display_surface.get_rect().width)

        camera_y = -self.player.sprite.rect.centery + self.display_surface.get_rect().centery

        if self.player.sprite.rect.centery < map_top + self.display_surface.get_rect().centery:
            camera_y = -map_top
        if self.player.sprite.rect.centery > map_bottom - self.display_surface.get_rect().centery:
            camera_y = -(map_bottom - self.display_surface.get_rect().height)
        
        camera = (camera_x, camera_y) 
        
        for item in self.tiles:
            item.draw(self.display_surface, camera)

        self.player.sprite.draw(self.display_surface, camera)

class Tile(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        self.image = pygame.Surface((tile_size,tile_size))
        self.image.fill('grey')
        self.rect = self.image.get_rect(topleft = pos)
        self.x = self.rect.x
        self.y = self.rect.y

    def update(self, x_shift, y_shift):
        self.x += x_shift
        self.y -= y_shift
        self.rect.topleft = round(self.x), round(self.y)

    def draw(self, screen, camera):
        screen.blit(self.image, self.rect.move(camera))
    
class Player(pygame.sprite.Sprite):
    def __init__(self,pos):
        super().__init__()
        # Image
        self.image = pygame.Surface((32,64))
        self.image.fill('red')
        self.rect = self.image.get_rect(topleft = pos)

        # Movement
        self.direction = pygame.math.Vector2(0,0)
        self.speed = 8
        self.gravity = 0.8
        self.jump_speed = -20

        # Status
        self.on_ground = False

    def get_inputs(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_SPACE]:
            self.jump()
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            self.direction.x = 1
        elif keys[pygame.K_a] or keys[pygame.K_LEFT]:
            self.direction.x = -1
        else:
            self.direction.x = 0

    def apply_gravity(self):
        self.direction.y += self.gravity
        self.rect.y += self.direction.y

    def jump(self):
        if self.on_ground:
            self.direction.y = self.jump_speed

    def update(self):
        self.get_inputs()

    def draw(self, screen, camera):
        screen.blit(self.image, self.rect.move(camera))

# --- main ---

pygame.init()

# Settings
tile_size = 64
fov = 10
screen_width = 1200
screen_height = tile_size * fov

level_data = [
'                       ',
'                       ',
'                  XX   ',
'XX    XXX              ',
'XX                   XX',
'XXXX        XX         ',
'XXXX  P   XX           ',
'XX    X  XXX    XX  X  ',
'      X  XXX    XX  XX ',
'   XXXX  XXXXX  XX  XXX',
'XXXXXXX  XXXXX  XX  XXX']


# Game setup
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption('Platformer')
clock = pygame.time.Clock()
game = Game()

# Main
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    
    screen.fill('black')
    game.run()

    pygame.display.update()
    clock.tick(60)