python编程——外星人入侵游戏pygame(一)
外星人入侵游戏一:武装飞船安装Pygame创建窗口及响应用户输入更改屏幕背景色创建设置类创建ship飞船类重构game_functions模块控制飞船——按键添加子弹外星人入侵游戏一:武装飞船
如果你有C语言基础并想要学习Python语言,小星强烈推荐去看袁国忠翻译的《python编程入门到实践》,原版作者Eric Matthes。作者讲的很详细,整个内容都是循序渐进,由简单的到复杂,可以跟着书中代码去编程,很容易理解。
制作小游戏的第一步:本文将安装pygame,创建可以左右移动并能射击的飞船。先在系统中新建一个文件夹,在制作游戏的过程中创建的.py文件都保存到这里。
安装Pygame
这里用的Windows系统,使用pip来安装(记得将pip更新到最新版)。
1.检查Python版本
C:\WINDOWS\system32>Python -m pip --versionpip 19.0.3 from D:\Python\lib\site-packages\pip (python 3.7)
这里记住Python版本,因为后面安装pygame需要选择和当前Python环境一致的。如果执行起来有错误,尝试把pip换成pip3或者更新pip。
2.Windows系统中安装pygame
访问https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame,找到pygame的版本类,选择对应的版本,这里cp37对应Python版本3.7,win_amd64对应电脑是64位。
python -m pip install --user pygame‑1.9.6‑cp37‑cp37m‑win_amd64.whl
创建窗口及响应用户输入
(记得先建立一个空白的文件夹,Python项目都将创建在这个文件夹内)
首先创建一个空的Python窗口,命名main或者其他都可以,将会以这个.py文件作为主函数。
import sysimport pygamedef run_game():# 初始化游戏并创建一个屏幕对象pygame.init()screen = pygame.display.set_mode((1200, 800))pygame.display.set_caption("Alien Invasion")# 开始游戏的主循环while True:# 监视键盘和鼠标事件for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()# 让最近绘制的屏幕可见pygame.display.flip()run_game()
对象screen是创建的屏幕,所有的图形元素都将在其中绘制。它也是一个surface,在Pygame中,surface是屏幕的一部分,用于显示游戏元素。在这个游戏中,每个元素(如外星人或飞船)都是一个surface。display.set_mode()返回的surface表示整个游戏窗口。我们激活游戏的动画循环后,每经过一次循环都将自动重绘这个surface。在while中处理时间循环和屏幕更新的代码。这里的for循环监视用户是否按下窗口的关闭按钮,对于按下事件响应退出游戏的动作。pygame.display.flip()命令Pygame让最近绘制的屏幕可见,即更新游戏窗口。
更改屏幕背景色
在while前设置bg_color属性,在while内调用screen.fill(bg_color)绘制。在Pygame中,颜色是以RGB值指定的。这种颜色由红色、绿色和蓝色值组成,其中每个值的可能取值范围都为0~255。颜色值(255, 0, 0)表示红色,(0, 255, 0)表示绿色,而(0, 0, 255)表示蓝色。
def run_game():--snip--pygame.display.set_caption("Alien Invasion")# 设置背景色bg_color = (230, 230, 230)# 开始游戏主循环.while True:# 监听键盘和鼠标事件--snip--# 每次循环时都重绘屏幕screen.fill(bg_color)# 让最近绘制的屏幕可见pygame.display.flip()run_game()
创建设置类
每次给游戏添加新功能时,通常也将引入一些新设置。下面来编写一个名为settings的模块(新建py文件),其中包含一个名为Settings的类,用于将所有设置存储在一个地方,以免在代码中到处添加设置。要修改游戏,只需修改settings.py中的一些值,而无需查找散布在文件中的不同设置。
class Settings():# """存储《外星人入侵》的所有设置的类"""def __init__(self):# """初始化游戏的设置"""# 屏幕设置self.screen_width = 1200self.screen_height = 800self.bg_color = (230, 230, 230)
在主函数py文件中先添加
from settings import Settings
再修改
# screen = pygame.display.set_mode((1200, 800))ai_settings = Settings()screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))# screen.fill(bg_color)screen.fill(ai_settings.bg_color)
创建ship飞船类
(百度下载个飞船图片,建议把尺寸改小)选择用于表示飞船的图像后,需要将其显示到屏幕上。我们将创建一个名为ship的模块(ship.py),其中包含Ship类,它负责管理飞船的大部分行为。
import pygameclass Ship():def __init__(self, screen):"""初始化飞船并设置其初始位置"""self.screen = screen# 加载飞船图像并获取其外接矩形self.image = pygame.image.load('images/ship.bmp')self.rect = self.image.get_rect()self.screen_rect = screen.get_rect()# 将每艘新飞船放在屏幕底部中央self.rect.centerx = self.screen_rect.centerxself.rect.bottom = self.screen_rect.bottomdef blitme(self):"""在指定位置绘制飞船"""self.screen.blit(self.image, self.rect)
为加载图像,我们调用了pygame.image.load()。这个函数返回一个表示飞船的surface,而我们将这个surface存储到了self.image中。使用get_rect()获取相应surface的属性rect。Pygame的效率之所以如此高,一个原因是它让你能够像处理矩形(rect对象)一样处理游戏元素,即便它们的形状并非矩形。像处理矩形一样处理游戏元素之所以高效,是因为矩形是简单的几何形状。应rect对象的属性center、centerx或centery。要让游戏元素与屏幕边缘对齐,可使用属性top、bottom、left或right;要调整游戏元素的水平或垂直位置,可使用属性x和y,它们分别是相应矩形左上角的x和y坐标。
将把飞船放在屏幕底部中央:首先将表示屏幕的矩形存储在self.screen_rect中,再将self.rect.centerx(飞船中心的x坐标)设置为表示屏幕的矩形的属性centerx,并将self.rect.bottom(飞船下边缘的y坐标)设置为表示屏幕的矩形的属性bottom。Pygame将使用这些rect属性来放置飞船图像,使其与屏幕下边缘对齐并水平居中。
然后可以在主函数文件中创建飞船并绘制。运行主函数文件后,可以看到飞船在屏幕底部中心位置。
from ship import Ship# while前创建ship = Ship(screen)# while内绘制ship.blitme()
重构game_functions模块
重构简单理解就是将现有的代码进行分类重新构建成独立的函数或模块,简化函数里的操作,增强阅读性和扩展性。这里将对管理事件的代码移到新建的game_functions.py里。
import sysimport pygamedef check_events():"""响应按键和鼠标事件"""for event in pygame.event.get():if event.type ==pygame.QUIT:sys.exit()def update_screen(ai_settings, screen, ship):"""更新屏幕上的图像,并切换到新屏幕"""# 每次循环时都重绘屏幕screen.fill(ai_settings.bg_color)ship.blitme()# 让最近绘制的屏幕可见pygame.display.flip()
然后在主函数文件中导入并调用
import game_functions as gf# while中调用gf.check_events()gf.update_screen(ai_settings, screen, ship)
控制飞船——按键
事件都是通过方法pygame.event.get()获取的,,每次按键按下都被注册为一个KEYDOWN事件,释放会注册KEYUP事件。定义←→按键让飞船左右移动,定义移动速度,限制移动区域。
# ship属性中添加def __init__(self, ai_settings, screen):self.ai_settings = ai_settings# 在飞船的属性center中存储小数值self.center = float(self.rect.centerx)self.moving_right = Falseself.moving_left = Falseself.ship_speed_factor = 1.5# ship类加移动情况def update(self):"""根据移动标志调整飞船的位置"""if if self.moving_right and self.rect.right < self.screen_rect.right:self.center += self.ai_settings.ship_speed_factorif self.moving_left and self.rect.left > 0::self.center -= self.ai_settings.ship_speed_factor# 根据self.center更新rect对象self.rect.centerx = self.center# 主函数中ship = Ship(ai_settings, screen)# while中ship.update()# game_functions中check_events(ship)加入响应按键事件elif event.type == pygame.KEYDOWN:if event.key == pygame.K_RIGHT:ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.type == pygame.KEYUP:if event.key == pygame.K_RIGHT:ship.moving_right = Falseelif event.key == pygame.K_LEFT:ship.moving_left = False
添加子弹
飞船能够左右移动了,现在需要加入射击的子弹元素,添加子弹向上穿行,并且在屏幕上边缘消失。
首先添加子弹的settings设置
# 在settings中加入子弹设置self.bullet_speed_factor = 1self.bullet_width = 3self.bullet_height = 15self.bullet_color = 60, 60, 60
然后创建新的py文件,命名为bullet
import pygamefrom pygame.sprite import Spriteclass Bullet(Sprite):"""一个对飞船发射的子弹进行管理的类"""def __init__(self, ai_settings, screen, ship):"""在飞船所处的位置创建一个子弹对象"""super(Bullet, self).__init__()self.screen = screen# 在(0,0)处创建一个表示子弹的矩形,再设置正确的位置self.rect = pygame.Rect(0, 0, ai_settings.bullet_width,ai_settings.bullet_height)self.rect.centerx = ship.rect.centerxself.rect.top = ship.rect.top#存储用小数表示的子弹位置self.y = float(self.rect.y)self.color = ai_settings.bullet_colorself.speed_factor = ai_settings.bullet_speed_factordef update(self):"""向上移动子弹"""#更新表示子弹位置的小数值self.y -= self.speed_factor#更新表示子弹的rect的位置self.rect.y = self.ydef draw_bullet(self):"""在屏幕上绘制子弹"""pygame.draw.rect(self.screen, self.color, self.rect)
Bullet类继承了我们从模块pygame.sprite中导入的Sprite类。通过使用精灵,可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,需要向__init__()传递ai_settings、screen和ship实例,还调用了super()来继承Sprite。代码super(Bullet, self).init()使用了Python 2.7语法。这种语法也适用于Python 3,但你也可以将这行代码简写为super().init()。
飞船能发射多个子弹,所以用编组(group)存储有效子弹。这个编组将是pygame.sprite.Group类的一个实例;pygame.sprite.Group类类似于列表,但提供了有助于开发游戏的额外功能。
# 在主函数中from pygame.sprite import Group# while前bullets = Group()# while内gf.check_events(ai_settings, screen, ship, bullets)ship.update()bullets.update()gf.update_screen(ai_settings, screen, ship, bullets)
接下来就是操作开火,定义按下空格键发射一个子弹,由于松开空格键不会有事件发生。
# 在按键keydown事件中修改elif event.key == pygame.K_SPACE:# 创建一颗子弹,并将其加入到编组bullets中new_bullet = Bullet(ai_settings, screen, ship)bullets.add(new_bullet)# 重绘屏幕update_screen(ai_settings, screen, ship, bullets)# 在飞船和外星人后面重绘所有子弹for bullet in bullets.sprites():bullet.draw_bullet()
运行后看到可以射击出子弹,子弹出屏幕后就消失,但是子弹依旧存在于编组中(子弹的Y坐标为负数,越来越小),将会继续消耗内存和处理能力,因此需要删除这些已经消失的子弹。
# 主函数while中绘制前删除已消失的子弹gf.update_bullets(bullets)# settings中限制子弹数量self.bullets_allowed = 3# game_function中的pygame.K_SPACE修改创建子弹条件if len(bullets) < ai_settings.bullets_allowed:new_bullet = Bullet(ai_settings, screen, ship)# game_function中重构子弹的更新def update_bullets(bullets):"""更新子弹的位置,并删除已消失的子弹"""# 更新子弹的位置bullets.update()# 删除已消失的子弹for bullet in bullets.copy():if bullet.rect.bottom <= 0:bullets.remove(bullet)
在for循环中,不应从列表或编组中删除条目,因此必须遍历编组的副本。我们使用了方法
copy()来设置for循环,保证在for内修改的编组不会影响到bullet的判断。
最后主函数运行下,试试飞船移动和子弹射击功能。接下来两篇文章讲述创建外星人和创建积分。
如果写入以上代码没有正常运行,可以参考小星的源代码。