使用 Python 写一个简易版的 Pong 游戏

介绍

雅达利(Atari) 是诺兰·布什内尔(Nolan Key Bushnell) 和泰得・都布尼(Ted Dabney)在1972年成立的电脑公司,它是街机、家用电子游戏机和家用电脑的早期拓荒者。《Pong》是雅达利在1972年11月29日推出的一款投币式街机游戏,它是一款模拟乒乓球比赛的2D体育游戏,Pong 来自乒乓球被打击后所发出的声音。Pong 的设计师是 艾伦·奥尔康(Allan Alcorn)。

在游戏中, 玩家能和电脑玩家或另一位人类玩家进行游戏。玩家在此游戏中需要控制乒乓球拍上下移动来反弹乒乓球。当玩家未能反弹乒乓球的话,对方就会得到一分。玩家在此游戏的目的就是尽量反弹乒乓球并夺取高分以击败对手。

接下来,我们使用Python 模仿 Pong 游戏实现两位人类玩家控制乒乓球拍上下移动反弹乒乓球对战。

开发环境

  • Visual Studio Code
  • Python3.7

Turtle(海龟绘图) 模块简介

Turtle 库是 Python 语言中一个直观有趣的图形绘制函数库,在海龟绘图中,我们可以通过指令让一只带着钢笔的虚拟的海龟在屏幕上移动,它爬行的轨迹成为了所绘制的图形。

绘图坐标系统

在使用 Turtle 模块之前,我们先简单地认识海龟绘图的坐标系统。Turtle 模块的坐标系统和平时在数学课程上的笛卡尔坐标系一样,绘图界面的中心点为坐标原点(0,0),越往右边 x 坐标坐标越大,越往上边 y 坐标越大,如图:

使用海龟绘制图形

在 Visual Studio Code 上新建一个 名为 my_turtle.py 的Python 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import turtle
# 创建Screen对象
screen = turtle.Screen()
# 使用Screen 的 setup() 方法设置绘图屏幕的大小,宽度为500像素,高为400像素
screen.setup(500, 400)

# 创建turtle(海龟)对象
myTurtle = turtle.Turtle()
# 向前移动海龟150个像素
myTurtle.forward(150)
# 向左移动海龟90个像素
myTurtle.left(90)
# 向前移动海龟75个像素
myTurtle.forward(75)

# 退出
screen.exitonclick()

  • import turtle : 导入 turtle 模块,本示例中使用 turtle 模块的 Screen() 以及 Turtle() 两个方法;

  • screen = turtle.Screen(): 利用 turtle 模块的 Screen() 方法产生一个Screen对象,并赋值给 screen 变量;

  • screen.setup(500, 400) :利用Screen 对象的 setup() 方法设定绘图屏幕宽为500像素,长为400 像素;

  • myTurtle = turtle.Turtle(): 利用 turtle 模块的 Turtle() 方法产生一个 Turtle 对象并赋值给变量 myTurtle

  • myTurtle.forward(150) : 调用forward() 方法向前(沿着x坐标向右)移动150个像素距离;

  • myTurtle.left(90): 调用 left() 方法让海龟左转90 度

    注意:turtle.Turtle() 乌龟 对象创建时,预设方向为面向东方,也就是 沿x坐标右边;

  • myTurtle.forward(75): 调用forward() 方法向前移动75个像素距离(沿y坐标向上);

  • screen.exitonclick() : 用户点击程序窗口时,程序退出。

运行 my_turtle.py:Visual Studio Code -> 右键-> Run Python File Terminal

运行效果:

运行代码后,生成了一个 500*400 的程序窗口,小海龟出现在窗口中心,它的预设方向为沿x坐标右边。

小海龟首先向前(沿着x坐标向右)移动150个像素距离,然后左转90 度,最后向前(沿着x坐标向右)移动75个像素距离。

用户点击程序窗口,程序退出。

现在已经使用 turtle 模块作了简单的图形绘制,下面将正式开发 Pong 游戏。

Pong 游戏窗口配置

在 Visual Studio Code 上新建一个 名为 pong.py 的Python 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#  引入turtle模块
import turtle
# 创建一个 Screen 对象,并赋给变量 wn
wn = turtle.Screen()
# 设置程序窗口的标题为 Pong by @MatrixTech
wn.title("Pong by @MatrixTech")
# 设置程序窗体的背景颜色为黑色
wn.bgcolor("black")
# 设置绘图屏幕宽为 800 像素大小,高为 600 像素大小
wn.setup(width=800, height=600)
# tracer方法调用禁止动画显示
wn.tracer(0)

# 主循环
while True:
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()

这里特别注意的是,wn.tracer(0) 使用tracer方法禁止动画显示,然后通过wn.update() 对屏幕进行刷新,让绘制的图形一次性显示在窗口,无需漫长地等待绘制过程。

运行 pong.py:Visual Studio Code -> 右键-> Run Python File Terminal

运行成功后,会出现游戏应用的窗体。

添加球拍和乒乓球

添加球拍 A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#  引入turtle模块
import turtle
# 创建一个 Screen 对象,并赋给变量 wn
wn = turtle.Screen()
# 设置程序窗口的标题为 Pong by @MatrixTech
wn.title("Pong by @MatrixTech")
# 设置程序窗体的背景颜色为黑色
wn.bgcolor("black")
# 设置游戏屏幕宽为 800 像素大小,高为 600 像素大小
wn.setup(width=800, height=600)
# tracer方法调用禁止动画显示
wn.tracer(0)

# 球拍 A (左边球拍)
# 通过 turtle 模块创建一个 Turtle 对象,并赋给变量 paddle_a
paddle_a = turtle.Turtle()
# 设置速度为0
paddle_a.speed(0)
# 设置形状为矩形
paddle_a.shape("square")
# 设置颜色为白色
paddle_a.color("white")
# 画笔抬起,移动时不画线
paddle_a.penup()
# 前往 x 坐标为 -350, y 坐标为 0 的位置
paddle_a.goto(-350,0)

# 球拍 B

# 乒乓球


# 游戏主循环
while True:
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()

运行程序,查看效果:

程序运行后,球拍A是一个长和高都是20像素的方块,因此我们需要调整它形状大小。

使用 turtle.shapesize 方法可以调整球拍的形状大小。

turtle.shapesize(stretch_wid=None, stretch_len=None, outline=None)

  • stretch_wid : 接受正数值,垂直方向拉伸;

  • stretch_len : 接受正数值,水平方向拉伸;

  • outline :接受正数值,形状轮廓描边的宽度;

返回或设置画笔的属性 x/y-拉伸因子和/或轮廓。海龟基于拉伸因子调整外观: stretch_wid 为垂直于其朝向的宽度拉伸因子,stretch_len 为水平于其朝向的长度拉伸因子,决定形状轮廓线的粗细。

turtle.shapesize 方法的详细介绍可参阅手册 :

https://docs.python.org/zh-cn/3/library/turtle.html#turtle.shapesize

球拍A 方块原来是20*20 像素,现在垂直方向拉伸5,水平拉伸1,最终长水平方向为20像素,垂直方向为100像素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
......
......
......

# 球拍 A (左边球拍)
# 通过 turtle 模块创建一个 Turtle 对象,并赋给变量 paddle_a
paddle_a = turtle.Turtle()
# 设置速度为0
paddle_a.speed(0)
# 设置形状为矩形
paddle_a.shape("square")
# 设置颜色为白色
paddle_a.color("white")
# 球拍A 在垂直方向拉伸5(100 像素),水平方向拉伸1(20 像素)
paddle_a.shapesize(stretch_wid=5,stretch_len=1)
# 画笔抬起 -- 移动时不画线
paddle_a.penup()
# 前往 x 坐标为 -350, y 坐标为 0 的位置
paddle_a.goto(-350,0)
......
......
......

运行,查看效果:

添加球拍 B

球拍B的代码与球拍A的代码相似,因此复制球拍A的代码块,做些许修改即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#  引入turtle模块
import turtle
# 创建一个 Screen 对象,并赋给变量 wn
wn = turtle.Screen()
# 设置程序窗口的标题为 Pong by @MatrixTech
wn.title("Pong by @MatrixTech")
# 设置程序窗体的背景颜色为黑色
wn.bgcolor("black")
# 设置程序屏幕宽为 800 像素,高为 600 像素
wn.setup(width=800, height=600)
# tracer方法调用禁止动画显示
wn.tracer(0)

# 球拍 A (左边球拍)
# 通过 turtle 模块创建一个 Turtle 对象,并赋给变量 paddle_a
paddle_a = turtle.Turtle()
# 设置速度为0
paddle_a.speed(0)
# 设置形状为矩形
paddle_a.shape("square")
# 设置颜色为白色
paddle_a.color("white")
paddle_a.shapesize(stretch_wid=5,stretch_len=1)
# 画笔抬起 -- 移动时不画线
paddle_a.penup()
# 前往 x 坐标为 -350, y 坐标为 0 的位置
paddle_a.goto(-350,0)

# 球拍 B (右边球拍)
# 通过 turtle 模块创建一个 Turtle 对象,并赋给变量 paddle_b
paddle_b = turtle.Turtle()
# 设置速度为0
paddle_b.speed(0)
# 设置形状为矩形
paddle_b.shape("square")
# 设置颜色为白色
paddle_b.color("white")
paddle_b.shapesize(stretch_wid=5,stretch_len=1)
# 画笔抬起 -- 移动时不画线
paddle_b.penup()
# 前往 x 坐标为 350, y 坐标为 0 的位置
paddle_b.goto(350,0)

# 乒乓球


# 游戏主循环
while True:
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()

运行程序,查看效果:

添加乒乓球

在原点添加一个方块作乒乓求,添加的方法和上面的球拍添加一样, 代码如下:

注意:由于篇幅所限,不一定显示完整代码,”……” 代表省略的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
......
......
......

# 球拍 B (右边球拍)
# 通过 turtle 模块创建一个 Turtle 对象,并赋给变量 paddle_b
paddle_b = turtle.Turtle()
# 设置速度为0
paddle_b.speed(0)
# 设置形状为矩形
paddle_b.shape("square")
# 设置颜色为白色
paddle_b.color("white")
paddle_b.shapesize(stretch_wid=5,stretch_len=1)
# 画笔抬起 -- 移动时不画线
paddle_b.penup()
# 前往 x 坐标为 350, y 坐标为 0 的位置
paddle_b.goto(350,0)

# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.penup()
ball.goto(0,0)

# 游戏主循环
while True:
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()

运行代码,查看效果:

实现球拍的移动

步骤

  • 创建控制球拍垂直方向上下移动的函数;
  • 把移动函数与键盘按键事件绑定,程序监听键盘按键事件;
  • 用户按下被绑定的按键,触发移动的函数;

移动球拍 A

球拍A 向上移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
......
......
......

# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.penup()
ball.goto(0,0)

# 向上移动球拍 A
def paddle_a_up():
# 获得 paddle_a 的y坐标数值,并赋给变量 y
y = paddle_a.ycor()
# 在原来的数值基础上,增加20个像素
y += 20
# 重新设置 paddle_a 的y坐标,使其向上移动20个像素的距离
paddle_a.sety(y)

# 键盘事件绑定
# 事件监听
wn.listen()
# 键盘 w 键的按下事件与方法 paddle_a_up 绑定
wn.onkeypress(paddle_a_up,'w')

运行程序,查看效果:

球拍A 向下移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
......
......
......

# 函数
# 向上移动球拍 A
def paddle_a_up():
# 获得 paddle_a 的 y 坐标数值,并赋给变量 y
y = paddle_a.ycor()
# 在原来的数值基础上,增加20个像素
y += 20
# 重新设置 paddle_a 的y坐标,使其向上移动20个像素的距离
paddle_a.sety(y)

# 向下移动球拍 A
def paddle_a_down():
# 获得 paddle_a 的y坐标数值,并赋给变量 y
y = paddle_a.ycor()
# 在原来的数值基础上,减少20个像素
y -= 20
# 重新设置 paddle_a 的y坐标,使其向下移动20个像素的距离
paddle_a.sety(y)

# 键盘事件绑定
# 事件监听
wn.listen()
# 键盘 w 键的按下事件与方法 paddle_a_up 绑定
wn.onkeypress(paddle_a_up,'w')
# 键盘 s 键的按下事件与方法 paddle_a_down 绑定
wn.onkeypress(paddle_a_down,'s')

运行程序,查看效果:

移动球拍 B

球拍B的移动实现与球拍A的一样,可以参考球拍A移动的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 函数
......
......
......

# 向上移动球拍 B
def paddle_b_up():
# 获得 paddle_b 的y坐标数值,并赋给变量 y
y = paddle_b.ycor()
# 在原来的数值基础上,增加20个像素
y += 20
# 重新设置 paddle_b 的y坐标,使其向上移动20个像素的距离
paddle_b.sety(y)

# 向下移动球拍 B
def paddle_b_down():
# 获得 paddle_b 的y坐标数值,并赋给变量 y
y = paddle_b.ycor()
# 在原来的数值基础上,减少20个像素
y -= 20
# 重新设置 paddle_b 的y坐标,使其向下移动20个像素的距离
paddle_b.sety(y)

# 键盘事件绑定
# 事件监听
wn.listen()
# 键盘 w 键的按下事件与方法 paddle_a_up 绑定
wn.onkeypress(paddle_a_up,'w')
# 键盘 s 键的按下事件与方法 paddle_a_down 绑定
wn.onkeypress(paddle_a_down,'s')
# 键盘 ↑ 键的按下事件与方法 paddle_b_up 绑定
wn.onkeypress(paddle_b_up,'Up')
# 键盘 ↑ 键的按下事件与方法 paddle_b_down 绑定
wn.onkeypress(paddle_b_down,'Down')

......
......
......

运行程序,查看效果:

实现乒乓球的移动

程序运行后,在游戏的主循环里不断地按一个坐标增量去更新原先的乒乓球坐标,从而实现乒乓球的移动。

乒乓球的移动

为乒乓球添加坐标的增量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......
......
......

# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.penup()
ball.goto(0,0)
# 乒乓球的 x 坐标增量
ball.dx = 2
# 乒乓球的 y 坐标增量
ball.dy = 2
......
......
......

在游戏主循环中更新乒乓球坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
......
......
......

# 游戏主循环
while True:
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()
# 移动乒乓球
ball.setx(ball.xcor() + ball.dx)
ball.sety(ball.ycor() + ball.dy)

......
......
......

如果在添加上述代码后运行程序,就会看到乒乓球向右上角移动然后消失。

我们可以为乒乓球添加边界检测解决其消失的问题,实现当乒乓球碰撞到窗口边界后反弹。

我们先把乒乓球对象的 ball.penup() 注释,让“钢笔”放下,这样海龟移动时候就出现轨迹,方便我们看看它碰撞边界后反弹的轨迹。

上边界检测

注释 ball.penup() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
......
......
......

# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
# ball.penup()
ball.goto(0,0)
# 乒乓球的 x 坐标增量
ball.dx = 2
# 乒乓球的 y 坐标增量
ball.dy = 2

......
......
......

在游戏主循环添加上边界检测代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
......
......
......
......
......

# 游戏主循环
while True:
print("x: ",ball.xcor()," y: ",ball.ycor() )
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()
# 移动乒乓球
ball.setx(ball.xcor() + ball.dx)
ball.sety(ball.ycor() + ball.dy)

# 上边界检测
if ball.ycor() > 290:# 当乒乓球的 y 坐标大于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1

运行程序后,看到效果:

程序启动后,乒乓球从坐标原点(0,0)以增量 (dx=2,dy=2) 往右上角移动。当乒乓球的 y 坐标大于 290 时,乒乓球被被反弹,以增量(dx=2,dy=-2)移动。

下边界检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
......
......
......
......
......

# 游戏主循环
while True:
print("x: ",ball.xcor()," y: ",ball.ycor() )
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()
# 移动乒乓球
ball.setx(ball.xcor() + ball.dx)
ball.sety(ball.ycor() + ball.dy)

# 上边界检测
if ball.ycor() > 290:# 当乒乓球的 y 坐标大于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1
# 下边界检测
if ball.ycor() < -290:# 当乒乓球的 y 坐标小于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(-290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1

为了查看效果,在运行程序之前,还需要修改乒乓球的坐标增量如下:

1
2
3
4
5
6
7
8
9
10
11
# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
# ball.penup()
ball.goto(0,0)
# 乒乓球的 x 坐标增量
ball.dx = 2
# 乒乓球的 y 坐标增量
ball.dy = -2

ball.dy = 2 改成 ball.dy = -2 后,乒乓球在程序运行后会向右下方移动。

运行程序,查看结果:

至此,已经完成了上下边界的检测,可以把 ball.penup() 的注释取消:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
......
......
......

# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.penup()
ball.goto(0,0)
# 乒乓球的 x 坐标增量
ball.dx = 2
# 乒乓球的 y 坐标增量
ball.dy = 2

......
......
......

左右边界检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
......
......
......

# 游戏主循环
while True:
......
......
......

# 下边界检测
if ball.ycor() < -290:# 当乒乓球的 y 坐标小于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(-290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1

# 右边界检测
if ball.xcor() > 390: #当乒乓球的 y 坐标大于 390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1

# 左边界检测
if ball.xcor() < -390: #当乒乓球的 y 坐标小于 -390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1

球拍 A 的 y 坐标为 -350,球拍 B 的 y 坐标为 350。

当乒乓球的 y 坐标大于350 时候,说明球拍 B 没有接住乒乓球,球拍 B 失一分,球拍 A 得一分;

反之,当乒乓球的 y 坐标小于 - 350 时候,说明球拍 A 没有接住乒乓球,球拍 A 失一分,球拍 B 得一分;

运行程序,查看结果:

实现球拍和球的碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
......
......
......

# 游戏主循环
while True:
......
......
......

# 右边界检测
if ball.xcor() < -390: #当乒乓球的 y 坐标小于 -390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1

# 乒乓球与球拍 B 的碰撞检测
if (ball.xcor() > 340 and ball.xcor() < 350) and (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() - 50 ):
ball.setx(340)
ball.dx *= -1

# 乒乓球与球拍 A 的碰撞检测
if (ball.xcor() < -340 and ball.xcor() > -350) and (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() - 50 ):
ball.setx(-340)
ball.dx *= -1

运行程序,查看效果:

分数计算

创建 Pen 绘制得分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
......
......
......

# 乒乓球
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.penup()
ball.goto(0,0)
# 乒乓球的 x 坐标增量
ball.dx = 2
# 乒乓球的 y 坐标增量
ball.dy = -2

# Pen
pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
# 隐藏海龟,加快绘制速度
pen.hideturtle()
pen.goto(0,260)
pen.write("玩家 A: 0 玩家 B: 0", align="center", font=("Courier",24,"normal"))

......
......
......

运行程序,查看效果:

计算得分

分别为玩家 A 和玩家 B 添加分数变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
......
......
......

# Pen
pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
# 隐藏海龟,加快绘制速度
pen.hideturtle()
pen.goto(0,260)
pen.write("玩家 A: 0 玩家 B: 0", align="center", font=("Courier",24,"normal"))

# 得分
score_a = 0
score_b = 0

......
......
......

添加计分逻辑代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
......
......
......

# 游戏主循环
while True:
......
......
......

# 左边界检测
if ball.xcor() > 390: #当乒乓球的 y 坐标大于 390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1
# score_a 得 1 分
score_a += 1
# 清除画笔 pen 的绘图
pen.clear()
# 重新绘制得分结果
pen.write("玩家 A: {} 玩家 B: {}".format(score_a,score_b), align="center", font=("Courier",24,"normal"))

# 右边界检测
if ball.xcor() < -390: #当乒乓球的 y 坐标小于 -390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1
# 玩家 B 得 1 分
score_b += 1
# 清除画笔 pen 的绘图
pen.clear()
# 重新绘制得分结果
pen.write("玩家 A: {} 玩家 B: {}".format(score_a,score_b), align="center", font=("Courier",24,"normal"))

......
......
......

运行程序,查看效果:

添加音效

MacOS 系统

如果在 MacOS 上开发 Pong, 可以引入 os 模块使用 sytem 方法调用 afplay 命令播放音效文件。

在顶部引入 os 模块

1
2
3
4
#  引入turtle模块
import turtle
# 引入 os 模块
import os

在碰撞检测的地方添加音效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 游戏主循环
while True:
# print("x: ",ball.xcor()," y: ",ball.ycor() )
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()
# 移动乒乓球
ball.setx(ball.xcor() + ball.dx)
ball.sety(ball.ycor() + ball.dy)

# 上边界检测
if ball.ycor() > 290:# 当乒乓球的 y 坐标大于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1
os.system("afplay bounce.wav&")


# 下边界检测
if ball.ycor() < -290:# 当乒乓球的 y 坐标小于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(-290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1
os.system("afplay bounce.wav&")

# 左边界检测
if ball.xcor() > 390: #当乒乓球的 y 坐标大于 390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1
# score_a 得 1 分
score_a += 1
# 清除画笔的绘图
pen.clear()
# 重新生成新的绘图
pen.write("玩家 A: {} 玩家 B: {}".format(score_a,score_b), align="center", font=("Courier",24,"normal"))

# 右边界检测
if ball.xcor() < -390: #当乒乓球的 y 坐标小于 -390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1
# 玩家 B 得 1 分
score_b += 1
# 清除画笔的绘图
pen.clear()
# 重新生成新的绘图
pen.write("玩家 A: {} 玩家 B: {}".format(score_a,score_b), align="center", font=("Courier",24,"normal"))


# 乒乓球与球拍 B 的碰撞检测
if (ball.xcor() > 340 and ball.xcor() < 350) and (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() - 50 ):
ball.setx(340)
ball.dx *= -1
os.system("afplay bounce.wav&")

# 乒乓球与球拍 A 的碰撞检测
if (ball.xcor() < -340 and ball.xcor() > -350) and (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() - 50 ):
ball.setx(-340)
ball.dx *= -1
os.system("afplay bounce.wav&")

Windows 系统

如果在 Windows 系统下开发 Pong ,可以使用 winsound 模块播放 音效文件。

在源码文件顶部引入 winsound 模块

1
2
3
4
5
6
7
8
#  引入turtle模块
import turtle
# Windows 系统发声模块
#import winsound

......
......
......

在碰撞检测的地方添加音效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
......
......
......
# 游戏主循环
while True:
# print("x: ",ball.xcor()," y: ",ball.ycor() )
# 执行 TurtleScreen 刷新。在每帧绘制结束后调用update方法进行屏幕刷新,让绘制的图形一次性显示在窗口里。
wn.update()
# 移动乒乓球
ball.setx(ball.xcor() + ball.dx)
ball.sety(ball.ycor() + ball.dy)

# 上边界检测
if ball.ycor() > 290:# 当乒乓球的 y 坐标大于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1
# Windows 系统下播放 bounce.wav 音频文件
#winsound.PlaySound("bounce.wav", winsound.SND_ASYNC)


# 下边界检测
if ball.ycor() < -290:# 当乒乓球的 y 坐标小于 290 像素时候
# 设置乒乓球的 y 坐标设置为290
ball.sety(-290)
# 把乒乓球 y 坐标的增量变成 -2
ball.dy *= -1
# Windows 系统下播放 bounce.wav 音频文件
#winsound.PlaySound("bounce.wav", winsound.SND_ASYNC)

# 左边界检测
if ball.xcor() > 390: #当乒乓球的 y 坐标大于 390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1
# score_a 得 1 分
score_a += 1
# 清除画笔的绘图
pen.clear()
# 重新生成新的绘图
pen.write("玩家 A: {} 玩家 B: {}".format(score_a,score_b), align="center", font=("Courier",24,"normal"))

# 右边界检测
if ball.xcor() < -390: #当乒乓球的 y 坐标小于 -390 像素时候
# 乒乓球恢复到原点坐标
ball.goto(0,0)
# 改变开球方向
ball.dx *= -1
# 玩家 B 得 1 分
score_b += 1
# 清除画笔的绘图
pen.clear()
# 重新生成新的绘图
pen.write("玩家 A: {} 玩家 B: {}".format(score_a,score_b), align="center", font=("Courier",24,"normal"))


# 乒乓球与球拍 B 的碰撞检测
if (ball.xcor() > 340 and ball.xcor() < 350) and (ball.ycor() < paddle_b.ycor() + 50 and ball.ycor() > paddle_b.ycor() - 50 ):
ball.setx(340)
ball.dx *= -1
# Windows 系统下播放 bounce.wav 音频文件
#winsound.PlaySound("bounce.wav", winsound.SND_ASYNC)

# 乒乓球与球拍 A 的碰撞检测
if (ball.xcor() < -340 and ball.xcor() > -350) and (ball.ycor() < paddle_a.ycor() + 50 and ball.ycor() > paddle_a.ycor() - 50 ):
ball.setx(-340)
ball.dx *= -1
# Windows 系统下播放 bounce.wav 音频文件
#winsound.PlaySound("bounce.wav", winsound.SND_ASYNC)

源代码

源代码地址 : https://github.com/matrixtechxyz/Pong

参考资源

單片機項目開發紀錄 如何在一台机器上管理多个 Github 账号

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×