【问题标题】:How to make a bouncy ball in PyGame, Python如何在 PyGame、Python 中制作弹力球
【发布时间】:2020-12-01 03:43:08
【问题描述】:

我正在使用 PyGame 在 python 中开发 Pong 游戏,我想学习如何制作一个有弹性的球。我做了很多研究和 youtube 观看,但我没有取得任何进展。知道如何使球从使用pygame.draw.rect() 和许多其他形状绘制的某些形状上反弹会很有用。但请不要提供制作 Pong 游戏的实际代码。

【问题讨论】:

  • 首先,我建议弄清楚这种没有任何视觉效果的东西背后的逻辑。只有球和球拍的坐标和尺寸,你将如何确定球是否碰撞,然后你会改变什么让它“反弹”。
  • @dantechguy 我真的不知道这就是我来问的原因:(
  • 可能值得搜索现有的工作 pong 代码,并查看它以尝试理解其中使用的逻辑。尝试弄清楚别人的代码做了什么似乎令人生畏,但能够做到这一点也是一项非常重要的技能。如果你还想帮忙,就喊一声,我会尽力解释一下。
  • 我正在尽我所能从头开始
  • 这里的答案将解释任何好的教程都会解释的相同概念。如果您尝试从头开始构建某些东西但不知道如何构建,那么这可能表明您应该在尝试之前尝试获得更扎实的理解。查看和理解现有代码将帮助您学习更多,而不是在您可能不知道所需的一切时试图弄清楚它:D 如果您仍然有信心想要答案,那么我当然会,但请记住这一点.学习别人的代码一点也不作弊。

标签: python pygame


【解决方案1】:

我将彻底解释这个解释,所以如果您发现某些部分很明显,请不要感到惊讶。

现在,正如我在评论中提到的,要真正了解正在发生的事情,尝试在没有任何 GUI 的情况下编写逻辑会很有帮助。这强调您对概念的理解。


概念一:x & y 坐标

好的,开始吧。对于 2D 图形,在任何 2D 环境中,我们都知道项目的位置可以由两个变量表示:xy 坐标。没有要求这些变量是专门的 xy,这只是约定,但重要的是拥有这两个变量的想法。

通过以任意组合更改这两个值,我们可以在该 2D 世界中任何位置定位项目。这同样适用于数轴,例如,我们只需要 一个 变量来将项目定位在 1D 世界中的任何位置。


在这张图片中,我们可以看到大十字已定位在 2D 世界中(“2D 世界”在本例中是图形)。 xy 这两个变量的组合为 (200, 100) 将十字架定位在该特定位置,这两个变量的其他组合无法将其准确定位。

这是第一个要理解的重要概念:坐标系的概念。能够通过更改两个值在 2D 空间中定位项目。 或者你可以看到,二维空间中的每个位置都有自己的xy坐标,两者都是准确的。

同样,正如使用字母 xy 是约定一样,它也是约定x 值描述 水平 位置,y 值描述垂直位置。您通常会看到更大的x 值是“更正确”,而更大的y 值是“更上”。这会将坐标 (0, 0) 放在这样一个 2D 世界的左下方。

但并非总是如此,例如此图和 Pygame! Pygame 将更大的y 值视为“向下”,因此 Pygame 的坐标 (0, 0) 实际上位于其 2D 世界的左上角(在本例中,“2D 世界”是Pygame 的图形窗口)。


概念二:速度

能够在 2D 世界中放置物品是件好事,但如果我们不能移动这些物品,那就太没用了!现在在这里非常相关的是框架的基本知识。 Pygame 窗口每秒会更新很多次,因为 - 如果不更新,那么图片就不会改变!每次更新时,都称为frame。这就是术语每秒帧数 (fps) 的来源,它本质上是告诉您每秒更新多少次。

所以,假设我们想要在我们的 2D 世界中移动一个项目向右(也就是增加 x 值)(同样,Pygame 中的这个“2D 世界”就是窗口)。我们将如何做这件事?如果项目位于坐标 (200, 100),要向右移动它,我们需要做的就是增加 x 的值,对吗?好吧,让我们看看如果我们将大十字'x 的值增加 100(所以它向右移动)会是什么样子。

但如果我们只在一帧内完成这一切,那么它基本上只是将 100 传送到右侧!而且不会称​​那为“移动”。如果我想到一些移动的东西,那么它更像是从一个位置逐渐转移到另一个位置。

嗯,好吧.. 那么,与其一次性移动 100 次,不如将其移动 5 次,20 次。 20 * 5 仍然是 100,所以到最后它仍然会移动 100,但在这种情况下,因为我们将移动分散到 20 个 5 块中,它看起来人眼。

更具体地说,我们可以将项目每帧移动 5,20 帧。由于窗口每秒更新一定数量的帧(通常这可以默认为每秒 30 或 60 帧),因此每帧之间有一个小的时间间隔,并且每帧仅移动 5 帧,我们最终得到 将 100 的运动从一瞬间扩展到不到一秒。


一直以来,我们一直在讨论将这个项目每帧移动 5,但老实说,它可以是任何东西!它可以每帧移动 1,它可以每帧移动 -5(所以向后),它可以每帧移动 2.5(所以两帧中移动 5)。这个值,这个每帧移动的量,可以称为速度。速度越高(因此每帧移动的距离越长),项目似乎移动得越快,因为它在相同的时间内覆盖了更多的距离。

现在我们只考虑向右移动项目(技术上也向左移动了一个量),因此我们可以称这个每帧水平移动量 x velocity。这同样适用于上下移动项目,所以每帧垂直移动的量y velocity

x = 200
y = 100

x_velocity = 5
y_velocity = 0

while True:
    # each loop can be thought of as one frame
    x += x_velocity
    y += y_velocity

    # ... other pygame code stuff ...

在上面的代码中,每帧item的x值增加5,所以我们可以说x velocity是5。由于y值没有变化,@ 987654356@ 为 0。xy 速度本质上是项目的水平和垂直速度。值越大,沿该轴移动的速度越快,如果为负值,则沿相反方向移动。

这是要理解的第二个重要概念:速度的概念。通过每帧更改其坐标来移动项目位置的能力,以及它移动的量(或“变化量”)称为速度。


概念三:弹跳

目前,到目前为止,我们可以通过设置 xy 速度并通过这些速度每帧更改 xy 值来逐渐在屏幕上移动项目。那么当我们撞墙时会发生什么?那么项目应该反弹吧?在当前状态下,项目只会继续离开边缘窗口。

让我们举一个具体的例子好吗?如果球的x velocity 为 -5,y velocity 为 0(所以它每帧向左移动 5 次),并且它撞到了左边的墙上,会发生什么?好吧,既然我们知道我们需要让球向后移动,我们通过否定速度来改变它的方向(将它从 -5 变成 5)。球撞到了墙,所以它应该水平弹跳,所以x velocity应该被否定(所以它现在向右移动)。

好的,所以我们知道如何让它水平弹跳,但是我们怎么知道什么时候它需要弹跳呢?

只要项目向左移动(所以我们知道它需要反弹),我们可以看到它的 x 值为负(又名 <0)。所以,我认为推导出以下规则是相当安全的:

if x < 0:
    x_velocity = abs(x_velocity)

abs() 函数使您给出的数字为正数。所以abs(-5) -&gt; 5,还有abs(5) -&gt; 5。所以上面的规则实际上意味着:“当项目的 x 值小于 0 时(因此它通过了左墙),使其 x velocity 为正(使其向右移动,使其'反弹')” .

按照相同的逻辑,我们可以将相同的逻辑应用于y 值:

if y < 0:
    y_velocity = abs(y_velocity)

下一个自然出现的问题是关于底部和右侧的墙壁。既然我们怎么知道物品何时通过了那些?看看下面的图片可能会给我们一些启示:

我们可以直观地看到右墙在x500,底部在y400。在这种情况下,窗口大小为500x400。由于左墙的 x 值为 0,我们知道右墙必须位于窗口宽度的 x 值。底墙也是如此,因为顶墙的 y 值为 0,所以底墙必须为窗口高度的 y 值。因此我们可以完成所有 4 面墙的弹跳代码来实现:

# left wall
if x < 0:
    x_velocity = abs(x_velocity)

# top wall
if y < 0:
    y_velocity = abs(y_velocity)

# right wall
if x > width:
    x_velocity = -abs(x_velocity)

# bottom wall
if y > height:
    y_velocity = -abs(y_velocity)

-abs() 的原因是因为在右墙和底墙,我们需要分别让球向左/向上反弹,并且在每种情况下,对应的 xy 速度都始终为负.我们首先用abs() 使它们始终为正,然后用- 否定它以使-abs() 始终为负。

我们可以将它与我们之前的速度代码结合起来,制作一个球在 4 个墙壁内弹跳的伪 Python 代码:

x = 200
y = 100

x_velocity = 5
y_velocity = 5

while True:
    x += x_velocity
    y += y_velocity

    # left wall
    if x < 0:
        x_velocity = abs(x_velocity)
    
    # top wall
    if y < 0:
        y_velocity = abs(y_velocity)
    
    # right wall
    if x > width:
        x_velocity = -abs(x_velocity)
    
    # bottom wall
    if y > height:
        y_velocity = -abs(y_velocity)

了解如何操纵物品的速度来模拟弹跳是迄今为止最棘手的部分,但对最终产品同样重要。


概念四:桨

到目前为止,我们刚刚实现了一些通用的球弹跳规则,那么我们从专门的类似乒乓球的东西开始怎么样?接下来缺少的是桨,所以让我们考虑一下如何使它们发挥作用。

下面我描绘了 Pygame 窗口,红线是桨可能所在的位置。

首先,让我们利用我们的新知识来弄清楚这些桨的一些事情。比如说,我们将如何跟踪它们的位置!我们知道我们可以使用两个值来存储项目的位置,我们将在这里使用其中一个。 y 值告诉我们项目的垂直位置(或“距顶部的距离”)。

所以,看上图,我们可以看到左桨在正上方。它的“距顶部的距离”为零!,sooo 这意味着我们可以说左侧桨的y 值为 0。而右侧桨距顶部 100,所以它的 y 值为 100。简单的东西!

但是,这还不是全部。在我们谈论单点之前,它没有像桨那样的高度!在这种情况下,桨看起来有 200 高。但这里有一个问题:这些桨可以具有的最大y 值是多少?所以,如果桨是 200 高,窗户的高度是 400,那么 400 以上的 200 意味着 400 - 200,这意味着最大值是 200!


好的,所以我们已经弄清楚如何存储桨的垂直位置(y 值),最大 y 值它可以有! (如果没有最大值,那么它会从底部消失!)接下来要做的是如何移动它。假设只要我们按住W 键,左桨应该向上移动 5(所以从y 到 -5),并且按住 S 键应该将桨向下移动 5(所以 +5到y)。

我们将要检查每一帧按下了哪些键,然后将拨片向该方向移动 5。还记得之前每帧移动的量是如何被称为速度的吗?那么这里同样适用!唯一的区别是桨只在按下键时移动,但它仍然适用于这里:由于桨每帧移动 5(当按下键时),桨的速度将为 5!显然这可以是任何东西,但 5 对我来说听起来不错。让我们看一些伪python代码:

# start the paddle at the top
paddle_y = 0
paddle_velocity = 5

# each loop is one frame
while True:
    if key_pressed('w'):
        # we are subtracting so it moves upwards
        paddle_y -= paddle_velocity
    if key_pressed('S'):
        paddle_y += paddle_velocity

哦等等!!我完全忘记了限制!现在使用此代码,桨叶可以从屏幕上移开……这太糟糕了。好的,我们知道到顶部的距离不能小于零,所以范围是0 to window_height - paddle_height。我们只需要确保它不会超出这些值!如果y确实超出,那么我们将把它移回极限。查看处理此问题的更新代码:

# start the paddle at the top
paddle_y = 0
paddle_velocity = 5
paddle_height = 200

# each loop is one frame
while True:
    if key_pressed('w'):
        # we are subtracting so it moves upwards
        paddle_y -= paddle_velocity
    if key_pressed('S'):
        paddle_y += paddle_velocity

    # limits the minimum y to 0
    if paddle_y < 0:
        paddle_y = 0
    # limit the maximum y to keep it on the screen
    if paddle_y > (height - paddle_height):
        paddle_y = (height - paddle_height)

函数key_pressed()只是我为了演示目的而编造的,但在Pygame中应该有类似的东西。


酷豆,我们已经动起来了!桨的下一部分是让球从他们身上反弹。看图片,我们可以看到桨实际上没有宽度。如果有的话,它们是墙壁的一部分。在正常的乒乓球比赛中,侧壁是出界的,所以球拍可以看作是墙的一部分,没有出界。墙的一小部分移动的部分,可以反弹球并且不会出界。

让我们对到目前为止的代码做一些事情。首先让我们结合球和桨代码,我们将改变左右墙为界外,我们将添加第二个控件,右桨与OL 用于控件:

ball_x = 200
ball_y = 100
ball_x_velocity = 5
ball_y_velocity = 5

left_paddle_y = 0
right_paddle_y = 0
paddle_velocity = 5
paddle_height = 200

# each loop is one frame
while True:

    # BALL
    ball_x += ball_x_velocity
    ball_y += ball_y_velocity

    # left wall
    if x < 0:
        out_of_bounds()
    
    # top wall
    if y < 0:
        ball_y_velocity = abs(ball_y_velocity)
    
    # right wall
    if x > width:
        out_of_bounds()
    
    # bottom wall
    if y > height:
        ball_y_velocity = -abs(ball_y_velocity)

    # PADDLES
    if key_pressed('w'):
        left_paddle_y -= paddle_velocity
    if key_pressed('S'):
        left_paddle_y += paddle_velocity
    if key_pressed('O'):
        right_paddle_y -= paddle_velocity
    if key_pressed('L'):
        right_paddle_y += paddle_velocity

    if left_paddle_y < 0:
        left_paddle_y = 0
    if left_paddle_y > (height - paddle_height):
        left_paddle_y = (height - paddle_height)
    if right_paddle_y < 0:
        right_paddle_y = 0
    if right_paddle_y > (height - paddle_height):
        right_paddle_y = (height - paddle_height)

不错!下一步的概要是:如果球撞到左墙或右墙,则只有在球拍内时才反弹球。下一个自然问题是“我们如何知道球是否在桨内?”。

再看这张图片,特别是在右桨,要让球从右桨反弹,它需要在它的顶部和底部之间。所以我们需要比较球的y 值和桨的末端。桨的顶部就是它的y 值,桨的底部是它的y 值+它的高度。因此,要让球位于右桨内,必须满足以下条件:

if (ball_y > right_paddle_y) and (ball_y < right_paddle_y + paddle_height):
    # bounce!

让我们为左右桨添加弹跳代码好吗?

ball_x = 200
ball_y = 100
ball_x_velocity = 5
ball_y_velocity = 5

left_paddle_y = 0
right_paddle_y = 0
paddle_velocity = 5
paddle_height = 200

# each loop is one frame
while True:

    # BALL
    ball_x += ball_x_velocity
    ball_y += ball_y_velocity

    # left wall, only bounce if its inside the left paddle
    if x < 0:
        if (ball_y > left_paddle_y) and (ball_y < left_paddle_y + paddle_height):
            ball_x_velocity = abs(ball_x_velocity)
        else:
            out_of_bounds()
    
    if y < 0:
        ball_y_velocity = abs(ball_y_velocity)
    
    # right wall, only bounce if its inside the right paddle
    if x > width:
        if (ball_y > right_paddle_y) and (ball_y < right_paddle_y + paddle_height):
            ball_x_velocity = -abs(ball_x_velocity)
        else:
            out_of_bounds()
    
    if y > height:
        ball_y_velocity = -abs(ball_y_velocity)

    # PADDLES
    if key_pressed('w'):
        left_paddle_y -= paddle_velocity
    if key_pressed('S'):
        left_paddle_y += paddle_velocity
    if key_pressed('O'):
        right_paddle_y -= paddle_velocity
    if key_pressed('L'):
        right_paddle_y += paddle_velocity

    if left_paddle_y < 0:
        left_paddle_y = 0
    if left_paddle_y > (height - paddle_height):
        left_paddle_y = (height - paddle_height)
    if right_paddle_y < 0:
        right_paddle_y = 0
    if right_paddle_y > (height - paddle_height):
        right_paddle_y = (height - paddle_height)

尼托!!我们现在添加了工作桨:D 游戏在技术上可以运行,但还有一项改进需要添加。

【讨论】:

  • 我刚刚对你的大量答案和问题投了赞成票,所以你现在有了更高的声誉:D 你的帮助很棒
  • 尽管我很感激,但只有在您觉得它真正有帮助的情况下,您才应该投票,并且 SO 有防止大规模投票的系统。尽管如此,这个手势还是很友好的! :D
  • 好吧,但是你的名声已经下降了。为什么?
  • 因为 SO 有 serial voting detectors 可以反转这种情况,在这种情况下,它们会撤销大量投票。
  • 哦,我明白了
猜你喜欢
  • 1970-01-01
  • 2013-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-17
  • 1970-01-01
相关资源
最近更新 更多