admin管理员组

文章数量:1597494

原文:Introduction to Python for Kids

协议:CC BY-NC-SA 4.0

十九、项目:turtle贪食蛇游戏

在前几章中,我们深入研究了 Tkinter 。我们学习了如何在 Tkinter 中创建窗口小部件,设计它们的样式,让它们在事件发生时做一些事情,以及在画布上绘图。我们还着眼于创建两个大项目——一个井字棋和一个绘画应用。

本章我们回到turtle。所有这些章节我们都在研究turtle,但是我们从来没有创造出一个真实世界的应用。所以,让我们在本章中创建一个贪食蛇游戏。

贪食蛇游戏

这是一个非常简单的游戏。你有你的蛇,它在我们的游戏中被画成一个正方形。我们从它的头部开始,当你按下任何一个箭头键时,头部就会向箭头所指的方向移动。

然后,你有一个红色的,成熟的苹果,正好和你的蛇头一样大。它出现在随机的位置,引诱你的蛇头去吃它。

每当你的蛇接触到苹果时(我们假设它当时吃了苹果),你的苹果就会消失在蛇的胃里。蛇是一部分一部分长的(刚吃过,所以应该是长的吧?).另一个苹果出现在屏幕上的另一个随机位置。

蛇每吃一个苹果,记分牌就加 1。

但是如果蛇头撞上了屏幕的四面墙中的任何一面或者撞上了自己的身体(长这么大!),游戏结束!☹

够简单的游戏,不是吗?你玩过吗?我们的最终游戏看起来会像这样(图 19-1 )。

图 19-1

贪食蛇游戏

我们的蛇当时已经吃了六个苹果,长了六个身体部位(包括头在内有七个)。

那好吧。既然您已经知道了贪食蛇游戏是如何工作的,那么您一定对我们需要编写什么样的代码来实现这一切有一个简单的想法。别担心。我会详细解释一切。

此外,不要混淆你需要写每段代码的顺序。当我解释事情的时候,它可能看起来很混乱,但是我已经在这一章的结尾按照正确的顺序包括了整个代码。自己编游戏的时候可以参考一下。

我们开始吧!这将是一个稍微有点长,但非常有益的旅程!

导入所需的模块

这个游戏需要三个模块。你需要乌龟包来画蛇、得分和苹果。你需要随机包装,使苹果出现在随机的位置,这是游戏的主要方面之一。

最后,你需要“时间”包。我们以前见过这个包,它让一个循环或函数暂停一段指定的时间。我们现在需要它来让我们的蛇以可控的速度移动。如果我们不调整节奏,我们的蛇会在一眨眼的时间里离开屏幕。

import turtle
import time
import random

设置海龟屏幕

我们将设置一个海龟屏幕,步骤和我们通常使用的一样。将标题设为“贪食蛇游戏”,背景色设为“黑色”。

s = turtle.Screen()
s.title('Snake Game')
s.bgcolor('Black')

但是在本例中,我们将使用 setup()函数来设置屏幕的宽度和高度(以像素为单位)。我们需要一个指定的宽度和高度,这样我们就知道屏幕上的所有内容,这样我们就可以指定精确的坐标来移动我们的蛇。

s.setup(width = 500, height = 500)

最后,让我们去掉每当我们在屏幕上画东西时出现的动画。动画是漂亮的,是的,但是我们将会画这么多东西,画得太快了,以至于动画化每一幅画对我们的游戏来说都不是很好。

您可以使用 tracer()方法(在我们的屏幕中)并输入 0 来实现这一点。看那个!你已经在turtle中学到了一堆新东西。

s.tracer(0) #gets rid of animation

现在,运行程序,你会得到一个像这样的黑屏(图 19-2 )。

图 19-2

游戏屏幕

创建并初始化所需的变量

我们已经在我们的节目中看到了这一点。无论何时你创建一个程序,你都需要一些贯穿整个程序的“全局”变量。我们也有一些。

有一个“蛇”列表,它将包含我们蛇的每一部分的“龟”。每当我们绘制一条蛇的一部分(包括头部)时,我们将创建一只新的乌龟,这样所有的乌龟可以一起工作,同时绘制整条蛇。通过将这些海龟存储在一个列表中,我们可以随时访问它们,并得到它们的位置(你会看到如何操作)。

snake = []

我们要做 20 码的。这是你的方块的宽度和高度(蛇头,蛇的部分,苹果)。我要把这个值设为常数。

size = 20

让我们还创建一个变量“key”来存储按下了哪个键:“u”表示上箭头键,“d”表示下箭头键,“l”表示左箭头键,“r”表示右箭头键。当我们开始游戏时,这个值将是一个空字符串。

key = ''

最后,让我们做一个“score”变量,并在开始游戏时将其值初始化为 0。

score = 0

画头像

现在我们已经初始化了变量,让我们画出我们的头,让它出现在我们的屏幕上。

我们将为此创建一个新的海龟(头)。使其速度为 0,形状为正方形,颜色为绿色。最后,将其移动到位置 0,0(屏幕中心)。

#Draw head
head = turtle.Turtle()
head.speed(0)
head.shape('square')
head.color('Green')
head.penup()
head.goto(0, 0)

让我们也将这个头添加到“蛇”列表中。因为列表是空的,所以它将占据列表的第一个位置。

snake.append(head) #get the first head

运行程序,告诉我你看到了什么。还是黑屏吗?我们的乌龟在哪里?!

好吧,我想我们看不到任何东西,因为有追踪器。我们去掉了动画,记得吗?这次我们需要一个游戏循环来纠正错误。

Pygame 中你会学到更多关于游戏循环的知识,但是现在,只要知道每个游戏都需要一个永不结束的循环(通常是 while 循环)在游戏还“开着”的时候运行。

现在让我们创建这样一个循环,并使用(我们屏幕的)update()方法在每次循环执行时更新屏幕。

while True:
    s.update()

就这样!现在再次运行程序,你会在你的屏幕中间看到一个可爱的小蛇头(图 19-3 )。

图 19-3

蛇头

画第一个苹果

现在我们已经画了蛇,让我们在它的第一个随机位置画第一个苹果。为此我们需要另一只乌龟,我们打算给它取名为“苹果”。

#Draw first apple
apple = turtle.Turtle()
apple.speed(0)
apple.shape('square')

使它的颜色为红色,让我们把它移动到一个随机的位置。

apple.color('Red')
apple.penup()

阅读下面一行代码。我们正在生成一个介于–11 和 11 之间的随机数,并将其乘以 20。如果你把某个东西乘以 20,你就在创造 20 的倍数,这正是我们想要的,因为我们的蛇头每次移动都会向前移动 20 点。

如果我们的蛇必须获胜,它应该能够完全叠加苹果,因此苹果应该与蛇出现在同一条运动线上。我们需要 20 的倍数才能实现。

为什么是–11,11 的范围?嗯,你可以把它放大一点,也许是-11,12,所以实际范围是-11 到 11,但整个前提是苹果应该出现在屏幕内。

–11 * 20 等于–220。这是我们正方形左上角的 x,y 位置,然后是正方形,大小为 20。所以,我们正方形的右上角将是–240,对吗?

这就是它应该结束的地方。如果我们再向左移动,我们的苹果可能会消失。

aX = random.randint(-11,11)*20
aY = random.randint(-11,11)*20

最后,让我们来看看我们刚刚创建的随机 x 和 y 坐标。

apple.goto(aX,aY)

你有没有注意到我们的笔(头和苹果)总是“向上”?那是因为我们不会和他们一起抽签。这次乌龟(笔)将成为游戏角色,而不是他们的图画。

让我们运行程序,我们得到这个(图 19-4 )。

图 19-4

头和第一个苹果

太好了。我们有一个蛇头,固定在屏幕的中间,一个苹果出现在屏幕内的任意位置。

多次运行这个程序,你会注意到苹果每次都被画在不同的位置。酷吧?

现在,在程序的最后(在这一行之前添加下一行代码),添加以下内容:

s.mainloop()

这是为了确保在我们关闭屏幕之前,屏幕是打开的,所以在我们玩游戏的时候,提示不会出现在 Shell 中。

我的屏幕记录了我的箭头按压吗?

大多数游戏都有移动控制。我们要么使用操纵杆,要么使用键盘按键。我们的游戏很简单,所以我们会坚持使用键盘按键。

当我们按下箭头键时,我们能让我们的蛇动起来吗?向上、向下、向左或向右箭头键。

要让您的屏幕“监听”键盘按键,即知道按键被按下,请使用屏幕的 listen()方法。现在,你的屏幕正在监听。将这几行代码放在你画出第一个苹果和蛇头之后,但是在 while 循环(游戏循环)之前。

#Listen to the events and act
s.listen()

现在,当按键发生时,您可以使用 onkeypress()方法来调用用户定义的函数。这类似于我们在 Tkinter 中的工作方式,唯一的区别是我们的函数调用在我们寻找的“事件”之前。

我们的事件是“上”、“下”、“左”和“右”。这些是 onkeypress()函数所期望的值,所以将它们放在引号内,并且不改变大小写。你的函数可以是任何东西。我把我的设定好了,设定好了,设定好了,设定好了,设定好了。

s.onkeypress(set_up,'Up')
s.onkeypress(set_down,'Down')
s.onkeypress(set_left,'Left')
s.onkeypress(set_right,'Right')

现在我们已经调用了我们的函数,我们需要创建它们(否则我们会得到一个错误)。让我们定义一下 onkeypresses 上面的函数。每个函数将加载全局“key”变量,并将值更改为“up”、“down”、“right”或“left”。

但是我们需要记录一些东西。在贪食蛇游戏中,蛇不能向后移动,否则它们只会撞到自己的身体(从而结束游戏),所以我们需要检查用户是否试图向后移动。

例如,如果 key 的当前值是‘down ’,那么接下来我们不应该将该值更改为‘up’。忽略特定按键等等。

def set_up():
    global key #so the global variable key can be used in a local context here
    if(key != 'down'):
        key = 'up'
def set_down():
    global key
    if(key != 'up'):
        key = 'down'
def set_left():
    global key
    if(key != 'right'):
        key = 'left'
def set_right():
    global key
    if(key != 'left'):
       key = 'right'

好吧,我们已经正式改变方向了。但是如果我们现在运行这个程序,我们看不出有什么不同。按键。发生什么事了吗?没有。我们还没有编写让我们的蛇移动的代码!让我们接下来做那件事。

让我们的蛇头移动

所以,在贪食蛇游戏中,一旦我们设定了一个方向,蛇就会自动向那个方向移动,直到我们再次改变方向。所以基本上,一旦蛇开始移动,它会继续移动,直到撞到什么东西。

为了创建这种自动移动,我们将在游戏循环中调用 moveHead()函数。

while True:
    s.update()
    moveHead()

但是,我们不会就此止步。我们将在每次迭代时以 0.2 秒的延迟运行 while 循环,这样人眼就可以看到蛇在移动。

循环的每次迭代都在几微秒内执行。Python 和你的电脑就是这么强大,这么快。但是,这是一个游戏。我们需要人眼能看到的东西,所以让我们放慢我们的程序,好吗?每次迭代后让它休眠 0.2 秒。

time.sleep(0.2)

好了,现在我们完成了“while”循环,让我们创建 moveHead()方法来设置头部的 x 和 y 坐标。

我们将为每个函数调用连续改变头部的 x 和 y 坐标 20 点(这发生在 0.2 秒的延迟),因此头部每 0.2 秒向前移动 20 点(图 19-5 )。

图 19-5

游戏屏幕坐标

请看前面的插图。如果你想向左移动你的蛇,减少 X 的值,同时保持 Y 的值不变。若要向右移动,请增加 X 的值。若要向上移动,请增加 Y 的值(X 保持不变)。若要向下移动,请减小 y 的值。

够简单吗?现在让我们把它应用到我们的代码中吧!

#Make it move based on the set direction

我们不需要在这个函数中加载“key ”,因为我们没有改变/重新分配它的值。我们只是在找回它的价值。使用 xcor()和 ycor()方法检索“头”龟(我们游戏中的蛇头)的当前 x 或 y 坐标。现在你知道为什么我们在列表中存储整个海龟了。这是为了让我们可以用它来获得很多关于它的信息(比如它的位置)。

增加或减少 x 或 y 坐标的“大小”(20 像素),因为这是我们的测量。我们的苹果也将出现在这些点上。

def moveHead():
    if key == 'up':
        head.sety(head.ycor() + size)
    if key == 'down':
        head.sety(head.ycor() - size)
    if key == 'left':
        head.setx(head.xcor() - size)
    if key == 'right':
        head.setx(head.xcor() + size)

现在,运行程序并尝试让蛇移动(图 19-6 )。会的!

图 19-6

移动蛇头

如果你试着向后移动,它不会。你为什么不试着自己看看?

让记分牌开始吧

现在我们的蛇头在动了,我们需要开始得分了。在我们让蛇每次“吃”一个苹果时都成长之前,让我们在屏幕的右上角绘制记分卡,这样我们就可以跟踪代码。将这段代码放在你画第一个苹果的代码下面。不要担心订单。我将把整个代码按照正确的顺序粘贴到本章的末尾。

我将为记分牌创建另一只海龟,因为我想让它在其他海龟工作的时候“画”分数。我将它定位在点 120,120(朝向右上角)。

#Draw the score
sc = turtle.Turtle()
sc.speed(0)
sc.pencolor('White')
sc.penup()
sc.goto(120,220)

首先,让我们用 Arial 字体写“分数:0”,20 分,粗体格式。随着游戏的进展,我们会更新数值。

sc.write('Score:0',font=('Arial',20,'bold'))

让我们最终隐藏这只乌龟,因为我们只需要它画的东西(不像“苹果”和“蛇”龟)。

sc.hideturtle()

运行程序,你得到这个(图 19-7 )。

图 19-7

创建记分板

是的,我们有记分牌了!伙计们,我们快到了!

我们的蛇在吃东西!

现在我们已经完成了我们的记分牌,让我们让我们的蛇吃。现在,如果我们的蛇碰到苹果,什么也不会发生。它会从旁边经过。它不会长大,我们的苹果也不会消失。

将接下来的几行代码(在函数定义之前)放在“while 循环”中,紧接在 s.update()方法之后。

所以,我们要检查我们的蛇的“头”和苹果之间的距离。如果该距离小于或等于 0,也就是说,如果蛇头完全与苹果合并,那么我们希望完成两件事情:

  1. 在另一个随机位置绘制的新苹果。我们将创建一个 drawApple()函数来完成这项工作。

  2. 蛇的尾部又多了一个身体部分。我们将创建一个新的“海龟”作为我们头部的身体部分。让我们创建一个 drawSnake()函数来完成这项工作。

Turtle 有一个 distance()方法,它检查一个对象是否在另一个对象的特定距离内。在我们的例子中,我们将检查我们的对象是否与另一个对象没有距离(完全叠加)。

#check for eating
if head.distance(apple) <= 0: #completely superimposed
    drawApple()
    #Create a new body part
    drawSnake() #keep the tail - old head

让我们将 Score 的值增加 1,并调用 changeScore()方法,将 score 的当前值发送给它。此方法将更新记分卡。

score += 1
changeScore(score)

好了,这就是我们的 while 循环(现在)。我们现在需要定义三个函数(在调用“while”循环之上):一个绘制新的苹果,一个更改分数,下一个绘制新的蛇身体部分,因为它必须生长(它刚刚吃了东西,不是吗?).

#Draw apple function

按照和之前画第一个苹果时一样的步骤,得到下一个 x 和 y 坐标,然后把“苹果”龟移动到那个点。就这样!

def drawApple():
    aX = random.randint(-11,11)*20
    aY = random.randint(-11,11)*20
    apple.goto(aX,aY)

现在,让我们画蛇的身体部分。每当我们的蛇头吃一个苹果,我们就要创造一个新的乌龟。所以,每次我们的 drawSnake()函数被调用时,一个新的 turtle“sBody”就会被创建。它将是方形的,颜色是绿色的,就像我们的蛇“头”一样。让我们将新的部分添加到“snake”列表中。

#draw snake
def drawSnake():
    sBody = turtle.Turtle()
    sBody.speed(0)
    sBody.shape('square')
    sBody.color('Green')
    sBody.penup()
    snake.append(sBody) #insert at the end

现在,让我们研究 changeScore 方法。让比分“海龟”sc 回到 120,220 的位置(起始位置)。让我们使用 clear()方法清除当前的内容,用当前的分数创建一个新的字符串,并重写文本。因为速度为 0,所以你不会看到任何实时发生的情况,所以对于我们人类的眼睛来说,它看起来就像记分牌在无缝地更新自己。

def changeScore(score):
    sc.goto(120,220)
    sc.clear()
    string = 'Score: {}'.format(score)
    sc.write(string,font=('Arial',20,'bold'))
    sc.hideturtle()

让我们运行程序,尝试吃一些苹果(图 19-8 )。

图 19-8

创建新的蛇的部分(蛇吃)

嗯,记分牌似乎在正常更新。苹果确实消失了,并出现在一个新的位置。每次我们的蛇吃东西时,我们似乎都会得到新的“身体部分”,但它们没有连接在一起,也没有一起移动。并且,看起来新的身体部分正在彼此之上出现(在屏幕的中间),所以对我们的眼睛来说,我们只看到一个身体部分,而现在应该有两个(因为分数是 2)。

为什么呢?我们还没有要求他们这么做。你知道,在编程中,你需要对每一件小事进行详细的说明。所以,我们就这么做吧,好吗?

让整条蛇动起来

让我们通过调用 moveBody()来更新 while 循环。这个函数会将蛇的身体部分附加到头部,并使身体部分随之移动。

一旦我们完成了,我们的 while 循环将看起来像这样:

while True:
    s.update()
    #check for eating
    if head.distance(apple) <= 0: #completely superimposed
        drawApple()
        #Create a new body part
        drawSnake() #keep the tail - old head
        score += 1
        changeScore(score)
    moveBody() #RIGHT HERE!
    moveHead()
    time.sleep(0.2)

确保对 moveBody()函数的调用是在 moveHead()函数之前的**,这样它们在新的头部被绘制之前移动,这样看起来就像实际的移动。**

现在,让我们定义 moveBody()函数。我们基本上要交换坐标。

因为我们在程序开始时附加了" head “,” snake “列表的第 0 个索引将包含我们的” head "海龟,对吗?然后,我们附加了其他身体部分,也在蛇头后添加了乌龟。所以,为了模拟运动,我们需要这些身体部位移动它们的坐标。

现在,只有“Head”移动,因为这是我们编写的唯一代码(moveHead()函数)。所以,每次“头”移动到一个新的坐标,紧挨着它的身体部分(在“蛇”列表中)应该移动到头的旧坐标。现在,第一个身体部位旁边的身体部位应该移动到第一个身体部位的旧位置,以此类推,直到蛇的所有部位都向前移动了 20 个像素。

我们如何才能做到这一点?在编程中,每当你想交换值时,你需要一个临时变量来保存旧值。

因此,我们将创建一个临时列表 temp 来存储蛇身体部位的所有当前 x 和 y 坐标。

为此,我们将创建一个“for”循环来遍历“snake”列表。

def moveBody():
    temp = []
    #create a list of the current positions

让我们在列表的一个“项目”中只存储每个蛇部分的 x 和 y 坐标。我们将在一个列表中创建字典。

for i in snake:
    x = i.xcor()
    y = i.ycor()
    temp.append({'x': x, 'y': y})

现在我们有了临时名单,让我们交换一下。我们不需要改变第零个索引中的项目的位置(这是我们的头,它自己移动),所以让我们创建一个 for 循环,从 1 开始循环到蛇的长度(1-(len–1)),这只是身体部分。

#Move entire body
for i in range(1,len(snake)):

因为“temp”已经有了整条蛇(包括头部)的“旧的”x 和 y 坐标,我们就使用它。所以,让我们让第 I 个位置的乌龟(从第一个索引开始)去第 I–1 个位置的乌龟的 x 和 y 坐标(这是我们的头,开始)。

就是这样!因为我们使用了一个字典来存储我们在 temp 中的值,所以我们需要像这样访问它们。因此,“temp”中第一项的 x 值将是 temp[0][‘x’],依此类推。

snake[i].goto(temp[i-1]['x'],temp[i-1]['y'])

让我们检查一下我们的蛇现在是否移动(图 19-9 )。

图 19-9

让整条蛇动起来

它完美地移动和生长。哇哦!

现在,游戏的最后一部分。碰撞检查!

碰撞检查

在为下一次睡眠移动头部之前,我们需要做碰撞检查,这样下一次移动就不会发生。我们将调用 checkCollision 函数,如果有冲突,它将返回 True,如果函数调用的结果确实为 True,我们将打破游戏循环(while loop)。更新后的 while 循环如下:

while True:
    s.update()
    #check for eating
    if head.distance(apple) <= 0: #completely superimposed
        drawApple()
        #Create a new body part
        drawSnake() #keep the tail - old head
        score += 1
        changeScore(score)
    moveBody()
    moveHead()
    #Before moving the head for the next round, check for collision
    if checkCollision():
        break
    time.sleep(0.2)

例如,如果我们将碰撞检查放在其他地方,在身体移动之前,我们可能会在游戏中看到不一致。例如,尝试在 moveBody()和 moveHead()之间放置碰撞检查。乍一看,这似乎合乎逻辑,但通过这样做,你正在创建一个身体,移动它,但随后在你移动头部的 之前立即检查碰撞 。这将导致身体碰撞,因为现在你的第一个身体部位和你的头在同一位置。

因此,在检查碰撞之前,让我们完全移动我们的蛇。

现在,让我们定义碰撞检查函数。这个函数将加载全局“key”变量,因为我们要把它改回一个空字符串,所以移动会暂时停止。

我们还将创建一个变量“collision ”,并将其默认值设为 False。

def checkCollision():
    global key
    collision = False

先检查一下有没有撞墙。很简单,真的。如果头部的 x 或 y 坐标大于 240 或小于–240,则碰撞为真。

#wall collision
if head.xcor() < -240 or head.xcor() > 240 or head.ycor() < -240 or head.ycor() > 240:
    collision = True

现在,对于身体碰撞,让我们再次循环 1 到蛇列表的长度(只有身体部分,而不是头部)。如果头部的 x 和 y 坐标与蛇身体任何部分的 x 和 y 坐标相同,那么就发生了身体碰撞,碰撞再次成立。

#body collision
for i in range(1,len(snake)):
    if head.xcor() == snake[i].xcor() and head.ycor() == snake[i].ycor():
       collision = True

最后,如果碰撞是真的,那么让 key 再次成为空字符串(暂停移动)。游戏基本上结束了。如果 collision 不为真,那么什么都不会发生,下一次“while”循环将继续。

if collision == True:
    key = '' #pause the movement

接下来我们需要做三件事:

  1. 暂停程序 1 秒钟,让用户意识到游戏已经结束。

  2. 将蛇(及其所有部分)和苹果从屏幕上移开,这样它就基本上“消失”了。

  3. 画一个“游戏结束”的信息。我们需要一只新的海龟来做这件事。我们将保留记分卡,以便用户知道他们最后的得分。

    time.sleep(1) #pause for a bit so user registers what happened
    
    

让我们通过“蛇”循环,并将其所有部分移动到 2000,2000(基本上离开屏幕)。让我们也把苹果搬到一个更远的位置。

        for s in snake:
            s.goto(2000,2000) #make it go off the screen
        apple.goto(2500,2500)
We’re going to create an ordinary Turtle, move it to the point -170,0 and draw ‘GAME OVER’ in white.
        #game over message
        game = turtle.Turtle()
        game.penup()
        game.goto(-170,0)
        game.pencolor('white')
        game.write('GAME OVER!',font=('Arial',40,'bold'))
        game.hideturtle()
return collision

最后,让我们将碰撞返回到调用函数。就是这样!我们已经完成了我们的游戏!

让我们检查一下碰撞是否有效,好吗?

先检查一下有没有撞墙(图 19-10 )。

图 19-10

壁碰撞

是的,它工作了。

现在进行车身碰撞(图 19-11 )。

图 19-11

身体碰撞

那边有一条盘绕的蛇!

在提到的 1 秒钟延迟后,一切都消失了,我们只剩下记分牌和“游戏结束”的消息。如果用户想再次玩,他们必须再次运行程序(图 19-12 )。

图 19-12

游戏结束消息

咻!时间很长,但绝对值得!我希望你和我一起创作这个游戏时有很多乐趣。我知道我做到了!

整个代码

现在,正如承诺的那样,整个代码:

#import the required modules
import turtle
import time
import random

#setup the screen
s = turtle.Screen()
s.title('Snake Game')
s.bgcolor('Black')
s.setup(width = 500, height = 500)
s.tracer(0) #gets rid of animation

#create and assign the required variables
snake = []
size = 20
key = ''
score = 0

#Draw the head
head = turtle.Turtle()
head.speed(0)
head.shape('square')
head.color('Green')
head.penup()
head.goto(0, 0)
snake.append(head) #get the first head

#Draw the first apple
apple = turtle.Turtle()
apple.speed(0)
apple.shape('square')
apple.color('Red')
apple.penup()
#generate a random integer that's a multiple of 20
#multiples of 20 that doesn't go beyond the screen (250,-250)
aX = random.randint(-11,11)*20
aY = random.randint(-11,11)*20
apple.goto(aX,aY)

#Draw the scoreboard at the beginning
sc = turtle.Turtle()
sc.speed(0)
sc.pencolor('White')
sc.penup()
sc.goto(120,220)
sc.write('Score:0',font=('Arial',20,'bold'))
sc.hideturtle()

#Change the direction of the snake
def set_up():
    #so the global variable key can be used in a local context here
    global key
    if(key != 'down'):
        key = 'up'
def set_down():
    global key
    if(key != 'up'):
        key = 'down'
def set_left():
    global key
    if(key != 'right'):
        key = 'left'
def set_right():
    global key
    if(key != 'left'):
       key = 'right'

#Make the snake move based on the set direction
def moveHead():
    if key == 'up':
        head.sety(head.ycor() + size)
    if key == 'down':
        head.sety(head.ycor() - size)
    if key == 'left':
        head.setx(head.xcor() - size)
    if key == 'right':
        head.setx(head.xcor() + size)

#make the new snake body move (if the snake has grown)
def moveBody():
    temp = []
    #create a list of the current positions
    for i in snake:
        x = i.xcor()
        y = i.ycor()
        temp.append({'x': x, 'y': y})
    #Move entire body
    for i in range(1,len(snake)):
        snake[i].goto(temp[i-1]['x'],temp[i-1]['y'])

#Draw apple function
def drawApple():
    #generate a random integer that's a multiple of 20
    #multiples of 20 that doesn't go beyond the screen (250,-250)
    aX = random.randint(-11,11)*20
    aY = random.randint(-11,11)*20
    apple.goto(aX,aY)

#Create a new snake part
def drawSnake():
    sBody = turtle.Turtle()
    sBody.speed(0)
    sBody.shape('square')
    sBody.color('Green')
    sBody.penup()
    snake.append(sBody) #insert at the end

#Update the score
def changeScore(score):
    sc.goto(120,220)
    sc.clear()
    string = 'Score: {}'.format(score)
    sc.write(string,font=('Arial',20,'bold'))
    sc.hideturtle()

#Check for collision – wall & body
def checkCollision():
    global key
    collision = False
    #wall collision
    if head.xcor() < -240 or head.xcor() > 240 or head.ycor() < -240 or head.ycor() > 240:
        collision = True
    #body collision
    for i in range(1,len(snake)):
        if head.xcor() == snake[i].xcor() and head.ycor() == snake[i].ycor():
           collision = True
    if collision == True:
        key = '' #pause the movement
        time.sleep(1) #pause for a bit so user registers what happened
        for s in snake:
            s.goto(2000,2000) #make it go off the screen
        apple.goto(2500,2500)
        #game over message
        game = turtle.Turtle()
        game.penup()
        game.goto(-170,0)
        game.pencolor('white')
        game.write('GAME OVER!',font=('Arial',40,'bold'))
        game.hideturtle()
    return collision

#Listen to the events and act on the required key presses
s.listen()
s.onkeypress(set_up,'Up')
s.onkeypress(set_down,'Down')
s.onkeypress(set_left,'Left')
s.onkeypress(set_right,'Right')

#The main game loop that keeps the game running
while True:
    s.update()
    #check for eating
    if head.distance(apple) <= 0: #completely superimposed
        drawApple()
        #Create a new body part
        drawSnake() #keep the tail - old head
        score += 1
        changeScore(score)
    #Move the body first, and then the head to the new position
    moveBody()
    moveHead()
    #Before moving the head for the next round, check for collision
#If there's a collision, stop the game loop – no more movement
    if checkCollision():
        break
    #A delay of 0.2 seconds before each movement
    time.sleep(0.2)

#Keep the screen open until the user closes it
s.mainloop()

摘要

在这一章中,我们用我们的turtle包创建了一个贪食蛇游戏。我们学到了很多新东西,比如使用时间模块暂停你的程序一会儿,创建游戏循环,获取你的海龟的位置,移动你的游戏角色,碰撞检查,在 2D 游戏中记分,等等。

在下一章中,让我们来了解一下关于 Pygame 的一切。我们将学习如何在 Pygame 中创建简单的 2D 游戏,这是专门为制作游戏而创建的。

二十、成为 Pygame 的游戏开发者

在前一章中,我们用乌龟创建了一个贪食蛇游戏。

在这一章中,让我们学习一个简介 Pygame ,一个被广泛用于 2D 游戏开发的平台。让我们学习所有关于创建我们的角色,使用图像作为角色,设置和修改你的屏幕,使你的角色移动,碰撞检测,从枪中射出子弹,分数,文本,等等!

什么是Pygame

Pygame 是一个跨平台的平台,由多个 Python 模块组成。它是为编写视频游戏而设计的。当你开始时,它可能看起来很简单,但一旦你深入了解它,它就非常强大,你可以用它创建任何东西,从简单的基于文本的游戏到复杂的,复杂的,多玩家的世界游戏。

在这一章中,让我们学习一下 Pygame 的基础知识,因为探索它的全部特性和功能已经超出了本章的范围。

安装并导入Pygame

任何超出标准 Python 代码的东西都需要安装,我说的对吗?这也适用于 Pygame

但是问题不像 TkinterTurtle , Pygame 不会安装在你的标准 Python 安装中。所以,你需要单独安装。

要在你的 Python 安装中安装 Pygame ,进入你的命令提示符和你已经完成 Python 安装的文件夹(图 20-1 )。

图 20-1

打开命令提示符

在命令提示符下,键入以下内容:

pip install pygame

按 Enter 键并等待几秒钟。您应该会得到这样的消息(图 20-2 )。

图 20-2

安装 Python

就这样!Pygame 现已安装在您的系统中。接下来让我们在程序中使用它。打开 Python Shell 并创建一个新脚本。随便你怎么叫,就是别叫 pygame。

不像 Tkinterturtle,你不能只是导入 pygame 就完事大吉。您还需要初始化库。使用 init()方法可以做到这一点。你的程序只有在初始化后才能运行。

import pygame
pygame.init()

就这样!我们已经导入了 Pygame ,并且已经准备好了!

设置您的游戏屏幕!

创作游戏的下一步是什么?到目前为止你已经创造了很多,那你为什么不猜呢?没错。一个屏幕。我们需要一个所有事情都发生的屏幕。

让我们现在做那件事。我将定义一个变量“screen”(您可以随意命名自己的屏幕),并使用 display.set_mode 方法来创建具有我想要的尺寸的屏幕。

不过,您需要在一个元组或列表中给出屏幕的宽度和高度。否则你会得到一个错误。

screen = pygame.display.set_mode((500,500))

现在,让我们运行我们的程序,看看我们得到了什么(图 20-3 )。

图 20-3

python 窗口

我们有我们的屏幕。喔!

但是试着关闭你的屏幕,你会发现你不能。这是因为与其他软件包不同, Pygame 需要特殊指令来关闭屏幕。因此,我们将梳理屏幕上发生的所有事件,选择与鼠标左键单击“x”(关闭)按钮相对应的事件,并要求 Pygame 在单击发生时“退出”。

够简单吗?开始吧。

如你所知,每个游戏都需要一个游戏循环。一个永无止境的循环,只有在游戏结束时才会结束。 Pygame 也不例外。我们将创建一个“while”循环,当单击关闭按钮时,该循环变为 false。

game = True

现在,我们将使用一个“for”循环来梳理屏幕上发生的所有事件。您可以使用 pygame.event.get()方法获取事件列表,并且可以遍历它们。对于每次迭代,检查 event.type 是否为 pygame。退出(我们正在寻找的关闭按钮点击)。如果是,将“game”设为 False,以便 while 循环停止执行。

while game:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game = False

一旦我们退出游戏循环,我们将使用 pygame.quit()方法关闭屏幕。

现在试着关闭屏幕。关门了吗?没错。

让你的屏幕漂亮

现在,让我们使我们的屏幕漂亮!我们为什么不从改变屏幕的标题开始呢?

您需要使用 display.set_caption 方法来实现这一点。将这几行代码放在创建屏幕的那一行下面(游戏循环的上面)。

pygame.display.set_caption('My first game')

运行程序,看看你会得到什么(图 20-4 )。

图 20-4

自定义您的屏幕

我们的标题变了!

我们的屏幕现在是黑色的。我们为什么不换颜色呢?为了设置我们的颜色,我们将使用 RGB 值。

r 代表红色,G 代表绿色,B 代表蓝色。这三种颜色称为原色,这三种颜色的不同深浅和组合构成了你随处可见的其余颜色。

所以,有了这三个值,我们就可以想出几乎任何颜色。这三种颜色的值从 0 到 255,其中 0 表示没有任何颜色,255 表示有颜色。

自然,(0,0,0)是完全没有颜色,会给我们黑色,(255,255,255)是完全有颜色,会给我们白色。

您可以通过以下网站找到您想在程序中使用的任何颜色的 RGB 颜色代码:

https://htmlcolorcodes/

有很多其他网站给你同样的信息。在线搜索“颜色选择器”或“rgb 颜色代码”来查找它们。

现在我们已经了解了颜色是如何工作的,让我们用红色填充我们的屏幕。那就是 255,0,0(只有红色的完全存在)。

在“while”循环中,在我们查看事件的 for 循环下面,添加以下代码行:

screen.fill((255,0,0))

但是如果你现在运行这个程序,你不会看到变化。为什么会这样?嗯,屏幕并不是每次迭代都会更新。您需要使用 display.update()方法来更新您的屏幕。

pygame.display.update()

现在,再次运行你的程序,你会得到这个(图 20-5 )。

图 20-5

更改屏幕背景

我们的颜色变了!喔!

在屏幕上创造你的角色

您可以使用 draw 方法绘制线条、矩形(或正方形)、圆形或多边形。这些可以成为你的游戏角色。

划清界限相当容易。语法如下:

pygame.draw.line(screen,color,(x1,y1),(x2,y2),width)

您需要指定线条的绘制位置(屏幕)、颜色(RGB)、线条起点和终点的 x 和 y 坐标(元组中的每一对),最后是线条的粗细。

让我们在程序中尝试一下。将这一行代码放在 display.update()方法的上方,这样这一行代码就会更新到屏幕上:

pygame.draw.line(screen,(255,255,0),(50,50),(100,150),10)

运行程序,你会得到这个(图 20-6 )。

图 20-6

画直线

我们把线放在了我们想要的位置!

接下来,我们来看一个长方形。语法是这样的:

pygame.draw.rect(screen,color,(x,y,width,height),outline)

我们需要指定矩形左上角点的 x 和 y 位置及其宽度和高度。如果你提到相同的宽度和高度值,你会得到一个正方形。最后一个值表示您想要填充还是轮廓。如果您将轮廓设为 0,您将得到一个完全填充的矩形。任何其他值都会得到一个轮廓。让我给你看两者的例子:

pygame.draw.rect(screen,(153,255,102),(100,200,100,100),0)

运行程序,你会得到这个(图 20-7 )。

图 20-7

绘制矩形

现在,我们将轮廓设为 50(填充 50%)(图 20-8 )。

图 20-8

50%填充

现在,让我们画一个圆。语法是这样的:

pygame.draw.circle(screen,color,(x,y),radius,outline)

x 和 y 点是圆心的 x 和 y 坐标。然后,提及半径和轮廓(如果你不需要完整的填充)。

pygame.draw.circle(screen,(0,102,255),(300,200),50,0)

运行程序,你会得到这个(图 20-9 )。

图 20-9

画圆

很好!

最后,你可以画多边形(任意数量的线)。

pygame.draw.polygon(screen,color,((x1,y1),(x2,y2)...(xn,yn)))

我们先画一个三角形。

pygame.draw.polygon(screen,(128,0,0),((150,350),(50,450),(250,450)))

也许是五边形,下一个?

pygame.draw.polygon(screen,(253,0,204),((400,300),(300,300),(350,450),(450,450),(450,350)))

运行程序,你会得到这个(图 20-10 )。

图 20-10

绘制多边形

形状就是这样!现在,让我们看看图像。

让我们从零开始为我们的形象。这是一个非常简单的过程。你需要加载你的图像(一次,在你的游戏循环之外),并在你希望它出现的精确坐标上“blit”它,所以它在屏幕上得到更新。

指定图像所在的确切路径。如果你不想让事情变得复杂,把你的图片和你的 Python 文件放在同一个文件夹里,你只需要提到文件的名字,就可以了。

image = pygame.image.load('ball.png')

然后,使用“blit”将其显示在屏幕上。

while game:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game = False
    screen.fill((255,0,0))
    screen.blit(image,(200,150))
    pygame.display.update()

运行程序,你会得到这个(图 20-11 )。

图 20-11

绘制图像

这就对了。我们的形象在这里。

移动你的角色

移动我们的角色相当容易。你只需要改变角色的 x 和/或 y 坐标,就大功告成了。如果你想要连续的移动,在游戏循环的每一次迭代中不断改变它。

让我们试着移动我们的球,好吗?

我想让它向下移动,直到它的 y 值达到 400(由于我们的图像的高度是 100,当它的 y 达到 400 时,它的底部会接触到屏幕),然后停止,好吗?就这么办吧。

让我们导入 pygame 和时间。这里我们需要时间模块,因为我们要减慢迭代的速度,这样人眼就可以看到球的运动。

import pygame
import time

pygame.init()
screen = pygame.display.set_mode((500,500))
image = pygame.image.load('ball.png')

game = True

我们将创建一个保存第一个值 150 的“y”变量。

y = 150
while game:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game = False
    screen.fill((255,0,0))

让我们 blit 图像。

screen.blit(image,(200,y))

只要 y 不是 400,我们就让 y 值在循环的每次迭代中增加 1。

if y != 400:
    y += 1

让我们更新屏幕,让程序在每次循环迭代之间休眠 0.005 秒。

    pygame.display.update()
    time.sleep(0.005)
pygame.quit()

就这样!运行程序(图 20-12 ,你会看到一个平滑的向下运动,直到球碰到屏幕底端。

图 20-12

移动你的角色

键盘按压事件

好了,我们现在可以移动我们的球了。但是,如果我们想根据用户输入来移动它,比如键盘输入事件,该怎么办呢?

假设我想根据用户在键盘上按下的箭头键向四个方向移动我的球。我该怎么做?

还记得我们在寻找退出事件时循环的事件吗?我们也可以对按键事件使用相同的循环。

寻找 KEYDOWN 事件,当用户在游戏屏幕上按下一个键时,它只记录。当你寻找 KEYDOWN 的时候,你会得到一个事件字典。将它们放入变量中。让我们把我们的命名为“钥匙”。

要注册左箭头键,请搜索键[K_LEFT]。如果该值为真,则向左移动(x 减 1)。

要注册右箭头键,请搜索 keys[K_RIGHT],如果正确,则将 x 增加 1。

要注册向上箭头键,搜索 keys[K_UP],如果为真,则将 y 值减 1。

要注册向下箭头键,请搜索 keys[K_DOWN],如果为真,则将 y 值增加 1。

为了获得连续的运动,引入方向变量,它将根据你设置的方向连续增加或减少你的 x 或 y 值。

让我们为 x 和 y 设置一个初始值,方向变量为 0,因为此时球不动。

x = 200
y = 150
xd = 0
yd = 0

while game:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game = False

现在,让我们寻找我们的事件。如果用户希望将方向设置为向左,那么 xd 应该变为–1,而 yd 保持不变。遵循相同的逻辑,为其余的方向。

    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_LEFT:
            xd = -1
            yd = 0
        if event.key == pygame.K_RIGHT:
            xd = 1
            yd = 0
        if event.key == pygame.K_UP:
            yd = -1
            xd = 0
        if event.key == pygame.K_DOWN:
            yd = 1
            xd = 0
screen.fill((255,0,0))

现在,在 blit 图像和更新屏幕之前,将 xd 和 yd 值添加到当前的 x 和 y 值中。

    x += xd
    y += yd
    screen.blit(image,(x,y))
    pygame.display.update()
    time.sleep(0.005)
pygame.quit()

现在,当你设定一个方向时,球将继续向那个方向移动,直到你改变它(就像在我们的贪食蛇游戏中一样)。

但是如果我们只想让屏幕随着键盘按键一起移动呢?当我们停止按箭头键时,我们希望球停止移动。

有一个 KEYUP 活动可以帮助你做到这一点。在 for 循环中,检查 KEYUP 事件是否已经发生,并在内部“if”语句中,检查当前事件是 LEFT、RIGHT、DOWN 还是 UP 事件。

如果是这样,停止改变 xd 和 yd 值(使它们为 0),你将停止移动。

if event.type == pygame.KEYUP:
    if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_UP or event.key == pygame.K_DOWN:
        xd = 0
        yd = 0

就这样!

迷你项目——弹跳球

在这个项目中,我们将创建一个在屏幕上上下跳动的弹力球。当它击中屏幕的顶部或底部时,它应该反转方向并像这样继续。够简单吗?让我们用 pygame 来做这件事。

  1. 让我们从导入 pygame 和 time 开始。

  2. 然后,让我们初始化 pygame 并创建我们的屏幕。它的宽度和高度都是 500。

import pygame
import time

  1. 现在,让我们创建一个变量 y,并把它设为 0。这是因为随着上下弹跳,唯一会改变的值是 y 值。
pygame.init()
screen = pygame.display.set_mode((500,500))

  1. 我们还需要一个“游戏”变量,该变量当前为真,但当用户关闭屏幕时将变为假。
y = 0

  1. 让我们也创建一个方向变量“d ”,它默认为 1。我们将球的 y 值增加 1(向上移动)和–1(向下移动)。这个变量会改变球的方向。
game = True

  1. 现在,让我们创建我们的游戏循环。
d = 1

  1. 首先,让我们创建退出条件。如果事件类型是 pygame。退出,让游戏变得虚假。
while game:

  1. 然后,让我们用白色填充我们的屏幕。
for event in pygame.event.get():
    if event.type == pygame.QUIT:
        game = False

  1. 然后,我们用 draw.circle 方法在位置 250,y(一开始是,250,0)画一个红球。它的半径是 25,是一个完全填充的圆,所以最后一个属性是 0。
screen.fill((255,255,255))

  1. 让我们使用 display.update 方法来确保每次循环运行时屏幕都得到更新。
#draw a ball
    #circle draw function
    #where you want to draw it, color of the circle, position, width
    pygame.draw.circle(screen,(0,0,255),(250,y),25,0)

  1. 如果我们让游戏保持原样,我们的球会移动得太快以至于人眼看不到。所以,让我们放慢循环的迭代速度。每次迭代后会有 0.005 秒的延迟。
pygame.display.update() #update the screen in the output window

  1. 现在,让我们设置墙壁碰撞条件。当 y 为 488 时(由于我们的球的直径为 25,我们需要球的另一半可见,所以我们将其设置为 488 而不是 500),让我们减小 y 的值,因为我们需要球向上移动。所以 d 将会是-1。
time.sleep(0.005)

  1. 如果 y 是 12,那么增加 y 的值,“d”将是+1。
if y == 488:
    d = -1

  1. 最后,一旦我们脱离了 if elif 语句,让我们用“y”的当前值加上“d”。
elif y == 12:
    d = 1

    y += d
pygame.quit()

就这样!运行该程序,你将拥有一个弹跳球(图 20-13 )。

图 20-13

弹跳球

摘要

在这一章中,我们学习了 pygame 的基础知识。我们学习了如何设置我们的游戏屏幕,设置游戏循环,创建我们的角色(形状和图像),让他们移动,检测墙壁碰撞,以及检测键盘事件。

在下一章,让我们应用本章所学的知识,再多一点,来创建一个太空入侵者游戏!

二十一、项目:太空射手与Pygame

在前几章,我们学习了 Pygame 的基础知识。我们学习了创建游戏屏幕、关闭屏幕、美化屏幕、创建角色、移动角色等等。

在这一章中,让我们应用我们到目前为止所学的,甚至更多,来创建一个太空射击游戏。您还将学习如何为游戏创建文本和记分卡。

太空射击游戏

这是一个非常简单的游戏。你有一艘更像枪的宇宙飞船。当你按下左右箭头键时,它可以向左或向右移动。

然后,你有你的敌人。三排敌人,总共 21 个,他们会朝飞船移动。如果他们击中飞船,游戏就结束了!

为了防止这种情况,飞船可以向敌人射击。它一次只能射出一颗子弹。子弹在每次射击后(当它击中敌人或屏幕的上壁时)重新加载,因此飞船可以再次射击。

子弹每击中敌人一次,你得一分,击中的敌人消失。如果你杀死了所有的 21 个敌人,他们会重新加载,你会得到一个新的三排 21 个敌人。重新开始射击,直到你输了!

看那个(图 21-1 )。敌人就在附近,所以我们需要清除那一排来活命。我们已经击中了两个敌人,我们的分数是 2。

图 21-1

决胜比赛

这是一个足够简单的游戏,有很多改进的潜力(更多的关卡,更快的速度,更多的子弹,更多的敌人),所以让我们开始吧!

导入所需的模块

我们需要 pygame 模块来创建游戏本身,还需要 time 模块来减慢角色的速度,使其足以被人眼看到。

import pygame
import time

初始化一切

我们来初始化 Pygame 及其字体包(写记分牌)。

pygame.init()
pygame.font.init() #To use text

接下来,让我们创建我们的游戏屏幕,并将标题设置为“太空射手”。

screen = pygame.display.set_mode((500,500))
pygame.display.set_caption('Space Shooters')

让我们也创建一个“font”变量来存储我们需要使用的字体,即字体类型“Arial”和大小 40。

font = pygame.font.SysFont('Arial',40)

我们需要两个游戏条件:一个在游戏结束(敌人击中飞船)时为真的“over”和一个在用户关闭窗口时为假的“game”。

over = False #Game over
game = True #Closed the game window

就这样!让我们运行程序,我们得到这个(图 21-2 )。

图 21-2

游戏屏幕

我们有屏幕了!

游戏循环

接下来,让我们创建我们的游戏循环。

while game:

让我们首先创建窗口“关闭”条件。你已经知道如何创建它了。

#Close window condition - Quit
for event in pygame.event.get():
    if event.type == pygame.QUIT:
        game = False

同时,让我们用黑色填充屏幕。当然,这不会有太大的区别,因为 pygame 屏幕的默认颜色是黑色。

screen.fill((0,0,0))

程序退出游戏循环后,关闭窗口:

pygame.quit()

不要担心代码序列。我将把整个代码按照它应该被写的顺序粘贴在这一章的末尾。

现在,再次运行程序并尝试关闭窗口。会有用的!

创造宇宙飞船

现在,让我们创建我们的飞船,并使它出现在屏幕上。

将这几行代码放在游戏循环上面。

#Create the spaceship

我要加载我为这个项目得到的 spaceship.png 图像。这是一艘漂亮的小飞船,指向上方。

spaceship = pygame.image.load('spaceship.png')

现在,让我们设定宇宙飞船的初步位置。水平居中,x 位置 250,y 位置 390(朝向屏幕底部)。让我们也将方向设置为 0 作为默认值。当我们使宇宙飞船移动时,我们可以增加或减少它。

sp_x = 250
sp_y = 390
sp_d = 0

要使飞船出现在屏幕上,在游戏循环中,在 for 循环下,包含以下代码行:

if over == False:
    screen.blit(spaceship,(sp_x,sp_y))

如果游戏仍然是真的,那么将图像 blit 到我们设置的 x 和 y 坐标位置。

最后,更新显示:

pygame.display.update()

让我们运行程序,我们得到这个(图 21-3 )。

图 21-3

定位你的宇宙飞船

我们有自己的太空船。耶!

移动飞船

你已经知道如何让角色移动了,对吗?我们需要完成以下工作:

  1. 根据按下的箭头键,向右或向左移动飞船。

  2. 当用户停止按箭头键时,停止移动飞船。

在这种情况下,我们需要寻找两个事件:KEYUP 和 KEYDOWN。

在 KEYUP 中,我们需要寻找两个键:K_LEFT 和 K_RIGHT。

让我们回到我们的游戏循环和 for 循环,在这里我们遍历了屏幕上发生的所有事件,并包含了接下来的两个条件。

寻找 KEYDOWN 条件,如果“下”事件中按下的键是左键(左箭头键),那么空间方向减 1,这意味着飞船会向左(水平)移动。

如果按下的键是右箭头键,那么空间方向增加 1,表示飞船会向右(水平)移动。

if event.type == pygame.KEYDOWN:
    #Make spaceship move
    if event.key == pygame.K_LEFT:
        sp_d = -1
    if event.key == pygame.K_RIGHT:
        sp_d = 1

现在,让我们让飞船停止移动,如果箭头键被释放。让我们寻找一个 KEYUP 事件,并检查释放的键是否是左右箭头键。

#Make spaceship stop if not moving
if event.type == pygame.KEYUP:
    if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:

如果是,将飞船方向返回到 0,这样位置没有变化,它只是停在用户离开的地方。

sp_d = 0

但是我们不能就此止步。如果我们希望 sp_d 值在游戏循环的每一次迭代中都移动,我们需要在 for 循环之外将 sp _ d 值加到 sp_x 值上。

#Spaceship move condition
sp_x += sp_d

将前面的代码行放在飞船 blit 上面,并“更新”代码行。

现在,运行代码并尝试移动飞船。哇哦!真快。我真的控制不了我的飞船。为什么会这样?

嗯,我们没有间隔游戏循环迭代,是吗?让我们在每次迭代后暂停程序(游戏)0.005 秒。将这一行代码放在“显示”代码行之上。

time.sleep(0.005)

现在,运行整个程序,试着左右移动你的飞船,你会得到这个(图 21-4 )。

图 21-4

让飞船在箭头压力下移动

有效。没错。

创造和移动敌人

现在让我们转移敌人!我们需要三排七个敌人,总共 21 个。它们将具有相同的属性(图像),但唯一的区别是它们的位置。

让我们创建包含所有值的列表。一个保存图像,这样它可以在游戏循环中 blit,一个保存所有的“x”位置,一个保存所有的“y”位置,最后,一个保存敌人的运动(方向)。

#Create enemy
enemy = []
enemy_x = []
enemy_y = []
enemy_d = []

让我们也记录下活着的敌人的数量。计数器将从 0 开始,每击落一个敌人就加 1。当数字达到 21 时,我们要重置一切,再次画出三排新的敌人,让他们倒下继续游戏。

enemy_count = 0

现在,让我们设定敌人的 x 和 y 位置。为此,我们将创建一个从 0 到 20(范围为 21)的 for 循环。

对于第一行(从迭代 0 到 6),x 位置将从 0 开始,并以 70-0、70、140、210、280 等倍数增加。

y 位置将位于–60(远离屏幕,在顶部),但仍然靠近可见部分,因为这是第一行。

每个敌人的距离值都是 0.5,因为这是他们倒下的速度。

for i in range(21):
#Row 1
if i <= 6:
    enemy.append(pygame.image.load('enemy.png'))
    enemy_x.append(70 * i)
    enemy_y.append(-60)
    enemy_d.append(0.5)

看那个!为了创建 70 的倍数,我只是用“I”乘以 70,因为“I”无论如何都会取 0 到 6 之间的值。

现在,第二排有点棘手。我们仍然需要 x 值为 70 的倍数,但是我们不能再使用“I ”,因为,对于第二行,“I”将从 7 到 13。所以,让我们把“I”减去 7,同时乘以 70。

这组敌人的 y 值将会是–120,比第一行稍微靠后一点。

#Row 2
elif i <= 13:
    enemy.append(pygame.image.load('enemy.png'))
    enemy_x.append(70 * (i-7))
    enemy_y.append(-120)
    enemy_d.append(0.5)

同样,让我们用 70 乘以 I–14 得到第三行,也就是最后一行的 x 值,并将 y 值设为–180。

#Row 3
else:
    enemy.append(pygame.image.load('enemy.png'))
    enemy_x.append(70 * (i-14))
    enemy_y.append(-180)
    enemy_d.append(0.5)

就这样!我们已经确定了敌人的位置。让他们出现,然后倒下。

在游戏循环(while 循环)中,在你“blit”飞船之后,让我们创建另一个“for”循环,运行 21 次(0 到 20)。

就像我们在宇宙飞船上做的一样,如果游戏还没有结束,我们只会吸引敌人。

这里我们需要检查两个条件:

  1. 如果敌人的“y”位置超过 500(它已经到达屏幕的末端),那么让它回到–60 的“y”位置。够了。为什么?嗯,第一排会先消失,然后第二排,最后第三排。一切都在不断移动,所以如果我们只是将每一行移回–60,前一行的移动将补偿下一行在同一点的出现。

  2. 如果 y 位置还没到 500,那么我们需要把敌人下移。把敌人 d 的值加到敌人 y 的值上,然后把这个敌人传送到屏幕上。

#Draw enemies and make them move
for i in range(21):
    if over == False:
        #enemy wall collision
        if enemy_y[i] >= 500:
            enemy_y[i] = -60
        else:
            #Draw enemies
            enemy_y[i] += enemy_d[i]
            screen.blit(enemy[i],(enemy_x[i],enemy_y[i]))

就这样!我们的敌人现在应该行动了。让我们检查一下(图 21-5 )。

图 21-5

创造敌人

没错。我们有三排移动的敌人!

发射子弹

接下来,让我们发射子弹。我们需要做三件事:

  1. 在游戏循环之外创建子弹,但在用户开火(按空格键)之前不要 blit 它。

  2. 检查游戏循环中的“空格”按下事件(在遍历所有事件的 for 循环中,以及在我们进行 KEYDOWN 事件检查的“if”语句中),如果发生了,设置子弹的 x 和 y 位置并改变其方向。

  3. 最后,在 events“for”循环之外,但在游戏循环之内,将子弹 blit 到屏幕上(如果它已经发射)。让我们同时检查一下是否有撞墙的情况,如果子弹碰到了墙,就把它带回到原来的位置。

好吧。现在我们知道我们需要做什么,让我们写同样的代码。

我们将加载“bullet.png”图像,这将是我们的项目符号。首先,我们将子弹的 x 和 y 位置设置为–100,这样它就不在屏幕上了,玩家看不到。让我们也将移动值 bullet_d 设置为 0,这样就没有移动。

#create the bullet
bullet = pygame.image.load('bullet.png')
#place it off the screen to start with
bullet_x = -100
bullet_y = -100
bullet_d = 0

最后,我们将创建一个变量“fire”来保存子弹的状态。如果用户发射了子弹,这个变量的值将从 False(默认值)变为 True。

fire = False

现在,让我们注册“space”按键。转到游戏循环,在遍历所有事件的 for 循环中,查找注册 KEYDOWN 事件的“if”语句。在该语句中,键入以下内容:

注册 K_SPACE 新闻事件。只要“发射”值为假(子弹之前没有发射过),如果用户单击空格键,让子弹移动。

现在把“火”变成真的(因为子弹已经发射了)。将项目符号的 x 和 y 值定位到飞船的当前 x 和 y 值。最后,使 bullet_d 值为–2,这样它就会向上移动。

#Make bullet fire
if event.key == pygame.K_SPACE:
    if fire == False:
        fire = True
        bullet_x = sp_x
        bullet_y = sp_y
        bullet_d = -2

现在,让我们把子弹上膛。

在 for 循环之外,在我们 blit 飞船的代码之上,但是在我们改变了飞船的 x 值之后(所以新的 x 值被赋给了子弹),如果“fire”为真,“over”为假(游戏仍然有效),blit 子弹。

#Fire bullet
if fire == True and over == False:

我们已经将 x 值设置为 bullet_x+12,这样它就从飞船后面消失了。

screen.blit(bullet,(bullet_x+12, bullet_y))

接下来,让我们将项目符号的 y 值增加 bullet_d 的值(在本例中减少,因为 bullet_d 的值将为–2)。

bullet_y += bullet_d

最后,我们来检查一下是否撞墙。一旦子弹到达屏幕顶部(y 为 0 或更小),如果“fire”值仍然为真(仍然发射),让我们将子弹的 x 和 y 值改回飞船的 x 和 y 值,并使 bullet_d 值为 0,于是它开始移动。让我们也将“fire”的值设为 False,这样子弹就不再“blit”到屏幕上,直到它再次被发射。

#bullet wall collision
if bullet_y <= 0 and fire == True:
    bullet_x = sp_x
    bullet_y = sp_y
    bullet_d = 0
    fire = False

运行代码,你会得到这个(图 21-6 )。

图 21-6

射出箭

我们的子弹现在能用了!

创建并显示记分板

现在我们已经有了我们所有的角色,他们正在按照我们想要的方式移动,让我们创建我们的记分牌,这样我们就可以在向敌人射击时显示分数。

让我们先创建我们的记分牌。

#Create scoreboard

“score”的值将从 0 开始。

score = 0

接下来,让我们创建另一个变量 score_text,它存储我们希望在游戏开始时显示的字符串,即 Score: 0。

score_text = 'Score: {}'.format(score)

最后,让我们使用 Pygame 中的“字体”选项来渲染这个 score_text。文本颜色将是(255,255,255),这是白色的。这是 RGB。我们已经谈过了。

score_board = font.render(score_text,False,(255,255,255))

如果我们现在运行程序,我们什么也看不到,因为我们还没有在游戏循环中渲染记分牌。让我们现在做那件事。

screen.blit(score_board,(350,0))

将前面的代码放在 time.sleep 代码行的上方。

让我们运行我们的代码,我们将得到这个(图 21-7 )。

图 21-7

记分板

我们有记分牌了,耶!

杀死敌人

现在,让我们创建当子弹击中敌人时杀死敌人的代码行。对于循环的每一次迭代,我们将不断地寻找子弹和所有 21 个敌人之间的碰撞。

因此,让我们打开一个“for”循环来实现这一点。将这个放在游戏循环中,在你“blit”所有敌人的下方。

for i in range(21):

现在,我们需要碰撞条件。这很简单。如果子弹和敌人之间的距离(最左上角的位置)小于或等于 55,我们就有碰撞。这将覆盖从左上角到敌人其余部分的任何一点的子弹。

为此,让我们从敌人的坐标中减去子弹的坐标(因为它们在屏幕的底部,所以更高)。让我们得到这个减法的绝对值,这样无论两个字符在哪里,我们只得到我们需要的“差”值,没有符号。

if abs(bullet_x+12 - enemy_x[i]) <= 55 and abs(bullet_y - enemy_y[i])

为什么是 bullet_x+12?那是因为我们在那个“x”点“blit”子弹。

如果发生碰撞,我们需要将子弹带回原位,并使子弹运动值 bullet_d 为 0。

#bring bullet back to position
bullet_x = sp_x
bullet_y = sp_y
bullet_d = 0

让我们也把“fire”设为 False,因为我们已经发射完子弹了。它完成了我们派它去做的事情。

fire = False

现在,在同一个“if”语句中,让我们打开更多的 if 和 else 语句,将敌人带回原位(而不是移动)。它会在那个位置等待,直到当前集合中的所有敌人都被杀死,这样三排敌人再次形成。

还记得我们在定位敌人时使用的条件吗?让我们用同样的方法来定位他们,这样一旦 21 个敌人都被杀死,他们就可以出发了。

#bring enemy back to position
if i < 7:
    enemy_x[i] = 70 * i
    enemy_y[i] = -60
elif i < 14:
    enemy_x[i] = 70 * (i-7)
    enemy_y[i] = -120
else:
    enemy_x[i] = 70 * (i-14)
    enemy_y[i] = -180

最后,让敌人的移动值为 0,来停止它的移动(等待其余的加入它),并增加敌人计数 1。

enemy_d[i] = 0
enemy_count += 1

当子弹击中敌人时会发生什么?敌人死亡,回到原来的位置。子弹也会回到原来的位置,但是分数也会增加!

让我们接下来做那件事。让我们增加分数,重新渲染。

#increase score
score += 1
score_text = 'Score: {}'.format(score)
score_board = font.render(score_text,False,(255,255,255))

就这样!我们现在可以杀死敌人了。让我们看看它是否有效,好吗?(图 21-8

图 21-8

杀死敌人

哇哦!我们现在可以杀死我们的敌人,我们的分数也相应增加!

关掉太空船!

最后,让我们为飞船和敌人创造一个碰撞的条件,这样我们就可以结束游戏了。将这几行代码放在您编写敌人-子弹碰撞代码行的代码下面。

过程是一样的。对于我们的游戏循环的每一次迭代,我们将循环通过所有的敌人,并检查他们是否有一个击中了我们的飞船。

#Enemy-spaceship collision
for i in range(21):

碰撞条件将是飞船和敌人的 x 和 y 值之间的差值,如果它们小于或等于 50,游戏结束。

if abs(sp_x - enemy_x[i]) <= 50  and abs(sp_y - enemy_y[i]) <= 50:
    #game over

让“过”成真。如果 over 是真的,那么我们就不会把飞船和敌人(更不用说子弹)blit 到屏幕上了,记得吗?这意味着他们将从屏幕上消失,我们将只剩下记分牌。

#make everything disappear
over = True

现在让我们试试(图 21-9 )。

图 21-9

关掉太空船

是的,它工作了!

重新吸引敌人

在碰撞检查之后,我们需要检查用户是否杀死了所有的敌人。如果所有 21 个都从屏幕上消失了,我们需要将敌人计数值重置回 0,并使它们再次从屏幕顶部落下。

#Set enemy move condition
if enemy_count == 21:
    for i in range(21):
        enemy_d[i] = 0.5
    enemy_count = 0

让我们运行程序,并检查这是否可行(图 21-10 )。

图 21-10

重新吸引敌人

看那个!我们有了第二排敌人,现在我们的分数是 23!O:

游戏结束!

最后,让我们在敌人击中飞船时写下“游戏结束”。当“结束”为真时写“游戏结束”,这意味着发生了碰撞。

#Game over
if over == True:
    #Draw game over text

让我们创建一个新的游戏字体,字体类型为 Arial,字体大小为 80。让我们在我们想要的文本上呈现该字体。使颜色变白。最后,将它“blit”到屏幕上的位置 50,200(围绕屏幕的中心)。

game_over_font = pygame.font.SysFont('Arial',80)
game_over = game_over_font.render('GAME OVER',False,(255,255,255))
screen.blit(game_over,(50,200))

让我们运行我们的代码,我们得到这个(图 21-11 )。

图 21-11

屏幕上的游戏

哇哦!我们的游戏结束了!

很简单,不是吗?尝试一下,也许试着改进它(更多的级别,更多的难度,等等。).

整个代码

现在,这里是完整的代码,就像承诺的那样:

import pygame
import time

pygame.init()
pygame.font.init() #To use text

screen = pygame.display.set_mode((500,500))

pygame.display.set_caption('Space Invaders')

font = pygame.font.SysFont('Arial',40)

over = False #Game over
game = True #Closed the game window

#Create the spaceship
spaceship = pygame.image.load('spaceship.png')
sp_x = 250
sp_y = 390
sp_d = 0

#Create enemy
enemy = []
enemy_x = []
enemy_y = []
enemy_d = []

enemy_count = 0

#Position enemies - 3 rows of enemies
for i in range(21):
    #Row 1
    if i <= 6:
        enemy.append(pygame.image.load('enemy.png'))
        enemy_x.append(70 * i)
        enemy_y.append(-60)
        enemy_d.append(0.5)

    #Row 2
    elif i <= 13:
        enemy.append(pygame.image.load('enemy.png'))
        enemy_x.append(70 * (i-7))
        enemy_y.append(-120)
        enemy_d.append(0.5)

    #Row 3
    else:
        enemy.append(pygame.image.load('enemy.png'))
        enemy_x.append(70 * (i-14))
        enemy_y.append(-180)
        enemy_d.append(0.5)

#create the bullet
bullet = pygame.image.load('bullet.png')
#place it off the screen to start with
bullet_x = -100
bullet_y = -100
bullet_d = 0
fire = False

#Create scoreboard
score = 0
score_text = 'Score: {}'.format(score)
score_board = font.render(score_text,False,(255,255,255))

while game:
    #Close window condition - Quit
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game = False

        if event.type == pygame.KEYDOWN:
            #Make spaceship move
            if event.key == pygame.K_LEFT:
                sp_d = -1
            if event.key == pygame.K_RIGHT:
                sp_d = 1
            #Make bullet fire
            if event.key == pygame.K_SPACE:
                if fire == False:
                    fire = True
                    bullet_x = sp_x
                    bullet_y = sp_y
                    bullet_d = -2
        #Make spaceship stop if not moving
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                sp_d = 0

    screen.fill((0,0,0))

    #Spaceship move condition
    sp_x += sp_d

    #Fire bullet
    if fire == True and over == False:
        screen.blit(bullet,(bullet_x+12, bullet_y))
        bullet_y += bullet_d

    #bullet wall collision
    if bullet_y <= 0 and fire == True:
        bullet_x = sp_x
        bullet_y = sp_y
        bullet_d = 0
        fire = False

    if over == False:
        screen.blit(spaceship,(sp_x,sp_y))

    #Draw enemies and make them move
    for i in range(21):
        if over == False:
            #enemy wall collision
            if enemy_y[i] >= 500:
                enemy_y[i] = -60
            else:
                #Draw enemies
                enemy_y[i] += enemy_d[i]
                screen.blit(enemy[i],(enemy_x[i],enemy_y[i]))

        #Bullet-enemy collision
    for i in range(21):
        if abs(bullet_x+12 - enemy_x[i]) <= 55 and abs(bullet_y - enemy_y[i]) <= 55:
            #bring bullet back to position
            bullet_x = sp_x
            bullet_y = sp_y
            bullet_d = 0
            fire = False

            #bring enemy back to position

            if i < 7:
                enemy_x[i] = 70 * i
                enemy_y[i] = -60
            elif i < 14:
                enemy_x[i] = 70 * (i-7)
                enemy_y[i] = -120
            else:
                enemy_x[i] = 70 * (i-14)
                enemy_y[i] = -180

            enemy_d[i] = 0
            enemy_count += 1

            #increase score
            score += 1
            score_text = 'Score: {}'.format(score)
            score_board = font.render(score_text,False,(255,255,255))

    #Enemy-spaceship collision
    for i in range(21):
        if abs(sp_x - enemy_x[i]) <= 50  and abs(sp_y - enemy_y[i]) <= 50:
            #game over
            #make everything disappear
            over = True

    #Set enemy move condition
    if enemy_count == 21:
        for i in range(21):
            enemy_d[i] = 0.5
        enemy_count = 0

    screen.blit(score_board,(350,0))

    #Game over
    if over == True:
        #Draw game over text
        game_over_font = pygame.font.SysFont('Arial',80)
        game_over = game_over_font.render('GAME OVER',False,(255,255,255))
        screen.blit(game_over,(50,200))

    time.sleep(0.005)

    pygame.display.update()

pygame.quit()

摘要

在这一章中,我们用 Pygame 创建了一个太空射击游戏。我们在游戏中应用了前一章所学的知识,我们还学习了所有关于碰撞检测和在游戏屏幕上渲染文本的知识。

在下一章中,让我们来看看用 Python 进行 web 开发的概述。我们将简要介绍用 HTML 创建网页,用 CSS 设计网页,用 JavaScript 使网页动态化,以及在 Python 自己的 Flask 中创建第一个程序。

本文标签: 孩子们入门指南Python