开发大型项目的时候,先做好规划再动手编写项目
一段简单的描述:
在游戏外星人入侵中,玩家控制着一艘最初出现在屏幕底部中央的飞船,玩家可以使用方向键左右移动飞船,还可以使用空格键进行射击,玩家将所有外星人都消灭干净之后,将会出现一批新的外星人,移动速度更快,只要有外星人撞到了玩家的飞船或者是到达了屏幕底部,玩家就会失去一艘飞船,玩家损失三艘飞船之后,游戏结束
在开始编程前,首先要安装pygame,看其他大牛的就好
首先创造一个空的pygame窗口,使用pygame编写的游戏的基本结构如下:
首先我们导入模块sys和pygame,模块pygame包含游戏开发所需要的基本功能,玩家退出时,我们将会使用模块sys退出游戏
最开始是函数run_game():
pygame默认会创建一个黑色屏幕,但这太乏味了,我们可以为其设置另一种背景颜色:
我们每一次为游戏添加新功能的时候,都会引入一些新的设置,此处我们编写一个模块专门用来存储这些类,要修改游戏设置,就只用在这个地方做修改即可,而无需查找散布在文件之中的不同设置
最初的setting类如下:
使用settings:
在主程序文件中,我们导入settings类,调用pygame.init(),再创键一个settings实例,并将其存储在变量ai_settings中,创建屏幕的时候,使用了si_settings的属性screen_width和screen_height,接下来填充屏幕的时候,也会使用到ai_settings来访问背景颜色
下面将飞创加入到游戏中,为了在屏幕中绘制出玩家的飞机,我们将会加载一幅图像,再使用pygame方法blit()来绘制它
另外注意:在为游戏选择素材的时候,务必要注意许可
选择用于表示飞船的图像之后,需要将其显示到屏幕上,我们将创建一个名为ship的模块,其中包含ship类,负责管理飞船的大部分行为
首先我们导入了模块pygame,ship的方法_init_()会接受两个参数,引用self和screen,后者制定了飞船要画在什么地方,
(1)为了加载图像,我们调用了函数pygame.image.load(),这个函数会返回一个表示飞船的surface,而我们将这个surface存储在self.image中(2)加载图像之后,我们使用函数get_rect()来获取响应surface的属性rect。python的效率这么高,一个原因就是其能够像处理矩形(rect对象)一样处理游戏元素,即使其形状并不是矩形,游戏玩家几乎注意不到我们处理的对象是矩形
之后我们更新一下alien_invasion.py使其创建一艘飞船
我们导入ship类,并在创建屏幕之后创建一个名为ship的ship实例,注意必须要在主while循环前创建该实例,以免每一次循环的时候都创建一艘飞船,填充背景之后,我们将调用ship.blitme()将飞船绘制在屏幕上
在大型项目之中,经常需要在添加新代码前重构既有代码,重构旨在简化既有代码的结构,使其更加容易扩展
在本节中我们将会创建一个名为game_function的新模块,将存储在大型项目中用到的函数,通过创建这个新模块,可以避免alien_invasion太长,使其逻辑更容易理解
我们首先将管理事件的代码转移到一个名为check_events()的函数之中,以简化alien_invasion中的run_game(),通过隔离事件循环,可以将事件管理与游戏中的其他方面分离
将check_events()放在一个名为game_function的模块之中
下面我们要修改alien_invasion.py,使其导入模块game_function(),并将事件循环替换为对函数check_events()的调用
注意,在主程序文件之中,不再需要直接导入sys,因为当前只在模块game_function()中使用了它,出于简化的目的,我们给导入的模块game_function指定了别名gf
为了进一步简化代码,我们将更新屏幕的代码移到一哥名为update_screen()的函数之中,并将这个函数放在模块game_functions.py中
新函数包含三个形参,ai_settings,screen,ship,
现在需要将alien_invasion.py的while循环中更新屏幕的代码替换为对函数update_screen的调用
这两个函数让while循环更简单并且让后续开发更加容易,在模块game_functions而不是run_game()中完成了大部分工作
鉴于一开始我们只想使用一哥文件,因此我们没有立即引入模块game_functions,这让我能够了解实际的开发过程,一开始将代码编写的尽可能简单,并在项目越累越复杂时候进行重构
接下来,我们让玩家能够左右移动飞船,为此我编写代码,在用户按左或者右箭头键的时候做出响应
每一次用户按键的时候,都将会在pygame中注册一个事件,事件是通过pygame.event.get()来获取的,每一次按键(按下去)都被注册为一个KEYDOWN事件
当检测到KEYDOWN事件的时候,我们还需要检查一下按下的是否是特定的键,如果是就执行相应的操作
**我们在函数check_events()中包含了形参ship ,因为玩家按右箭头键的时候,需要将飞船向右移动,在函数check-event()内部我们在事件循环中添加了一个elif代码块,以便在pygame检测到KEYDOWN事件时做出响应,读取属性event.key,以检查按下的是否是右箭头键(pygame.K_RIGHT),如果是就将ship.rect.centerx的值加1,从而将飞船向右移动
**
之后在alien_invasion中,我们需要更新update_screen()代码,将ship作为实参传递给他
**玩家按住右箭头键不动的时候,我们希望飞船不断向右移动,直到玩家松开为止,我们将让游戏检测pygame.KEYUP事件,以便玩家松开按键的时候,我们知道这一点,然后我们结合KEYDOWN和KEYUP事件,以及一个名为moving_right的标志,来实现持续移动
(1)当飞船不动时,标志moving_right将为False,玩家按下右箭头的时候,标志设置为True,松手时重新设置为False
(2)飞船的属性都有ship类控制,因此我为这个类再添加一个moving_right的属性和update的方法,方法update()检查moving_right的状态,如果是true,就调整飞机的位置 **
之后修改check_events(),使其在玩家按下右箭头的时候将moving_right设置为true,并在玩家松开时设置为false
注意,我们不直接调整飞船的位置,而实通过调整标志,这是一种很好的思想,代码延展性很好
最后,我们还要修改一下alien_invasion中的while循环,每次循环都调用方法update()
飞船的位置在检测到键盘事件之后更新,这样玩家按键时,飞船的位置将会更新,这样可以确保使用更新后的位置绘制在屏幕上
我们还需要添加飞船向右移动的逻辑,将要再次修改ship和check_events
如果玩家同时按下左右箭头键,那么就会触发两个不同的事件,飞船将会纹丝不动
当前,在每一次执行while循环的时候,飞船最多可以移动1像素,但是我们可以在setting类中添加属性ship_speed_factor,用于控制飞船的速度,我们根据这个属性决定飞船每次循环的时候最多移动多少距离
我们将速度设置为小数值,可以在后面加快的游戏节奏中更加细致的控制飞船的速度,但是rect的centerx等属性就只能存储整数值,因此需要我们对ship类做一些修改
(1)我们在_init_()的形参列表中添加了ai_settings,让飞船能够获取其速度设置,接下来,我们将ai_settings的值存储在一个属性中,以便之后能在update()中使用它
在alien_invasion中创建ship实例时,需要传入实参ai_settings
问题:如果玩家按住箭头键的时间足够长,飞船将会移到屏幕外面,现在要来修复这个问题,让飞船到达屏幕边缘的时候就停止移动,为此我们需要修改ship类中的update()
随着游戏开发的进行,函数check_events()将会越来越长,我们将其部分代码放在两个函数中,一个处理KEYDOWN事件,另一个处理KEYUP事件
我们创建了两个新函数,check_keydown_events()和check_keyup_events(),它们都包含形参event和ship,这两个函数的代码是从check_events()中复制而来的,因此我们将函数check_events中相应的代码替换成了对这两个函数的调用,现在,函数check_events()更简单,代码结构更加清晰,这样在其中响应玩家的其他输入的时候也会更加容易
之后我们将会添加射击功能,即新增一个bullet.py的文件,并对一些既有文件进行修改。
当前我们有四个文件,其中包含很多类,函数,方法,再继续开发之前,我们先回顾一下这些文件
这个是主文件,alien_invasion.py会创建一系列整个游戏都会用到的对象,存储在ai_settings中的设置,存储在screen中的主显示surface以及一个飞船实例,还包含游戏的主循环,这是一个调用check_events(),ship.update()和update.screen()的while循环
要玩这个游戏,就只需要运行文件alien_invasion.py,其他文件包含的代码被直接或者间接地导入到这个文件中
文件settings.py,这个类只包含方法_init_(),它初始化游戏外观以及飞船速度属性
这个文件之中包含一系列函数,游戏的大部分工作是由它们完成的。函数check_events()检查按键和松开,并使用辅助函数check_keydown_events()和check_keyup_events()来辅助处理,就目前而言,这些函数管理飞船的移动.
还包含函数update_screen(),用于每次执行主循环的时候都重绘制屏幕
此文件包含ship类,这个类包含方法_init_(),管理飞船位置的方法update(),以及在屏幕上绘制飞船的方法blitme()
下面来添加设计功能,我们将来编写玩家按空格键时发射子弹的代码,子弹在屏幕中向上穿行,在抵达屏幕边缘的时候消失
首先更新settings.py设置,在其方法_init_()的末尾存储新类bullet所需的值
这些设置用于创建宽3像素,高15像素的深灰色子弹,子弹的速度比飞船略低
下面来创建存储bullet类的文件bullet.py
bullet类先是继承了模块pygame.sprite中导入的sprite类,通过使用精灵,可以将游戏中的相关元素编组,进而同时操作编组中的所有元素
(1)我们创建了子弹的属性rect,子弹并非是基于图像的,因此我们必须使用pygame.rect()在空白处开始创建一个矩形,创建这个矩形的时候必须要提供矩形左上角的x坐标,y坐标,还有就是举行的宽度和高度(子弹的宽度和高度都是从ai_settings中获取的),我们在(0,0)处创建这个矩形,但是接下来的两行代码会将矩形移动到正确的位置,子弹的初始位置取决于飞船当前的位置
(2)我们将子弹的centerx设置为飞船的rect.centerx子弹应该从飞船顶部射出,因此我们将表示子弹的rect的top属性设置为飞船的rect的top属性,让子弹看起来像是从飞船中射出的
(3)我们将子弹的y坐标存储为小数值,以便能够微调子弹的速度,同时我们将settings类中设置的子弹的颜色和速度分别存储在self.color和self.speed_factor中
(1)方法update()管理子弹的位置,发射出去之后,子弹在屏幕中向上移动,这意味着y坐标值将会不断减小,这样做来更新子弹的位置,同时属性speed_factor让我们能够随着游戏的运行或根据需
要来提高子弹的速度,调整游戏的难度
(2)需要绘制子弹的时候,我们就调用函数draw_bullet(),函数draw_rect()使用存储在self.color中的颜色来填充表示子弹的rect占据的部分
(1)我们导入了pygame.sprite中的Group类,上面我们创建了一个group实例,并将其命名为bullets,这个编组是在while循环外面创建的,这样就无需每次运行循环的时候都创建一个新的编组(2)如果循环内部创建这样一个编组,游戏运行的时候就会创建数千个编组,导致游戏速度很慢。如果游戏速度很慢,记得要仔细看看主循环内部发生了什么
(3)在主循环内部,我们将bullets传递给了函数check_events()和update_screen(),在前者中,需要玩家按空格键的时候处理bullets,而在后者中,需要更新绘制到屏幕上的bullets
(4)当你对编组待用update的时候,编组会自动对其中的每一个精灵都是用update()因此代码行bullet.update()将会对编组bullets中的每一个子弹都调用bullet.update()
运行时,我们可以左右移动飞机,并且可以发射任意数量的子弹,子弹在屏幕上向上穿行,抵达屏幕顶部时消失(消失只是直观上的感受,此时没有消失)
当然,子弹到达屏幕顶部消失,仅仅是以为pygame无法在【屏幕外面绘制它们,这些子弹实际上依然存在,它们的y坐标是负数,并且还会越来越小,长此以往是个问题,这样会消耗我们的内存和处理能力
我们需要将这些已消失的子弹删除,就需要一个检测条件,那就是子弹的rect的buttom属性为0,就表示子弹已经到达顶部
很多射击游戏都会限制子弹的数量,在我们的游戏中也可以做这样的设定
玩家按空格时候,我们就检查bullets的长度,如果len(bullets)小于3,我们就创建一个新子弹,但是如果已有三颗未消失的子弹,则玩家按空格的时候什么也不会发生,也就是说,屏幕上最多有三颗子弹
编写并检查子弹管理的代码之后,可将其移到模块game_function中,以便让主程序看上去尽可能简单,我们擦黄建一个名为update_bullets()的新函数,并将其加到game_function.py的结尾
这样以来,alien_invasion.py的while循环又变得简单了
我们让主程序包含尽可能少的代码,这样只看函数名就能迅速的知道游戏中发生的情况。主循环检查游戏的输入,然后更新飞船的位置,于此同时更新未消失子弹的位置·,接下来,我们使用更新后的位置来绘制新屏幕
接下来我们将发射子弹的代码移到一个独立的函数中,这样在check_keydown_events()中只需要使用一行代码就可以发射子弹,让elif代码块变得非常简单
**在本章中,你学习了,游戏开发计划的制订,使用pygame编写游戏的基本结构,如何设置背景色,以及如何将设置存储在可供游戏的各个部分访问的独立类中;如何在屏幕上 绘制图像,以及如何让玩家控制游戏元素的移动;如何创建自动移动的元素,如在屏幕中向上飞驰的子弹,以及如何删除不再需要的对象;如何定期重构项目的代码,为后续开 发提供便利。
**