楼主: CDA网校
100 0

用Python从零编写Pong游戏 [推广有奖]

管理员

已卖:189份资源

泰斗

5%

还不是VIP/贵宾

-

威望
3
论坛币
128372 个
通用积分
12969.6046
学术水平
278 点
热心指数
286 点
信用等级
253 点
经验
232127 点
帖子
7150
精华
19
在线时间
4419 小时
注册时间
2019-9-13
最后登录
2026-3-11

初级热心勋章

楼主
CDA网校 学生认证  发表于 8 小时前 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币
图片来源:Feelfarbig Magazine(Unsplash)
图片来源:Feelfarbig Magazine(Unsplash)

Pong游戏是数字娱乐史上最早、最具标志性的游戏之一。在其经典版本中,游戏模拟了一场乒乓球比赛,两个球拍沿屏幕垂直移动,击打一个弹跳的球。每位玩家控制一个球拍,需将球击回给对方,否则将给对方加一分。

这款游戏的历史颇具趣味性。Pong游戏由艾伦·奥尔康(Allan Alcom)在被雅达利(Atari)公司聘用时,作为测试项目设计编写。随后这款游戏大获成功,在全球的酒吧和酒馆售出了大量游戏机——有趣的是,这些游戏机常常因人们投入的大量硬币而堵塞,最终酒吧老板们不得不联系雅达利公司来维修机器!

在本教程中,我们将使用Python的面向对象编程(OOP)方法来编写Pong游戏。这是一个中级水平的Python编程教程,要求学习者具备Python基础知识的初步认知,包括列表、……

理解项目

编写这款游戏有多种方法。我们可以使用直接的方法,逐步执行每个任务并进行必要的重复操作;也可以使用Python的面向对象编程方法,避免重复代码,使代码整洁有序。我们将选择第二种方法,因为这会让游戏程序更系统、更简洁!

我们将使用Python的Turtle模块进行可视化游戏开发。Turtle模块是一个内置功能,能让我们以简单的方式可视化代码。它主要包含一只“海龟”,会根据编码者的指令在屏幕上移动,绘制图形和线条。它是创建入门级游戏的强大工具,能通过可视化屏幕提供即时反馈。

以下是我们将按顺序完成的关键任务:

  • 创建游戏屏幕——这是显示Pong游戏的屏幕。

  • 创建球拍及球拍类(Paddle Class)——这段代码将在屏幕上创建一个球拍,并配置其移动方式;我们将把这段代码转换为一个类,作为创建两个球拍(一个在左侧,一个在右侧)的蓝图。

  • 创建球类(Ball Class)和球对象——继续使用面向对象编程方法,我们将创建一个通用的球类,然后创建在屏幕上移动的球,并定义其相关方法。

  • 检测球与上下墙壁的碰撞——这段代码将检测球与上下墙壁的碰撞,若发生碰撞,使球沿y轴反弹。

  • 检测球与球拍的碰撞——这段代码将检测球是否与球拍碰撞。若碰撞,则使球反弹;若球拍未击中球,则对方玩家得分,球回到屏幕中央重新开始游戏。

  • 创建计分板类(Scoreboard Class)和计分板对象——这段代码包括在单独的Python文件中创建计分板类,并在主游戏文件中创建其对象。

创建游戏屏幕

第一项任务是创建游戏屏幕。该屏幕将为矩形,与真实游戏中的屏幕一致。我们首先在代码中导入turtle模块,使用其Screen类创建屏幕对象,并通过Screen类的setup()方法将其自定义为宽800像素、高600像素。我们将使用bgcolor()方法将背景颜色设置为黑色,通过title()方法将屏幕命名为“Pong Game”。以下是创建屏幕对象的代码:

from turtle import Turtle, Screen

# 设置游戏屏幕
screen = Screen()
screen.setup(width=800, height=600)
screen.bgcolor("black")
screen.title("Pong Game")

screen.exiton click()

注意,我们在最后一行使用了屏幕的exiton click()方法,以确保屏幕在我们点击之前保持显示状态。

如果对上述方法有任何疑问,可随时查看Turtle模块的官方文档,链接在此。

运行程序后,输出如下:

游戏屏幕(图片由作者提供)
游戏屏幕(图片由作者提供)

创建球拍及球拍类

下一项任务是创建球拍,即游戏屏幕两侧的矩形对象。我们将使用turtle模块的shape()函数创建这个球拍,通过color()方法将其自定义为白色,并使用shapesize()方法将其自定义为宽20像素、高100像素。注意,我们向shapesize()方法传递了5和1作为参数——这是因为shapesize()的单位不是像素,而是以20像素为基准。因此,要获得100像素的长度,我们需要传递5(因为20像素×5 = 100像素)。此外,我们将其定位在游戏开始时的右侧中间位置,即y坐标为0,x坐标为350(记住我们的屏幕宽800像素)。我们将使用penup()方法清除海龟的移动轨迹,并通过goto()方法将其移动到目标位置。

# 创建球拍
paddle = Turtle()
paddle.shape("square")
paddle.color("white")
paddle.shapesize(5,1)
paddle.penup()
paddle.goto(350,0)

上述代码的输出如下。我们可以看到游戏屏幕右侧创建了一个球拍,且没有海龟的移动轨迹。

球拍创建效果(图片由作者提供)
球拍创建效果(图片由作者提供)

运行上述代码将创建球拍,但我们会发现球拍是先被创建,然后才移动到目标位置。为了关闭动画效果,我们将在代码中添加屏幕类的tracer()方法,这也需要我们手动更新屏幕:

# 保留原有代码
screen.tracer(0)

screen.update()
screen.exiton click()

调用tracer()方法并传递0作为参数,将关闭动画效果。

创建球拍并通过关闭动画更新屏幕后,下一步是配置球拍的移动。为此,我们将使用屏幕监听器(screen listeners)。屏幕类的listen()方法允许我们监听键盘事件,onkey()方法允许我们在按下特定按键时调用已定义的函数。因此,我们将定义go_up和go_down两个函数,使球拍沿y轴上下移动。

def go_up():
    new_y = paddle.ycor() + 40
    paddle.goto(paddle.xcor(), new_y)

def go_down():
    new_y = paddle.ycor() - 40
    paddle.goto(paddle.xcor(), new_y)

如你所见,我们定义了球拍的上下移动函数,使其从原始位置垂直移动40像素。接下来,我们将使用屏幕监听器功能,让这些函数在按下键盘按键时被调用。

screen.listen()
screen.onkey(paddle.go_up, "Up")
screen.onkey(paddle.go_down, "Down")
球拍移动效果(图片由作者提供)
球拍移动效果(图片由作者提供)

现在我们已经创建了球拍并配置了其移动机制,接下来将代码转换为面向对象编程方法。这是因为我们的游戏需要两个球拍,而拥有一个能快速创建球拍的通用蓝图将使我们的任务更轻松。我们将重构代码,以便轻松创建另一个球拍。我们会将所有与球拍相关的代码移到另一个文件中,并在该文件中创建球拍类。

由于我们创建的球拍本质上是海龟对象,因此我们将使这个球拍类继承自Turtle类。因此,我们将在PyCharm IDE中创建一个新的Python文件,并在这个单独的Python文件中再次导入turtle模块的Turtle类。接下来,我们将使用类创建语法和def init()方法定义Paddle类。由于左右两个球拍在游戏屏幕上的位置不同,我们将x坐标和y坐标作为类的属性。

现在我们将使用面向对象编程中的继承概念,将Turtle类作为父类,球拍类将继承其属性和方法。接下来,我们只需将之前创建球拍时代码中的“paddle”替换为“self”关键字即可。

from turtle import Turtle, Screen

class Paddle(Turtle):
    def __init__(self,x,y):
        # 创建球拍对象
        super().__init__()
        self.shape("square")
        self.color("white")
        self.shapesize(51)
        self.penup()
        self.x = x
        self.y = y
        self.goto(x,y)

    # 配置球拍移动
    def go_up(self):
        new_y = self.ycor() + 40
        self.goto(self.xcor(), new_y)

    def go_down(self):
        new_y = self.ycor() - 40
        self.goto(self.xcor(),new_y)

如上述代码所示,我们还定义了Paddle类的两个方法——一个是向上移动,另一个是向下移动,这两个方法我们之前已经定义过。定义好Paddle类后,我们将创建球拍对象,并配置两个球拍的上下移动:

from paddle import Paddle

# 创建球拍对象
left_paddle = Paddle(-3500)
right_paddle = Paddle(3500)

# 配置球拍移动
screen.listen()
screen.onkey(right_paddle.go_up, "Up")
screen.onkey(right_paddle.go_down, "Down")
screen.onkey(left_paddle.go_up, "w")
screen.onkey(left_paddle.go_down, "s")

运行游戏

为了运行游戏并使用Screen的update()方法更新游戏,我们将定义一个while循环,该循环将持续运行,直到被外部停止或循环条件变为False。

# 游戏开始
game_is_on = True
while game_is_on:
    screen.update()

现在,当你运行主文件时,将看到游戏屏幕和球拍被创建,并且球拍能够移动。

创建球类及球对象

继续使用面向对象编程方法编写这款游戏,我们将创建Ball类作为通用蓝图,并在主Python文件中从中创建球对象。我们将球创建为海龟对象,使Ball类继承自父类Turtle。我们将使用Turtle类的color()和shape()方法,初始化一个白色的圆形球。和之前一样,我们将使用turtle的penup()方法隐藏海龟的移动轨迹。

from turtle import Turtle

class Ball(Turtle):

    def __init__(self):
        super().__init__()
        self.color("white")
        self.shape("circle")
        self.penup()

定义好球的属性后,我们还将创建球在游戏开始时就移动的方法。游戏开始时,球将位于游戏屏幕的中心,当屏幕刷新时,球将首先向右移动。我们将在主while循环中调用这个方法,这样球在游戏运行期间(即游戏处于开启状态时)将持续移动——也就是说,球的x坐标和y坐标将在每次游戏屏幕刷新时发生变化。

使球移动的方法是将其x坐标和y坐标各改变一定数值,暂时设为10。我们将定义球的move()方法,并编写上述逻辑:

class Ball(Turtle):

    # 保留之前的代码
    def move(self):
        new_x = self.xcor() + 10
        new_y = self.ycor() + 10
        self.goto(new_x, new_y)

我们将在游戏的while循环中调用球对象的这个方法:

# 游戏开始
game_is_on = True
while game_is_on:
    screen.update()
    ball.move()

运行代码后,我们会发现球很快消失,只剩下两个球拍。

代码运行效果(图片由作者提供)
代码运行效果(图片由作者提供)

我们可以通过注释掉screen.tracer()相关代码并重新运行,恢复动画效果。此时我们将看到两个球拍和球被创建并移动。

带动画的代码效果(图片由作者提供)
带动画的代码效果(图片由作者提供)

另一种可视化方法是使用time模块,在游戏的主while循环中添加延迟(无需注释掉tracer()函数),具体操作如下:

import time

# 保留原有代码
# 游戏开始
game_is_on = True
while game_is_on:
    time.sleep(0.1)
    screen.update()
    ball.move()

现在你可以看到球以较慢的速度移动,我们可以用球拍接住它。

检测球与上下墙壁的碰撞

现在我们的球已经创建并开始移动,我们需要设计一个机制,使球撞到上下墙壁时反弹;而对于左右墙壁,球应该被左右球拍接住。如果球未被接住,则意味着对方玩家得分。

因此,假设球从屏幕中心向右上角移动并到达角落,它需要反弹。简单来说,反弹就是沿y轴改变方向,因为球在x轴上仍然会向前移动。现在我们将定义Ball类的一个新方法bounce(),并在球到达边界时在主游戏循环中调用它:

from turtle import Turtle

class Ball(Turtle):

    def __init__(self):
        super().__init__()
        self.color("white")
        self.shape("circle")
        self.penup()
        self.x_move = 10
        self.y_move = 10

    def move(self):
        new_x = self.xcor() + self.x_move
        new_y = self.ycor() + self.y_move
        self.goto(new_x, new_y)

    def bounce(self):
        self.y_move *= -1

注意,在上述代码中,我们定义了Ball类的两个新属性x_move和y_move,并将它们设为10。然后,在move()方法中,我们用这些属性替换了之前的数值10。显然,这对我们的bounce()方法很有帮助。现在,每当球反弹时,它将向之前y方向的反方向移动。这意味着,如果球向上移动并撞到墙壁,y_move将从+10变为-10,球将向下移动(因为负数表示球向下移动);因此,撞到下墙壁时,y_move将从-10变为+10,球将向上移动。

现在,我们将这个条件添加到主while循环中:

while game_is_on:
    # 保留原有代码

    # 检测球与上下墙壁的碰撞
    if ball.ycor() > 275 or ball.ycor() < -275:
        ball.bounce_y()

在上述代码中,我们添加了检测球与墙壁碰撞的条件,然后调用bounce()方法。你可以为边界设置任何值,但经过多次尝试,275这个值是比较合适的!

球与上墙壁碰撞效果(图片由作者提供)
球与上墙壁碰撞效果(图片由作者提供)

检测球与球拍的碰撞

现在我们知道如何让球从上下墙壁反弹,下一步是检测球与球拍的碰撞,并让球从球拍反弹。我们将采用与之前类似的方法,只是这次针对的是x轴。

检测球与墙壁碰撞的常规方法是使用distance方法。如果两者之间的距离小于某个数值,我们可以判定它们已经接触/碰撞。但要注意,distance()函数是通过计算两个海龟对象中心之间的距离来工作的。在我们的案例中,一个是20×20像素的球,另一个是20×200像素的矩形球拍。它们之间的距离会沿球拍的长度变化。如果球撞到球拍的边缘,distance()方法可能无法判定两者已经接触。

我们可以添加另一个条件:检查球是否已经超过x轴上的某个点(以右侧球拍为例),并且与球拍的距离在50像素以内,那么就可以判定球已经接触到球拍。我们将这个条件添加到主while循环中。一旦检测到碰撞,我们将让球反弹,但这次是沿x轴方向。我们将重新定义反弹函数,这样就有两个反弹函数:一个用于与球拍碰撞时沿x轴反弹,另一个用于与墙壁碰撞时沿y轴反弹:

def bounce_y(self):
    self.y_move *= -1

def bounce_x(self):
    self.x_move *= -1
while game_is_on:
    ...

    # 检测球与右侧球拍的碰撞
    if ball.distance(right_paddle) < 50 and ball.xcor() > 320:
        ball.bounce_x()

    # 检测球与左侧球拍的碰撞
    elif ball.distance(left_paddle) < 50 and ball.xcor() < -320:
        ball.bounce_x()

注意,经过多次尝试和球与球拍碰撞的可视化测试,我们设置了320这个数值。

球与球拍碰撞效果(图片由作者提供)
球与球拍碰撞效果(图片由作者提供)

如果其中一个球拍未击中球,那么对方玩家将获得一分,游戏将重新开始,球回到屏幕中心。为了检测球拍是否未击中球,我们可以通过观察球是否超出水平轴上的某个点来判断。我们知道屏幕宽度为800像素,球拍位于x轴的350像素处,因此球拍的实际范围是从340像素到360像素(因为球拍宽20像素),所以如果球超出x轴的360像素,就意味着球拍未击中球。这意味着我们需要将球重置到起始位置(0,0)。我们将定义Ball类的reset_position()方法,在满足上述条件时调用该方法。此外,我们还将添加一个功能:让球反向移动,即从向右移动改为向左移动。

class Ball(Turtle):
    ...
    def reset_position(self):
        self.goto(00)
        self.bounce_x()

bounce_x()方法将使球像撞到球拍时一样反向移动。将这些条件添加到游戏的主while循环中:

while game_is_on:
    ...
    # 检测右侧球拍未击中球
    if ball.xcor() > 380:
        ball.reset_position()

    # 检测左侧球拍未击中球
    if ball.xcor() < -380:
        ball.reset_position()

运行上述代码,我们将看到当球拍未击中球时的效果:球将反向移动,朝向另一个球拍。现在,只剩下创建一个计分板来记录和显示每位玩家的得分。

创建计分板

为了显示和更新每位玩家的得分,我们将在一个新的Python文件中定义计分板类(Scoreboard Class)。我们将创建继承自Turtle类的Scoreboard类,并定义帮助海龟对象进行书写的属性。首先,我们将初始化两个属性l_score(左侧玩家得分)和r_score(右侧玩家得分),并在游戏开始时将它们设为0。我们将定义两个方法l_point和r_point,每当有玩家未击中球时调用这两个方法,为对方玩家加分。我们还将定义一个名为update_scoreboard()的方法,在玩家获得额外分数时调用该方法。该方法被调用时,将简单地更新计分板。

以下是计分板类的创建代码:

from turtle import Turtle

class Scoreboard(Turtle):
    def __init__(self):
        super().__init__()
        self.color("white")
        self.penup()
        self.hideturtle()
        self.l_score = 0
        self.r_score = 0
        self.update_scoreboard()

    def update_scoreboard(self):
        self.clear()
        self.goto(-100200)
        self.write(self.l_score, align="center", font=("Arial"40"normal"))
        self.goto(100200)
        self.write(self.r_score, align="center", font=("Arial"40"normal"))

    def l_point(self):
        self.l_score += 1
        self.update_scoreboard()

    def r_point(self):
        self.r_score += 1
        self.update_scoreboard()

update_scoreboard()方法创建一个海龟,在主屏幕上显示两位玩家的得分。注意,我们在这里使用了Turtle模块的write()函数。

接下来,我们将在主文件中导入并创建计分板对象,并使用该对象访问其方法,以满足以下两个条件:每当有玩家的球拍未击中球时,对方玩家将获得一分。

from scoreboard import Scoreboard

# 初始化计分板对象
scoreboard = Scoreboard()

while game_is_on:
    ...
   # 检测右侧球拍未击中球
    if ball.xcor() > 380:
        ball.reset_position()
        scoreboard.l_point()

    # 检测左侧球拍未击中球
    if ball.xcor() < -380:
        ball.reset_position()
        scoreboard.r_point()

至此,游戏的设计和编码就完成了。运行主Python文件,将生成游戏屏幕及其组件,游戏开始时球将自动移动。现在,你只需要找一个玩家来一起玩这款游戏!

你也可以通过修改代码来改变游戏速度(这需要你自己去探索!)。

总结

在本文中,我们借助Python的Turtle模块开发了经典的Pong游戏。我们使用面向对象编程的概念创建类、初始化属性和方法,并从这些类中在主游戏文件中创建对象。这是一个中级水平的Python项目,如果你在代码的某个部分遇到困难,请务必参考Python官方文档,或复习你的基础知识,尤其是面向对象编程(OOP)相关知识。

推荐学习书籍 《CDA一级教材》适合CDA一级考生备考,也适合业务及数据分析岗位的从业者提升自我。完整电子版已上线CDA网校,累计已有10万+在读~ !

免费加入阅读:https://edu.cda.cn/goods/show/3151?targetId=5147&preview=0

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:python Scoreboard Python基础 Magazine listener

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
扫码
拉您进交流群
GMT+8, 2026-3-11 23:51