开发大型项目时,制定好规划后再动手编写代码很重要。规划可确保你不偏离轨道,从而提高项目成功的可能性。
《外星人入侵》游戏的描述:
在游戏《外星人入侵》中,玩家控制一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键射击。游戏开始时,一群外星人出现在天空中,并向屏幕下方移动。玩家的任务是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,其移动速度更快。只要有外星人撞到玩家的飞船或到达屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。
开发的第一个阶段就是船舰一艘飞船,它可左右移动,并且能在用户按空格键时开火。
import sys
import pygame
class AlienInvasion:
def __init__(self):
"""管理游戏资源和行为的类"""
pygame.init()
#创建游戏窗口,指定窗口尺寸为1200X800像素,并将显示窗口赋给属性self.screen
self.screen = pygame.display.set_mode((1200,800))
pygame.display.set_caption("Alien Invasion")
def run_game(self):
"""开始游戏的主循环"""
while True:
#监视键盘和鼠标事件
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__=='__main__':
#创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
首先导入模块sys和pygame。模块pygame包含开发游戏所需的功能,玩家退出时,使用工具sys来退出游戏。
赋给属性self.screen的对象是一个surface。在Pygame中,surface是屏幕的一部分,用于显示游戏元素。在这个游戏中每个元素(如外星人或飞船)都是一个surface。pygame.display.set_mode()返回的surface表示整个游戏的窗口。激活游戏动画循环后,每经过一次循环都将自动重绘这个surface,将用户触发的所有变化都反映出来。
这个游戏由方法run_game()控制。该方法包含一个不断运行的while循环,而这个循环包含一个事件循环以及管理屏幕更新的代码。事件是用户玩游戏时执行的操作,如按键或移动鼠标。为程序响应事件,可编写一个事件循环,以侦听事件并根据发生的事件类型执行合适的任务。
函数pygame.event.get()用于访问Pygame检测到的事件,这个函数返回一个列表,其中包含它在上一次被调用后发生的所有之间。所有键盘或鼠标事件都将导致这个for循环运行。
函数pygame.display.flip(),命令Pygame让最近绘制的屏幕可见。它在每次执行while循环时都绘制一个空屏幕,并擦去就屏幕,使得只有新屏幕可见。
class Settings:
"""存储游戏《外星人入侵》中所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
修改alien_invasion.py:
import sys
import pygame
from settings import Settings
class AlienInvasion:
def __init__(self):
"""管理游戏资源和行为的类"""
pygame.init()
self.settings=Settings()
self.screen = pygame.display.set_mode(
(self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
def run_game(self):
"""开始游戏的主循环"""
while True:
#监视键盘和鼠标事件
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
#每次循环时都重绘屏幕
"""
fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
"""
self.screen.fill(self.settings.bg_color)
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__=='__main__':
#创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
为了在屏幕上绘制玩家的飞船,我们将加载一幅图像,再使用Pygame方法blit()绘制它。
可以使用Pixabay网站提供的免费图形,无需授权许可即可使用并修改。
在游戏中机会可以使用任何类型的图像文件,但使用位图(.bmp)文件最为简单,因为Pygame默认加载位图。
选择图像时,要特别注意背景色,尽可能选择背景与透明或纯色的图像,便于使用图像编辑器将其背景替换为任意颜色。图像的背景色与游戏的背景色匹配时,游戏看起来最漂亮。
import pygame
class Ship:
"""管理飞船的类"""
# ai_game是当前AlienInvasion的实例的引用
def __init__(self, ai_game):
"""初始化飞船并设置其初始位置"""
self.screen = ai_game.screen
self.screen_rect = ai_game.screen.get_rect()
# 加载飞船图像并获取其外接矩形
"""
pygame.image.load()加载图像,并将飞船图像的位置传递给它
该函数返回一个表示飞船的surface
"""
self.image = pygame.image.load('images/ship.bmp')
"""
使用get_rect()函数获取相应surface的属性rect
以便后面能够使用它来指定飞船的位置
"""
self.rect = self.image.get_rect()
# 对于每艘新飞船,都将其放在屏幕底部的中央
self.rect.midbottom = self.screen_rect.midbottom
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
Pygame之所以高效,是因为它让你能够像处理矩形(rect对象)一样处理所有的游戏元素,即便其形状并非矩形。像处理矩形一样处理游戏元素之所以高效,是因为矩形是简单的几何形状。例如,将游戏元素视为矩形,Pygame能够更快地判断出它们是否发生了碰撞。这种做法的效果通常很好,游戏玩家机会注意不到我们处理的并不是游戏元素的实际形状。在这个类中,我们将把飞船和屏幕作为矩形进行处理。
处理rect对象时,可使用矩形四角和中心的x坐标和y坐标。可以通过设置这些值来指定矩形的位置。要让游戏元素居中,可设置相应的rect对象的属性center、centerx或centery;要让游戏元素与屏幕边缘对齐,可使用属性top、bottom、left或right。除此之外,还有一些组合属性,如midbottom、midtop、midleft和midright。要调整游戏元素的水平或垂直位置,可使用属性x和y,分别时相应矩形左上角的x坐标和y坐标。
Notice:在Pygame中,原点(0,0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200x800的屏幕上,原点位于左上角,而右下角的坐标为(1200,800)。这些坐标对应的是游戏窗口而不是物理屏幕。
import sys
import pygame
from settings import Settings
from ship import Ship
class AlienInvasion:
def __init__(self):
"""管理游戏资源和行为的类"""
pygame.init()
self.settings=Settings()
self.screen = pygame.display.set_mode(
(self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
self.ship=Ship(self)
def run_game(self):
"""开始游戏的主循环"""
while True:
#监视键盘和鼠标事件
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
#每次循环时都重绘屏幕
"""
fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
"""
self.screen.fill(self.settings.bg_color)
self.ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
if __name__=='__main__':
#创建游戏实例并运行游戏
ai = AlienInvasion()
ai.run_game()
导入ship类,并在创建屏幕后创建一个Ship实例。调用Ship()时,必须提供一个参数:一个AlienInvasion实例,这里self指向的是当前AlienInvasion实例。这个参数让Ship能够访问游戏资源,如对象screen。
在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易拓展。将越来越长的方法run_game()拆分成两个辅助方法(helper method)。辅助方法在类中执行任务,但并非是通过实例调用的。在Python中,辅助方法的名称以单个下划线打头。
def run_game(self):
"""开始游戏的主循环"""
while True:
self._check_events()
#每次循环时都重绘屏幕
"""
fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
"""
self.screen.fill(self.settings.bg_color)
self.ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
def _check_events(self):
#监视键盘和鼠标事件
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
def run_game(self):
"""开始游戏的主循环"""
while True:
self._check_events()
self._update_screen()
def _update_screen(self):
#每次循环时都重绘屏幕
"""
fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
"""
self.screen.fill(self.settings.bg_color)
self.ship.blitme()
#让最近绘制的屏幕可见
pygame.display.flip()
此时已将绘制背景和飞船以及切换屏幕的代码移到了方法_update_screen()中。现在很容易看出在每次循环中都检测到了新发生的事件并更新了屏幕。
新手可以先编写尽可能简单的代码,等项目越来约复杂后对其进行重构。
编写代码,在用户按左或右箭头时做出相应。
def _check_events(self):
#监视键盘和鼠标事件
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+=1
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
#移动标志
self.moving_right=False
def update(self):
if self.moving_right:
self.rect.x+=1
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
同时修改_check_events以及run_game:
def run_game(self):
"""开始游戏的主循环"""
while True:
self._check_events()
self.ship.update()
self._update_screen()
def _check_events(self):
#监视键盘和鼠标事件
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
#玩家松开右箭头
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
self.ship.moving_right = False
def _check_events(self):
#监视键盘和鼠标事件
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._check_keydown_enents(event)
elif event.type == pygame.KEYUP:
self._check_keyup_enents(event)
def _check_keydown_events(self,event):
#玩家按下箭头
if event.key == pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right = True
elif 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
elif event.key == pygame.K_LEFT:
self.ship.moving_left = False
def _check_keydown_events(self,event):
#玩家按下箭头
if event.key == pygame.K_RIGHT:
#向右移动飞船
self.ship.moving_right = True
elif event.key == pygame.K_LEFT:
#向左移动飞船
self.ship.moving_left = True
elif event.key == pygame.K_q:
sys.exit()
def __init__(self):
"""管理游戏资源和行为的类"""
pygame.init()
self.settings=Settings()
#在全屏模式下运行游戏
self.screen = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
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)
Notice:在全屏模式下运行这款游戏之前,请确认能够按Q键退出,因为Pygame默认不提供在全屏模式下退出游戏的方式。
射击功能:在玩家按空格键时发射子弹(用小矩形表示),子弹将在屏幕中向上飞行,抵达屏幕边缘后消失。
#子弹设置
self.bullet_speed = 1.0
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60,60,60)
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)
继承了从模块pygame.sprite导入的Sprite类。通过使用精灵(sprite),可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,init()还需要当前的AlienInvasion实例,还调用了super()来继承Sprite。
接下来写好管理子弹位置以及绘制子弹的方法:
def update(self):
self.y-=self.settings.bullet_speed
self.rect.y=self.y
def draw_bullet(self):
pygame.draw.rect(self.screen,self.color,self.rect)
开发大型项目时,要在进入每个阶段之前回顾以下开发计划,搞清楚接下来要通过代码来完成哪些任务。
在项目中添加新功能,还应审核既有代码。每进入一个新阶段,项目通常会更复杂,因此,最好对混乱或低效的代码进行清理(重构)。
像创建Ship类那样创建Alien类,出于简化考虑,也将使用位图来表示外星人。
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
def __init__( self, ai_game):
#初始化为行人并设置其起始位置
super().__init__()
self.screen = ai_game.screen
#加载外星人图像并设置其rect属性
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 _create_fleet(self):
#创建外星人群
#首先创建一个外星人
alien = Alien(self)
self.aliens.add(alien)
要绘制一群外星人,需要确定一行能容纳多少外星人以及要绘制多少行。首先计算外星人的水平间距并创建一行外星人,再确定可用的垂直空间并创建一群外星人。
avaliable_space_x = self.settings.screen_width - (2*alien_width)
number_aliens_x = avaliable_space_x // (2*alien_width)