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键的时候就可以退出游戏了。

Pygame1.jpg

这个弹球小游戏,虽然没有什么亮点,但这个小游戏已经初成雏形了。

弹力球游戏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转换为秒。

Pygame2.jpg

当然还可以做的更好,比如多个小球最好能有光照效果,还有就是细化鼠标指针在像素级别上的精确性等等。

收尾

当然相对于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的值来调整。


个人工具