接着做接着做接着做
来源:《Python编程:从入门到实践》
game_functions
的新模块,它存储大量让《外星人入侵》运行的函数game_functions
,可避免alien_invasion.py
太长,逻辑也更易理解管理事件的代码移到check_events()的函数中,以简化run_game()并隔离事件管理循环
game_functions.py
import sys
import pygame
def check_events():
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
sys和pygame
函数check_events()不需要形参
,函数体复制alien_invasion.py的事件循环alien_invasion.py
import pygame
import game_functions as gf
def run_game():
--snip--
# 开始游戏的主循环
while True:
gf.check_events()
# 每次循环时都重绘屏幕
--snip--
sys
,因为模块game_functions
中使用了它game_functions.py
--snip--
def check_events():
--snip--
def update_screen(ai_settings, screen, ship):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
update_screen()包含三个形参:ai_settings、screen、ship
更新屏幕的代码替换为函数update_screen()
alien_invasion.py
--snip--
def run_game():
--snip--
# 开始游戏的主循环
while True:
gf.check_events()
gf.update_screen(ai_settings, screen, ship)
在模块game_functions完成大部分工作而不是run_game()
练习12-1 蓝色天空:创建一个背景为蓝色的Pygame窗口
test12-1.py
import sys
import pygame
from settings import Settings
def run_game():
pygame.init()
settings = Settings()
screen = pygame.display.set_mode(
(settings.screen_width, settings.screen_height))
pygame.display.set_caption("程浩宇的蓝色天空")
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(settings.bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
settings.py
class Settings():
def __init__(self):
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (70, 130, 180)
练习12-2 游戏角色:找一幅你喜欢的游戏角色位图图像或将一副图像转换为位图。创建一个类,将该角色绘制到屏幕中央,并将该图像的背景色设置为屏幕背景色,或将屏幕背景色设置为该图像的背景色
test12-2.py
直接用现成的小飞船吧,背景色用上面的蓝色,再把小飞船的背景色改成屏幕背景色…
将角色绘制到屏幕中央,设置相应rect对象的属性centerx、centery即可
下面就把模块ship放上来吧,主程序和模块settings变动不大
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.centery = self.screen_rect.centery
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
方法pygame.event.get()
获取的,因此在函数check_events()
中,每次按键都将被注册为一个KEYDOWN事件
右箭头键,我们就增大飞船的rect.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
函数check_events()中包含形参ship
check_events()
内部,在事件循环中添加了一个elif代码块
,以便在Pygame检查到KEYDOWN事件时做出响应属性event.key
,检查按下的是否是右箭头键(pygame.K_RIGHT)
,若是,ship.rect.centerx的值加1,飞船右移
alien_invasion.py
# 开始游戏的主循环
while True:
gf.check_events(ship)
gf.update_screen(ai_settings, screen, ship)
pygame.KEYUP
事件,以便玩家松开右箭头键时我们知道这一点结合使用KEYDOWN和KEYUP事件,以及一个moving_right的标志来实现持续移动
方法update()
和一个属性moving_right
:ship.py
class Ship():
def __init__(self, screen):
--snip--
# 移动标志
self.moving_right = False
def update(self):
"""根据移动标志调整飞船的位置"""
if self.moving_right:
self.rect_centerx += 1
def blitme(self):
--snip--
game_functions.py
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
--snip--
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
moving_right设置为True
elif代码块
,用于响应KEYUP事件:玩家松开→时,将moving_right设置为False
while循环
,以便每次执行循环时都调用飞船的方法update()
:alien_invasion.py
# 开始游戏的主循环
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings, screen, ship)
Ship类
和函数check_events()
:ship.py
def __init__(self, screen):
--snip--
# 移动标志
self.moving_right = False
self.moving_left = False
def update(self):
"""根据移动标志调整飞船的位置"""
if self.moving_right:
self.rect.centerx += 1
if self.moving_left:
self.rect.centerx -= 1
标志moving_left
方法update()中,添加了一个if代码块而不是elif代码块
:当玩家同时按下左右箭头键,将先增大self.rect.centerx,再降低这个值,即飞船的位置保持不变check_events()
:game_functions.py
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
--snip--
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.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
这里之所以使用elif代码块,是因为每个事件都只与一个键相关联
如果同时按下了←→,将检测到两个不同事件
Settings类
中添加属性ship_speed_factor
,用于控制飞船的速度settings.py
class Settings():
def __init__(self):
--snip--
# 飞船的设置
self.ship_speed_factor = 1.5
ship.py
def __init__(self, ai_settings, screen):
"""初始化飞船并设置其初始位置"""
self.screen = screen
self.ai_settings = ai_settings
--snip--
# 将每艘新飞船放在屏幕底部中央
--snip--
#在飞船的属性center中存储小数值
self.center = float(self.rect.centerx)
# 移动标志
self.moving_right = False
self.moving_left = False
def update(self):
"""根据移动标志调整飞船的位置"""
# 更新飞船的center值,而不是rect
if self.moving_right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left:
self.center -= self.ai_settings.ship_speed_factor
# 根据self.center更新rect对象
self.rect.centerx = self.center
__init__()
的形参列表中添加了ai_settings,让飞船能够获取其速度设置
可以使用小数来设置rect的属性,但rect将只存储这个值的整数部分
新属性self.center
函数float()将self.rect.centerx的值转换为小数,并将结果存储到self.center中
self.center的值增加或减去ai_settings.ship_speed_factor的值
self.rect.centerx将只存储self.center的整数部分,但对显示飞船而言,这问题不大
alien_invasion.py
def run_game():
--snip--
# 创建一艘飞船
ship = Ship(ai_settings, screen)
--snip--
ship.py
def update(self):
"""根据移动标志调整飞船的位置"""
# 更新飞船的center值,而不是rect
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
# 根据self.center更新rect对象
self.rect.centerx = self.center
game_functions.py
def check_keydown_events(event, ship):
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
def check_keyup_events(event, ship):
"""响应松开"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def check_events(ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
__init__()
末尾存储新类Bullet
所需的值:settings.py
def __init__(self):
--snip--
# 子弹设置
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
Bullet类
,前半部分:bullet.py
import pygame
from pygame.sprite import Sprite
class 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.centerx
self.rect.top = ship.rect.top
# 存储用小数表示的子弹位置
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
Bullet类
继承从模块pygame.sprite中导入的Sprite类
精灵
,可将游戏中相关的元素编组,进而同时操作编组中的所有元素
__init__()传递ai_settings、screen和ship实例,还调用了super()来继承Sprite
注意:代码super(Bullet, self).init() 使用了Python2.7语法。这种语法也适用于Python3,但你也可以将这行代码简写为super().init()
bullet.py
的第二部分——方法update() 和 draw_bullet()
:bullet.py
def update(self):
"""向上移动子弹"""
# 更新表示子弹位置的小数值
self.y -= self.speed_factor
# 更新表示子弹的rect的位置
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子弹"""
pygame.draw.rect(self.screen, self.color, self.rect)
方法update()管理子弹的位置
draw_bullet()
函数draw.rect()使用存储在self.color中的颜色填充表示子弹的rect占据的屏幕部分
在alien_invasion.py中创建一个编组(group)
,存储所有有效的子弹,以便能够管理发射出去的所有子弹这个group将是pygame.sprite.Group类的一个实例
pygame.sprite.Group类 类似于列表,但提供有助于开发游戏的额外功能
group
在屏幕上绘制子弹,以及更新每颗子弹的位置:alien_invasion.py
from pygame.sprite import Group
--snip--
def run_game():
--snip--
# 创建一艘飞船
ship = Ship(ai_settings, screen)
# 创建一个用于存储子弹的编组
bullets = Group()
# 开始游戏的主循环
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_screen(ai_settings, screen, ship, bullets)
--snip--
创建了一个Group实例,将其命名为bullets
注意:如果在循环内部创建这样的编组,游戏运行时将创建数千个子弹编组
当你对编组调用update()时,编组将自动对其中的每个精灵调用update()
,因此代码行bullets.update()将为编组bullets中的每颗子弹调用bullet.update()
game_functions.py
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_SPACE:
# 创建一颗子弹,并将其加入到编组bullets中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
--snip--
def check_events(ai_settings, screen, ship, bullets):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
--snip--
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship, bullets)
--snip--
def update_screen(ai_settings, screen, ship, bullets):
--snip--
# 在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
编组bullets传递给了check_keydown_events()
。一个名为new_bullet的Bullet实例
),并使用方法add()将其加入到编组bullets中
方法bullets.sprites()返回一个列表,其中包含编组bullets的所有精灵
draw_bullet()
alien_invasion.py
# 开始游戏的主循环
while True:
--snip--
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
--snip--
在for中,不应从列表或编组中删除条目,因此必须遍历编组的副本
copy()
来设置for循环,这能够在循环中修改bulletssettings.py
# 子弹设置
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
self.bullets_allowed = 3
game_functions.py
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
# 创建一颗子弹,并将其加入到编组bullets中
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
game_functions.py
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
def fire_bullet(ai_settings, screen, ship,bullets):
"""如果还没有达到限制,就发射一颗子弹"""
# 创建一颗子弹,并将其加入到编组bullets中
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)