Build a mouse game with Python
出自Full Circle 中文项目主页
现在,大多数的游戏仅开发就需几年的时间,还不包括大量的美工和多媒体技术花费时间,但是一些有趣的小游戏,程序员单枪匹马也能写成。比如,开发诸如Tetris(俄罗斯方块)的游戏肯定不需要500个码农,也不需要好莱坞式大手笔的美工和多媒体处理。Alexey Pajitnov就是其中的杰出代表人物,但在那些唯利是图的资本家插手之后另当别论了。
本篇将向我们展示如何用Python快速开发一个简单的鼠标小游戏,如果你第一次阅读此类编程指南,或之前从未用Python写过代码,你肯定会感叹原来编写游戏竟然如此简单。而Python向来以代码的简单明了而被人称道。
如果你对C语言或是PHP语言比较熟悉,你将会发现其实Python更简洁,比如,Python是以缩进来标示代码块的,而不是花括号:
def saystuff(somestring):
print "String passed: ", somestring
saystuff("Wowzers")
如果你是Python新手,首先确保你的系统已经安装了Python(有些系统默认安装Python,没有的话可以使用软件包管理工具安装)。然后打开编辑器输入以上代码并保存为test.py,接着启动终端并输入:
python test.py
不出意外的话,Python将打印一行信息。这个例子包含一个函数saystuff,它将打印传入的字符串,显然函数内的代码是以tab来缩进的。
代码从第一次调用saystuff开始执行,将Wowzers作为输入参数,就是这么简单,而且你现在就是在用Python写代码!
对了,除此以外还需要pygame模块。Pygame用Python作了SDL的封装,从而可以在程序中显示图片以及输出音乐。Pygame使用很广泛,可能在系统的包仓库中就能找到,否则就只能去www.pygame.org自行下载安装了。
弹力球
根据底层机制的不同,游戏可以分为多种类型,但是大多数游戏都是sprite(子图,图形对象)按照某个路径移动:
1、初始化屏幕,显卡,计数器等。
2、启动循环直到玩家退出或是游戏结束。
4、在屏幕上显示游戏画面。
4、捕捉玩家输入的操作(如按键、鼠标点击)
5、检查游戏逻辑(如玩家是否击中敌人)
6、并相应地刷新游戏画面。
7、循环回到第三步
接下来,我们将写一个小游戏,里面有几个小球,并且当小球碰到屏幕边缘就会反弹,而玩家的任务就是尽量避免鼠标碰到它们。这个看起来很容易,但是如果让它们随机移动的话——比如几个小球移动快慢不一样的时候——就有点儿难度了。
而将鼠标停在屏幕左下角是很难侥幸通关的,因为小球可能随时加速弹到到那儿。你需要全神贯注的盯着屏幕且动作很灵活。而计数器将记录你玩了多久。这个游戏大致就这样了。
但是首先,我们得解决实现小球碰到屏幕边缘能弹回的问题,小球什么时候应该弹回呢?就是要用两个变量来保存小球的运动。
每次循环都把这两个变量的值加到小球的当前位置中,如果小球向右移动,每循环一次都把水平位置的坐标变量+1,如果碰到屏幕边缘就将水平位置坐标变量-1,这样就向左运动了。
如果你怀疑这样的可行性,可以以下代码实验,在这里可以找到源代码。在运行代码之前,需要把ball.png放在同一目录下,这张图片是游戏中的小球,32x32像素,黑底,小球是白色。
如果你想自己制作ball.png,首先创建一个32x32像素的图片,然后用黑色填充,接着画圆并用白色填充,最后保存为ball.png,要和ball1.py在同一目录下,然后运行 python ball1.py。你当然也可以使用源代码中自带的小球图片。
from pygame import * # Use Pygame's functionality!
ballpic = image.load('ball.png')
done = False
ballx = 0 # Ball position variables
bally = 0
ballxmove = 1
ballymove = 1
init() # Start Pygame
screen = display.set_mode((640, 480)) # Give us a nice window
display.set_caption('Ball game') # And set its title
while done == False:
screen.fill(0) # Fill the screen with black (colour 0)
screen.blit(ballpic, (ballx, bally)) # Draw ball
display.update()
time.delay(1) # Slow it down!
ballx = ballx + ballxmove # Update ball position
bally = bally + ballymove
if ballx > 600: # Ball reached screen edges?
ballxmove = -1
if ballx < 0:
ballxmove = 1
if bally > 440:
ballymove = -1
if bally < 0:
ballymove = 1
for e in event.get(): # Check for ESC pressed
if e.type == KEYUP:
if e.key == K_ESCAPE:
done = True
现在我们分析一下这些代码。第一行告诉python我们需要使用Pygame库的功能,然后载入ball.png图片并命名为ballpic,接着声明一个布尔变量以判断游戏是否应该结束。
随后声明位置和运动方向变量,这四行相当重要。其中ballx和bally是记录小球在屏幕中的位置的,以屏幕左上角为原点,右下角为640,480。
ballxmove和ballymove用来记录每次循环时位置的增量。刚开始初始化为1,所以游戏刚开始时,ballx和bally每次循环都加一,于是小球向右下运动。即刚开始从左上角开始,沿对角线向右下角运动。
紧接着,创建一个Pygame窗口并且启动主循环,首先清屏,根据当前位置绘制小球(以#开始的语句为注释),下面的代码片段决定了小球的运动轨迹:
ballx = ballx + ballxmove
bally = bally + ballymove
if ballx > 600:
ballxmove = -1
if ballx < 0:
ballxmove = 1
if bally > 440:
ballymove = -1
if bally < 0:
ballymove = 1
前两行刷新小球的水平和垂直位置,如果ballxmove和ballymove为1,那么小球将向右和向下分别移动一个像素。
随后的if语句判断小球是否到达屏幕边缘,如果是就相应地改变ballxmove和ballymove的值。
例如,此时小球水平方向到达600像素处了,那么它就应该弹回,所以当前位置-1,也就是向左移动了一个像素。
通过这些代码,已经可以让小球在屏幕内移动了,效果不错吧!最后的几行代码用来处理键盘事件,这样当我们按ESC键的时候就可以退出游戏了。
这个弹球小游戏,虽然没有什么亮点,但这个小游戏已经初成雏形了。
弹力球游戏2.0版
现在为止,我们已经完成了基本的游戏框架,接着加入更多的小球,以及添加探测鼠标指针是否与小球碰撞的代码。对于前者,可以使用字典来记录所有的小球,这个具有很大的灵活性:比如我们可以很容易就拥有大量的小球,而不是用ball0,ball1,ball2等命名小球,那样的话太繁琐并且限制很大。而字典是Python内置类型,简单而功能强大:
mydict = {'Bach': 100, 'Handel': 75, 'Vivaldi': 90}
print mydict['Vivaldi']
上面,我们把三个单词和三个数字分别关联起来,然后输出'Vivaldi'的值,也就是90。我们将使用字典来保存小球的X,Y,X运动和Y运动的值,有点像C语言中的结构体。但是C中内存管理确实是个噩梦,然而在Python中根本不用考虑这个,直接创建我们的小球然后存入字典中。
最后考虑一下如何探测碰撞。如何知道鼠标指针和小球碰撞的话呢?从逻辑上来讲,想当然的做法就是每次都比较鼠标指针和所有小球的坐标。但是在这里我们可以投机取巧,注意小球是白色的,而背景是黑色的,所以简单的判断鼠标指针所在位置是否是白色就可以了,何乐而不为呢?同时这个只需要一行代码,而且速度很快……
下面是其代码,可以在这个工程的源码中找到,文件名为ball2.py,还有相应的ball.png图片:
from pygame import *
import random
ballpic = image.load('ball.png')
ballpic.set_colorkey((0,0,0))
numballs = 10
delay = 5
done = False
balls = []
for count in range(numballs):
balls.append(dict)
balls[count] = {'x': 0, 'y': 0, 'xmove': random.randint(1, 2), 'ymove': random.randint(1, 2)}
init()
screen = display.set_mode((640, 480))
display.set_caption('Ball game')
event.set_grab(1)
while done == False:
screen.fill(0)
for count in range(numballs):
screen.blit(ballpic, (balls[count]['x'], balls[count]['y']))
display.update()
time.delay(delay)
for count in range(numballs):
balls[count]['x'] = balls[count]['x'] + balls[count]['xmove']
balls[count]['y'] = balls[count]['y'] + balls[count]['ymove']
for count in range(numballs):
if balls[count]['x'] > 620:
balls[count]['xmove'] = random.randint(-2, 0)
if balls[count]['x'] < -10:
balls[count]['xmove'] = random.randint(0, 2)
if balls[count]['y'] > 470:
balls[count]['ymove'] = random.randint(-2, 0)
if balls[count]['y'] < -10:
balls[count]['ymove'] = random.randint(0, 2)
for e in event.get():
if e.type == KEYUP:
if e.key == K_ESCAPE:
done = True
if screen.get_at((mouse.get_pos())) == (255, 255, 255, 255):
done = True
print "You lasted for", time.get_ticks()/1000, "seconds!"
上述代码的思想基本上跟上一版一样,但是加入了一些有趣的代码。代码开始部分,在加载图片后,我们将它colorkey设置为(0, 0, 0),也就是黑色的RGB值,即小球图片中黑色像素设置为透明的。
小球很多时这个很重要,因为我们想让白色的小球在任何时刻任何情况下都能正常显示,即使多个小球有重叠。
后面的numballs和delay变量是用来控制游戏的难度的,显而易见numballs控制游戏中的小球数量,而delay决定每次循环时应该休眠多少毫秒。保持当前值就可以了,当然如果你想挑战更高难度的话可以相应的增加numballs和减小delay的值。
balls = [] 初始化一个ball对象数组,在Python中,这个数组是可变的,我们不需要设置数组大小。
for count in range(numballs):
这一行代码是个循环,每次循环都向balls数组中添加一个新的字典对象,并给字典对象赋初值:左上角屏幕坐标,向右下角运动的变化量,取值为1或2。这样我们便有了10个小球,并以一个随机速度开始移动。
接下来依旧是初始化屏幕,并且设置event.set_grab(1)从而将鼠标指针限制在游戏窗口内,这样就不能把鼠标移到窗口之外了。然后就是主循环,用黑色刷屏,并使用for循环显示所有的小球。
然后刷新屏幕并休眠,接着再遍历balls数组更新小球的位置以及运动速度。每个小球都拥有自己的xmove和ymove,所以它们是相互独立的。
紧随其后的就是游戏实现逻辑,即判断小球是否到达窗口的边缘。同时我们也做了一些修改,让小球可以越出窗口边缘一点点(因为小球是32x32像素的)。这个在游戏中是必须的,否则只要你让鼠标指针停留在任意一个角落就永远不会跟小球碰到!修改过后小球就可以到达窗口的任意位置,这样就避免了刚才的bug。
最后是三行新添加代码:screen.get_at()返回指定位置的像素值,这里调用mouse.get_pos()取得当前位置作为它参数。如果鼠标所在位置的像素值为(255,255,255)即白色的话,(鼠标和小球有碰撞)那么游戏结束。
游戏结束后调用time.get_ticks()输出玩家的存活时间,time.get_ticks()返回的是毫秒,所以要除以1000转换为秒。
当然还可以做的更好,比如多个小球最好能有光照效果,还有就是细化鼠标指针在像素级别上的精确性等等。
收尾
当然相对于55行代码来说,已经不错了。前面说过,如果感觉不过瘾你可以调节numballs的值来增加难度,其实默认的10已经很难了,但如果你是那种骨灰级的游戏发烧友,可以尝试挑战15或20。
这个游戏还有很多方面需要完善,比如小球每次到达窗口边缘时就改变随机值。
Pygame还有很多其他很有趣的功能,例如添加音效或是背景音乐并能随着游戏的节奏而改变,并且不多的几行代码就能搞定。www.pygame.org/docs/网站有各种文档可以增进你对Pygame的函数和功能的了解,同时它还提供API文档。
我曾经用过很多种语言编写游戏,从刚开始的Amiga Blitz Basic 到后来的 C#-SDL 以及现在的 Mono/.Net, 我可以很负责任的说Pygame是我见过的最享受的游戏编程环境,它可以很轻松的实现你的各种创意!心动不如行动,马上开始吧!
我希望有更炫的画面!
就前端显示来说最后这一版本并不是完善,然而我们可以通过增加背景图片来美化它,但是别忘了鼠标和小球碰撞的检测机制——寻找白色的像素,所以在背景图片中千万不要有白色像素(255,255,255 RGB),否则当鼠标经过时游戏就会终止。
首先确保图片大小为640x280,如果不确定图片中是否包含白色像素,可以用Gimp降低图像亮度来解决,然后将图片保存在ball2.py所在的目录下并命名为background.png。现在打开ball2.py输入下面这句代码:
backdrop = image.load('background.jpg')
这样背景图片的内容就载入了内存,直接使用就可以了。然后将后面的screen.fill(0)替换为scren.blit(backdrop, (0, 0))就可以显示背景图片了:
screen.blit(backdrop, (0,0))
显示时先是背景图片然后才是各个小球。注意如果背景图片特别复杂的话(比如包含很多种不同的颜色),那个blitting过程就会降低游戏的速度,不过可以通过调节小球的速度和delay的值来调整。

