零基础入门Python游戏
上QQ阅读APP看书,第一时间看更新

3.5 游戏循环

前面已经创建好了游戏窗口,但是还没有创建游戏循环,其实还并不算真正地把程序运行起来了。现在就让我们实现游戏循环,如同所有Pygame游戏程序,飞机大战的游戏循环也应该不断执行以下几件事:

·事件检测处理;

·状态更新;

·碰撞检测处理;

·屏幕绘制。

下面来看代码。还是Game类,我们在其中定义了一个run()函数,用作该程序的游戏循环。

game.py

在run()函数中,通过while True实现游戏循环,这样只要执行run()函数,就会进入游戏死循环,不断执行事件处理、状态更新等一系列操作。

上面代码中的内容稍后再做详细解释,我们先来看看如何执行该函数。这里,在plane_wars.py模块中调用Game类的run()函数,代码如下:

plane_wars.py

这样即可进入游戏循环,把程序运行起来。

现在回过头看看Game类中新增的代码。

首先,开头引入了必要的模块sys和stats,留作后面使用。在__init__()函数中,初始化了两个变量,一个是pygame.time.Clock类的对象self.clock,另一个是我们在前面定义的Stats类的对象self.stats。在run()函数的while循环中,首先调用self.handle_events()处理事件;然后在RUN状态下更新状态和进行碰撞检测,这里所更新的状态包括背景图的位置、子弹的数目和位置、敌机的数目和位置,其中,self.bg.update()用作更新背景状态,self.update_bullets()用作更新子弹状态,self.update_enemies()用作更新敌机状态,self.handle_collision()用作碰撞检测处理;接下来调用self.update_screen()绘制游戏窗口;最后执行self.clock.tick(60)限定游戏帧率,使它的刷新速度不大于60fps,这样做的目的一是为了使游戏在不同的机器中运行时保持速度一致,二是为了合理展现游戏中的动画效果。

接下来我们就一个个地定义run()中所调用的函数。

3.5.1 事件检测处理

首先是self.handle_events(),它用来对事件进行检测和处理,定义如下:

game.py

在handle_events()中,我们只对QUIT、MOUSEBUTTONDOWN、MOUSEMOTION这三类事件进行响应处理。检测到的如果是QUIT,则调用pygame.quit()和sys.exit()退出程序;如果是MOUSEBUTTONDOWN,则调用self.handle_mousedown_event()对该事件进行更进一步的处理;如果是MOUSEMOTION,则调用self.handle_mousemotion_event()对该事件进行更进一步的处理。

对于self.handle_mousedown_event()函数,我们需要在其中判断游戏当前所处的状态,根据不同的状态做不同的处理。在WELCOME状态下响应处理Start按钮,在RUN状态下响应处理Pause/Resume按钮,在PAUSE状态下也响应处理Pause/Resume按钮,在GAMEOVER状态下响应处理Restart按钮和Exit按钮。

再来看self.handle_mousemotion_event()函数。实际上,只有当前游戏状态为RUN时才需要对MOUSEMOTION事件进行处理。在RUN状态下,当该事件到来时,一是需要更新我方飞机的位置,二是需要更新Pause/Resume按钮的外观。

注意:并不是所有的鼠标移动都会带来我方飞机位置的移动,只有当鼠标左键按住飞机拖动时,我方飞机才会有位置的更新,所以在更新我方飞机的位置时,需要判断event的buttons属性,buttons[0]为1才代表鼠标左键被按下。

这里只是先给出一个框架,上面代码中的注释部分,我们后面再慢慢实现。

3.5.2 状态更新

本游戏所涉及的状态更新包括背景状态更新、子弹状态更新、敌机状态更新、我机状态更新,其中,只有我机状态更新是放在MOUSEMOTION事件到来时实现的,其他三类状态更新都是放在游戏循环中实现的,这样可以不间断地更新它们的状态,让它们在画面上移动起来。

背景状态的更新所使用的函数为self.bg.update(),其中self.bg实际上是背景类的对象,它包含了一个名为update()的方法,用来更新背景图的位置。我们先把对它的调用放在这里,背景类与背景类的对象self.bg后面再去定义。

子弹状态的更新所使用的函数为self.update_bullets(),主要用来创建子弹与更新子弹的位置,定义如下:

game.py

它目前还只是一个框架,并没有实质性的内容,注释中的功能,我们后面再慢慢实现。

敌机状态的更新所使用的函数为self.update_enemies(),主要用来创建敌机与更新敌机的位置,定义如下:

game.py

与update_bullets()类似,它目前也只是一个框架,函数体中的内容留到后面实现。

注意:无论是背景的更新、子弹的更新还是敌机的更新,它们都只在游戏的RUN状态下才需要执行,其他状态下并没有必要更新它们。

3.5.3 碰撞检测处理

下面定义self.handle_collision(),它用来对游戏中所有可能涉及的碰撞情况进行检测和处理,定义如下:

game.py

该函数需要检测处理两类碰撞情况:一是子弹与敌机的碰撞,如果发生碰撞则代表敌机被子弹击中;二是我机与敌机的碰撞,如果发生碰撞则代表我机被击毁。

关于self.handle_collision()函数,也还是先把它的框架放在这里,函数体中的具体内容后面再去实现。

3.5.4 屏幕绘制

最后定义self.update_screen(),它用来向display surface绘制内容以更新屏幕显示,定义如下:

game.py

该函数中的代码还是比较直观的,主要负责在不同的游戏状态下向屏幕绘制不同的内容。如果是WELCOME状态,则绘制背景、Logo、Start按钮;如果是RUN或者PAUSE状态,则绘制背景、我机、子弹、敌机、Pause/Resume按钮、记分牌;如果是GAMEOVER状态,则需要绘制背景、提示方框、Restart按钮、Exit按钮。在函数的最后,调用pygame.display.flip()更新屏幕,把之前绘制的内容显示出来。

当然,类似于前面提到的许多其他函数,该函数目前也只是一个框架,其内部的众多功能需要留到后面慢慢实现。

其实,了解了该程序的游戏循环也就理解了它的整体框架,剩余几乎所有代码都是为游戏循环服务的,它们最终都会被run()函数直接或间接调用,后续就让我们逐渐增加代码以完善前面定义的这些函数。