Как правильно создавать и обновлять объекты в оконном мире Pymunk?

Я спросил, как получить из окна камеры вид мира Pymunk + Pygame, и хотя я получил щедро объяснил ответ, я не уверен, потому что человек использует Pygame Surface для бликации. В API говорится, что поверхность предназначена для представления изображения и обработки и точки зрения рендеринга. Я чувствовал, что это неэффективный способ рендеринга объектов Pymunk, особенно когда они кинематические.

Итак, я попытался изменить один из примеров Pymunk (я прокомментировал «Nav added» везде, где я делал изменения), чтобы перемещать шары и статические линии при нажатии клавиш со стрелками. Создается эффект камеры. Но когда я использую клавиши со стрелками для перемещения объекта вправо, кажется, что даже координаты мыши перемещаются.

введите здесь описание изображения
Я думал, что проблема связана со строкой p = event.pos[X]+cameraX, flipy(event.pos[Y])+cameraY, но даже после изменения ее на p = event.pos[X], flipy(event.pos[Y]) проблема сохраняется.

"""This example lets you dynamically create static walls and dynamic balls

"""
__docformat__ = "reStructuredText"

import pygame
from pygame.locals import *
from pygame.color import *

import pymunk
from pymunk import Vec2d


X,Y = 0,1
### Physics collision types
COLLTYPE_DEFAULT = 0
COLLTYPE_MOUSE = 1
COLLTYPE_BALL = 2

def flipy(y):
    """Small hack to convert chipmunk physics to pygame coordinates"""
    return -y+600

def mouse_coll_func(arbiter, space, data):
    """Simple callback that increases the radius of circles touching the mouse"""
    s1,s2 = arbiter.shapes
    s2.unsafe_set_radius(s2.radius + 0.15)
    return False

def main():            
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    clock = pygame.time.Clock()
    running = True

    # Camera offsets (Nav added)
    cameraX = 0
    cameraY = 0

    ### Physics stuff
    space = pymunk.Space()
    space.gravity = 0.0, -900.0

    ## Balls
    balls = []

    ### Mouse
    mouse_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
    mouse_shape = pymunk.Circle(mouse_body, 3, (0,0))
    mouse_shape.collision_type = COLLTYPE_MOUSE
    space.add(mouse_shape)

    space.add_collision_handler(COLLTYPE_MOUSE, COLLTYPE_BALL).pre_solve=mouse_coll_func   

    ### Static line
    line_point1 = None
    static_lines = []
    run_physics = True

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                running = False
            elif event.type == KEYDOWN and event.key == K_p:
                pygame.image.save(screen, "balls_and_lines.png")
            elif event.type == MOUSEBUTTONDOWN and event.button == 1:
                p = event.pos[X]+cameraX, flipy(event.pos[Y])+cameraY#Nav added
                body = pymunk.Body(10, 100)
                body.position = p
                shape = pymunk.Circle(body, 10, (0,0))
                shape.friction = 0.5
                shape.collision_type = COLLTYPE_BALL
                space.add(body, shape)
                balls.append(shape)
            #Nav added key detection event
            if event.type == KEYDOWN:
                if event.key == K_UP:
                    cameraY -= 10
                    print("x:"+str(cameraX)+" y:"+str(cameraY))
                if event.key == K_LEFT: 
                    cameraX -= 10
                if event.key == K_DOWN:
                    cameraY += 10
                if event.key == K_RIGHT:
                    cameraX += 10                

            elif event.type == MOUSEBUTTONDOWN and event.button == 3: 
                if line_point1 is None:
                    line_point1 = Vec2d(event.pos[X]+cameraX, flipy(event.pos[Y])+cameraY)#Nav added
            elif event.type == MOUSEBUTTONUP and event.button == 3: 
                if line_point1 is not None:                    
                    line_point2 = Vec2d(event.pos[X]+cameraX, flipy(event.pos[Y])+cameraY)#Nav added
                    body = pymunk.Body(body_type=pymunk.Body.STATIC)
                    shape= pymunk.Segment(body, line_point1, line_point2, 0.0)
                    shape.friction = 0.99
                    space.add(shape)
                    static_lines.append(shape)
                    line_point1 = None

            elif event.type == KEYDOWN and event.key == K_SPACE:    
                run_physics = not run_physics

        p = pygame.mouse.get_pos()
        mouse_pos = Vec2d(p[X]+cameraX, flipy(p[Y])+cameraY)#Nav added
        mouse_body.position = mouse_pos


        if pygame.key.get_mods() & KMOD_SHIFT and pygame.mouse.get_pressed()[0]:
            body = pymunk.Body(10, 10)
            body.position = mouse_pos
            shape = pymunk.Circle(body, 10, (0,0))
            shape.collision_type = COLLTYPE_BALL
            space.add(body, shape)
            balls.append(shape)

        ### Update physics
        if run_physics:
            dt = 1.0/60.0
            for x in range(1):
                space.step(dt)

        ### Draw stuff
        screen.fill(THECOLORS["white"])

        # Display some text
        font = pygame.font.Font(None, 16)
        text = """LMB: Create ball
LMB + Shift: Create many balls
RMB: Drag to create wall, release to finish
Space: Pause physics simulation"""
        y = 5
        for line in text.splitlines():
            text = font.render(line, 1,THECOLORS["black"])
            screen.blit(text, (5,y))
            y += 10

        for ball in balls:           
            r = ball.radius
            v = ball.body.position
            rot = ball.body.rotation_vector
            p = int(v.x)+cameraX, int(flipy(v.y))+cameraY#Nav added
            p2 = Vec2d(rot.x, -rot.y) * r * 0.9
            pygame.draw.circle(screen, THECOLORS["blue"], p, int(r), 2)
            pygame.draw.line(screen, THECOLORS["red"], p, p+p2)

        if line_point1 is not None:
            p1 = line_point1.x+cameraX, flipy(line_point1.y)+cameraY#Nav added
            p2 = mouse_pos.x+cameraX, flipy(mouse_pos.y)+cameraY#Nav added
            pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])

        for line in static_lines:
            body = line.body

            pv1 = body.position + line.a.rotated(body.angle)
            pv2 = body.position + line.b.rotated(body.angle)
            p1 = pv1.x+cameraX, flipy(pv1.y)+cameraY#Nav added
            p2 = pv2.x+cameraX, flipy(pv2.y)+cameraY#Nav added
            pygame.draw.lines(screen, THECOLORS["lightgray"], False, [p1,p2])

        ### Flip screen
        pygame.display.flip()
        clock.tick(50)
        pygame.display.set_caption("fps: " + str(clock.get_fps()))

if __name__ == '__main__':
    doprof = 0
    if not doprof: 
        main()
    else:
        import cProfile, pstats

        prof = cProfile.run("main()", "profile.prof")
        stats = pstats.Stats("profile.prof")
        stats.strip_dirs()
        stats.sort_stats('cumulative', 'time', 'calls')
        stats.print_stats(30)

Правильно ли я поступаю, перемещая все позиции шара и статических линий в каждом кадре? Я предполагаю, что более эффективным способом было бы перемещать их все в каждом кадре, но рисовать только те, которые находятся в пределах экрана?

С другой стороны, в примере, подобном приведенному ниже, статические строки добавляются перед циклом выполнения, и это заставляет меня задуматься, как правильно реализовать камеру в этом случае. Возможно, у меня нет другого выхода, кроме как использовать Surface.

"""This example spawns (bouncing) balls randomly on a L-shape constructed of 
two segment shapes. Not interactive.
"""

__version__ = "$Id:$"
__docformat__ = "reStructuredText"

# Python imports
import random

# Library imports
import pygame
from pygame.key import *
from pygame.locals import *
from pygame.color import *

# pymunk imports
import pymunk
import pymunk.pygame_util


class BouncyBalls(object):
    """
    This class implements a simple scene in which there is a static platform (made up of a couple of lines)
    that don't move. Balls appear occasionally and drop onto the platform. They bounce around.
    """
    def __init__(self):
        # Space
        self._space = pymunk.Space()
        self._space.gravity = (0.0, -900.0)
        # Camera offsets (Nav added)
        self.cameraX = 0
        self.cameraY = 0
        # Physics
        # Time step
        self._dt = 1.0 / 60.0
        # Number of physics steps per screen frame
        self._physics_steps_per_frame = 1

        # pygame
        pygame.init()
        self._screen = pygame.display.set_mode((600, 600))
        self._clock = pygame.time.Clock()

        self._draw_options = pymunk.pygame_util.DrawOptions(self._screen)

        # Static barrier walls (lines) that the balls bounce off of
        self._add_static_scenery()

        # Balls that exist in the world
        self._balls = []

        # Execution control and time until the next ball spawns
        self._running = True
        self._ticks_to_next_ball = 10

    def run(self):
        # Main loop
        while self._running:
            # Progress time forward
            for x in range(self._physics_steps_per_frame):
                self._space.step(self._dt)

            self._process_events()
            self._update_balls()
            self._clear_screen()
            self._draw_objects()
            pygame.display.flip()
            # Delay fixed time between frames
            self._clock.tick(50)
            pygame.display.set_caption("fps: " + str(self._clock.get_fps()))

    def _add_static_scenery(self):
        static_body = self._space.static_body
        #Nav added offsets
        static_lines = [pymunk.Segment(static_body, (111.0+self.cameraX, 280.0+self.cameraY), (407.0+self.cameraX, 246.0+self.cameraY), 0.0),
                        pymunk.Segment(static_body, (407.0+self.cameraX, 246.0+self.cameraY), (407.0+self.cameraX, 343.0+self.cameraY), 0.0)]
        for line in static_lines:
            line.elasticity = 0.95
            line.friction = 0.9
        self._space.add(static_lines)

    def _process_events(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                self._running = False
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                self._running = False
            elif event.type == KEYDOWN and event.key == K_p:
                pygame.image.save(self._screen, "bouncing_balls.png")

            #Nav added key detection event
            if event.type == KEYDOWN:
                if event.key == K_UP:
                    self.cameraY -= 10
                    print("x:"+str(self.cameraX)+" y:"+str(self.cameraY))
                if event.key == K_LEFT: 
                    self.cameraX -= 10
                if event.key == K_DOWN:
                    self.cameraY += 10
                if event.key == K_RIGHT:
                    self.cameraX += 10


    def _update_balls(self):
        self._ticks_to_next_ball -= 1
        if self._ticks_to_next_ball <= 0:
            self._create_ball()
            self._ticks_to_next_ball = 100
        # Remove balls that fall below 100 vertically
        balls_to_remove = [ball for ball in self._balls if ball.body.position.y+self.cameraY < 100]#Nav added offset
        for ball in balls_to_remove:
            self._space.remove(ball, ball.body)
            self._balls.remove(ball)

    def _create_ball(self):
        mass = 10
        radius = 25
        inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
        body = pymunk.Body(mass, inertia)
        x = random.randint(115+self.cameraX, 350+self.cameraY)#Nav added offset
        body.position = x+self.cameraX, 400+self.cameraY #Nav added offset
        shape = pymunk.Circle(body, radius, (0, 0))
        shape.elasticity = 0.95
        shape.friction = 0.9
        self._space.add(body, shape)
        self._balls.append(shape)

    def _clear_screen(self):
        self._screen.fill(THECOLORS["white"])

    def _draw_objects(self):
        self._space.debug_draw(self._draw_options)


if __name__ == '__main__':
    game = BouncyBalls()
    game.run()

Таким образом, я пытаюсь создать мир намного больше, чем показанный ниже (типичный мир типа Марио), где прямоугольная область в пунктирных линиях видна пользователю на всем мониторе компьютера и отображается некоторый текст состояния. по углам площадки. Любая часть мира, которая находится за пределами пунктирных линий, находится за пределами поля зрения монитора.
Все, что я хочу знать, это правильный способ создать такой вид камеры, добавить статические и кинематические объекты в этот мир и обновить их в то время как щелчки мыши и ввод с клавиатуры относятся к видимой области экрана. Это сбивает с толку, потому что Pygame и Pymunk имеют разные способы рисования и обновления элементов. Яркий пример будет огромным подспорьем для сообщества.

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

Кроме того, если это многопользовательская игра, может потребоваться отдельная область просмотра для другого человека (паука), который смотрит на тот же мир через подключение к локальной сети.


person Nav    schedule 20.03.2019    source источник


Ответы (1)


(В первом коде, который вы включили) Проблема в том, что вы добавляете камеру X и Y как при рисовании, так и при обработке ввода с помощью мыши. Вы должны изменить это так, чтобы при рисовании вместо этого вычитали X и Y камеры.

Вы можете проверить логику, добавив небольшой оператор печати переменной p в логику, которая создает мяч при щелчке мышью.

"""This example lets you dynamically create static walls and dynamic balls

"""
__docformat__ = "reStructuredText"

import pygame
from pygame.locals import *
from pygame.color import *

import pymunk
from pymunk import Vec2d


X,Y = 0,1
### Physics collision types
COLLTYPE_DEFAULT = 0
COLLTYPE_MOUSE = 1
COLLTYPE_BALL = 2

def flipy(y):
    """Small hack to convert chipmunk physics to pygame coordinates"""
    return -y+600

def mouse_coll_func(arbiter, space, data):
    """Simple callback that increases the radius of circles touching the mouse"""
    s1,s2 = arbiter.shapes
    s2.unsafe_set_radius(s2.radius + 0.15)
    return False

def main():            
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    clock = pygame.time.Clock()
    running = True

    # Camera offsets (Nav added)
    cameraX = 0
    cameraY = 0

    ### Physics stuff
    space = pymunk.Space()
    space.gravity = 0.0, -900.0

    ## Balls
    balls = []

    ### Mouse
    mouse_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
    mouse_shape = pymunk.Circle(mouse_body, 3, (0,0))
    mouse_shape.collision_type = COLLTYPE_MOUSE
    space.add(mouse_shape)

    space.add_collision_handler(COLLTYPE_MOUSE, COLLTYPE_BALL).pre_solve=mouse_coll_func   

    ### Static line
    line_point1 = None
    static_lines = []
    run_physics = True

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                running = False
            elif event.type == KEYDOWN and event.key == K_p:
                pygame.image.save(screen, "balls_and_lines.png")
            elif event.type == MOUSEBUTTONDOWN and event.button == 1:
                p = event.pos[X]-cameraX, flipy(event.pos[Y]-cameraY)#Nav added
                #print("mouseX:"+str(p[0])+" mouseY:"+str(p[1]))
                body = pymunk.Body(10, 100)
                body.position = p
                shape = pymunk.Circle(body, 10, (0,0))
                shape.friction = 0.5
                shape.collision_type = COLLTYPE_BALL
                space.add(body, shape)
                balls.append(shape)
            #Nav added key detection event
            if event.type == KEYDOWN:
                if event.key == K_UP:
                    cameraY -= 10
                if event.key == K_LEFT: 
                    cameraX -= 10
                if event.key == K_DOWN:
                    cameraY += 10
                if event.key == K_RIGHT:
                    cameraX += 10                

            elif event.type == MOUSEBUTTONDOWN and event.button == 3: 
                if line_point1 is None:
                    line_point1 = Vec2d(event.pos[X]-cameraX, flipy(event.pos[Y]-cameraY))#Nav added
            elif event.type == MOUSEBUTTONUP and event.button == 3: 
                if line_point1 is not None:                    
                    line_point2 = Vec2d(event.pos[X]-cameraX, flipy(event.pos[Y]-cameraY))#Nav added
                    body = pymunk.Body(body_type=pymunk.Body.STATIC)
                    shape= pymunk.Segment(body, line_point1, line_point2, 0.0)
                    shape.friction = 0.99
                    space.add(shape)
                    static_lines.append(shape)
                    line_point1 = None

            elif event.type == KEYDOWN and event.key == K_SPACE:    
                run_physics = not run_physics

        p = pygame.mouse.get_pos()
        mouse_pos = Vec2d(p[X]-cameraX, flipy(p[Y]-cameraY))#Nav added
        mouse_body.position = mouse_pos


        if pygame.key.get_mods() & KMOD_SHIFT and pygame.mouse.get_pressed()[0]:
            body = pymunk.Body(10, 10)
            body.position = mouse_pos
            shape = pymunk.Circle(body, 10, (0,0))
            shape.collision_type = COLLTYPE_BALL
            space.add(body, shape)
            balls.append(shape)

        ### Update physics
        if run_physics:
            dt = 1.0/60.0
            for x in range(1):
                space.step(dt)

        ### Draw stuff
        screen.fill(THECOLORS["white"])

        # Display some text
        font = pygame.font.Font(None, 16)
        text = """LMB: Create ball
                LMB + Shift: Create many balls
                RMB: Drag to create wall, release to finish
                Space: Pause physics simulation"""
        y = 5
        for line in text.splitlines():
            text = font.render(line, 1,THECOLORS["black"])
            screen.blit(text, (5,y))
            y += 10

        for ball in balls:           
            r = ball.radius
            v = ball.body.position
            rot = ball.body.rotation_vector
            p = int(v.x)+cameraX, int(flipy(v.y))+cameraY#Nav added
            p2 = Vec2d(rot.x, -rot.y) * r * 0.9
            pygame.draw.circle(screen, THECOLORS["blue"], p, int(r), 2)
            pygame.draw.line(screen, THECOLORS["red"], p, p+p2)

        if line_point1 is not None:
            p1 = line_point1.x+cameraX, flipy(line_point1.y-cameraY)#Nav added
            p2 = mouse_pos.x+cameraX, flipy(mouse_pos.y-cameraY)#Nav added
            pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])

        for line in static_lines:
            body = line.body

            pv1 = body.position + line.a.rotated(body.angle)
            pv2 = body.position + line.b.rotated(body.angle)
            p1 = pv1.x+cameraX, flipy(pv1.y)+cameraY#Nav added
            p2 = pv2.x+cameraX, flipy(pv2.y)+cameraY#Nav added
            pygame.draw.lines(screen, THECOLORS["lightgray"], False, [p1,p2])

        ### Flip screen
        pygame.display.flip()
        clock.tick(50)
        pygame.display.set_caption("fps: " + str(clock.get_fps()))

if __name__ == '__main__':
    doprof = 0
    if not doprof: 
        main()
    else:
        import cProfile, pstats

        prof = cProfile.run("main()", "profile.prof")
        stats = pstats.Stats("profile.prof")
        stats.strip_dirs()
        stats.sort_stats('cumulative', 'time', 'calls')
        stats.print_stats(30)

Примечание: я прочитал другой вопрос / ответ, возможно, этот дополнительный вопрос будет лучше для вашего другого вопроса, поскольку кажется, что третий пример более или менее отвечает на него?

person viblo    schedule 21.03.2019
comment
Фух! После мучительных проб и ошибок это наконец сработало. Спасибо. Значит, тогда должна быть возможность реализовать эффект камеры, перемещая все тела с помощью camX и camY, верно? Мне не нужно переносить его на pygame Surface, как посоветовал другой человек. Причина, по которой я чувствовал, что копирование на pygame Surface может быть неправильным, заключается в том, что я создаю динамические тела в pymunk, и замораживание их на фиксированном Surface просто казалось неправильным, а вычислительная мощность и интенсивность памяти. Как-то кажется лучше позволить им свободно перемещаться в пространстве и рисовать только тела в пределах границ камеры. Я ошибся? - person Nav; 22.03.2019
comment
Да, это обычный способ сделать это. Все объекты в мире имеют координаты, и когда вы их рисуете, вы настраиваете их в соответствии с положением камеры. Использование поверхностного метода, как вы заметили, очень ограничено. Основная причина, по которой я могу его использовать, заключается в том, что вы используете отладочный код отрисовки, предоставленный pymunk. Он не поддерживает какую-либо логику камеры, поэтому метод поверхности - единственный способ (но у меня есть планы немного расширить его, чтобы разрешить камеру) - person viblo; 23.03.2019