你有没有想过游戏是如何开发的?其实视频游戏开发并没有你想想象的那么复杂。这里将介绍经典的游戏案例,就是所谓12岁少年开发的一个兔子和鼹鼠的简单游戏。由兔子充当英雄,打败一群鼹鼠来保卫城堡。
为了编写这个游戏,我们使用Python语言。Python语言是Arduino、Raspberry Pi和其它一些单芯片微控器的嵌入式开发语言。本文可以让初学者熟悉一些Python库的使用方法,而这些库文件在嵌入式项目开发时同样会用到。Python语言入门简单,易学好用。
Python学习交流群:835017344,这里是python学习者聚集地,有大牛答疑,有资源共享!有想学习python编程的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。
安装Python
为了能够在电脑上测试本教程,需要安装Python 2.7(并非3.3.0)。Windows平台直接运行安装程序,安装完成后会得到Python的IDLE(集成开发环境)。
如果你使用的是Mac电脑,那么Python默认已经安装了。打开终端程序,在Python提示符下,可以测试一下Python是否正确安装。输入”print 1+1″并回车,如果显示的结果是2,则说明Python已经正确安装了。
如果Python工作正常,那么就安装PyGame用于Python的游戏开发。PyGame是Python的游戏开发库,可以让游戏开发更加简单。这个库提供了图像处理以及音频回放等游戏开发中会用到的一些常用函数。PyGame安装成功后,会在Python的程序目录c:\python27\lib\site-packages增加一个pygame目录。
从文件运行Python代码
如果你想开发一个大一点的程序(例如游戏),你就应该将你的代码保存在一个文件中,这样可以避免重复输入代码。
有很多方法可以编写Python代码,其中一个方法就是使用例如Windows平台的记事本或者Mac电脑的TextEdit。输入”print 1+1″后,将文件保存为1.py(1是示例文件名,你可替换成自己喜欢的文件名)。
在Windows平台,直接双击这个文件就可以执行。而如果是Mac电脑,需要打开终端,输入python命令,然后将1.py拖入终端窗口,再按回车执行程序。需要说明的是,Windows平台在运行程序时会一闪而过,马上关闭窗口,在代码最后加入一条input()语句,窗口就不会自己关闭了。
另一种方法是在IDLE编辑器中输入代码,这也是我们将要采用的方法。在Windows平台中,运行IDLE(Python GUI)程序,就可以打开可视化开发工具。首先会启动Python的shell程序,然后在”File”菜单中选择”New File”点击,会进入编辑模式,你就可以使用这个编辑器来编辑自己的代码了,代码输入完成后记得保存。最后可以按F5进入运行模式,来查看程序运行结果是否正确。注意到开始启动的shell窗口是没有run菜单的,只有在编辑器里才有run菜单。示例如下图所示:
添加资源文件
现在你几乎可以开发你的游戏了,但是一个游戏怎么能够没有漂亮的画面和动人的音效呢。本教程示例游戏所需的全部资源文件会在教程末尾提供下载。
在你的硬盘中新建一目录,用于本游戏的开发,然后在这个目录中新建一个名叫Resources的子目录。将资源文件中的”audio”和”images”目录分别拷贝到这个子目录中即可。
软件开发
好了,首先在屏幕上创建一个小兔子的形象。打开IDLE,新建一个编辑文本并输入如下代码:
<pre class="prettyprint hljs awk" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># 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)</pre>
将这段代码保存在游戏文件目录,并命名为game.py。
现在,我们对代码进行讲解。
1-Import library程序段是用来加载pygame库,从而可以在我们的程序中使用其各种函数。
2 – Initialize the game程序段是用来初始化pygame,并新建一个640*480的pygame窗体。
3 – Load images程序段是新建一个pygame的对象,并给它加载一个小兔子的图片
4 – Keep looping through的While语句用于保持只下缩进代码的循环运行
5 – Clear the screen before drawing it again程序段是用来清屏的,调用fill()函数用其参数值对应的颜色来填充屏幕,达到清屏的效果,本例是使用黑色。
6 – Draw the screen elements程序段是在屏幕上构建一个元素,本段程序达到的功能就是在从窗口左上点为原点,x轴(向右)方向100像素、y轴方向(向下)100像素的位置添加小兔子的图片至屏幕。
7 – Update the screen程序段是调用flip()函数更新窗体的显示内容。
8 – loop through the events程序段是用来监测用户事件的,也是程序的主要部分。本程序调用了一个循环来监测用户事件,根据不同的事件来做不同的处理。目前代码只对退出事件做了处理。
可以试着运行这段代码,如果一切顺利的话,会显示一个有一只兔子的窗口。如下图1所示:
图1
添加背景
通过更多的screen.blit()函数调用,可以给游戏添加背景。
在#3号程序段末尾添加如下程序代码,将草地和城堡的图片加载。
<pre class="prettyprint hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">grass = pygame.image.load ("resources/images/grass.png")
castle = pygame.image.load ("resources/images/castle.png")</pre>
这两条语句是将草地和城堡的图片载入,并把他们放到特定的变量中。请注意这里草地图像的分辨率很小,不足以覆盖整个屏幕区域,因为游戏窗体大小是640*480。
将如下代码添加到程序的#6部分,根据逻辑关系最好添加在player对象之前,毕竟是先有背景再有游戏角色比较合理。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">for x in range(width/grass.get_width()+1):
for y in range(height/grass.get_height()+1):
screen.blit(grass,(x100,y100))
screen.blit(castle,(0,30))
screen.blit(castle,(0,135))
screen.blit(castle,(0,240))
screen.blit(castle,(0,345 ))</pre>
正如你看到的一样,这里采用了”for”循环,首先在X轴方向依次沿y轴向下添加草地图片,最后草地图片填满整个窗体屏幕。后面的几行代码是将城堡的图片添加至屏幕。如果一切都工作正常,那么会得到图2所示的效果。
图2
让小兔子动起来
接下来,是给游戏程序添加一些实际的游戏元素,比如让兔子对按键进行响应。要做到这点,一个比较实用的方法就是实时监视哪个按键被按下。一个比较简单的方法是创建一个按键状态序列,用于响应你想在游戏中使用的按键。
将如下代码添加到游戏程序的#2部分的窗口初始化之后:
<pre class="hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">keys = [False, False, False, False]
playerpos=[100,100]</pre>
这段代码比较容易理解,按键列表将记录每个按键的状态。我将使用”WSAD”四个按键来控制兔子的动方向,W为向上、S为向下、A为向左和D为向右。
playerpos变量保存了每次移动后重新构建player对像的位置,也就是移动后小兔子的位置。
现在你需要修改程序代码,并且使用playerpos变量。修改程序代码中的屏幕绘制部分为如下代码:
<pre class="hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">screen.blit(player, (100,100))</pre>
修改为
<pre class="hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">screen.blit(player, playerpos)</pre>
更新按键被按下的元组数据,pygame让检测按键状态变得很简单,只需要在事件类型中加入pygame.KEYDOWN和pygame.KEYUP事件,并且在每个事件中增加event.key项等于对应的按键值即可。将如下代码添加到事件响应部分#8的pygame.QUIT事件之后,注意程序的缩进不要出错。
<pre class="prettyprint hljs vbnet" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>
这段程序的作用是检查对应按键是否被按下或释放。如果相应按键被按下或释放,则会更新keys元组中的相应状态值。
最后,需要更新playerpos的值来对按键动作进行响应。这也比较简,将下面的代码加入到程序代码中,注意这段代码应该与”for”循环处于同一等级。
<pre class="prettyprint hljs perl" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># 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</pre>
这段代码是用来检测按键状态,并根据按键对player对象在新的位置进行重新绘制。
运行程序试一下,会发现界面同原来的程序一致,按”WSAD”键进行测试,如果小兔子动可以移动,说明目前为止程序工作正常。
用鼠标转动兔子
你的兔子现在可以通过按键控制上下左右移动,但是它能够根据鼠标点的位置来调整它的朝向么?这点用三角函数就很容易解释。看一下图3插图对于这点的解释。
图3
如上图所示,如果(5,3)点是兔子的位置,而(2,4)是鼠标的位置。那么你就很容易得出两个点间的旋转角度z是两点间相应差值的反正切函数值。当然,在你知道了这个角度后,你就很容易根据这个角度来旋转兔子了。
你将会在游戏编程中经常用到上述内容。使用pygame的表面旋转rotate(degrees)函数。注意这个z值是弧度值,使用的atan2函数来自于python的标准数学库。
在程序的第一部分#1将数学库引入,用以下代码替换#6部分中的player.blit内容。
<pre class="prettyprint hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># 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)</pre>
上面的代码中,首先是取得鼠标的位置和player对象的位置值,以便进行atan2函数运算。
然后,将atan2函数计算出来的弧度值转换成角度值(弧度与角度的转换公式是弧度值乘以57.29或者乘以360/2Pi)。
由于兔子一旋转,那么它的位置将发生变化,所以需要计算兔子的新位置并在屏幕上重新显示它。其中用来计算旋转后兔子位置的算法比较难于理解,其实其中的数学道理并不是那么难,你只需要用相对坐标来思考。关键是看图像的中心,因为这个坐标不会因旋转而改变。
pygame.transform.rotate函数会旋转一个对象,并返回一个旋转后的对像。由于旋转后的图像具有width和height属性,可以使用get_rect方法获得。现在使用原x值减去旋转图像的宽度一半以及原y值减去旋转图像高度一半的计算方法就可以保证显示出来的图像中心点保持不变。最后用blit方法将旋转后的对像显示出来就达到目的了。
再运行一次程序,现在小兔子不仅可以响应 “WSAD”进行移动,同时还会根据鼠标位置进行转动。