使用 Pymunk 和 Pygame 横向滚动.如何移动相机/视口以仅查看世界的一部分? [英] Side scrolling with Pymunk and Pygame. How to move the camera / viewport to view only part of the world?

查看:67
本文介绍了使用 Pymunk 和 Pygame 横向滚动.如何移动相机/视口以仅查看世界的一部分?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

因此,在寻找如何构建摄像机跟随玩家的环境时,人们(包括我)最终会

到目前为止还没有什么疯狂的事情.

<小时>

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

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

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

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

导入pygame随机导入类玩家(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))定义更新(自我,事件,dt):按下 = pygame.key.get_pressed()移动 = pygame.Vector2((0, 0))如果按下 [pygame.K_w]:移动 += (0, -1)如果按下 [pygame.K_a]: move += (-1, 0)如果按下 [pygame.K_s]: move += (0, 1)如果按下 [pygame.K_d]: 移动 += (1, 0)如果 move.length() >0: move.normalize_ip()self.pos += move*(dt/5)self.rect.center = self.pos定义主():pygame.init()屏幕 = pygame.display.set_mode((500, 500))时钟 = pygame.time.Clock()dt = 0玩家 = 玩家()精灵 = pygame.sprite.Group(玩家)# 世界"现在比屏幕大# 所以我们实际上有任何东西可以移动相机背景 = pygame.Surface((1500, 1500))background.fill((30, 30, 30))# 相机只有两个值:x 和 y# 我们在这里使用向量是因为它比元组更容易处理相机 = pygame.Vector2((0, 0))对于 _ 范围(3000):x, y = random.randint(0, 1000), random.randint(0, 1000)pygame.draw.rect(背景, pygame.Color('绿色'), (x, y, 2, 2))为真:事件 = pygame.event.get()对于事件中的 e:如果 e.type == pygame.QUIT:返回# 复制/粘贴,因为我很懒# 只需移动相机按下 = pygame.key.get_pressed()camera_move = pygame.Vector2()如果按下[pygame.K_UP]:camera_move += (0, 1)如果按下[pygame.K_LEFT]:camera_move += (1, 0)如果按下[pygame.K_DOWN]:camera_move += (0, -1)如果按下[pygame.K_RIGHT]:camera_move += (-1, 0)如果 camera_move.length() >0:camera_move.normalize_ip()相机 += 相机移动*(dt/5)sprites.update(事件,dt)# 在绘制之前,我们通过相机的 x 和 y 值移动所有内容screen.blit(背景,相机)对于精灵中的 s:screen.blit(s.image, s.rect.move(*camera))pygame.display.update()dt = 时钟.tick(60)如果 __name__ == '__main__':主要的()

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

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

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

<小时>

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

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

导入系统导入pygame从 pygame.locals 导入 *进口pymunk从 pymunk.vec2d 导入 Vec2d导入 pymunk.pygame_util导入shapes_for_draw_demos定义主():pygame.init()屏幕 = 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))相机 = pygame.Vector2((0, 0))时钟 = pygame.time.Clock()font = pygame.font.SysFont("Arial", 16)空间 = pymunk.Space()标题 = shape_for_draw_demos.fill_space(space)# 信息颜色 = pygame.color.THECOLORS["黑色"]选项 = pymunk.pygame_util.DrawOptions(pymunk_layer)为真:对于 pygame.event.get() 中的事件:如果 event.type == QUIT 或 \event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):返回elif event.type == KEYDOWN 和 event.key == K_p:pygame.image.save(屏幕,pygame_util_demo.png")# 复制/粘贴,因为我很懒按下 = pygame.key.get_pressed()camera_move = pygame.Vector2()如果按下[pygame.K_UP]:camera_move += (0, 1)如果按下[pygame.K_LEFT]:camera_move += (1, 0)如果按下[pygame.K_DOWN]:camera_move += (0, -1)如果按下[pygame.K_RIGHT]:camera_move += (-1, 0)如果 camera_move.length() >0:camera_move.normalize_ip()相机 += 相机移动*5screen.fill(pygame.color.THECOLORS["white"])pymunk_layer.fill((12,12,12))space.debug_draw(选项)screen.blit(pymunk_layer,相机)screen.blit(font.render("pygame_util.DrawOptions()的演示示例", 1, color), (205, 680))对于标题中的标题:x, y = 标题[0]y = 700 - yscreen.blit(font.render(caption[1], 1, color), camera + (x,y))pygame.display.flip()时钟滴答(30)如果 __name__ == '__main__':sys.exit(main())

From the pymunk examples I've seen that there's a difference between the pymunk coordinates and pygame coordinates. Also, that pymunk is meant just for the 2D physics, while pygame is for rendering objects/sprites on the screen.

So when searching for how to build an environment where the camera follows the player, people (including me) end up getting confused. I've seen the examples here, here, here and here (even surprised that nobody answered this), but given the number of questions related to the same topic being asked repeatedly, I honestly feel the answers do not adequately explain the concept and request that the simplest possible example be shown to the community, where all the code is explained with comments.

I've worked in 3D environments like OGRE and OSG where the camera was a proper concept that could be defined with a view frustum, but I'm surprised the 2D world does not have a pre-defined function for it. So:

If not in the official tutorials of pymunk or pygame, at least could a simple example be provided (with a pymunk body as the player and few pymunk bodies in the world) as an answer here, where a player moves around in a 2D pymunk+pygame world and the camera follows the player?

解决方案

OK, I'll try to make this simple (I assume basic pygame knowledge).

First, let's start with something basic. A little sprite that you can move around the world:

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

Nothing crazy so far.


So, what is a "camera"? It's just a x and an y value we use to move the entire "world" (e.g. everything that is not UI). It's an abstraction between the coordinates of our game objects and the screen.

In our example above, when a game object (the player, or the background) wants to be drawn at position (x, y), we draw them at the screen at this very position.

Now, if we want to move around a "camera", we simply create another x, y-pair, and add this to the game object's coordinates to determine the actual position on the screen. We start to distinguish between world coordinates (what the game logic thinks where the position of an object is) and the screen coordinates (the actual position of an object on the screen).

Here's our example with a "camera" ("camera" in quotes) because it's really just two values:

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

Now you can move the camera with the arrow keys.

That's it. We just move everything a little bit before blitting it to the screen.

For a more complete example (supporting sprites, stopping at the edge of the world, smooth movement), see this question.


And for using pymunk: it just works. It's not affected by drawing stuff to another position, since it works with the world coordinates, not the screen coordinates. The only pitfall is that pymunk's y-axis is flipped compared to pygame's y-axis, but you probably know this already.

Here's an example:

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


Note that when you use pymunk.Space.debug_draw, you won't be able to translate the world coordinates to screen coordinates, so it would be best to simply draw the pymunk stuff to another Surface, and translate that very Surface.

Here's pymunk's pygame_util_demo.py with a moving camera:

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

这篇关于使用 Pymunk 和 Pygame 横向滚动.如何移动相机/视口以仅查看世界的一部分?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆