自己学习翻译,原文链接。
此文为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这本书,将加快你的学习速度。
然后,再回到这里——兔子与獾之间的战斗即将开始,让我们准备好随时进入战斗吧!
如果你使用的是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文件的方法。一,通过文本编辑器,例如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
下面分析一下代码:
笔记:当其他编程语言,例如Objective-C, Java或者PHP用大括号将while循环和if语句执行的代码括起来,Python使用缩进表示代码块。所以,对齐的缩进对Python来说非常重要 - 请牢记这点。 :]
笔记:根据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]))
尝试运行程序。当你点击鼠标的时候,兔子会射箭了!
现在你又了城堡,有了会移动并且射击的战士,但是不是少了什么?对,向城堡发动攻击并向它们射箭的敌人。
这一步中,我们会随机产生一些跑向城堡的獾。獾的数量会越来越多。所以,我们必须做下面几件事情:
是不是很简单?
首先,天剑下面的代码到#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 (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秒):
如果城堡被摧毁:
If time is up (90000 ms or 90 seconds) then:
If the castle is destroyed then:
两种方法计算射箭准确度:
笔记: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()
运行代码,现在游戏有了背景音乐和音效,当射击和击中时,游戏更加有趣了!