该整个项目了
在前面十一章的学习中,我们已经对Python这门语言有了一个比较基本的认识和了解,那么接下来,我们会通过一些项目的学习,来加深和拓展我们对Python的认知。那么我们首先就是尝试做一个游戏——武装飞船。
因为Python做游戏我也是第一次,所以要是一步步学着做完了,估计回来再写也写不清楚,就一步步来,一步步写吧。
准备工作
首先,我们需要下载pygame,这里下载的方法有很多,我们可以之间在终端输入pip install pygame就可以实现下载,也可以在官网上下载安装,这里其实还是很容易的,判断安装好的方法就是利用终端打开Python之后输入import pygame,导入pygame模块,显示了pygame的版本就Ok了。
开始游戏项目
这里我们首先创建了一个pygame的窗口,执行下面的代码,会显示一块黑色的屏幕。
#模块sys中有退出游戏的工具
import sys
#模块pygame具备开发游戏所需的功能
import pygame
class AlienInvasion:
'''管理游戏资源和行为的类'''
def __init__(self):
#初始化背景设置,让pygame能够正常的工作
pygame.init()
#创建显示窗口,将显示窗口赋给属性screen,这样整个类中所有方法都能够使用他。
#传递给screen的对象是一个surface,是屏幕的一部分,用于显示游戏元素
self.screen=pygame.display.set_mode((1200,800))
pygame.display.set_caption("Alien Invasion")
def run_game(self):
while True:
#监视键盘和鼠标事件,pygame.event,get()这个函数返回一个列表,列表内是上次被调用后发生的所有事件
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
#让程序绘制的屏幕可见
pygame.display.flip()
if __name__=='__main__':
ai=AlienInvasion()
ai.run_game()
设置一个背景颜色
如果说上面的代码默认生成一个黑色屏幕有些单调的话,我们可以通过下面两行代码,进行背景颜色的调整,第一行放到__init__里面,第二行加到run_game里面即可
self.bg_color=(230,230,230)
self.screen.fill(self.bg_color)
这里需要提及的是,在很多软件里面,颜色都是通过RGB或者RGBA来显示的,RGB分别表示红绿蓝三种颜色,A则是透明度的意思,在OpenGL,Android studio,以及我们目前的pygame里面都是用这种方式表示颜色的(因为我只在这三个地方接触到了这个),例子中设置的背景颜色是浅灰色。
创建一个设置类
之前,我们将程序的一些属性设置和运行放到了一起,这样一来,如果我们要修改设置就不是很方便,现在,我们另起一个文件,专门存放设置类,这样之后修改就会容易一些。
class Settings:
"""用来存储我们这个游戏里面所有设置的类"""
def __init__(self):
#初始化游戏的设置
#屏幕设置
self.screen_width=1200
self.screen_height=800
self.bg_color=(230,230,230)
from settings import Settings
class AlienInvasion:
'''管理游戏资源和行为的类'''
def __init__(self):
#初始化背景设置,让pygame能够正常的工作
pygame.init()
# #创建显示窗口,将显示窗口赋给属性screen,这样整个类中所有方法都能够使用他。
# #传递给screen的对象是一个surface,是屏幕的一部分,用于显示游戏元素
# self.screen=pygame.display.set_mode((600,400))
# self.bg_color=(230,230,230)
self.settings=Settings()
self.screen=pygame.display.set_mode((self.settings.screen_width,self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
添加图像
任何一个游戏,都不可能只有代码,图片也是游戏中一个重要的组成部分。在pygame里面,我们最好使用的是位图,也就是bmp文件,因为这个是pygame默认加载的。这里我们添加一个飞船的位图。
如果需要类似资源的话,可以在Pixabay等网站搜索免费图形。
创建ship类
既然有了飞船,我们就要创建一个文件来管理他。
import pygame
class Ship:
"""管理飞船的类"""
def __init__(self,ai_game):
#初始化飞船,设置飞船的初始位置
self.screen=ai_game.screen
self.screen_rect=ai_game.screen.get_rect()
#加载飞船图像并且获取其外接矩形
self.image=pygame.image.load('images/ship.bmp')
self.rect=self.image.get_rect()
#对于每一艘新的飞船,我们都把他放到屏幕底部中央
self.rect.midbottom=self.screen_rect.midbottom
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
在这部分代码里面,我们见到了很多的rect,这也是pygame高效的原因之一,因为这能让我们像处理矩形一样来处理很多的游戏元素。矩形处理让我们进行判断,调整位置更加容易了。
在完成了ship类的创建之后,我们进行飞船的绘制,在两个方法中分别假如代码:
self.ship=Ship(self)
self.ship.blitme()
最后的结果如下
代码的重构
随着我们一步步写下来,似乎run_game的功能越来越多,也越来越长了,而这样是不利于我们后续进行扩展的,所以我们对这部分代码进行一下重构。
这里我们把之前一个函数的功能分成了两个函数,run_game只负责调用这两个函数。
def run_game(self):
while True:
#用来相应按键和鼠标事件
self._check_events()
#用来重新绘制图像,并切换到新的屏幕
self._update_screen()
def _check_events(self):
#监视键盘和鼠标事件,pygame.event,get()这个函数返回一个列表,列表内是上次被调用后发生的所有事件
for event in pygame.event.get():
#响应特定事件
if event.type==pygame.QUIT:
sys.exit()
def _update_screen(self):
#每次循环都需要重绘屏幕
self.screen.fill(self.settings.bg_color)
#绘制飞船
self.ship.blitme()
#让程序绘制的屏幕可见
pygame.display.flip()
驾驶飞船
接下来,我们终于可以和用户对接了,我们将实现在用户按左右键时飞船的移动。
def _check_events(self):
#监视键盘和鼠标事件,pygame.event,get()这个函数返回一个列表,列表内是上次被调用后发生的所有事件
for event in pygame.event.get():
#响应特定事件
if event.type==pygame.QUIT:
sys.exit()
#判断动了键盘
elif event.type==pygame.KEYDOWN:
#判断按了向右键
if event.key==pygame.K_RIGHT:
#向右移动飞船
self.ship.rect.x+=10
这里,我们在监视键盘鼠标的函数里面添加了新的判断条件,如果按了向右键,飞船横坐标+10像素,实现了飞船的移动,但是我们只能是点一下,动一下,接下来我们要实现持续移动。
为了实现持续移动我们进行了如下更改:
在判断键盘执行的时候,从之前直接动飞船,改成调整飞船状态,飞船自己来动位置。
def _check_events(self):
#监视键盘和鼠标事件,pygame.event,get()这个函数返回一个列表,列表内是上次被调用后发生的所有事件
for event in pygame.event.get():
#响应特定事件
if event.type==pygame.QUIT:
sys.exit()
#判断按了键盘
elif event.type==pygame.KEYDOWN:
#判断按了向右键
if event.key==pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right=True
#判断按了向左键
if event.key==pygame.K_LEFT:
#向左移动飞船
self.ship.moving_left=True
#判断松了按键
elif event.type==pygame.KEYUP:
#判断是向右键
if event.key==pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right=False
#判断是向左键
if event.key==pygame.K_LEFT:
#向左移动飞船
self.ship.moving_left=False
在飞船的类中,进行了如下调整
def __init__(self,ai_game):
#初始化飞船,设置飞船的初始位置
self.screen=ai_game.screen
self.screen_rect=ai_game.screen.get_rect()
#加载飞船图像并且获取其外接矩形
self.image=pygame.image.load('images/ship.bmp')
self.rect=self.image.get_rect()
#对于每一艘新的飞船,我们都把他放到屏幕底部中央
self.rect.midbottom=self.screen_rect.midbottom
#移动标志
self.moving_right=False
self.moving_left=False
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect)
def update(self):
if self.moving_right:
self.rect.x+=1
if self.moving_left:
self.rect.x-=1
再次调整——速度
我们将飞船的速度也放进了Setting文件中,同时把速度从1改为1.5,将速度设置成为小数,可以更细致的控制飞船的速度。
ship文件中的修改部分:
#对于每一艘新的飞船,我们都把他放到屏幕底部中央
self.rect.midbottom=self.screen_rect.midbottom
#在飞船的属性x中存储小数值,这样会更加精确
self.x=float(self.rect.x)
注意这两部分的先后关系,颠倒的话飞船会出现在最左下角。
def update(self):
if self.moving_right:
self.x+=self.settings.ship_speed
if self.moving_left:
self.x-=self.settings.ship_speed
#根据self.x更新self.rect.x
self.rect.x=self.x
因为self.rect只能存整数,我们又设置了一个self.x用来存小数,之后再把值传回去。
在处理好了飞船的移动之后,我们要解决一个问题——飞船一直移动,会飞出屏幕外,所以我们还需要进行修改,让飞船一直在我们这个屏幕里面。
限制飞船活动范围
修改部分代码,在修改飞船位置之前先实现一个飞船和屏幕位置的判断。
def update(self):
if self.moving_right and self.rect.right<self.screen_rect.right:
self.x+=self.settings.ship_speed
if self.moving_left and self.rect.left>0:
self.x-=self.settings.ship_speed
#根据self.x更新self.rect.x
self.rect.x=self.x
再次重构
随着我们这个飞船功能的开发,我们需要再次进行一波重构,这次的重构对象是_check_event()方法。将按键和松键分开。
def _check_events(self):
#监视键盘和鼠标事件,pygame.event,get()这个函数返回一个列表,列表内是上次被调用后发生的所有事件
for event in pygame.event.get():
#响应特定事件
if event.type==pygame.QUIT:
sys.exit()
#判断按了键盘
elif event.type==pygame.KEYDOWN:
self._check_keydown_events(event)
#判断松了按键
elif event.type==pygame.KEYUP:
self._check_keyup_events(event)
def _check_keydown_events(self,event):
#判断按了向右键
if event.key==pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right=True
#判断按了向左键
if event.key==pygame.K_LEFT:
#向左移动飞船
self.ship.moving_left=True
def _check_keyup_events(self,event):
#判断是向右键
if event.key==pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right=False
#判断是向左键
if event.key==pygame.K_LEFT:
#向左移动飞船
self.ship.moving_left=False
退出方法
目前我们都是通过窗口提供的X键来退出的,但是对于很多喜欢全屏游戏的用户来说,他们是用不到这个功能的,所以我们还是需要提供一个快捷键来让用户退出。
def _check_keydown_events(self,event):
#判断按了向右键
if event.key==pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right=True
#判断按了向左键
if event.key==pygame.K_LEFT:
#向左移动飞船
self.ship.moving_left=True
#提供一个退出游戏的快捷键
if event.key==pygame.K_q:
sys.exit()
全屏游戏
我们上面退出快捷键也是为了这个目标而准备的。为了实现这一点,我们需要做出如下修改:
def __init__(self):
#初始化背景设置,让pygame能够正常的工作
pygame.init()
# #创建显示窗口,将显示窗口赋给属性screen,这样整个类中所有方法都能够使用他。
# #传递给screen的对象是一个surface,是屏幕的一部分,用于显示游戏元素
# self.screen=pygame.display.set_mode((600,400))
# self.bg_color=(230,230,230)
self.settings=Settings()
# self.screen=pygame.display.set_mode((self.settings.screen_width,self.settings.screen_height))
#让pygame生成一个覆盖整个显示器的屏幕
self.screen=pygame.display.set_mode((0,0),pygame.FULLSCREEN)
#使用屏幕的rect属性来更新对象settings
self.settings.screen_width=self.screen.get_rect().width
self.settings.screen_height=self.screen.get_rect().height
pygame.display.set_caption("Alien Invasion")
self.ship=Ship(self)
不过一定要确定之前设置好了退出快捷键,不然可能会出不来。
射击
这也是我们对宇宙飞船最后的加工了,让他具有射击的功能,这样我们就有了一块可以适用很多射击游戏的拼图了。
首先,我们需要添加子弹设置,所以我们需要再起一个文件:
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""管理飞船所发射的子弹的类"""
def __init__(self,ai_game):
'''在飞船当前的位置创造一个子弹对象'''
#继承情况下的初始化
super().__init__()
self.screen=ai_game.screen
self.settings=ai_game.settings
self.color=self.settings.bullet_color
#在(0,0)处创建一个子弹矩形,之后在设置正确的位置
self.rect=pygame.Rect(0,0,self.settings.bullet_width,self.settings.bullet_height)
self.rect.midtop=ai_game.ship.rect.midtop
#存储用小数表示的子弹位置
self.y=float(self.rect.y)
def update(self):
"""向上移动子弹"""
self.y-=self.settings.bullet_speed
#更新表示子弹的rect位置
self.rect.y=self.y
def draw_bullet(self):
#在屏幕上绘制子弹
pygame.draw.rect(self.screen,self.color,self.rect)
我们在编写子弹的类的时候,需要继承pygame的Spirit类,通过使用精灵,我们可以把所有的子弹编组统一进行操作管理。
另外,setting里面也要添加:
#子弹设置
self.bullet_speed=1.0
self.bullet_width=3
self.bullet_height=15
self.bullet_color=(60,60,60)
在主文件中需要添加这部分实现将子弹存到编组里面
self.bullets=pygame.spirite.Group()
#用来更新子弹位置 self.bullets.update()
终于开火了
主要修改位置:
def _fire_bullet(self):
#创建一颗子弹,并加入到编组中去
new_bullet=Bullet(self)
self.bullets.add(new_bullet)
def _update_screen(self):
#每次循环都需要重绘屏幕
self.screen.fill(self.settings.bg_color)
#绘制飞船
self.ship.blitme()
#遍历编组,绘制子弹
for bullet in self.bullets.sprites():
bullet.draw_bullet()
#让程序绘制的屏幕可见
pygame.display.flip()
处理消失的子弹
现在我们已经可以实现子弹的发射了,但是我们知道,子弹出了屏幕之后依旧存在,只是看不到了而已,但是依旧会对程序造成负担,所以,我们需要对于已经出了屏幕的子弹进行删除。
def run_game(self):
while True:
#用来相应按键和鼠标事件
self._check_events()
#用来判断这一时刻飞船的位置
self.ship.update()
#用来更新子弹位置
self.bullets.update()
#删除消失的子弹
#for循环过程中间本身不可以修改列表或编组的元素,所以我们使用的是副本(copy)
for bullet in self.bullets.copy():
if bullet.rect.bottom<=0:
self.bullets.remove(bullet)
print(len(self.bullets))
#用来重新绘制图像,并切换到新的屏幕
self._update_screen()
于是,到现在,我们终于是完成了这样一个宇宙飞船,他可以移动,可以开火,是一个不错的游戏拼图了。之后的过程中,我们会继续扩展,最后做成一个小游戏。
(我的天,这个也太多了,比我前面两个还多。。。)