Камера в pyglet + pymunk

Нужно было реализовать камеру для 2D платформера на pyglet с физическим движком pymunk. В pyglet нет стандартной реализации камеры, использовал пример с репозитория pyglet - camera_group. Возникла проблема в том, что физические объекты привязаны к экрану и движение камеры на них не работает. Предполагаю, что можно как то использовать Canvas и разделить визуал (с камерой) от всего остального мира, но способ реализации не нашел.

Вот так это выглядит: Сама прога

Вот сам код:

from random import randrange, randint

import pyglet
from pyglet.gl import *
from pyglet.window import key, Window, mouse
from pyglet.sprite import Sprite

import pymunk
from pymunk import pyglet_util
from pymunk import Space, Circle, Poly, Body

from camera import CenteredCameraGroup


WIDTH, HEIGHT = 1920, 1080

pyglet.gl.glClearColor(0.5,0,0,1)

# basic display
display = Window(WIDTH, HEIGHT)

# input handler
keys = key.KeyStateHandler()
display.push_handlers(keys)

# camera as sprite group
camera = CenteredCameraGroup(display, WIDTH / 2, HEIGHT / 2, zoom=1)

batch = pyglet.graphics.Batch()

background_image = pyglet.image.SolidColorImagePattern((150, 150, 150, 255)).create_image(WIDTH, HEIGHT)
background = Sprite(background_image, 0, 0)

tile_image = pyglet.image.SolidColorImagePattern((100, 100, 100, 255)).create_image(32, 32)
tiles = [Sprite(img=tile_image, x=i * 32, y=HEIGHT/4, group=camera, batch=batch) for i in range(int(WIDTH / 32))]

# pymunk physics
options = pyglet_util.DrawOptions(batch=batch) # <- Тут привязан Batch, но эффекта это никакого не даёт
world = Space()
world.gravity = (0, -8000)

# platform
platform_vertices = ((0, HEIGHT/4), (0, HEIGHT/4+32), (WIDTH, HEIGHT/4), (WIDTH, HEIGHT/4+32))
platform = Poly(world.static_body, platform_vertices)
platform.color = (200, 200, 200, 255)
world.add(platform)


def ball_spawner(pos):
    radius = randint(10, 50)
    body = Body(1, pymunk.moment_for_circle(1, radius, radius))
    body.position = pos
    shape = Circle(body, radius)
    shape.elasticity = 0.5
    shape.friction = 1.0
    shape.color = [randrange(256) for i in range(3)] + [255]
    world.add(body, shape)


def update_physics(dt):
    world.step(dt)


@display.event
def on_draw():
    display.clear()
    background.draw()
    world.debug_draw(options)
    batch.draw()


@display.event
def on_mouse_press(x, y, button, modifiers):
    if button == mouse.LEFT:
        ball_spawner((x, y))


def update_input(dt):
    if keys[key.W]:
        camera.y += 200 * dt
    if keys[key.S]:
        camera.y -= 200 * dt
    if keys[key.A]:
        camera.x -= 200 * dt
    if keys[key.D]:
        camera.x += 200 * dt

    if keys[key.UP]:
        camera.zoom += 0.1

    if keys[key.DOWN]:
        if camera.zoom > 0.05:
            camera.zoom -= 0.05


if __name__ == "__main__":
    pyglet.clock.schedule_interval(update_physics, 1 / 60)
    pyglet.clock.schedule_interval(update_input, 1 / 60)
    pyglet.app.run()


Ответы (1 шт):

Автор решения: Альберт Давыдов

Нашел решение своей проблемы. Вместо camera_group нужно использовать пример с camera, тогда всё будет выглядеть как и задумано.

Вот результат:

введите сюда описание изображения

Вот готовый код решения:

from random import randrange, randint

import pyglet
from pyglet.gl import *
from pyglet.window import key, Window, mouse
from pyglet.sprite import Sprite

import pymunk
from pymunk import pyglet_util
from pymunk import Space, Circle, Poly, Body

from camera import CenteredCamera


WIDTH, HEIGHT = 1920, 1080

pyglet.gl.glClearColor(0.5,0,0,1)

# basic display
display = Window(WIDTH, HEIGHT)

# input handler
keys = key.KeyStateHandler()
display.push_handlers(keys)

# camera
camera = CenteredCamera(display)  # Использую CenteredCamera, а не CenteredCameraGroup

batch = pyglet.graphics.Batch()

background_image = pyglet.image.SolidColorImagePattern((150, 150, 150, 255)).create_image(WIDTH, HEIGHT)
background = Sprite(background_image, 0, 0)

tile_image = pyglet.image.SolidColorImagePattern((100, 100, 100, 255)).create_image(32, 32)
tiles = [Sprite(img=tile_image, x=i * 32, y=HEIGHT/4, batch=batch) for i in range(int(WIDTH / 32))]

# pymunk physics
options = pyglet_util.DrawOptions()
world = Space()
world.gravity = (0, -8000)

# platform
platform_vertices = ((0, HEIGHT/4), (0, HEIGHT/4+32), (WIDTH, HEIGHT/4), (WIDTH, HEIGHT/4+32))
platform = Poly(world.static_body, platform_vertices)
platform.color = (200, 200, 200, 255)
world.add(platform)


def ball_spawner(pos):
    radius = randint(10, 50)
    body = Body(1, pymunk.moment_for_circle(1, radius, radius))
    body.position = pos
    shape = Circle(body, radius)
    shape.elasticity = 0.5
    shape.friction = 1.0
    shape.color = [randrange(256) for i in range(3)] + [255]
    world.add(body, shape)


def update_physics(dt):
    world.step(dt)


@display.event
def on_draw():
    display.clear()
    background.draw()
    with camera:  # Всё, что статично должно отрисовываться через with camera
        world.debug_draw(options)
        batch.draw()



@display.event
def on_mouse_press(x, y, button, modifiers):
    if button == mouse.LEFT:
        ball_spawner((camera.position[0], camera.position[1]))  # <- спавн шара на позиции камеры


def update_input(dt):
    if keys[key.W]:
        camera.position = (camera.position[0], camera.position[1] + 200 * dt)
    if keys[key.S]:
        camera.position = (camera.position[0], camera.position[1] - 200 * dt)
    if keys[key.A]:
        camera.position = (camera.position[0] - 200 * dt, camera.position[1])
    if keys[key.D]:
        camera.position = (camera.position[0] + 200 * dt, camera.position[1])

    if keys[key.UP]:
        camera.zoom += 0.1

    if keys[key.DOWN]:
        if camera.zoom > 0.05:
            camera.zoom -= 0.05


if __name__ == "__main__":
    pyglet.clock.schedule_interval(update_physics, 1 / 60)
    pyglet.clock.schedule_interval(update_input, 1 / 60)
    pyglet.app.run()


→ Ссылка