[翻译]Python游戏编程入门


自己学习翻译,原文链接

此文为Tutorial Team成员 Julian Meyer, 一个13岁的Python开发者所写. 你可以去Google+或者Twitter找到他.

你有没有想过,一个计算机游戏如何编写的?它并不像你想的那么复杂!

这篇教程将教你编写一个小游戏,叫做兔子与獾(Bunnies and Badgers), 游戏内容是英雄兔子必须修筑城堡,防御獾群的攻击。:O

这个游戏用Python编写. 这里的Python并不是指蟒蛇哦! :]

Python是一门计算机编程语言. 我们选择Python来写该教程,因为它简单,有趣并且容易学习。

如果你是一名Python新手,先阅读Think Python: How to Think Like a Computer Scientist这本书,将加快你的学习速度。

然后,再回到这里——兔子与獾之间的战斗即将开始,让我们准备好随时进入战斗吧!

开始之前:安装Python    


如果你使用的是Windows操作系统,必须先安装Python。注意,安装版本是2.7.3,而不是3.3.0!安装好后,在开始菜单找到IDLE(Python GUI), 并启动。

如果你的系统是Mac,系统已经预装Python!只需要打开终端(/Applications/Utilities/Terminal.app),输入python,并按下Enter键。

笔记:如果你自己从Python官网下载并安装了程序(为了保证PyGame正常运行),确定Python启动的文件夹为"/Applications/Python 2.7"。

正确安装后,启动Python会看到如下信息:

Python 2.7.3 (v2.7.3:70274d53c1dd, Apr  9 2012, 20:52:43) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

笔记:输入exit(),回车, 或者按Control+D, 可以退出Python提示符环境(尖括号提示符,>>>)。

在Python提示符界面,输入 print 1+1,并按回车,屏幕输出2,程序运行正常!你也刚刚写下你的第一个Python程序!

Python正常工作后,你必须安装PyGame,才能开发游戏。

PyGame是一个方便游戏编程的模块!它提供了图片操作和声音回放函数,可以非常方便的整合到游戏中。

这里下载PyGame程序安装包,注意下载的是python 2.7版本。

笔记:上面链接下载的PyGame与Mac默认安装的Python并不兼容,你必须从Python官网重新下载并安装Python,或者通过MacPorts安装python和PyGame。

验证一下PyGame是否安装正确,在Python运行提示符(>>>)后面输入 import pygame并回车,没有返回任何输出,说明安装正确。

相反,如果输出下面的提示,说明PyGame没有正确安装。

Python 2.7.2 (default, Jun 20 2012, 16:23:33) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygame
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named pygame
>>>

如果安装失败,在论坛上面截图提问,我会回到你的问题。

运行文件中的Python代码

通过提示符环境,可以运行比较剪短的Python代码,如果想要运行大型一点的程序(例如游戏),需要将代码保存到一个文件中,否则你会一遍又一遍的重复输入代码。

这里有几种运行Python文件的方法。一,通过文本编辑器,例如Notepad(Windows),或者TextEdit(Mac).打开一个新的文本文件,输入Python代码(例如,print 1+1),然后保存为XXX.py

在Windows系统下,双击改文件可以运行它。在Mac系统,打开终端,输入python ,将你保存的文件拖到终端窗口,并按Enter。

另一种方法,是在IDLE编辑器中输入,这篇教程中,我们将用此方法。运行IDLE,选择菜单File>New Window,会打开一个新的文本编辑器,在文本编辑器中输入代码,选择菜单File>Save,来保存文件,或者选择Run>Run Module (F5),来运行代码。

请记住,Run菜单仅出现在文本编辑器上面。

添加游戏资源

你已经可以创建自己的游戏了,但在此之前,游戏里面怎么可以没有好看的图片和声音效果呢?我讲所有的图片和声音打包成一个ZIP文件,你可以在这里下载

 下载文件后,在电脑新建一个文件夹,并将下载的压缩包解压到该文件夹下,解压后会多出一个resources的子文件夹,内容如下图展示:

现在正式开始编写兔子与獾。 :]

 第一步:你好,兔子


运行Python IDLE,在新编辑窗口输入下面这段代码:

# 1 - Import library
import pygame
from pygame.locals import *
 
# 2 - Initialize the game
pygame.init()
width, height = 640, 480
screen=pygame.display.set_mode((width, height))
 
# 3 - Load images
player = pygame.image.load("resources/images/dude.png")
 
# 4 - keep looping through
while 1:
    # 5 - clear the screen before drawing it again
    screen.fill(0)
    # 6 - draw the screen elements
    screen.blit(player, (100,100))
    # 7 - update the screen
    pygame.display.flip()
    # 8 - loop through the events
    for event in pygame.event.get():
        # check if the event is the X button 
        if event.type==pygame.QUIT:
            # if it is quit the game
            pygame.quit() 
            exit(0)

将文件保存到你的文件夹,并命名为 game.py

下面分析一下代码:

  1. 导入PyGame库,以在程序中使用PyGame提供的函数。
  2. 初始化游戏,并设置窗口。
  3. 加载兔子图片。
  4. while循环

笔记:当其他编程语言,例如Objective-C, Java或者PHP用大括号将while循环和if语句执行的代码括起来,Python使用缩进表示代码块。所以,对齐的缩进对Python来说非常重要 - 请牢记这点。 :]

  1. 将背景填充成黑色
  2. 添加一直兔子在屏幕上,坐标为(100,100)
  3. 更新屏幕
  4. 监听是否有新的事件产生,如果新事件为关闭屏幕,则退出程序。

笔记:根据PyGame官方文档,你不必调用 pygame.quit(),当编译器关闭的时候会自动调用,但是,在Mac系统上游戏会一直运行,直到 pygame.quit()被调用。

运行代码,看到下面的窗口:

好啦,兔子已经在屏幕上准备开始战斗了!

但是一只兔子站在黑漆漆的背景上面,画面看起来既弱又单调,现在我们想办法让它更好看一些。:]

 第二步:丰富画面


增加多一些背景元素,可以通过调用screen.blit()完成。

Let’s start by adding a background to the game scene. This can be done with a couple more screen.blit()calls.

在#3,加载兔子之后,增加下面的代码

grass = pygame.image.load("resources/images/grass.png")
castle = pygame.image.load("resources/images/castle.png")

加载图片,并将其放入变量。接下来可以将它们绘制在屏幕上,但是,如果你检查草地的图片,会发现,它并不能覆盖整个屏幕,因此,需要循环覆盖整个屏幕。

将下面的代码添加到#6开始的地方(绘制兔子之前):

    for x in range(width/grass.get_width()+1):
        for y in range(height/grass.get_height()+1):
            screen.blit(grass,(x*100,y*100))
    screen.blit(castle,(0,30))
    screen.blit(castle,(0,135))
    screen.blit(castle,(0,240))
    screen.blit(castle,(0,345 ))

如你所见,代码第一个for循环覆盖屏幕的宽度,第二个for循环覆盖屏幕的高度。其余的代码,绘制4个城堡在屏幕上。

运行代码后,看到下面的窗口:

不错 - 开始好看了!:]

第三步:让兔子移动


接下来添加一些真正的游戏元素,例如让兔子响应按键。

为了这样做,首先得想一个办法跟踪特定时候按动了哪个按键。你可以简单的创建一个按键状态列表——它包含游戏中用到的每一个按键状态。

添加下面的代码到#3结束的地方(设置屏幕高度和宽度的后面)。

keys = [False, False, False, False]
playerpos=[100,100]

它的作用一看就明白了。keys按顺序跟踪【WASD】是否被按下。列表每个元素代表一个按钮,第一个代表W,第二个代表A...

playerpos变量代表兔子的坐标。游戏中,兔子移动到其他不同的地方,只需要讲兔子的坐标保存在变量里面,然后再新的坐标重新绘制即可。

现在修改一下之前的代码,用新的坐标变量绘制兔子,在#6里面,将

screen.blit(player, (100,100))

改成

 screen.blit(player, playerpos)

下面,更新keys代表那个按钮。PyGame提供event.key函数,可以很方便的监测按钮是否按下。

在#8最后,在event.type==pygame.QUIT后面,输入下面的代码(与pygame.QUIT对齐):

        if event.type == pygame.KEYDOWN:
            if event.key==K_w:
                keys[0]=True
            elif event.key==K_a:
                keys[1]=True
            elif event.key==K_s:
                keys[2]=True
            elif event.key==K_d:
                keys[3]=True
        if event.type == pygame.KEYUP:
            if event.key==pygame.K_w:
                keys[0]=False
            elif event.key==pygame.K_a:
                keys[1]=False
            elif event.key==pygame.K_s:
                keys[2]=False
            elif event.key==pygame.K_d:
                keys[3]=False

哇!看起来很多代码呢,但你把它按if分开看,并不复杂。

首先检查某个按钮是否按下。然后,哪个按钮被按下,是不是游戏中用到的按钮,并根据其状态更新keys变量。

最后,更新plasyerpos变量以响应按键。是不是很简单。

添加下面的代码在game.py最后(缩进一层):

    # 9 - Move player
    if keys[0]:
        playerpos[1]-=5
    elif keys[2]:
        playerpos[1]+=5
    if keys[1]:
        playerpos[0]-=5
    elif keys[3]:
        playerpos[0]+=5

这段代码检查哪个按钮被按下,并根据按键改版兔子的x,y坐标,移动兔子的位置。

运行代码看到之前的画面,尝试按下【WASD】,是的,它动了!

第四步:让兔子旋转


是的,现在兔子已经可以根据按键移动了,但是,如果它能朝着鼠标点击的方向移动,而不是一直往同一个方向移动,是不是会更酷呢?用三角学可以很容易实现。

看下面的图解

上面的图片中,如果(5,3)是兔子的位置,而(2,4)是鼠标现在的位置,你可以计算出夹角(z),通过atan2三角函数,知道了夹角,很容易可以知道兔子应该往哪个方向移动。:]

如果对这部分不是很明白,没关系——不会影响你的下一步。但这是你需要使用Math类的原因。在游戏编程当中,你经常需要使用到它。

现在将上面的内容应用到游戏当中。你可以使用PyGame提供的Surface.rotate(degrees)函数实现它。请记住,这里的Z值为弧度。

 atan2函数在python math 库中。所以,添加下面的代码到#1的开始:

import math

然后,用下面的代码替换#6的最后一行(兔子绘制代码):

   # 6.1 - Set player position and rotation
    position = pygame.mouse.get_pos()
    angle = math.atan2(position[1]-(playerpos[1]+32),position[0]-(playerpos[0]+26))
    playerrot = pygame.transform.rotate(player, 360-angle*57.29)
    playerpos1 = (playerpos[0]-playerrot.get_rect().width/2, playerpos[1]-playerrot.get_rect().height/2)
    screen.blit(playerrot, playerpos1)

让我们看一下上面的代码。首先,你会得到鼠标和兔子的位置,然后将它们带入atan2函数,得到角度。然后将角度转换为弧度(乘以弧度近似值57.29或者360/2pi)。

随着兔子的旋转,它的坐标会改变。你需要计算出它新的坐标,并重新绘制在屏幕上。

再次运行程序。如果你只按下【WASD】,程序还是像之前一样,但是移动鼠标,会发现兔子转动了,Cool!

第五步:射箭,兔子,快射!


兔子可以转向四周了,现在可以增加更多动作了。向敌人射箭如何?这可不是一直温柔的兔子!

这步稍微有点复杂,因为你要跟踪所有的箭,更新、旋转、出屏幕时删除。首先,在初始化那一节#2,的最后增加变量。

acc=[0,0]
arrows=[]

第一个变量保存兔子的准确度,第二个变量保存被跟踪的箭的数量。准确度包括了两个数字,射箭的数量和獾被击中的数量。后面我们会根据这两个数字,计算出准确度的百分比。

先加载箭的图片到#3最后:

arrow = pygame.image.load("resources/images/bullet.png")

当用户点击鼠标,应该将箭射出。添加下面的代码到#8最后,处理新的事件。

        if event.type==pygame.MOUSEBUTTONDOWN:
            position=pygame.mouse.get_pos()
            acc[1]+=1
            arrows.append([math.atan2(position[1]-(playerpos1[1]+32),position[0]-(playerpos1[0]+26)),playerpos1[0]+32,playerpos1[1]+32])

这段代码检查鼠标是否被按下,获取鼠标位置,并根据兔子的位置、角度和鼠标的位置,计算出箭的角度。这个角度保存在arraws变量中。

    # 6.2 - Draw arrows
    for bullet in arrows:
        index=0
        velx=math.cos(bullet[0])*10
        vely=math.sin(bullet[0])*10
        bullet[1]+=velx
        bullet[2]+=vely
        if bullet[1]<-64 or bullet[1]>640 or bullet[2]<-64 or bullet[2]>480:
            arrows.pop(index)
        index+=1
        for projectile in arrows:
            arrow1 = pygame.transform.rotate(arrow, 360-projectile[0]*57.29)
            screen.blit(arrow1, (projectile[1], projectile[2]))
vely 和 velx 根据三角函数计算得到,10是箭的速度。如果只检查箭是否被射出,箭会消失。第二个for循环按箭的角度逐个绘制出它们。

尝试运行程序。当你点击鼠标的时候,兔子会射箭了!

第六步:发动攻击吧!獾!


现在你又了城堡,有了会移动并且射击的战士,但是不是少了什么?对,向城堡发动攻击并向它们射箭的敌人。

这一步中,我们会随机产生一些跑向城堡的獾。獾的数量会越来越多。所以,我们必须做下面几件事情:

  1. 将獾添加到一个数组中。
  2. 更新獾的数组,并检查它们是否被击中。
  3. 重新绘制獾。

是不是很简单?

首先,天剑下面的代码到#2的最后:

badtimer=100
badtimer1=0
badguys=[[640,100]]
healthvalue=194

上面设置了一个计时器(和一些其它值),这样随着游戏的进行,会增加一只新的獾。

The above sets up a timer (as well as a few other values) so that the game adds a new badger after some time has elapsed. You decrease the badtimer every frame until it is zero and then you spawn a new badger.

添加下面的代码到#3最后:

badguyimg1 = pygame.image.load("resources/images/badguy.png")
badguyimg=badguyimg1

第一行和之前加载图片的代码是类似的。第二行做多了一个备份,这样你可以更容易的让它移动起来。

下一步,更新并绘制獾,添加下面的代码到#6.2后面:

    # 6.3 - Draw badgers
    if badtimer==0:
        badguys.append([640, random.randint(50,430)])
        badtimer=100-(badtimer1*2)
        if badtimer1>=35:
            badtimer1=35
        else:
            badtimer1+=5
    index=0
    for badguy in badguys:
        if badguy[0]<-64:
            badguys.pop(index)
        badguy[0]-=7
        index+=1
    for badguy in badguys:
        screen.blit(badguyimg, badguy)

这是很大一段代码,代码的第一行检查计时器是否为0,如果是新建一直獾并根据计时器运行的时间重新设置计时器。第一个循环更新獾的x坐标,检查獾是否离开屏幕,如果离开屏幕从列表里面删除。第二个循环绘制獾群。

为了运用随机函数,同样需要加载random模块。所以,把下面的代码加到#1的最后:

import random

 最后把下面这行代码放到while后面(#4), 这样每一帧计时器都会减少:

badtimer-=1

试一下运行所有的代码。现在它看起来更像一个游戏了——你可以射箭,左右移动,獾群会不停的向你移动。

但是,獾群怎么直接穿过城堡了?我们再加一些代码...

把下面的代码放在 index += 1前面,#6.3的第一个for循环:

        # 6.3.1 - Attack castle
        badrect=pygame.Rect(badguyimg.get_rect())
        badrect.top=badguy[1]
        badrect.left=badguy[0]
        if badrect.left<64:
            healthvalue -= random.randint(5,20)
            badguys.pop(index)
        # 6.3.3 - Next bad guy

这段代码非常简单。如果獾距右边的距离小于64,就删掉这只并减少游戏的生命值,随机减少5~20之间的一个数字。(一会儿会显示目前的生命值到屏幕上。)

如果这个时候运行代码,会看到袭击到城堡的獾消失了,虽然看不见,但獾减少了生命值。

第七步:让箭击中獾


獾袭击城堡,但射箭对它们没有效果!这样兔子如何保卫它的家园呢?

现在让箭射中獾,这样才可以保卫城堡,并且赢得游戏!可以说,你必须循环所有的獾,并且在嵌套循环每只箭,检查箭是否击中獾。如果击中,删除獾和箭,并且增加兔子的准确率。

在#6.3.1后面加上下面的代码:

        #6.3.2 - Check for collisions
        index1=0
        for bullet in arrows:
            bullrect=pygame.Rect(arrow.get_rect())
            bullrect.left=bullet[1]
            bullrect.top=bullet[2]
            if badrect.colliderect(bullrect):
                acc[0]+=1
                badguys.pop(index)
                arrows.pop(index1)
            index1+=1

 在上面的代码中,有一点必须提一下。if语句是一个PyGame的内建函数,它检查两块面积是否有重合。其它的代码和我上面说的一样。

如果再运行代码,会发现可以射杀獾了。

第八步:添加一个HUD,显示生命值和时间


游戏编写的差不多了,有了进攻和防御。现在需要添加记分方法和兔子成绩了。

最简单的方法是添加一个HUD (Heads Up Display) ,显示城堡的生命值。也可以加多一个时间,显示游戏进行了多长时间。

先增加时间。将下面的代码加到#7的前面:

 # 6.4 - Draw clock
    font = pygame.font.Font(None, 24)
    survivedtext = font.render(str((90000-pygame.time.get_ticks())/60000)+":"+str((90000-pygame.time.get_ticks())/1000%60).zfill(2), True, (0,0,0))
    textRect = survivedtext.get_rect()
    textRect.topright=[635,5]
    screen.blit(survivedtext, textRect)

上面的代码建了一个PyGame自带的数据格式,范围为24. 它用来记住时间文本,然后再将文本绘制到屏幕上。

The above code simply creates a new font using the default PyGame font set to size 24. Then that font is used to render the text of the time onto a surface. After that, the text is positioned and drawn onscreen.

接下来增加生命进度条。绘制之前,需要先加载进度条图片。将下面的代码加到#3后面:

healthbar = pygame.image.load("resources/images/healthbar.png")
health = pygame.image.load("resources/images/health.png")

第一个是红色的图片,表示满血状态。第二个是绿色的图片,用来表示目前失去的生命值。

增加下面的代码在#6.4后面,画出生命进度条:

# 6.5 - Draw health bar
    screen.blit(healthbar, (5,5))
    for health1 in range(healthvalue):
        screen.blit(health, (health1+8,8))

先画出了整条红色的生命条。再根据剩余的生命值,用一定数量的绿色将其覆盖。

再运行代码,会看到计时器和生命条。

第九步:胜利或者失败


但这是怎么回事?不管你玩多久游戏,即使生命值为0,游戏还在进行!不仅如此,你还可以继续击杀还。生命条还没有起作用,需要制定一套规则判断游戏胜负。

所以,让我们添加胜利、失败条件和相应界面。这样做需要修改现在的循环,根据条件判断玩家输赢,并在屏幕上展示。

这里有一个输/赢的基本规则:

如果时间耗尽(90000毫秒 或者 90秒):

  • 游戏结束
  • 设置结果为 1 或者 胜利

如果城堡被摧毁:

  • 游戏结束
  • 设置结果为 1 或者胜利

If time is up (90000 ms or 90 seconds) then:

  • Stop running the game
  • Set outcome of game to 1 or win

If the castle is destroyed then:

  • Stop running game
  • Set outcome of game to 1 or win

两种方法计算射箭准确度:

笔记:acc[0]*1.0将acc[0]转化为浮点型。如果不这样做,除法将返回一个整数,而不是小数。

添加下面的代码到game.py最后:

#10 - Win/Lose check
    if pygame.time.get_ticks()>=90000:
        running=0
        exitcode=1
    if healthvalue<=0:
        running=0
        exitcode=0
    if acc[1]!=0:
        accuracy=acc[0]*1.0/acc[1]*100
    else:
        accuracy=0
# 11 - Win/lose display        
if exitcode==0:
    pygame.font.init()
    font = pygame.font.Font(None, 24)
    text = font.render("Accuracy: "+str(accuracy)+"%", True, (255,0,0))
    textRect = text.get_rect()
    textRect.centerx = screen.get_rect().centerx
    textRect.centery = screen.get_rect().centery+24
    screen.blit(gameover, (0,0))
    screen.blit(text, textRect)
else:
    pygame.font.init()
    font = pygame.font.Font(None, 24)
    text = font.render("Accuracy: "+str(accuracy)+"%", True, (0,255,0))
    textRect = text.get_rect()
    textRect.centerx = screen.get_rect().centerx
    textRect.centery = screen.get_rect().centery+24
    screen.blit(youwin, (0,0))
    screen.blit(text, textRect)
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit(0)
    pygame.display.flip()

这这最长的一段代码,但它并不复杂。

第一个if 语句检查时间是偶用完。第二个if语句检查城堡是否被摧毁。第三个计算精确度。接下来的if语句检查你是否赢得游戏,并正确展示画面。

当然,想要展示胜利、失败的图片到屏幕上,需要先将其加载。所以,添加下面的代码到#3的最后:

gameover = pygame.image.load("resources/images/gameover.png")
youwin = pygame.image.load("resources/images/youwin.png")

同时,修改将#4的代码

# 4 - keep looping through
while 1:
    badtimer-=1

为:

# 4 - keep looping through
running = 1
exitcode = 0
while running:
    badtimer-=1

running变量标识游戏是否继续执行,exitcode标识游戏胜利或者失败。再次运行程序,尝试一下完成游戏和失败!

第十步:音乐和音效!


游戏看起来已经很棒了,如何让它有声音呢?它太安静了!添加一些声音可以让游戏的感觉完全不一样。

PyGame添加声音也非常的简单。首先你需要把下面的代码添加到#2最后,初始化mixer。

pygame.mixer.init()

然后加载声音文件,并设置音量大小,在#3添加下面的代码:

# 3.1 - Load audio
hit = pygame.mixer.Sound("resources/audio/explode.wav")
enemy = pygame.mixer.Sound("resources/audio/enemy.wav")
shoot = pygame.mixer.Sound("resources/audio/shoot.wav")
hit.set_volume(0.05)
enemy.set_volume(0.05)
shoot.set_volume(0.05)
pygame.mixer.music.load('resources/audio/moonlight.wav')
pygame.mixer.music.play(-1, 0.0)
pygame.mixer.music.set_volume(0.25)

上面大部分代码都是加载声音和设置音量。注意pygame.mixer.music.load 一行,它加载了背景音乐,并且下一行设置其为无限循环。

上面做了一些声音的配置,下面在需要的地方加上一些音效。按下面注释的位置加上代码:

# section 6.3.1 after if badrect.left<64:
hit.play()
# section 6.3.2 after if badrect.colliderect(bullrect):
enemy.play()
# section 8, after if event.type==pygame.MOUSEBUTTONDOWN:
shoot.play()

运行代码,现在游戏有了背景音乐和音效,当射击和击中时,游戏更加有趣了!

原文地址:https://www.cnblogs.com/zhaoxy/p/4953871.html