我将彻底解释这个解释,所以如果您发现某些部分很明显,请不要感到惊讶。
现在,正如我在评论中提到的,要真正了解正在发生的事情,尝试在没有任何 GUI 的情况下编写逻辑会很有帮助。这强调您对概念的理解。
概念一:x & y 坐标
好的,开始吧。对于 2D 图形,在任何 2D 环境中,我们都知道项目的位置可以由两个变量表示:x 和 y 坐标。没有要求这些变量是专门的 x 和 y,这只是约定,但重要的是拥有这两个变量的想法。
通过以任意组合更改这两个值,我们可以在该 2D 世界中任何位置定位项目。这同样适用于数轴,例如,我们只需要 一个 变量来将项目定位在 1D 世界中的任何位置。
在这张图片中,我们可以看到大十字已定位在 2D 世界中(“2D 世界”在本例中是图形)。 x 和 y 这两个变量的组合为 (200, 100) 将十字架定位在该特定位置,这两个变量的其他组合无法将其准确定位。
这是第一个要理解的重要概念:坐标系的概念。能够通过更改两个值在 2D 空间中定位项目。 或者你可以看到,二维空间中的每个位置都有自己的x和y坐标,两者都是准确的。
同样,正如使用字母 x 和 y 是约定一样,它也是约定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。x 和 y 速度本质上是项目的水平和垂直速度。值越大,沿该轴移动的速度越快,如果为负值,则沿相反方向移动。
这是要理解的第二个重要概念:速度的概念。通过每帧更改其坐标来移动项目位置的能力,以及它移动的量(或“变化量”)称为速度。
概念三:弹跳
目前,到目前为止,我们可以通过设置 x 和 y 速度并通过这些速度每帧更改 x 和 y 值来逐渐在屏幕上移动项目。那么当我们撞墙时会发生什么?那么项目应该反弹吧?在当前状态下,项目只会继续离开边缘窗口。
让我们举一个具体的例子好吗?如果球的x velocity 为 -5,y velocity 为 0(所以它每帧向左移动 5 次),并且它撞到了左边的墙上,会发生什么?好吧,既然我们知道我们需要让球向后移动,我们通过否定速度来改变它的方向(将它从 -5 变成 5)。球撞到了左墙,所以它应该水平弹跳,所以x velocity应该被否定(所以它现在向右移动)。
好的,所以我们知道如何让它水平弹跳,但是我们怎么知道什么时候它需要弹跳呢?
只要项目向左移动(所以我们知道它需要反弹),我们可以看到它的 x 值为负(又名 <0)。所以,我认为推导出以下规则是相当安全的:
if x < 0:
x_velocity = abs(x_velocity)
abs() 函数使您给出的数字为正数。所以abs(-5) -> 5,还有abs(5) -> 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() 的原因是因为在右墙和底墙,我们需要分别让球向左/向上反弹,并且在每种情况下,对应的 x 或 y 速度都始终为负.我们首先用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中应该有类似的东西。
酷豆,我们已经动起来了!桨的下一部分是让球从他们身上反弹。看图片,我们可以看到桨实际上没有宽度。如果有的话,它们是墙壁的一部分。在正常的乒乓球比赛中,侧壁是出界的,所以球拍可以看作是墙的一部分,没有出界。墙的一小部分移动的部分,可以反弹球并且不会出界。
让我们对到目前为止的代码做一些事情。首先让我们结合球和桨代码,我们将改变左右墙为界外,我们将添加第二个控件,右桨与O 和L 用于控件:
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 游戏在技术上可以运行,但还有一项改进需要添加。