这一篇教程,我们来完成游戏的主程序模块。
最终编写完的游戏会有如下界面。
游戏界面汇总图:
接下来,根据上一篇教程的结构,我们编写每一个类的代码。
一、State类
从游戏界面汇总图中,我们能够看出,游戏中会有暂停状态(除关卡界面之外的界面)和运行状态(关卡界面)。
State类就是将这两个状态进行抽象,包括。
- 无论那个状态都需要对退出游戏的指令进行处理;
- 无论哪个状态都要显示界面内容。
对于退出游戏指令的处理,我们需要定义一个处理方法;
而显示内容我们需要定义两个方法,因为两个状态的内容显示要有不同的处理。
示例代码:
class State: """泛型游戏状态类,可以处理事件并在给定的表面上显示自身。""" def handle(self, event): """处理退出事件。""" if event.type == QUIT: sys.exit() if event.type == KEYDOWN and event.key == K_ESCAPE: sys.exit() def paused_display(self, screen): """暂停时显示。""" r = config.getint('Screen', 'red') b = config.getint('Screen', 'blue') g = config.getint('Screen', 'green') screen.fill((r, b, g)) # 屏幕填充颜色 pygame.display.flip() # 刷新显示屏幕 def display(self, screen): # 默认不作处理,由子类Level重写。 """刷新显示时。""" pass
二、Level类
这个类继承State类,主要对关卡运行时进行相应的处理。
具体实现过程,大家可以通过注释理解。
示例代码:
class Level(State): """游戏等级,用于计算共有多少敌机落下,移动游戏对象以及其他与游戏相关的逻辑任务。""" def __init__(self, number=1): """初始化关卡的等级和游戏对象。""" self.number = number # 初始化关卡级别 self.remaining = config.getint('Level', 'pre_level') # 初始化关卡敌机数量 speed = config.getint('Speed', 'drop_speed') # 获取设置文件中的敌机速度数值 speed += (self.number - 1) * config.getint('Speed', 'speed_increase') # 计算当前关卡敌机速度数值 self.enemy = objects.Enemy(speed) # 创建敌方飞机游戏对象 self.plane = objects.Plane() # 创建己方飞机对象 self.sprites = pygame.sprite.RenderUpdates(self.enemy, self.plane) # 创建游戏对象集添加游戏对象 def update(self, game): """关卡运行,并进行游戏结束或通过关卡的处理。""" self.sprites.update() # 刷新游戏对象 if self.plane.touches(self.enemy): # 如果碰撞到敌方飞机 game.next_state = GameOver() # 变更状态为游戏结束 elif self.enemy.landed: # 如果敌方飞机从屏幕底边移出 self.enemy.reset() # 重置敌方飞机位置 self.remaining -= 1 # 敌机数量减少1架 if self.remaining == 0: # 如果敌机数量为0 game.next_state = LevelCleared(self.number) # 变更状态为清空关卡 def display(self, screen): """关卡运行时的屏幕显示。""" r = config.getint('Screen', 'red') b = config.getint('Screen', 'blue') g = config.getint('Screen', 'green') screen.fill((r, b, g)) # 关卡运行时为屏幕填充颜色 updates = self.sprites.draw(screen) # 绘制屏幕外观 pygame.display.update(updates) # 刷新屏幕外观
三、Paused类
这个类继承自State类,是其它暂停状态类的超类。
暂停状态基本都是白色的屏幕和黑色的文字内容。
当然,也有例外。
欢迎界面做了两种不同的方案,方案1只有一张图片,方案2既有文字又有图片。
根据这些情况,我们分析一下如何处理。
- 如果只有图片直接绘制图片;
- 如果只有文字则在屏幕中心绘制文字;
- 如果既有文字又有图片,将图片绘制在文字上方,间隔20像素。
具体实现过程,请大家参考代码中的注释理解。
示例代码:
class Paused(State): """暂停游戏的状态,按任意键或点击鼠标退出暂停状态。""" next_state = None # 存储下一个游戏状态 finish = False # 记录是否完成按键或鼠标点击 image = None # 存储屏幕中显示的图片 text = '' # 存储屏幕中显示的文字 def handle(self, event): """处理按任意键继续游戏。""" State.handle(self, event) if event.type in [MOUSEBUTTONDOWN, KEYDOWN]: # 如果捕获鼠标点击和按键的事件 self.finish = True # 记录完成操作 def update(self, game): """按任意键时进入下一个游戏状态。""" if self.finish: # 如果按键或鼠标点击完成 game.next_state = self.next_state() # 将变量next_state中类的实例化,存入Game类进行处理。 def paused_display(self, screen): """暂停时显示的处理。""" State.paused_display(self, screen) # 重载超类中的方法 font = pygame.font.SysFont('SimHei', config.getint('Screen', 'font_size')) # 设置使用系统字体(用于支持中文) lines = self.text.strip().splitlines() # 获取子类中设置的文字内容并分行 height = len(lines) * font.get_linesize() # 计算文字高度 center, middle = screen.get_rect().center # 获取屏幕中心坐标值 top = middle - height // 2 # 计算显示内容中心点的y轴坐标 if self.image: # 如果有图片 image = pygame.image.load(self.image).convert_alpha() # 载入图片 img_rect = image.get_rect() # 创建矩形 top += img_rect.height // 2 # 重新计算顶部y轴坐标 if self.text: # 如果有文字 img_rect.midbottom = center, top - 20 # 设置图片位置为中心且底边与文字顶部间隔20像素。 else: # 否则 img_rect.center = center, middle # 设置图片中心点为屏幕中心点 screen.blit(image, img_rect) # 屏幕填充图片 antialias = True # 设置抗锯齿(平滑文字) black = 0, 0, 0 # 设置文字颜色 for line in lines: text = font.render(line.strip(), antialias, black) # 创建单行文字对象 txt_rect = text.get_rect() # 创建矩形 txt_rect.midtop = center, top # 设置文字显示位置 screen.blit(text, txt_rect) # 屏幕填充文字 top += font.get_linesize() # 逐行下移文字位置 pygame.display.flip() # 刷新屏幕显示
四、Paused类的子类(StartUp、Info、LevelCleared、GameOver)
这几个子类都比较简单,主要是定义暂停状态下屏幕中显示的内容。
其中,略有不同的是LevelCleared类,在这个类中要进行下一个关卡的实例化。
另外,为了能够方便更换欢迎界面的方案,在配置文件(config.ini)中我们新增一项配置内容。
示例代码:
[Welcome] skin = 1
各个Paused类的子类实现,大家可以根据下方代码中的注释理解。
示例代码:
class Info(Paused): """游戏信息。""" next_state = Level # 将类存入变量,以便在Paused类中实例化。 text = '''控制移动你的飞机, 不要被敌机撞到它。''' class StartUp(Paused): """进入游戏。""" next_state = Info # 将类存入变量,以便在Paused类中实例化。 if config.getint('Welcome', 'skin'): # 读取配置文件中欢迎界面的方案设置 text = '' image = config.get('Image', 'welcome') else: text = '老司机开飞机' image = config.get('Image', 'plane') class LevelCleared(Paused): """游戏过关。""" def __init__(self, number): """过关信息。""" self.number = number # 获取当前关卡级别 self.text = '''恭喜闯过第 %d 关, 点击继续下一关。''' % self.number def next_state(self): """创建下一关卡。""" return Level(self.number + 1) # 返回下移关卡的对象 class GameOver(Paused): """游戏结束。""" next_state = Level # 将类存入变量,以便在Paused类中实例化。 text = '游戏结束!'
五、Game类
这个类负责处理游戏的整个运行过程。
class Game: """负责主事件循环的游戏对象,完成在不同状态间切换的任务。""" def __init__(self): """初始化。""" self.state = None # 初始化当前状态 self.next_state = StartUp() # 初始化下一个状态为开始游戏的状态 def run(self): """运行游戏。""" pygame.init() # 游戏初始化 flag = 0 # 记录是否全屏的标志 if config.getint('Screen', 'full_screen'): # 如果配置文件中设置为全屏 flag = FULLSCREEN # 标志设置为全屏 screen_size = (config.getint('Screen', 'width'), config.getint('Screen', 'height')) # 读取配置文件中的屏幕尺寸设置 screen = pygame.display.set_mode(screen_size, flag) # 设置屏幕外观模式 pygame.display.set_caption('老司机开飞机') # 设置游戏窗口标题(非全屏时显示) pygame.mouse.set_visible(False) # 隐藏鼠标指针显示 while True: pygame.time.Clock().tick(200) # 设置每秒帧数 if self.state != self.next_state: # 如果有新的游戏状态 self.state = self.next_state # 切换到新的状态 self.state.paused_display(screen) # 屏幕中显示暂停状态的内容(切换状态会进入暂停状态) for event in pygame.event.get(): # 获取事件 self.state.handle(event) # 处理事件 self.state.update(self) # 游戏对象的当前状态内容刷新 self.state.display(screen) # 屏幕中显示刷新后的状态内容
到这里,我们就完成了这个项目所有代码的编写。
大家可以运行程序,查看一下是否能够正确执行。
示例代码:
if __name__ == '__main__': game = Game() game.run()
本节练习源代码:【点此下载】
转载请注明:魔力Python » 练习项目21:使用python制作游戏(下)