安装
我用的Python3.5。
3.4版本之后去
http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame
https://pypi.python.org/pypi/Pygame
这两个网址去下载,推荐第二个,第二个清楚。
注意对应版本,我下载的是pygame-1.9.3-cp35-cp35m-win_amd64.whl
,网上好多是讲Python2.*版本的,讲的太简单了,一点都不清楚,直接略过安装,难受。
下载下来是一个.whl
文件,将它放在项目的根目录下,然后在dos
命令窗口中,切换到项目目录,运行下列代码:
python -m pip install --user pygame-1.9.3-cp35-cp35m-win_amd64.whl
下载了哪个版本,后面的文件名就用哪个版本,不一定和我的相同。
然后创建一个文件:
import pygame
print(pygame.ver)
输出:
1.9.3
开始
创建Pygame窗口以及响应用户输入
创建一个空的Pygame窗口。
alien_invasion.py
import sys
import pygame
def 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()
pygame.init()
初始化背景设置,让Pygame能正确地工作。
pygame.display.set_mode()
创建一个游戏窗口的尺寸,整个游戏的所有图形元素都将在其中绘制。
实参(1200, 800)是一个元组,指定了窗口的尺寸。
对象screen
是一个surface
对象。
在Pygame中,surface
是屏幕的一部分,用于显示游戏元素。
在这个游戏中,每个元素(如外星人或飞船)都是一个surface
。
pygame.display.set_mode()
返回的surface
表示整个游戏窗口,每一次循环都将自动绘制这个surface
。
pygame.display.set_caption()
设定游戏的名字,名字会在窗口左上角显示。
事件(event)
事件是用户玩游戏时执行的操作,如按键或移动鼠标。
为了让程序响应事件,编写一个事件循环,以侦听事件,并根据相应的事件执行相应的任务。
pygame.event.get()
获得Pygame检测到的事件。
pygame.QUIT
当我玩剑单击游戏的关闭按钮时,将检测到pygame.QUIT
事件,根据此事件,调用sys.exit()
来退出游戏。
pygame.display.flip()
命令Pygame让最近绘制的屏幕可见。
每次执行一次while
循环都换绘制一个空屏幕,并擦去就屏幕,使得只有新屏幕可见。
我们在移动游戏元素时,pygame.display.flip()
将会不断更新屏幕,以显示元素的新位置,并在原来位置隐藏元素,营造平滑移动的效果。
设置背景颜色
--snip--
def run_game():
--snip--
pygame.display.set_caption("Alien Invasion")
# 设置背景颜色
bg_color = (0, 0, 15)
--snip--
# 重绘屏幕
screen.fill(bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
颜色
在Pygame中,颜色是以RGB值指定的。
创建一个颜色元组,在while
循环前指定一次就好。
screen.fill(bg_color)
用背景色填充屏幕,只接受一个实参:一种颜色。
创建设置类
编写一个名为settings
的类,其中包含一个,名为Settings
的类,用于将设置储存在一个地方,以免在代码中到处添加设置。
折让函数调用更加简单,且在项目增大时修改游戏的外观更容易。
settings.py
class Settings():
"""储存游戏所有的设置"""
def __init__(self):
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (0, 0, 15)
self.caption = "Alien Invasion 1.0"
alien_invasion.py
--snip--
from settings import Settings
def run_game():
# 初始化游戏,并创建一个屏幕对象
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption(ai_settings.caption)
--snip--
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
添加飞船图像
书上的图片丑的无法接受,自己找了好久,在这个网址找到的:
6m5m.com游戏素材资源共享平台
这个图片是png
格式的,背景是透明的,转换成bmp
即可。
在线图片格式转换
这个网址可以保留图片的透明背景,非常好。
在线图片切割
这个也不错。
创建ship类
ship.py
import pygame
class 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.centerx
self.rect.bottom = self.screen_rect.bottom
def blitme(self):
"""指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
构造方法中有一个screen
参数,制定了要将飞船绘制到什么地方。
pygame.image.load()
加载图像,返回一个飞船的surface
对象。
rect对象
rect这个对象是用来存储成对出现的参数,比如,一个矩形框的左上角坐标、宽度和高度,RECT结构通常用于Windows编程。
———摘自百度百科
处理rect
对象时,可以使用矩形四角和中心的
x
和
get_rect()
获取相应surface
的属性rect
。
Pygame的效率之所以如此之高,一个原因是它能够像处理矩形(rect
对象)一样处理游戏元素,即使它们的形状并非矩形。(诶哟不错哦)
rect对象属性
属性 | 意义 |
---|---|
centerx | 水平(X轴)中点坐标 |
centery | 垂直(Y轴)中点坐标 |
center | 矩形(rect对象)中点坐标 |
top | 顶部(Y轴)坐标 |
bottom | 底部(Y轴)坐标 |
left | 左边缘(X轴)坐标 |
right | 右边缘(X轴)坐标 |
blit()
接受一个surface
对象和一个rect
对象作为参数 。
在屏幕上的指定位置绘制图像。
重构:模块game_functions
函数check_events()
驾驶飞船
响应按键
每次按键都被注册为KETDOWN事件。
检测到KEYDOWN事件时,要判断按下的是否是特定的键。
例如,按下的如果是右箭头,就让飞船的centerx
属性增加。
game_functions.py
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.rect.centerx += 1
elif event.key == pygame.K_LEFT:
ship.rect.centerx -= 1
elif event.key == pygame.K_UP:
ship.rect.centery -= 1
elif event.key == pygame.K_DOWN:
ship.rect.centery += 1
可以移动了,但是,只能按一下,动一下。
允许不断移动
可以设置一个移动标志,为布尔型,若为True
则按指定方向移动,按下指定键时变为Ture
放开时变为False
。
ship.py
--snip--
def __init__(self, screen):
"""初始化飞船并设置其初始位置"""
--snip--
# 移动标志
self.moving_right = False
self.moving_left = False
self.moving_up = False
self.moving_down = False
def update(self):
"""根据移动标志调整飞船位置"""
if self.moving_down:
self.rect.centery += 1
if self.moving_up:
self.rect.centery -= 1
if self.moving_right:
self.rect.centerx += 1
if self.moving_left:
self.rect.centerx -= 1
--snip--
game_functions.py
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_UP:
ship.moving_up = True
elif event.key == pygame.K_DOWN:
ship.moving_down = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
elif event.key == pygame.K_UP:
ship.moving_up = False
elif event.key == pygame.K_DOWN:
ship.moving_down = False
alien_invasion.py
while True:
# 监视键盘和鼠标事件
gf.check_events(ship)
ship.update()
gf.update(ai_settings, screen, ship)
但是这里有个缺陷,如果我先按右键再按左键,它便停下不动了,作为一个游戏爱好者这不能让人接受。
所以我想稍微改动一下。
Ship()
类加入self.moving_right_step
属性。
ship.py
# 移动像素
self.moving_right_step = 1
self.moving_left_step = 1
self.moving_up_step = 1
self.moving_down_step = 1
当按下右键之后,再按下左键,左键的self.moving_left
变为True
,self.moving_left_step
赋值为2
。相对的看起来,加1
减2
就是向左正常移动,要的是效果嘛。
若接下来再松开左键,self.moving_left_step
赋值为1
,self.moving_left
变为False
。
但是,如果接下来我松开的是右键,那么我不仅要做上述的这些,而且,self.moving_left_step
已经被赋值为2
了,此时要检查self.moving_left
是否为True
,然后若为True
,就将self.moving_left_step
也赋值为1
,避免让飞船出现突然加速的bug。
实现顺利。
秘技.左右横跳!!!
限制飞船活动范围
为了避免这个飞船飞走,需要限制它的行动。
也将飞船的速度作为设置之一。
settings.py
--snip--
self.ship_speed_factor = 1
ship.py
def update(self):
"""根据移动标志调整飞船位置"""
if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
self.rect.centery += self.moving_down_speed
if self.moving_up and self.rect.top > 0:
self.rect.centery -= self.moving_up_speed
if self.moving_right and self.rect.right < self.screen_rect.right:
self.rect.centerx += self.moving_right_speed
if self.moving_left and self.rect.left > 0:
self.rect.centerx -= self.moving_left_speed
重构check_events()
game_functions.py
import sys
import pygame
def check_keydown_events(event, ship, ai_settings):
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
if ship.moving_left:
ship.moving_right_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_LEFT:
ship.moving_left = True
if ship.moving_right:
ship.moving_left_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_UP:
ship.moving_up = True
if ship.moving_down:
ship.moving_up_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_DOWN:
ship.moving_down = True
if ship.moving_up:
ship.moving_down_speed = 2 * ai_settings.ship_speed_factor
def check_keyup_events(event, ship, ai_settings):
"""响应松开"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
ship.moving_right_speed = ai_settings.ship_speed_factor
if ship.moving_left:
ship.moving_left_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_LEFT:
ship.moving_left = False
ship.moving_left_speed = ai_settings.ship_speed_factor
if ship.moving_right:
ship.moving_right_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_UP:
ship.moving_up = False
ship.moving_up_speed = ai_settings.ship_speed_factor
if ship.moving_down:
ship.moving_down_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_DOWN:
ship.moving_down = False
ship.moving_down_speed = ai_settings.ship_speed_factor
if ship.moving_up:
ship.moving_up_speed = ai_settings.ship_speed_factor
def check_events(ship, ai_settings):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship, ai_settings)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship, ai_settings)
def update(ai_settings, screen, ship):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
射击
好的,要开始buibuibui了。
创建Bullet类
bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""对飞船发射的子弹进行管理的类"""
def __init__(self, ai_settings, screen, ship):
"""在飞船所处的位置创建一个子弹对象"""
super().__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.centerx
self.rect.top = ship.rect.top
# 储存用小数表示的子弹位置
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed = ai_settings.bullet_speed
def update(self):
"""向上移动子弹"""
# 更新表示资单位值的小数值
self.y -= self.speed
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
将子弹存储到编组中
alien_invasion.py
# 储存子弹的编组
bullets = Group()
# 开始游戏的主循环
while True:
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, screen, bullets)
ship.update()
gf.update_screen(ai_settings, screen, ship, bullets)
当对编组bullets
调用update()
时,将对其中的每个精灵(子弹)调用update()
。
开火
无需修改check_keyup_events()
,空格键松开什么都不会发生。
game_functions.py
--snip--
elif event.key == pygame.K_SPACE:
# 创建一颗子弹,并将其加入到编组bullets中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
--snip--
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship, ai_settings, screen, bullets)
def update_screen(ai_settings, screen, ship, bullets):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 绘制飞船
ship.blitme()
# 绘制子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
# 让最近绘制的屏幕可见
pygame.display.flip()
alien_invasion.py
while True:
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, screen, bullets)
ship.update()
bullets.update()
gf.update_screen(ai_settings, screen, ship, bullets)
删除已消失的子弹
子弹到达屏幕外部,但并没有消失,还占用着内存和处理器,所以,让它消失。
while True:
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, screen, bullets)
ship.update()
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
gf.update_screen(ai_settings, screen, ship, bullets)
限制子弹数量
不存在的,我不,这是我的游戏。
创建函数update_bullets()
为了让主程序尽量简单,将子弹管理部分作为一个函数。
game_functions.py
def update_bullets(bullets):
# 更新子弹位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
alien_invasion.py
while True:
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, screen, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
创建函数fire_bullet()
开火也作为一个函数。
game_functions.py
elif event.key == pygame.K_SPACE:
fire_bullets(bullets, ai_settings, screen, ship)
--snip--
def fire_bullets(bullets, ai_settings, screen, ship):
# 创建一颗子弹,并将其加入到编组bullets中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
射击个人优化
这个游戏只能按一下,发一颗子弹,作为一名游戏爱好者,这让我不能接受。
按住空格,什么时候不松开,它就得一个劲给我buibuibui。
所以,设置一个射击开关shooting
,为布尔型,这个开关作为Ship()
类的属性,而不是Bullet()
类的。
当其为True
,即按下空格时,不断射击。
并且修改上面刚创建的那个函数,怎么叫个开火函数?应该是装弹函数啊,给飞船装弹,bullets.update()
才是发射。
ship.py
import pygame
from bullet import Bullet
class Ship():
def __init__(self, screen, ai_settings):
--snip--
# 射击标志
self.shooting = False
def update(self, bullets, ai_settings, screen, ship):
# 射击
if self.shooting:
self.add_bullets(bullets, ai_settings, screen, ship)
def add_bullets(self, bullets, ai_settings, screen, ship):
# 创建一颗子弹,并将其加入到编组bullets中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
将装弹,添加到Ship()
类中。
alien_invasion.py
while True:
--snip--
ship.update(bullets, ai_settings, screen, ship)
--snip--
结果,不尽人意,甚至非常恶心的一大条:
我不服,再想。
可以这样,当最后一个发射的子弹的底部的坐标距离飞船的顶部坐标为3
像素的时候,才添加,而且这个应该可以读取到最后一个。
查阅官方文档,发现:
之前用的这个函数返回的是一个列表,那就好办了啊。
# 射击
if self.shooting and (len(bullets.sprites()) == 0
or ship.rect.top - bullets.sprites()[-1].rect.bottom >= 20):
self.add_bullets(bullets, ai_settings, screen, ship)
结果依然不尽人意,更恶心了。
可能计算机有延迟,子弹太多太快了,处理不过来,所以。
第二个解决方案,让计算机延迟执行子弹添加函数。
导入模块time
:
ship.py
# 射击
if self.shooting:
self.add_bullets(bullets, ai_settings, screen, ship)
time.sleep(0.01)
结果依然非常恶心:
那个sleep()
把整个程序都变慢了,单纯的变成了慢动作而已。
再想办法。
既然每次循环是“1帧”,那何不设置一个记录帧数的变量,这个变量应该在主程序while
循环之前就有,命名为ticks
。
alien_invasion.py
# 计数器
ticks = 0
# 开始游戏的主循环
while True:
# 控制游戏帧率
ticks += 1
if ticks >= ai_settings.animate_cycle:
ticks = 0
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, screen, bullets)
ship.update(bullets, ai_settings, screen, ship, ticks)
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
ship
的update()
函数添加一个新参数ticks
。
ship.py
# 射击
if self.shooting:
if ticks % 10 == 0:
self.add_bullets(bullets, ai_settings, screen, ship)
见证奇迹的时刻到了:
爽。
外星人
创建第一个外星人
创建Alien类
自己找的外星人的图片:
是不是有点熟悉,没错,如果你是《赛尔号》的骨灰老玩家,那你当年就应该体会过被它支配的恐怖,大BOSS————尤纳斯!!!!
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""外星人 类"""
def __init__(self, ai_settings, screen):
"""初始化外星人并设置其起始位置"""
# 类似Ship类的设置
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
self.image = pygame.image.load("images/alien.bmp")
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
def blitme(self):
"""绘制外星人"""
self.screen.blit(self.image, self.rect)
game_functions.py
def update_screen(ai_settings, screen, ship, bullets, alien):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 绘制飞船
ship.blitme()
# 绘制外星人
alien.blitme()
# 绘制子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
# 让最近绘制的屏幕可见
pygame.display.flip()
alien_invasion.py
# 创建外星人实例
alien = Alien(ai_settings, screen)
创建一群外星人
确定一行可以容纳多少个外星人
需要在屏幕两端留下边距,边距长与外星人宽度相等。
available_space_x = ai_settings.screen_width - (2 * alien_width)
每个外星人占据的空间应该是一个外星人的宽度的两倍,一个宽度为外星人的,一个为外星人右边的空白区域:
number_aliens_x = available_space_x / (2 * alien_width)
创建多行外星人
alien_invasion.py
# 创建外星人群
gf.create_fleet(ai_settings, screen, aliens)
# 开始游戏的主循环
while True:
# 控制游戏帧率
ticks += 1
aliens.draw(screen)
if ticks >= ai_settings.animate_cycle:
ticks = 0
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, screen, bullets)
ship.update(bullets, ai_settings, screen, ship, ticks)
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets, aliens)
game_functions.py
def update_screen(ai_settings, screen, ship, bullets, aliens):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 绘制飞船
ship.blitme()
# # 绘制外星人
aliens.draw(screen)
创建外星人群
game_functions.py
def create_fleet(ai_settings, screen, aliens):
"""创建外星人群"""
# 创建一个外星人,并计算一行可以容纳多少外星人
# 外星人间距为外星人宽度
new_alien = Alien(ai_settings, screen)
available_space_x = ai_settings.screen_width - (2 * new_alien.rect.width)
number_aliens_x = int(available_space_x / (2 * new_alien.rect.width))
for alien_number in range(0, number_aliens_x):
# 创建一个外星人并加入当前行
alien = Alien(ai_settings, screen)
print(type(alien.x))
alien.x = alien.rect.width * (2 * alien_number + 1)
alien.rect.x = alien.x
aliens.add(alien)
效果:
重构create_fleet()
game_functions.py
def create_alien(ai_settings, screen, aliens, alien_number, number_row):
# 创建一个外星人并加入当前行
alien = Alien(ai_settings, screen)
alien.x = alien.rect.width * (2 * alien_number + 1)
alien.y = alien.rect.height * 2 * number_row
alien.rect.x = alien.x
alien.rect.y = alien.y
aliens.add(alien)
def get_number_rows(ai_settings, screen, new_alien, ship):
available_space_y = ai_settings.screen_height - ship.rect.width - new_alien.rect.height
return int(available_space_y / (2 * new_alien.rect.height))
def create_fleet(ai_settings, screen, aliens, ship):
"""创建外星人群"""
new_alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens(ai_settings, screen, new_alien)
number_rows = get_number_rows(ai_settings, screen, new_alien, ship)
for number_row in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number, number_row)
因为外星人有点稍微的大所以结果有点不尽人意:
排列得太整齐了,作为一个游戏爱好者,这不能让人接受。
随机出现,也可说成,随机“不”出现,这个就好办多了:
game_functions.py
import sys
from random import randint
--snip--
def create_fleet(ai_settings, screen, aliens, ship):
"""创建外星人群"""
new_alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens(ai_settings, screen, new_alien)
number_rows = get_number_rows(ai_settings, screen, new_alien, ship)
for number_row in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number, number_row)
# 随机删除
for alien in aliens.copy():
if len(aliens.sprites()) > 6 and randint(0,1):
aliens.remove(alien)
顺便设置下限,不能让他把所有的都给我删了,不然打谁?
效果很棒:
randint()
使用前导入random
模块。
randint(a, b)
返回a
到b
之间的随机数,包括a
和b
。
源码如下:
def randint(self, a, b):
"""Return random integer in range [a, b], including both end points.
"""
return self.randrange(a, b+1)
including both end points
和randrange(a, b+1)
已经很清楚了,不再赘述。
让外星人移动
alien.py
def check_edge(self):
"""如果触碰边缘返回True"""
if self.rect.right == self.screen.get_rect().right or \
self.rect.left == self.screen.get_rect().left:
return True
game_functions.py
def check_fleet_edges(ai_settings, aliens):
"""有外星人到达边缘的措施"""
for alien in aliens.sprites():
if alien.check_edge():
change_fleet_direction(ai_settings, aliens)
break
def change_fleet_direction(ai_settings, aliens):
"""改变外星人群的运动方向"""
for alien in aliens.sprites():
alien.y += ai_settings.fleet_drop_speed
alien.rect.y = alien.y
ai_settings.fleet_direction *= -1
def update_aliens(ai_settings, aliens):
"""更新外星人群的位置"""
check_fleet_edges(ai_settings, aliens)
aliens.update(ai_settings)
alien_invasion.py
# 更新外星人状态
gf.update_aliens(ai_settings, aliens)
下降速度为1,太小了,但若是设置的大了些,就会很突兀,蹦的一下下来一大截,作为一个游戏爱好者,这不能让人接受。
实现思路:
在设置里加一个下落的标志self.drop = False
,若为真就“只”下落,不左右移动。
settings.py
# 下落标志
self.drop = False
# 下落开始时间(帧数)点
self.drop_ticks = 0
# 下落时间(帧数)
self.drop_cycle = 60
alien.py
def update(self, ai_settings):
if ai_settings.drop:
self.y += self.ai_settings.fleet_drop_speed
self.rect.y = self.y
else:
self.x += self.ai_settings.alien_speed_factor * ai_settings.fleet_direction
self.rect.x = self.x
在外星人到达边缘时,判断外星人是否正在下降,若正在下降并且到达下降时间,就停止下降,并改变左右移动方向,开始左右移动。
若是刚碰到边缘,就将下降标志self.drop = True
,记好时间点ticks
,作为函数change_fleet_direction()
实参,参数ticks
默认为0
,是可选参数,避免了多写一个函数的麻烦。
若正在下降,且还没下降完全,就不做改变,接着下降。
game_functions.py
def check_fleet_edges(ai_settings, aliens, ticks):
"""有外星人到达边缘的措施"""
for alien in aliens.sprites():
if alien.check_edge():
if ai_settings.drop and \
(ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
change_fleet_direction(ai_settings, aliens)
break
elif ai_settings.drop is False:
change_fleet_direction(ai_settings, aliens, ticks)
break
def change_fleet_direction(ai_settings, aliens, ticks=0):
"""改变外星人群的运动方向"""
if ticks:
ai_settings.drop = True
ai_settings.drop_ticks = ticks
else:
ai_settings.drop = False
ai_settings.fleet_direction *= -1
def update_aliens(ai_settings, aliens, ticks):
"""更新外星人群的位置"""
check_fleet_edges(ai_settings, aliens, ticks)
aliens.update(ai_settings)
效果很满意:
射杀外星人
终于到了该buibuibui时候了。
检测子弹与外星人的碰撞
game_functions.py
def update_bullets(bullets, aliens):
# 更新子弹位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# 检查是否有子弹击中了外星人
# 若击中了,删除相应的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
alien_invasion.py
while True:
# 计数
ticks += 1
aliens.draw(screen)
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings)
# 更新飞船状态
ship.update(bullets, ai_settings, screen, ship, ticks)
# 更新子弹状态
gf.update_bullets(bullets, aliens)
太无敌了,一会儿就死完了。
pygame.sprite.groupcollide()
将每颗子弹的rect
与每个外星人的rect
进行比较,并返回一个字典。
在这个字典中,每个键都是第一个参数组 中的精灵(子弹),而相应的值都是第二个参数组中的精灵(外星人)。
第三第四个参数为布尔型,第三个对应第一个参数组,第四个对应第二个参数组。
若位真True
,则碰撞后对应参数组的精灵消失,反之同理。
生成新的外星人
外星人那么可怜,那就多造一些吧。
game_functions.py
def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets):
"""检查是否有子弹击中了外星人
若击中了,删除相应的子弹和外星人
"""
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
bullets.empty()
create_fleet(ai_settings, screen, aliens, ship)
def update_bullets(ai_settings, screen, ship, bullets, aliens):
# 更新子弹位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets)
不好意思,我依旧无敌,此游戏毫无平衡性可言。
结束游戏
好歹让我输一次啊。
检测外星人和飞船碰撞
game_functions.py
def update_aliens(ai_settings, aliens, ticks, ship):
"""更新外星人群的位置"""
check_fleet_edges(ai_settings, aliens, ticks)
aliens.update(ai_settings)
# 检测外星人与飞船的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
print("!!!!!")
pygame.sprite.spritecollideany()
这个函数看名字就是sprite
-collide
-any
。
接受两个实参:一个精灵和一个编组。
当然,虽然ship
没有继承Sprite
类,但是,只要有其中的rect
属性就行。
它检查编组是否有成员与精灵发生了碰撞,找到第一个它找到的第一个与飞船发生了碰撞的成员后停止遍历数组。
返回第一个与精灵(飞船)碰撞的编组中的成员(外星人)。
响应外星人和飞船碰撞
编写GameStatus类
game_stats.py
class GameStats():
"""统计游戏各类信息"""
def __init__(self, ai_settings):
"""初始化统计信息"""
self.ai_settings = ai_settings
self.reset_stats()
def reset_stats(self):
"""初始化运行期间可能变化的统计信息"""
self.ship_left = self.ai_settings.ship_limit
game_functions.py
def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
"""响应飞船遭到碰撞后的事件"""
# 将ship_left 减一
stats.ship_left -= 1
# 清空所有子弹和外星人
aliens.empty()
bullets.empty()
# 创建新的外星人群,并将飞船至于中央
create_fleet(ai_settings, screen, aliens, ship)
ship.center_ship()
# 暂停
sleep(0.5)
ship.py
def center_ship(self):
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
有外星人到达底部
类比之前检查外星人到达边缘。
game_functions.py
def check_aliens_bottom(ai_settings, screen, aliens, bullets, stats, ship):
"""有外星人到达底部的措施"""
for alien in aliens.sprites():
if alien.rect.bottom >= screen.get_rect().bottom:
ship_hit(ai_settings, screen, ship, aliens, bullets, stats)
break
与撞到飞船时的效果相同。
小bug修复
之前做的外星人平滑下降的功能,有个bug,确切的说,是两个。
第一个:
若在外星人下降时将触碰边缘的外星人消灭,外星人会一直下降,就算将其全部消灭也无济于事。
bug重现还是也是一件技术活啊,试了好几次,手残。
看代码:
game_functions.py
def check_fleet_edges(ai_settings, aliens, ticks):
"""有外星人到达边缘的措施"""
for alien in aliens.sprites():
if alien.check_edge():
if ai_settings.drop and \
(ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
change_fleet_direction(ai_settings)
break
elif ai_settings.drop is False:
change_fleet_direction(ai_settings, ticks)
break
所有的方向改变的行为都是基于一个前提————触碰边缘,而控制下降的是ai_settings.drop
,我把边缘的外星人消灭了。
alien.check_edge()
就不会返回True
,第4行以下的代码一个也运行不了,根本改变不了ai_settings.drop
(下降标志),其与外星人一直下降。
就算到达底部,重新刷新一群外星人,由于下降标志不变,而外星人与屏幕左右有空隙,依然下降,噩梦循环。
所以,错了,就得改:
def check_fleet_edges(ai_settings, aliens, ticks):
"""有外星人到达边缘的措施"""
for alien in aliens.sprites():
if alien.check_edge():
if ai_settings.drop is False:
change_fleet_direction(ai_settings, ticks)
break
if ai_settings.drop and \
(ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
change_fleet_direction(ai_settings)
break
将下落时的情况单独列出来,与“有外星人触碰边缘”事件并列,相互为独立事件,所以不用elif
而用if
。
第二个:
当在下降途中外星人触碰到飞机或是屏幕底部,刷新后也会一直下降。
看代码:
game_functions.py
def create_fleet(ai_settings, screen, aliens, ship):
"""创建外星人群"""
new_alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens(ai_settings, new_alien)
number_rows = get_number_rows(ai_settings, new_alien, ship)
for number_row in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number, number_row)
# 随机删除
for alien in aliens.copy():
if len(aliens.sprites()) > 6 and randint(0, 1):
aliens.remove(alien)
刷新外星人群后,同样,一直下降,也是由于无法改变ai_settings.drop
下降标志。
当然,加上了上例的代码,有所改善,因为保证了每次下降的距离都会有限制。
但碰撞后还会下降一小段距离,接着上次没下降完的那一部分。
这次让下降的距离长些,好观察:
知错能改,善莫大焉:
def create_fleet(ai_settings, screen, aliens, ship):
"""创建外星人群"""
ai_settings.drop = False
ai_settings.fleet_direction = 1
每次创建外星人群,都初始化其运动状态。
两个bug完美解决
为自己鼓掌,piapiapiapiapiapia。
游戏结束
输了就输了,大不了再来一次。
当飞机用完之后游戏结束。
在GameStats
类中添加属性game_active
。
game_stats.py
--snip--
# 游戏刚启动时处于活动状态
self.game_active = True
game_dunctions.py
def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
"""响应飞船遭到碰撞后的事件"""
if stats.ship_left > 0:
# 将ship_left 减一
stats.ship_left -= 1
--snip--
else:
stats.game_active = False
如果玩家用完了所有飞船,将game_active
置为False
,游戏结束。
确定应运行游戏的哪些部分
alien_invasion.py
while True:
# 计数
ticks += 1
aliens.draw(screen)
if stats.game_active:
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings)
# 更新飞船状态
ship.update(bullets, ai_settings, screen, ship, ticks)
--snip--
注意最后一条ship.update(bullets, ai_settings, screen, ship, ticks)
是和if
语句并列的。
飞船用完后,将会停止游戏。
记分
添加Play按钮
先让游戏一开始处于非活动状态。
game_stats.py
def __init__(self, ai_settings):
"""初始化统计信息"""
self.ai_settings = ai_settings
self.reset_stats()
# 游戏刚启动时处于非活动状态
self.game_active = False
创建Button类
button.py
import pygame
from pygame.sprite import Sprite
import pygame.font
class Button():
def __init__(self, ai_settings, screen, msg):
"""初始化按钮信息"""
self.ai_settings = ai_settings
self.screen = screen
self.screen_rect = screen.get_rect()
# 设置按钮尺寸以及其他信息
self.width, self.height = 200, 50
self.color = (0, 255, 0)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 50)
# 创建按钮的rect对象将其居中
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 将msg渲染为图像,使其在按钮上居中
self.mas_image = pygame.font.render(msg, True, self.text_color, self.color)
self.msg_image_rect = self.mas_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
"""先绘出按钮,再绘制文本"""
self.screen.fill(self.color, self.rect)
self.screen.blit(self.mas_image, self.msg_image_rect)
pygame.font.SysFont()
官方文档:
create a Font object from the system fonts
SysFont(name, size, bold=False, italic=False) -> Font
Return a new Font object that is loaded from the system fonts. The font will match the requested bold and italic flags. If a suitable system font is not found this will fall back on loading the default pygame font. The font name can be a comma separated list of font names to look for.
就是返回一个字体对象,详细的就不翻译了,高中水平就能看得懂。
comma : 逗号
pygame.font.Font.render
draw text on a new Surface
render(text, antialias, color, background=None) -> Surface
This creates a new Surface with the specified text rendered on it. pygame provides no way to directly draw text on an existing Surface: instead you must use Font.render() to create an image (Surface) of the text, then blit this image onto another Surface.
返回了一个Surface
对象。
antialias[‘æntɪ’eɪlɪəs]:抗锯齿
render:在这里翻译成“渲染”,不是传递,给予,表达 之类的……
若要问抗锯齿是什么?
在屏幕上绘制按钮
game_functions.py
def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 若游戏处于非活动状态,绘制按钮
if not stats.game_active:
play_button.draw_button()
play_button.draw_button()
# 绘制飞船
ship.blitme()
# 绘制外星人
aliens.draw(screen)
# 绘制子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
# 让最近绘制的屏幕可见
pygame.display.flip()
开始游戏
添加鼠标事件:
game_functions.py
def check_events(ship, ai_settings, stats, play_button):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship, ai_settings)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship, ai_settings)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
check_play_button(stats, play_button, mouse_x, mouse_y)
def check_play_button(stats, play_button, mouse_x, mouse_y):
"""在玩家单击Play按钮时开始游戏"""
if play_button.rect.collidepoint(mouse_x, mouse_y):
stats.game_active = True
alien_invasion.py
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, stats, play_button)
重置游戏
每次单击Play按钮,都要重置游戏,重置统计信息、删除现有的外星人和子弹、创建一群新的外星人,并让飞船居中。
game_functions.py
def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
screen, aliens, bullets):
"""在玩家单击Play按钮时开始游戏"""
if play_button.rect.collidepoint(mouse_x, mouse_y):
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
# 清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
# 创建新的外星人群
create_fleet(ai_settings, screen, aliens, ship)
ship.center_ship()
将Play切换到非活动状态
存在一个问题,即:即使Play按钮不可见,玩家单击其原来所在的区域时,游戏自然会作出响应。游戏开始后,如果玩家不小心单击了Play按钮原来所处的区域,游戏奖重新开始。
可设置为,只有游戏在`game_active
为False
时,单击Play才有效。
game_functions.py
def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
screen, aliens, bullets):
"""在玩家单击Play按钮时开始游戏"""
if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
--snip--
隐藏光标
开始游戏后让光标隐藏,结束后再出现。
def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
"""响应飞船遭到碰撞后的事件"""
if stats.ship_left > 0:
--snip--
else:
stats.game_active = False
# 显示光标
pygame.mouse.set_visible(True)
def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
screen, aliens, bullets):
"""在玩家单击Play按钮时开始游戏"""
if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
# 隐藏光标
pygame.mouse.set_visible(False)
--snip--
提高等级
就这么玩下去没有什么难度,每消灭一群,都会提高外星人的难度。
修改飞船速度
settings.py
class Settings():
"""储存游戏所有的设置"""
--snip--
# 加快游戏节奏的节奏
self.speedup_scale = 1.1
self.initialiize_dynamic_settings()
def initialiize_dynamic_settings(self):
# 飞船速度
self.ship_speed_factor = 1
# 外星人左右移动速度设置
self.alien_speed_factor = 0.5
# 下落速度
self.fleet_drop_speed = 0.5
# 下落时间(帧数)
self.drop_cycle = 60
# 动画周期
self.animate_cycle = 60
def increase_speed(self):
"""提高速度设置"""
self.ship_speed_factor *= self.speedup_scale
# 外星人左右移动速度设置
self.alien_speed_factor *= self.speedup_scale
# 下落速度
self.fleet_drop_speed *= self.speedup_scale
# 下落时间(帧数)
self.drop_cycle *= self.speedup_scale
# 动画周期
self.animate_cycle *= self.speedup_scale
game_functions.py
def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets):
"""检查是否有子弹击中了外星人
若击中了,删除相应的子弹和外星人
"""
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
bullets.empty()
ai_settings.increase_speed()
create_fleet(ai_settings, screen, aliens, ship)
重置速度
game_functions.py
def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
"""响应飞船遭到碰撞后的事件"""
if stats.ship_left > 0:
--snip--
else:
stats.game_active = False
ai_settings.initialiize_dynamic_settings()
# 显示光标
pygame.mouse.set_visible(True)
记分
在GameStats
中添加score
属性:
game_stats.py
def __init__(self, ai_settings):
"""初始化统计信息"""
self.ai_settings = ai_settings
self.reset_stats()
# 游戏刚启动时处于非活动状态
self.game_active = False
# 得分
self.score = 0
显示得分
首先创建一个新类ScoreBoard
,类似Button
。
score_board.py
import pygame.font
class ScoreBoard():
"""显示得分"""
def __init__(self, ai_settings, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings
self.stats = stats
# 字体设置
self.text_color = (50, 50, 50)
self.font = pygame.font.SysFont(None, 50, True)
# 准备初始得分图像
def prep_score(self):
score_str = str(self.ai_settings.score)
self.score_image = self.font.render(score_str, True, self.text_color,
self.ai_settings.bg_color)
# 将得分屏幕放在左上角
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.score_rect.right - 20
self.score_rect.top = 20
def show_score(self):
"""在屏幕上显示得分"""
self.screen.blit(self.score_image, self.score_rect)
创建记分牌
game_functions.py
def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats, sb):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 显示分数
sb.show_score()
# 若游戏处于非活动状态,绘制按钮
if not stats.game_active:
play_button.draw_button()
# 绘制飞船
ship.blitme()
--snip--
alien_invasion.py
# 创建记分牌
sb = ScoreBoard(ai_settings, screen, stats)
--snip--
# 更新屏幕
gf.update_screen(ai_settings, screen, ship, bullets, aliens,
play_button, stats, sb)
在外星人被消灭时更新得分
settings.py
def __init__(self):
--snip--
# 外星人点数
self.alien_points = 50
def increase_speed(self):
--snip--
# 外星人点数
self.alien_points = int(self.speedup_scale * self.alien_points)
game_functions.py
def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb):
"""检查是否有子弹击中了外星人
若击中了,删除相应的子弹和外星人
"""
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score()
--snip--
最高得分
score_board.py
def show_score(self):
"""在屏幕上显示得分"""
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
def prep_high_score(self):
high_score_str = str(self.stats.high_score)
self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)
# 将得分屏幕放在右上角
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = 60
game_functions.py
def check_high_score(stats, sb):
"""检查是否出现了新的最高分"""
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high_score()
将最高分存入文件
game_functions.py
def check_high_score(stats, sb):
"""检查是否出现了新的最高分"""
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high_score()
save_high_score(stats)
def save_high_score(stats):
"""将最高分保存入文件"""
with open("high_score.json", 'w') as file:
json.dump(stats.high_score, file)
game_stats.py
--snip--
# 得分
self.score = 0
self.high_score = 0
self.get_high_score()
def get_high_score(self):
try:
with open("high_score.json") as file:
self.high_score = json.load(file)
except FileNotFoundError:
with open("high_score.json", 'w') as file:
json.dump(self.high_score, file)
这样最高分就算重新开始游戏也不会从零开始了。
显示等级
game_stats.py
# 难度等级
self.level = 1
def reset_stats(self):
"""初始化运行期间可能变化的统计信息"""
self.ship_left = self.ai_settings.ship_limit
self.score = 0
self.level = 1
score_board.py
def show_score(self):
"""在屏幕上显示得分"""
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.screen.blit(self.level_image, self.level_rect)
def prep_level(self):
"""将等级转为图像"""
self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color)
# 将登记显示在分数下面
self.level_rect = self.level_image.get_rect()
self.level_rect.top = self.score_rect.bottom + 10
self.level_rect.left = self.score_rect.left
game_functions.py
def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb):
"""检查是否有子弹击中了外星人
若击中了,删除相应的子弹和外星人
"""
--snip--
# 提高等级
stats.level += 1
sb.prep_level()
--snip--
显示余下的飞船
ship_small.py
import pygame
from pygame.sprite import Sprite
class ShipSmall(Sprite):
"""小飞船"""
def __init__(self, screen):
super().__init__()
self.screen = screen
self.image = pygame.image.load('images/ship_small.bmp')
self.rect = self.image.get_rect()
self.rect.top = 10
因为之前的飞船的图片太大,又做了个小的,重写了一个类。
类似于子弹一样,有多少个话多少个,写一个编组,把小飞船放进去。
score_board.py
def show_score(self):
"""在屏幕上显示得分"""
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.screen.blit(self.level_image, self.level_rect)
self.ships_small.draw(self.screen)
def prep_ships(self):
"""显示还剩下多少飞船"""
self.ships_small = Group()
for ship_number in range(self.stats.ship_left):
ship_small = ShipSmall(self.ai_settings)
ship_small.rect.left = 10 + ship_number * ship_small.rect.width
self.ships_small.add(ship_small)
其余的就和之前相似,在game_functions.py
和alien_invasion.py
中添加函数,以及参数即可,毫无难度可言。
完整代码展示
alien_invasion.py
import pygame
from pygame.sprite import Group
from settings import Settings
from ship import Ship
from game_stats import GameStats
from button import Button
from score_board import ScoreBoard
import game_functions as gf
def run_game():
# 初始化游戏,并创建一个屏幕对象
pygame.init()
# 创建设置实例
ai_settings = Settings()
# 创建储存游戏信息实例
stats = GameStats(ai_settings)
# 创建游戏屏幕
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
# 设定游戏名
pygame.display.set_caption(ai_settings.caption)
# 创建Play按钮
play_button = Button(ai_settings, screen, "Play")
# 创建记分牌
sb = ScoreBoard(ai_settings, screen, stats)
# 创建飞船实例
ship = Ship(screen, ai_settings)
# 储存子弹的编组
bullets = Group()
# 储存外星人的编组
aliens = Group()
# 计数器
ticks = 0
# 创建外星人群
gf.create_fleet(ai_settings, screen, aliens, ship)
# 开始游戏的主循环
while True:
# 计数
ticks += 1
aliens.draw(screen)
if ticks >= ai_settings.animate_cycle:
ticks = 0
# 监视键盘和鼠标事件
gf.check_events(ship, ai_settings, stats, play_button, screen, aliens, bullets, sb )
if stats.game_active:
# 更新飞船状态
ship.update(bullets, ai_settings, screen, ship, ticks)
# 更新子弹状态
gf.update_bullets(ai_settings, screen, ship, bullets, aliens, stats, sb)
# 更新外星人状态
gf.update_aliens(ai_settings, screen, aliens, bullets, stats, ticks, ship, sb)
# 更新屏幕
gf.update_screen(ai_settings, screen, ship, bullets, aliens,
play_button, stats, sb)
run_game()
game_functions.py
import sys
from random import randint
from time import sleep
import json
import pygame
from alien import Alien
def check_keydown_events(event, ship, ai_settings):
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
if ship.moving_left:
ship.moving_right_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_LEFT:
ship.moving_left = True
if ship.moving_right:
ship.moving_left_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_UP:
ship.moving_up = True
if ship.moving_down:
ship.moving_up_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_DOWN:
ship.moving_down = True
if ship.moving_up:
ship.moving_down_speed = 2 * ai_settings.ship_speed_factor
elif event.key == pygame.K_SPACE:
ship.shooting = True
elif event.key == pygame.K_q:
sys.exit()
def check_keyup_events(event, ship, ai_settings):
"""响应松开"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
ship.moving_right_speed = ai_settings.ship_speed_factor
if ship.moving_left:
ship.moving_left_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_LEFT:
ship.moving_left = False
ship.moving_left_speed = ai_settings.ship_speed_factor
if ship.moving_right:
ship.moving_right_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_UP:
ship.moving_up = False
ship.moving_up_speed = ai_settings.ship_speed_factor
if ship.moving_down:
ship.moving_down_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_DOWN:
ship.moving_down = False
ship.moving_down_speed = ai_settings.ship_speed_factor
if ship.moving_up:
ship.moving_up_speed = ai_settings.ship_speed_factor
elif event.key == pygame.K_SPACE:
ship.shooting = False
def check_events(ship, ai_settings, stats, play_button, screen, aliens, bullets, sb):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship, ai_settings)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship, ai_settings)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
screen, aliens, bullets, sb)
def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats, sb):
"""更新屏幕上的图像 , 并切换到新屏幕"""
# 重绘屏幕
screen.fill(ai_settings.bg_color)
# 显示分数
sb.show_score()
# 绘制飞船
ship.blitme()
# 绘制外星人
aliens.draw(screen)
# 绘制子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
# 若游戏处于非活动状态,绘制按钮
if not stats.game_active:
play_button.draw_button()
# 让最近绘制的屏幕可见
pygame.display.flip()
def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb):
"""检查是否有子弹击中了外星人
若击中了,删除相应的子弹和外星人
"""
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score()
check_high_score(stats, sb)
if len(aliens) == 0:
bullets.empty()
ai_settings.increase_speed()
# 提高等级
stats.level += 1
sb.prep_level()
create_fleet(ai_settings, screen, aliens, ship)
def update_bullets(ai_settings, screen, ship, bullets, aliens, stats, sb):
# 更新子弹位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb)
def get_number_aliens(ai_settings, new_alien):
# 创建一个外星人,并计算一行可以容纳多少外星人
# 外星人间距为外星人宽度
available_space_x = ai_settings.screen_width - (2 * new_alien.rect.width)
return int(available_space_x / (2 * new_alien.rect.width))
def create_alien(ai_settings, screen, aliens, alien_number, number_row):
# 创建一个外星人并加入当前行
alien = Alien(ai_settings, screen)
alien.x = alien.rect.width * (2 * alien_number + 1)
alien.y = alien.rect.height * (2 * number_row + 1)
alien.rect.x = alien.x
alien.rect.y = alien.y
aliens.add(alien)
def get_number_rows(ai_settings, new_alien, ship):
available_space_y = ai_settings.screen_height - ship.rect.width - new_alien.rect.height
return int(available_space_y / (2 * new_alien.rect.height))
def create_fleet(ai_settings, screen, aliens, ship):
"""创建外星人群"""
ai_settings.drop = False
ai_settings.fleet_direction = 1
new_alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens(ai_settings, new_alien)
number_rows = get_number_rows(ai_settings, new_alien, ship)
for number_row in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number, number_row)
# 随机删除
for alien in aliens.copy():
if len(aliens.sprites()) > 6 and randint(0, 1):
aliens.remove(alien)
def check_fleet_edges(ai_settings, aliens, ticks):
"""有外星人到达边缘的措施"""
for alien in aliens.sprites():
if alien.check_edge():
if ai_settings.drop is False:
change_fleet_direction(ai_settings, ticks)
break
if ai_settings.drop and \
(ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
change_fleet_direction(ai_settings)
break
def check_aliens_bottom(ai_settings, screen, aliens, bullets, stats, ship, sb):
"""有外星人到达底部的措施"""
for alien in aliens.sprites():
if alien.rect.bottom >= screen.get_rect().bottom:
ship_hit(ai_settings, screen, ship, aliens, bullets, stats, sb)
break
def change_fleet_direction(ai_settings, ticks=0):
"""改变外星人群的运动方向"""
if ticks:
ai_settings.drop = True
ai_settings.drop_ticks = ticks
else:
ai_settings.drop = False
ai_settings.fleet_direction *= -1
def update_aliens(ai_settings, screen, aliens, bullets, stats, ticks, ship, sb):
"""更新外星人群的位置"""
check_fleet_edges(ai_settings, aliens, ticks)
# 检查是否有外星人到达底端
check_aliens_bottom(ai_settings, screen, aliens, bullets, stats, ship, sb)
aliens.update(ai_settings)
# 检测外星人与飞船的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(ai_settings, screen, ship, aliens, bullets, stats, sb)
def ship_hit(ai_settings, screen, ship, aliens, bullets, stats, sb):
"""响应飞船遭到碰撞后的事件"""
if stats.ship_left > 0:
# 将ship_left 减一
stats.ship_left -= 1
# 清空所有子弹和外星人
aliens.empty()
bullets.empty()
# 创建新的外星人群,并将飞船至于中央
create_fleet(ai_settings, screen, aliens, ship)
ship.center_ship()
# 更新记分牌
sb.prep_ships()
# 暂停
sleep(0.5)
else:
stats.game_active = False
ai_settings.initialiize_dynamic_settings()
# 显示光标
pygame.mouse.set_visible(True)
def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
screen, aliens, bullets, sb):
"""在玩家单击Play按钮时开始游戏"""
if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
# 隐藏光标
pygame.mouse.set_visible(False)
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
# 重置分数
sb.prep_score()
# 重置等级
sb.prep_level()
# 重置飞船数
sb.prep_ships()
# 清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
# 创建新的外星人群
create_fleet(ai_settings, screen, aliens, ship)
ship.center_ship()
def check_high_score(stats, sb):
"""检查是否出现了新的最高分"""
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high_score()
save_high_score(stats)
def save_high_score(stats):
"""将最高分保存入文件"""
with open("high_score.json", 'w') as file:
json.dump(stats.high_score, file)
ship.py
import pygame
from bullet import Bullet
class Ship():
def __init__(self, screen, ai_settings):
"""初始化飞船并设置其初始位置"""
super().__init__()
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.centerx
self.rect.bottom = self.screen_rect.bottom
# 移动标志
self.moving_right = False
self.moving_left = False
self.moving_up = False
self.moving_down = False
# 移动像素
self.moving_right_speed = ai_settings.ship_speed_factor
self.moving_left_speed = ai_settings.ship_speed_factor
self.moving_up_speed = ai_settings.ship_speed_factor
self.moving_down_speed = ai_settings.ship_speed_factor
# 射击标志
self.shooting = False
def update(self, bullets, ai_settings, screen, ship, ticks):
"""根据移动标志调整飞船状态"""
# 移动
if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
self.rect.centery += self.moving_down_speed
if self.moving_up and self.rect.top > 0:
self.rect.centery -= self.moving_up_speed
if self.moving_right and self.rect.right < self.screen_rect.right:
self.rect.centerx += self.moving_right_speed
if self.moving_left and self.rect.left > 0:
self.rect.centerx -= self.moving_left_speed
# 射击
if self.shooting and ticks % 15 == 0:
self.add_bullets(bullets, ai_settings, screen, ship)
def blitme(self):
"""指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
def add_bullets(self, bullets, ai_settings, screen, ship):
# 创建一颗子弹,并将其加入到编组bullets中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
def center_ship(self):
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
ship_small.py
import pygame
from pygame.sprite import Sprite
class ShipSmall(Sprite):
"""小飞船"""
def __init__(self, screen):
super().__init__()
self.screen = screen
self.image = pygame.image.load('images/ship_small.bmp')
self.rect = self.image.get_rect()
self.rect.top = 10
settings.py
class Settings():
"""储存游戏所有的设置"""
def __init__(self):
self.screen_width = 1500
self.screen_height = 900
self.bg_color = (0, 0, 15)
self.caption = "Alien Invasion 1.0"
# 飞船速度
self.ship_speed_factor = 1
# 飞船数量
self.ship_limit = 3
# 子弹设置
self.bullet_speed = 3
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (255, 0, 0)
# 外星人左右移动速度设置
self.alien_speed_factor = 0.5
# 下落速度
self.fleet_drop_speed = 0.5
# 左右移动方向
self.fleet_direction = 1
# 下落标志
self.drop = False
# 下落开始时间(帧数)点
self.drop_ticks = 0
# 下落时间(帧数)
self.drop_cycle = 60
# 外星人点数
self.alien_points = 50
# 动画周期
self.animate_cycle = 60
# 加快游戏节奏的节奏
self.speedup_scale = 1.1
self.initialiize_dynamic_settings()
def initialiize_dynamic_settings(self):
# 飞船速度
self.ship_speed_factor = 1
# 外星人左右移动速度设置
self.alien_speed_factor = 0.5
# 下落速度
self.fleet_drop_speed = 0.5
# 下落时间(帧数)
self.drop_cycle = 60
# 外星人点数
self.alien_points = 50
# 动画周期
self.animate_cycle = 60
def increase_speed(self):
"""提高速度设置"""
self.ship_speed_factor *= self.speedup_scale
# 外星人左右移动速度设置
self.alien_speed_factor *= self.speedup_scale
# 下落速度
self.fleet_drop_speed *= self.speedup_scale
# 下落时间(帧数)
self.drop_cycle *= self.speedup_scale
# 动画周期
self.animate_cycle *= self.speedup_scale
# 外星人点数
self.alien_points = int(self.speedup_scale * self.alien_points)
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""外星人 类"""
def __init__(self, ai_settings, screen):
"""初始化外星人并设置其起始位置"""
# 类似Ship类的设置
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
self.image = pygame.image.load("images/alien.bmp")
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
self.y = float(self.rect.y)
def update(self, ai_settings):
if ai_settings.drop:
self.y += self.ai_settings.fleet_drop_speed
self.rect.y = self.y
else:
self.x += self.ai_settings.alien_speed_factor * ai_settings.fleet_direction
self.rect.x = self.x
def check_edge(self):
"""如果触碰边缘返回True"""
if self.rect.right >= self.screen.get_rect().right or \
self.rect.left <= self.screen.get_rect().left:
return True
game_stats.py
import json
class GameStats():
"""统计游戏各类信息"""
def __init__(self, ai_settings):
"""初始化统计信息"""
self.ai_settings = ai_settings
self.reset_stats()
# 游戏刚启动时处于非活动状态
self.game_active = False
# 飞船数量
self.ship_left = self.ai_settings.ship_limit
# 得分
self.score = 0
# 最高分
self.high_score = 0
self.get_high_score()
# 难度等级
self.level = 1
def reset_stats(self):
"""初始化运行期间可能变化的统计信息"""
self.ship_left = self.ai_settings.ship_limit
self.score = 0
self.level = 1
def get_high_score(self):
"""读取最高分"""
try:
with open("high_score.json") as file:
self.high_score = json.load(file)
except FileNotFoundError:
# 若第一次玩游戏,则创建文件,将 0 写入文件
with open("high_score.json", 'w') as file:
json.dump(self.high_score, file)
bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""对飞船发射的子弹进行管理的类"""
def __init__(self, ai_settings, screen, ship):
"""在飞船所处的位置创建一个子弹对象"""
super().__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.centerx
self.rect.top = ship.rect.top
# 储存用小数表示的子弹位置
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed = ai_settings.bullet_speed
def update(self):
"""向上移动子弹"""
# 更新表示资单位值的小数值
self.y -= self.speed
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
score_board.py
import pygame.font
from pygame.sprite import Group
from ship_small import ShipSmall
class ScoreBoard():
"""显示得分"""
def __init__(self, ai_settings, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings
self.stats = stats
# 字体设置
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 50, italic=True)
# 准备初始得分图像
self.prep_score()
self.prep_high_score()
self.prep_level()
self.prep_ships()
def prep_score(self):
score_str = str(self.stats.score)
self.score_image = self.font.render(score_str, True, self.text_color,
self.ai_settings.bg_color)
# 将得分屏幕放在右上角
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 10
self.prep_ships()
def show_score(self):
"""在屏幕上显示得分"""
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.screen.blit(self.level_image, self.level_rect)
self.ships_small.draw(self.screen)
def prep_high_score(self):
high_score_str = str(self.stats.high_score)
self.high_score_image = self.font.render(high_score_str, True,
self.text_color, self.ai_settings.bg_color)
# 将得分屏幕放在右上角
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = 10
def prep_level(self):
"""将等级转为图像"""
self.level_image = self.font.render(str(self.stats.level), True,
self.text_color, self.ai_settings.bg_color)
# 将登记显示在分数下面
self.level_rect = self.level_image.get_rect()
self.level_rect.top = self.score_rect.bottom + 10
self.level_rect.left = self.score_rect.left
def prep_ships(self):
"""显示还剩下多少飞船"""
self.ships_small = Group()
for ship_number in range(self.stats.ship_left):
ship_small = ShipSmall(self.ai_settings)
ship_small.rect.left = 10 + ship_number * ship_small.rect.width
self.ships_small.add(ship_small)