使用 Pymunk 和 Pygame 横向滚动。如何移动相机/视口以仅查看世界的一部分?

从 pymunk 示例中,我看到 pymunk 坐标和 pygame 坐标之间存在差异。此外,pymunk 仅用于 2D 物理,而 pygame 用于在屏幕上渲染对象/精灵。

因此,在寻找如何构建摄像机跟随玩家的环境时,人们(包括我)最终会感到困惑。我已经看过这里的例子,这里这里这里(甚至惊讶于没有人回答这个),但考虑到与同一主题相关的问题被反复询问的数量,老实说,我觉得这些答案并没有充分解释这个概念和要求向社区展示最简单的示例,其中所有代码都带有注释。

我曾在像 OGRE 和 OSG 这样的 3D 环境中工作过,其中相机是一个可以用视锥定义的正确概念,但我很惊讶 2D 世界没有预定义的功能。所以:

如果不在 pymunk 或 pygame 的官方教程中,至少可以提供一个简单的示例(以 pymunk 主体作为玩家,世界上很少有 pymunk 主体)作为答案,其中玩家在 2D pymunk+ 中四处移动pygame 世界和相机跟随玩家?


手掌心
浏览 702回答 1
1回答

临摹微笑

好的,我会尽量简化(我假设有基本的 pygame 知识)。首先,让我们从一些基本的东西开始。一个可以在世界各地移动的小精灵:import pygameimport randomclass Player(pygame.sprite.Sprite):    def __init__(self):        super().__init__()        self.image = pygame.Surface((32, 32))        self.image.fill(pygame.Color('dodgerblue'))        self.rect = self.image.get_rect()        self.pos = pygame.Vector2((100, 200))    def update(self, events, dt):        pressed = pygame.key.get_pressed()        move = pygame.Vector2((0, 0))        if pressed[pygame.K_w]: move += (0, -1)        if pressed[pygame.K_a]: move += (-1, 0)        if pressed[pygame.K_s]: move += (0, 1)        if pressed[pygame.K_d]: move += (1, 0)        if move.length() > 0: move.normalize_ip()        self.pos += move*(dt/5)        self.rect.center = self.posdef main():    pygame.init()    screen = pygame.display.set_mode((500, 500))    clock = pygame.time.Clock()    dt = 0    player = Player()    sprites = pygame.sprite.Group(player)    background = screen.copy()    background.fill((30, 30, 30))    for _ in range(1000):        x, y = random.randint(0, 1000), random.randint(0, 1000)        pygame.draw.rect(background, pygame.Color('green'), (x, y, 2, 2))    while True:        events = pygame.event.get()        for e in events:            if e.type == pygame.QUIT:                return        sprites.update(events, dt)        screen.blit(background, (0, 0))        sprites.draw(screen)        pygame.display.update()        dt = clock.tick(60)if __name__ == '__main__':    main()到目前为止没有什么疯狂的。那么,什么是“相机”?它只是我们用来移动整个“世界”的一个x和一个y值(例如,所有不是 UI 的东西)。它是我们游戏对象和屏幕坐标之间的抽象。在我们上面的例子中,当一个游戏对象(玩家或背景)想要在位置 绘制时(x, y),我们在屏幕的这个位置绘制它们。现在,如果我们想要围绕“摄像机”移动,我们只需创建另一个x, y-pair,并将其添加到游戏对象的坐标中以确定屏幕上的实际位置。我们开始区分世界坐标(游戏逻辑认为物体的位置在哪里)和屏幕坐标(物体在屏幕上的实际位置)。这是我们带有“camera”(引号中的“camera”)的示例,因为它实际上只是两个值:import pygameimport randomclass Player(pygame.sprite.Sprite):    def __init__(self):        super().__init__()        self.image = pygame.Surface((32, 32))        self.image.fill(pygame.Color('dodgerblue'))        self.rect = self.image.get_rect()        self.pos = pygame.Vector2((100, 200))    def update(self, events, dt):        pressed = pygame.key.get_pressed()        move = pygame.Vector2((0, 0))        if pressed[pygame.K_w]: move += (0, -1)        if pressed[pygame.K_a]: move += (-1, 0)        if pressed[pygame.K_s]: move += (0, 1)        if pressed[pygame.K_d]: move += (1, 0)        if move.length() > 0: move.normalize_ip()        self.pos += move*(dt/5)        self.rect.center = self.posdef main():    pygame.init()    screen = pygame.display.set_mode((500, 500))    clock = pygame.time.Clock()    dt = 0    player = Player()    sprites = pygame.sprite.Group(player)    # the "world" is now bigger than the screen    # so we actually have anything to move the camera to    background = pygame.Surface((1500, 1500))    background.fill((30, 30, 30))    # a camera is just two values: x and y    # we use a vector here because it's easier to handle than a tuple    camera = pygame.Vector2((0, 0))    for _ in range(3000):        x, y = random.randint(0, 1000), random.randint(0, 1000)        pygame.draw.rect(background, pygame.Color('green'), (x, y, 2, 2))    while True:        events = pygame.event.get()        for e in events:            if e.type == pygame.QUIT:                return        # copy/paste because I'm lazy        # just move the camera around        pressed = pygame.key.get_pressed()        camera_move = pygame.Vector2()        if pressed[pygame.K_UP]: camera_move += (0, 1)        if pressed[pygame.K_LEFT]: camera_move += (1, 0)        if pressed[pygame.K_DOWN]: camera_move += (0, -1)        if pressed[pygame.K_RIGHT]: camera_move += (-1, 0)        if camera_move.length() > 0: camera_move.normalize_ip()        camera += camera_move*(dt/5)        sprites.update(events, dt)        # before drawing, we shift everything by the camera's x and y values        screen.blit(background, camera)        for s in sprites:            screen.blit(s.image, s.rect.move(*camera))        pygame.display.update()        dt = clock.tick(60)if __name__ == '__main__':    main()现在您可以使用箭头键移动相机。就是这样。我们只是将所有内容稍微移动一下,然后再将其传输到屏幕上。更完整的例子(支持精灵,停在世界边缘,平滑移动),请看这个问题。对于使用 pymunk:它只是有效。它不受将东西绘制到另一个位置的影响,因为它适用于世界坐标,而不是屏幕坐标。唯一的缺陷是 pymunk 的 y 轴与 pygame 的 y 轴相比发生了翻转,但您可能已经知道这一点。下面是一个例子:import pygameimport randomimport pymunkclass Player(pygame.sprite.Sprite):    def __init__(self, space):        super().__init__()        self.space = space        self.image = pygame.Surface((32, 32))        self.image.fill(pygame.Color('dodgerblue'))        self.rect = self.image.get_rect()        self.pos = pygame.Vector2((100, 200))        self.body = pymunk.Body(1,1666)        self.body.position = self.pos        self.poly = pymunk.Poly.create_box(self.body)        self.space.add(self.body, self.poly)    def update(self, events, dt):        pressed = pygame.key.get_pressed()        move = pygame.Vector2((0, 0))        if pressed[pygame.K_w]: move += (0, 1)        if pressed[pygame.K_a]: move += (-1, 0)        if pressed[pygame.K_s]: move += (0, -1)        if pressed[pygame.K_d]: move += (1, 0)        if move.length() > 0: move.normalize_ip()        self.body.apply_impulse_at_local_point(move*5)        # if you used pymunk before, you'll probably already know        # that you'll have to invert the y-axis to convert between        # the pymunk and the pygame coordinates.        self.pos = pygame.Vector2(self.body.position[0], -self.body.position[1]+500)        self.rect.center = self.posdef main():    pygame.init()    screen = pygame.display.set_mode((500, 500))    clock = pygame.time.Clock()    dt = 0    space = pymunk.Space()    space.gravity = 0,-100    player = Player(space)    sprites = pygame.sprite.Group(player)    # the "world" is now bigger than the screen    # so we actually have anything to move the camera to    background = pygame.Surface((1500, 1500))    background.fill((30, 30, 30))    # a camera is just two values: x and y    # we use a vector here because it's easier to handle than a tuple    camera = pygame.Vector2((0, 0))    for _ in range(3000):        x, y = random.randint(0, 1000), random.randint(0, 1000)        pygame.draw.rect(background, pygame.Color('green'), (x, y, 2, 2))    while True:        events = pygame.event.get()        for e in events:            if e.type == pygame.QUIT:                return        # copy/paste because I'm lazy        # just move the camera around        pressed = pygame.key.get_pressed()        camera_move = pygame.Vector2()        if pressed[pygame.K_UP]: camera_move += (0, 1)        if pressed[pygame.K_LEFT]: camera_move += (1, 0)        if pressed[pygame.K_DOWN]: camera_move += (0, -1)        if pressed[pygame.K_RIGHT]: camera_move += (-1, 0)        if camera_move.length() > 0: camera_move.normalize_ip()        camera += camera_move*(dt/5)        sprites.update(events, dt)        # before drawing, we shift everything by the camera's x and y values        screen.blit(background, camera)        for s in sprites:            screen.blit(s.image, s.rect.move(*camera))        pygame.display.update()        dt = clock.tick(60)        space.step(dt/1000)if __name__ == '__main__':    main()请注意,当您使用 时pymunk.Space.debug_draw,您将无法将世界坐标转换为屏幕坐标,因此最好将 pymunk 的东西简单地绘制到另一个Surface,并将其转换为Surface.这是pygame_util_demo.py带有移动相机的 pymunk :import sysimport pygamefrom pygame.locals import *import pymunkfrom pymunk.vec2d import Vec2dimport pymunk.pygame_utilimport shapes_for_draw_demosdef main():    pygame.init()    screen = pygame.display.set_mode((1000,700))     pymunk_layer = pygame.Surface((1000,700))    pymunk_layer.set_colorkey((12,12,12))    pymunk_layer.fill((12,12,12))    camera = pygame.Vector2((0, 0))    clock = pygame.time.Clock()    font = pygame.font.SysFont("Arial", 16)    space = pymunk.Space()    captions = shapes_for_draw_demos.fill_space(space)    # Info    color = pygame.color.THECOLORS["black"]    options = pymunk.pygame_util.DrawOptions(pymunk_layer)    while True:        for event in pygame.event.get():            if event.type == QUIT or \                event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):                  return             elif event.type == KEYDOWN and event.key == K_p:                pygame.image.save(screen, "pygame_util_demo.png")                        # copy/paste because I'm lazy        pressed = pygame.key.get_pressed()        camera_move = pygame.Vector2()        if pressed[pygame.K_UP]: camera_move += (0, 1)        if pressed[pygame.K_LEFT]: camera_move += (1, 0)        if pressed[pygame.K_DOWN]: camera_move += (0, -1)        if pressed[pygame.K_RIGHT]: camera_move += (-1, 0)        if camera_move.length() > 0: camera_move.normalize_ip()        camera += camera_move*5        screen.fill(pygame.color.THECOLORS["white"])        pymunk_layer.fill((12,12,12))        space.debug_draw(options)        screen.blit(pymunk_layer, camera)        screen.blit(font.render("Demo example of pygame_util.DrawOptions()", 1, color), (205, 680))        for caption in captions:            x, y = caption[0]            y = 700 - y            screen.blit(font.render(caption[1], 1, color), camera + (x,y))        pygame.display.flip()        clock.tick(30)if __name__ == '__main__':    sys.exit(main())
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python