Python基础够扎实了,不妨挑战一下二十分钟开发一款游戏!

一、Python及Pygame简介

作为一款开源的程序设计语言,Python有着众多优势。凭借着越来越丰富、功能越来越强大的扩展库,Python在各个领域越来越突出:数据分析(R语言)、矩阵运算(Matlab)、深度学习……Python的强大正在于它有着如此多的外部扩展库,你可以只关注你感兴趣的功能,而不必在意它的底层是如何实现的。 目前流行的版本是2.7与3.5,后者是作为新版本有某些改变,但Python2用户仍居多。不过值得注意的是Python3对于Unicode的处理更加方便,也因此逐渐被普及。本文的所使用Python版本为3.5。

Pygame是Python的一个package,是一个很经典的游戏制作包,可以完成几乎所有的2D游戏制作。Pygame需要通过pip安装(具体可百度),其中各模块的方法和属性信息可在Pygame官网的帮助手册查询。本文使用的Pygame版本匹配Python3.5-win64。

**二、初次实现

现在我们尝试着用Pygame来制作一个微信打飞机小游戏。首先我们要做的是制作一个简单的游戏模型,而不是在一开始就让程序过于复杂。因此,我们从最简单的开始,先通过以下几步来实现一只飞机下降的动画。

(1)初始化Pygame主框架

(2)初始化plane类

(3)对按键进行检查并响应

(4)更新画面

(5)重复(3)(4)

首先,导入相关库。

import sys,pygame

from pygame.locals import*

from random import randrange

然后初始化pygame主框架。我们将会使用到display模块下这些函数:

set_mode:设定显示的类型和尺寸。默认为窗口,有可选参数FULLSCREEN(全屏)。

get_surface:返回一个可画图的Surface对象,相当于一帧。一个Surface对象到另一个Surface对象的变化即产生动画的变化。其blit()方法可更新Surface对象。

flip:更新显示。

update:更新屏幕一部分,可以提高游戏性能。可以配合后文的RenderUpdates类draw方法中返回的矩形列表使用。

set_caption:设定Pygame程序标题,窗口化时会用做窗口标题。

Pygame中两个最重要的类是Sprite类和Group类(Sprite类将在后文飞机初始化部分介绍)。Group类(和它的子类)是Sprite类的容器,RenderUpdates类是Group类的子类。创建了一个RenderUpdates类对象之后,可以使用add()方法向其中添加Sprite类对象,也就产生了一只飞机。用该容器存储Sprite对象的好处是容易管理,并且可以只对需要进行更新的显示部分进行更新,提高游戏性能。

初始化

pygame.init()

screen_size=400,600 #screen屏幕大小

pygame.display.set_mode(screen_size) #窗口

全屏为.set_mode(screen_size,FULLSCREEN)

pygame.mouse.set_visible(0) #隐藏鼠标

plane_image=pygame.image.load('plane2.png')

导入飞机图片(图片大小应做调整)

sprites=pygame.sprite.RenderUpdates()

创建Sprite的容器

sprites.add(plane())

向容器中添加一个plane对象

screen=pygame.display.get_surface()

可用于画图的Surface对象

bg=(255,255,255) #背景颜色RGB格式red-blue-green

screen.fill(bg) #屏幕填充背景颜色

pygame.display.flip() #显示更新后的屏幕

构造函数init(注意是两个下划线+)中image属性可为其赋值.png/.jpg图片以改变其外观,在这里我们先用plane_image代替,在前面的主函数编写中已为其指定具体的图片(本文图片均为自制,随代码给出。大家也可以直接在网上找图片素材或者PS自制,甚至可以直接用Python图像处理处理)。rect属性记录位置信息,rect.top/bottom/left/right分别记录坐标信息,rect.height/width分别记录长宽。Pygame中的Sprite类是所有可视游戏对象的基类,我们需要从它继承创建子类plane,用来表示下降的飞机,并覆盖它的构造函数和image,rect属性(相当于其数据成员)。

reset()函数随机确定飞机产生的横坐标,并固定纵坐标在画面最上方。

update()函数改变飞机的位置。当飞机抵达画面下方时,调用reset()函数随机重置其于顶端。

class plane(pygame.sprite.Sprite):

definit(self):

pygame.sprite.Sprite.init(self)

self.image=plane_image

self.rect=self.image.get_rect()

self.rect.top=-self.rect.height

self.rect.centerx=

randrange(screen_size[0]-self.rect.width)

+self.rect.width/2

控制飞机在窗口内

def update(self):

self.rect.top+=1

if self.rect.top>screen_size[1]:

self.kill()

飞机飞出屏幕则将其移除

接下来,只要在主程序中添加响应部分、更新部分和循环部分即可。

其中pygame.event.get()获取用户操作事件列表,比如键盘输入、鼠标点击等。可以通过判断按键来做出响应。现在我们只是简单地响应“退出”事件,在之后程序完善时完全可以使其响应“暂停”、“重新开始”等。

def clear_callback(surf,rect):

清除旧Sprite图形

surf.fill(bg,rect)

while True:

for eventinpygame.event.get():

if event.type==QUIT: #关闭窗口

sys.exit()

if event.type==KEYDOWNand

event.key==K_ESCAPE:

Esc退出

sys.exit()

sprites.clear(screen,clear_callback)

sprites.update() def clear_callback(surf,rect):

清除旧Sprite图形

surf.fill(bg,rect)

while True:

for eventinpygame.event.get():

if event.type==QUIT: #关闭窗口

sys.exit()

if event.type==KEYDOWNand

event.key==K_ESCAPE:

Esc退出

sys.exit()

sprites.clear(screen,clear_callback)

sprites.update()

调用update()方法更新Sprites对象

updates=sprites.draw(screen)

调用父类RenderUpdates的draw方法

返回需要更新的部分

pygame.display.update(updates)

更新显示

至此,我们将上述代码保存在一个.py文件中便可以运行了。结果如下:


但是,实现后会发现飞机的速度实在是太快了,我们可以考虑在其构造时为其加上帧率的限制。

首先,在第一步初始化主框架的时候定义pygame中的Clock对象。

定义一个时间对象

clock=pygame.time.Clock()

最大帧率

speed=100

然后在最后主程序循环中,更新之前加入语句:

clock.tick(speed)

三、进一步完善

现在我们已经有了一个基本的框架,但是这仅仅是一只慢慢飞过的飞机。不过不要灰心,我们只要再完成3小步,就可以制作出基本的打飞机小游戏啦。

(1)初始化我方战机

(2)实现敌方战机的产生

(3)实现敌我战机的相互作用

首先,仿造之前敌方战机的构造方式,编写如下类:

class myplane(pygame.sprite.Sprite):

definit(self):

pygame.sprite.Sprite.init(self)

self.image=myplane_image

self.rect=self.image.get_rect()

self.rect.bottom=screen_size[1]

self.rect.centerx=screen_size[0]/2

def update(self,pressed_keys):

if pressed_keys[K_UP]:

self.rect.move_ip(0,-3)

if pressed_keys[K_DOWN]:

self.rect.move_ip(0,3)

if pressed_keys[K_LEFT]:

self.rect.move_ip(-3,0)

if pressed_keys[K_RIGHT]:

self.rect.move_ip(3,0)

限定player在屏幕中

if self.rect.bottom>screen_size[1]:

self.rect.bottom=screen_size[1]

elif self.rect.top<=0:

self.rect.top=0

if self.rect.left<=0:

self.rect.left=0

elif self.rect.right>screen_size[0]:

self.rect.right=screen_size[0]

其中与plane类不同的地方有两处,一是其更新方式不同,它需要传入一个pressed_keys参数,代表键盘的按键,分别对上下左右四个键做出响应。二是要限制myplane对象运动不超出屏幕。

接着,我们不妨把子弹bullets类也编写了再把他们一起实例化。因为子弹的位置需要由myplane确定,因此我们可以在myplane类中定义特定的fire()函数,实现子弹的生成。

def fire(self):

axis=[self.rect.centerx,self.rect.top]

new_bullets=bullets(axis)

mybullets.add(new_bullets)

all_sprites.add(new_bullets)

以下是bullets类,其构造函数需坐标参数axis。

class bullets(pygame.sprite.Sprite):

definit(self,axis):

pygame.sprite.Sprite.init(self)

self.image=bullets_image

self.rect=self.image.get_rect()

self.rect.centerx=axis[0]

self.rect.centery=axis[1]

def update(self):

self.rect.top-=3

if self.rect.bottom<=0:

self.kill()

现在这三种类的实例化应改为:

sprites=pygame.sprite.RenderUpdates()

sprites.add(plane())

mysprites=pygame.sprite.RenderUpdates()

myplane=myplane()

mysprites.add(myplane)

mybullets=pygame.sprite.RenderUpdates()

all_sprites=pygame.sprite.RenderUpdates()

all_sprites.add(mysprites)

all_sprites.add(sprites)

all_sprites.add(mybullets)

all_sprites表示所有的类实例(或对象),这是为了更新的简便起见:

注意:其中plane()为plane类的对象,但我们无法直接对其进行操作;而myplane=myplane()这里简便起见对象与类取相同的名字,之后才能在主程序中直接调用其方法(函数)。

while True:

while True:

for eventinpygame.event.get():

if event.type==QUIT:

sys.exit()

elif event.type==KEYDOWNandevent.key==K_ESCAPE:

sys.exit()

myplane.fire()

pressed_keys=pygame.key.get_pressed()

clock.tick(speed)

all_sprites.clear(screen,clear_callback)

sprites.update()

mybullets.update()

mysprites.update(pressed_keys)

updates=all_sprites.draw(screen)

pygame.display.update(updates)

现在,我们已经有了可以运动的我方战机,虽然子弹速度太快以致于我们的子弹像是激光抢,我们接下来会解决这个问题,并且尝试着产生更多的敌方战机。

这两个问题都涉及到对象产生的速率,我们不妨先看看怎么产生更多敌方战机。首先应该定义产生敌机事件:

ADDENEMY=pygame.USEREVENT+1 #为添加敌人创建自定义事件

pygame.time.set_timer(ADDENEMY,1200) #产生事件的时间(毫秒)

通过这种方式,我们使产生敌机的速率降低,同样的我们也可以这样来限制子弹产生的速度(具体见源代码)。

然后再在循环过程中判断事件event是否为ADDENEMY来决定是否产生敌机。

现在我们的游戏变成了这样:


终于到了最后一步!我们只要为各种类之间加上相应的关系就完成啦。

对于敌机,只要触碰到子弹便被kill,对于我机,只要触碰到敌机便kill而且gameover了。在主循环中可加入如下判断语句:

for j in mybullets.sprites():

for i in antisprites.sprites():

if j.rect.colliderect(i.rect):

i.kill()

j.kill()

break

接下来判断我机与敌机的位置:

其中mybullets.sprites(),antisprites.sprites()分别返回现有的子弹,敌机对象的列表。它们是按产生时间先后顺序排列的。rect的colliderect()方法可以判断两个矩形是否相交并返回布尔值。通过遍历子弹和敌机对象,我们可以判断子弹和敌机的位置关系,但是考虑到一颗子弹应该只能消灭一只敌机,因此每当一颗子弹消灭一只敌机时,我们应跳出内层循环,继续遍历剩下的子弹对象。

if pygame.sprite.spritecollideany(myplane,antisprites):

myplane.kill()

游戏结束提示

screen.fill((255,255,255)) #填充颜色

font=pygame.font.Font(None,48) #设置字体

text="YOU LOST!"

height=font.get_linesize() #获取文字高度

设置字体显示参数

antialias=1#光滑字体

black=0,0,0#字体颜色

text2=font.render(text,antialias,black)

r=text2.get_rect() #获取文字图形的矩形

center,top=screen.get_rect().center #设置文字位置

r.midtop=center,top

screen.blit(text2,r) #更新Surfacee对象

pygame.display.flip() #更新显示

break

其中pygame.sprite.spritecollideany()方法可以用来判断两个Group里sprites对象间的交叉部分。

四、最后说几句

至此,我们已经制作出了简单的打飞机小游戏。其实还有很多可以丰富改善的地方,比如:

最后判断不同对象是否接触的时候仅仅用对象的矩形判断可能不够准确,为此我们可以考虑将对象的真实部分切分为几个小矩形再进行判断;

我们的程序仅仅包含在一个.py文件中,我们可以考虑对其进行封装,把游戏对象、游戏信息(得分、等级)、游戏状态(暂停、结束)等分别抽象成一个或几个类,使程序更容易拓展;

我们可以使用py2exe包将.py文件制作为.exe可执行文件。

反观全文,我们的整体思路应该是:

类的功能——类的实现——类间的关系。

我们从需要实现的功能出发,定义相应的类及其方法、属性;再到复杂的情形需要不同类的相互联系作用,因此再回头丰富某些类的方法(或者派生继承),使它们能产生联系;完成上述两步之后,又重新回到第一步,考虑类仍可完善的功能,循环反复……

为了给大家一个好的学习机会!小编给大家准备了十套Python零基础的学习资料

加小编Python学习群:862703141 即可自动获取大量python视频教程以及各类PDF!


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容