文章目录
背景部分
创建Pygame窗口以及响应用户输入
# invasion.py
import sys
import pygame
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
screen = pygame.display.set_mode((1200, 700))
pygame.display.set_caption("Thunder")
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
- 首先我们导入模块
pygame
和sys
。sys用于退出游戏 - 游戏以函数
run_game()
开头。 pygame.init()
用于初始化游戏背景。pygame.display.set_mode()
用于创建一个名为screen的显示窗口。实参(1200,700)是一个元组,指定游戏窗口的尺寸。- 对象screen是一个surface。在Pygame中,surface是屏幕的一部分,用于显示游戏元素(比如外星人、飞船),游戏中每个元素都是一个surface。激活游戏的动画循环后,每经过一次循环都将重新绘制这个surface。
- 为访问Pygame侦听到的时间,我们使用方法
pygame.event.get()
。所有的键盘和鼠标事件都将促使for循环运行。比如玩家点击窗口的关闭按钮时,将检测到pygame.QUIT事件,我们就调用sys.exit()
来退出游戏。 pygame.display.flip()
命令Pygame让最近绘制的屏幕可见。它在每次执行while循环时都会绘制一个空屏幕,并擦去旧屏幕。
绘制背景色
# 在while循环中加入如下语句:
screen.fill((230,230,230))
Pygame中,颜色是以RGB值表示的。
创建设置类
#settings.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
将所有游戏的设置存储在这个类中, 则invasion.py可修改:
import sys
import pygame
from settings import Settings
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
sett = Settings()
screen = pygame.display.set_mode(
(sett.screen_length, sett.screen_width)
)
pygame.display.set_caption("Thunder")
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(sett.bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
飞船部分
添加飞船图像
就选用书配套的素材吧
创建Ship类
import pygame
class Ship():
def __init__(self, screen):
self.screen = screen
self.image = pygame.image.load("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)
在屏幕上绘制飞船
在invasion.py中创建飞船对象,并调用其方法blitme():
import sys
import pygame
from settings import Settings
from ship import Ship
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
sett = Settings()
screen = pygame.display.set_mode(
(sett.screen_width, sett.screen_length)
)
pygame.display.set_caption("Thunder")
ship = Ship(screen)
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(sett.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
运行后结果:
重构:game_function模块
函数check_events()
我们把管理事件的代码移到一个名为check_events()的函数里,以简化run_game()
#game_function.py
import sys
import pygame
def check_events():
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
函数update_screen()
为进一步简化run_game(),将更新屏幕的代码移到一个名为update_screen()的函数里,并将函数定义放在game_function中
def update_screen(sett, screen, ship):
screen.fill(sett.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
飞船移动部分
响应按键
每当用户按键时,都在Pygame里注册一个事件。事件都是通过方法pygame.event.get()
获取的,因此在函数check_events()中,我们需要制定检查哪些类型的事件。
每次按键都被注册一个KEYDOWN
事件。检测到该事件后,我们需要检查是否按下了特定的键,执行特定的操作。比如按下右键后,要让飞船向右移动。
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
我们在参数列表里加入了ship,因为需要能够访问到飞船内部的属性。
允许不断移动
玩家按住→键是希望飞船不停移动,直到松开为止。
我们可以让游戏检测pygame.KEYUP
事件,然后结合KEYUP和KEYDOWN事件实现持续移动。
# ship.py
def __init__(self, screen):
...
self.right_move = False
def update(self):
if self.right_move:
self.rect.centerx += 1
在飞船的类内初始化时多添了一个属性移动标志变量right_move
多添了一个方法update()
,用于检查该标志变量,实现飞船属性更新:这个变量为True时,飞船就会向右移动。
而这个标志变量会因KEYDOWN变为True,因KEYUP变为False,以此来实现持续移动
同时,要在invasion.py的while循环里调用update()方法:
# invasion.py
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(sett, screen, ship)
左右移动
只需照着向右移动就能做出向左移动
调整飞行速度
每次执行while循环,飞船最多移动1像素。但可以在settings模块里加入属性ship_speed
,用于控制飞船的速度。
# settings.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
self.ship_speed = 0.7
同时在ship.py中修改:
class Ship():
def __init__(self, sett, screen):
...
self.center = float(self.rect.centerx)
...
...
def update(self):
if self.right_move:
self.center += sett.ship_speed
if self.left_move:
self.center -= sett.ship_speed
self.rect.centerx = self.center
- 在__init__()的形参中加入了setting类的sett,让飞船的方法update()可以获取其速度设置。
- rect只存储整数,所以我们新建一个属性
center
,用flota()
将rect.centerx
转化成小数存储到center
中。更新center之后,再根据它来更新控制飞船位置的rect.centerx(虽然centerx只存储self.center的整数部分,但对于显示飞船而言问题不大。)
限制飞船活动范围
为了防止飞船飞出屏幕外,我们在飞船位置变更前添加if语句判断飞船是否将飞出框外。
# ship.py
def update(self):
if self.right_move and self.rect.right < self.screen_rect.right:
self.center += self.sett.ship_speed
if self.left_move and self.rect.left > 0:
self.center -= self.sett.ship_speed
如果rect的左/右边缘没有触及屏幕左/右边缘,才可以移动。
重构check_event()
# game_function.py
def check_keydown(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
def check_keyup(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = False
elif event.key == pygame.K_LEFT:
ship.left_move = 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(event, ship)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
子弹部分
添加子弹设置
在setting.py中添加新类Bullet所需的值:
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
...
# 子弹设置
self.bullet_speed = 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, sett, screen, ship):
"""在飞船处创建一个子弹对象"""
super(Bullet, self).__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, sett.bullet_width, sett.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = sett.bullet_color
self.speed_factor = sett.bullet_speed
子弹并非基于图像,因此我们必须使用pygame.Rect()
类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的x坐标和y坐标,还有宽度和高度。我们先在(0,0)处创建一个矩形,并在接下来放在正确的位置,这个位置取决于飞船的位置。
接下来编写update()
和draw_bullet
方法
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)
将子弹存到编组中
在玩家每次按下空格时都射出一发子弹。首先我们在invasion.py中创建一个编组(Group)用于存储所有子弹,以便能够管理发射出去的子弹。
这个编组是pygame.sprite.Group类的一个实例;Group类 类似于列表,但提供了有助于游戏开发的功能。在主循环中,我们使用这个编组在屏幕上绘制子弹,更新每一个子弹的位置。
import sys
import pygame
import game_function as gf
from pygame.sprite import Group
from settings import Settings
from ship import Ship
def run_game():
# 初始化游戏并创建一个屏幕对象
...
bullets = Group()
# 开始游戏的主循环
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_screen(sett, screen, ship, bullets)
run_game()
我们将bullets作为实参传递给了check_events()和update_screen()。在check_event()中我们要用空格处理bullets;在update_screen中则要更新绘制到屏幕上的bullets。
当你对编组调用update()时,编组将自动对每一个"精灵"调用update(),即对每一个子弹。
开火
因为只有在按下空格键时飞船才会开火,所以我们只需修改check_keydown_events()
而不用修改keyup
# game_function.py
from bullet import Bullet
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def check_events(sett, screen, ship, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
def update_screen(sett, screen, ship, bullets):
screen.fill(sett.bg_color)
ship.blitme()
for bullet in bullets:
bullet.draw_bullet()
# 让最近绘制的屏幕可见
pygame.display.flip()
删除已经消失的子弹
我们需要将已经飞出屏幕的子弹删除,减少内存负担。
为此,我们需要在每次更新子弹位置后,检测rect的bottom属性小于0的子弹,并删除它们。
# invasion.py
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
gf.update_screen(sett, screen, ship, bullets)
- 在for循环中,不应从列表或编组中删除条目,因此必须是遍历编组的副本,故需要调用方法
copy()
,返回一个编组的副本。 - 输出编组的长度,即有效子弹的数量,是为了显示子弹的数量,核实已消失的子弹确实被删除了。
子弹效果如图:
限制子弹数量
多数同类型游戏里面都会有对子弹数量的限制,鼓励玩家有目标地射击。
我们在此限制子弹最大数量为4.
首先在Setting类里设置允许的最大子弹数:
#setting.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
...
# 子弹设置
...
self.bullet_allowed = 4
在check_keydown_event()中检测到空格前,添加if语句判断子弹数量(群组长度)是否已经超过最大限制。
# game_function.py
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
重构bullet函数
我们可以把子弹更新函数和删除子弹的代码写进一个函数update_bullet()
里:
# game_function.py
def update_bullet(bullets):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
故主循环里的代码可简化:
# invasion.py
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
gf.update_bullet(bullets)
gf.update_screen(sett, screen, ship, bullets)
同时,把检查子弹数量是否超额的代码已经添加新子弹的代码整合进一个fire_bullet()
函数里:
# game_function.py
de