0

我询问了如何获得 Pymunk+Pygame 世界的相机窗口视图,虽然我收到了一个慷慨解释的答案,但我不相信,因为该人使用 Pygame Surface 进行 blit。API 说 Surface旨在表示图像,从处理和渲染的角度来看,我觉得这是渲染 Pymunk 对象的一种低效方式,尤其是当它们是运动学时 。

因此,我尝试修改其中一个 Pymunk 示例(我在任何我进行更改的地方都评论了“ Nav added ”)以在按下箭头键时围绕球和静态线移动。它创造了一种相机般的效果。但是当我使用箭头键将对象向右移动时,甚至鼠标坐标似乎也随之移动。

在此处输入图像描述
我以为问题出在 line 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 有不同的绘制和更新元素的方式。一个明显的例子将是对社区的巨大帮助。

在此处输入图像描述

此外,如果它是多人游戏,则可能需要为通过 LAN 连接查看同一世界的其他人(蜘蛛)提供单独的视图区域。

4

1 回答 1

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)

注意:我阅读了另一个问题/答案,也许这个后续问题会更好地解决你的另一个问题,因为第三个示例似乎或多或少地回答了它?

于 2019-03-21T20:11:29.893 回答