为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

手掌心 2021-12-09 14:43:58
从 pymunk 示例中,我看到 pymunk 坐标和 pygame 坐标之间存在差异。此外,pymunk 仅用于 2D 物理,而 pygame 用于在屏幕上渲染对象/精灵。因此,在寻找如何构建摄像机跟随玩家的环境时,人们(包括我)最终会感到困惑。我已经看过这里的例子,这里,这里和这里(甚至惊讶于没有人回答这个),但考虑到与同一主题相关的问题被反复询问的数量,老实说,我觉得这些答案并没有充分解释这个概念和要求向社区展示最简单的示例,其中所有代码都带有注释。我曾在像 OGRE 和 OSG 这样的 3D 环境中工作过,其中相机是一个可以用视锥定义的正确概念,但我很惊讶 2D 世界没有预定义的功能。所以:如果不在 pymunk 或 pygame 的官方教程中,至少可以提供一个简单的示例(以 pymunk 主体作为玩家,世界上很少有 pymunk 主体)作为答案,其中玩家在 2D pymunk+ 中四处移动pygame 世界和相机跟随玩家?
查看完整描述

1 回答

?
临摹微笑

TA贡献1982条经验 获得超2个赞

好的,我会尽量简化(我假设有基本的 pygame 知识)。


首先,让我们从一些基本的东西开始。一个可以在世界各地移动的小精灵:


import pygame

import random


class 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.pos


def 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()

//img1.sycdn.imooc.com//61b1a5ca0001b7ca05040533.jpg

到目前为止没有什么疯狂的。


那么,什么是“相机”?它只是我们用来移动整个“世界”的一个x和一个y值(例如,所有不是 UI 的东西)。它是我们游戏对象和屏幕坐标之间的抽象。


在我们上面的例子中,当一个游戏对象(玩家或背景)想要在位置 绘制时(x, y),我们在屏幕的这个位置绘制它们。


现在,如果我们想要围绕“摄像机”移动,我们只需创建另一个x, y-pair,并将其添加到游戏对象的坐标中以确定屏幕上的实际位置。我们开始区分世界坐标(游戏逻辑认为物体的位置在哪里)和屏幕坐标(物体在屏幕上的实际位置)。


这是我们带有“camera”(引号中的“camera”)的示例,因为它实际上只是两个值:


import pygame

import random


class 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.pos


def 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()

//img1.sycdn.imooc.com//61b1a5e10001033405040530.jpg

现在您可以使用箭头键移动相机。


就是这样。我们只是将所有内容稍微移动一下,然后再将其传输到屏幕上。


更完整的例子(支持精灵,停在世界边缘,平滑移动),请看这个问题。


对于使用 pymunk:它只是有效。它不受将东西绘制到另一个位置的影响,因为它适用于世界坐标,而不是屏幕坐标。唯一的缺陷是 pymunk 的 y 轴与 pygame 的 y 轴相比发生了翻转,但您可能已经知道这一点。


下面是一个例子:


import pygame

import random

import pymunk


class 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.pos


def 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()

//img1.sycdn.imooc.com//61b1a5fd0001ae7204770530.jpg

请注意,当您使用 时pymunk.Space.debug_draw,您将无法将世界坐标转换为屏幕坐标,因此最好将 pymunk 的东西简单地绘制到另一个Surface,并将其转换为Surface.


这是pygame_util_demo.py带有移动相机的 pymunk :


import sys


import pygame

from pygame.locals import *


import pymunk

from pymunk.vec2d import Vec2d

import pymunk.pygame_util


import shapes_for_draw_demos


def 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())

//img1.sycdn.imooc.com//61b1a6160001b5eb10030732.jpg

查看完整回答
反对 回复 2021-12-09
  • 1 回答
  • 0 关注
  • 690 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信